Trading-Plattform-Pruefung: Abendkontrolle prueft die Sicherung des vorigen Handelstags (Paket E9, Task 726)

abendkontrolle_wsl.py nimmt --sicherung (Stand der Ablage vom Host) und meldet per ntfy, wenn die
Sicherung des vorigen Handelstags fehlt, kein Archiv hat, nicht bestaetigt ist oder die Bestaetigung
nicht passt; vor erster-tag still, ohne erster-tag ein Befund. abendkontrolle_angriffe.py mit
--nur-sicherung (60 Faelle), abendkontrolle_mutanten.py mit --nur und 30 Sicherungs-Mutanten.
Gegengeprueft: byte-gleich mit der Plankopie, Lauf gegen echte Archive ohne Abweichung.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0126RDfXiECscXipzf67uypJ
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-10-11 17:09:14 +02:00
1 parent c5db92c6de
commit 3e55bb915d
3 files changed
+514 -3

No files matched your search

@@ -4,8 +4,11 @@ Koordinator-Werkzeug, nicht fuers Repo. Jeder Mutant laeuft auf einer Wegwerfkop
In WSL (Venv) die Mutanten von abendkontrolle_wsl.py, unter Windows die von abendkontrolle.py und
rueckweg_aapl.py. Aufruf: python abendkontrolle_mutanten.py Exit 0, wenn keiner ueberlebt.
Ein Mutant, der das Verhalten nicht aendert, ist keine Luecke (AI/Regelwerk/pruefwerkzeuge.md, Abschnitt 8).
Seit Paket E9: --nur TEXT faehrt nur die Mutanten mit TEXT im Namen (etwa --nur Sicherung in WSL); ein
fuenftes Element im Tupel geht als Argument an den Treiber.
"""
import argparse
import shutil
import subprocess
import sys
@@ -64,6 +67,78 @@ WSL_MUTANTEN = [
("IBKR Muster aus", "abendkontrolle_wsl.py", " if muster in text:", " if False:"),
("IBKR Titel ohne Praefix", "abendkontrolle_wsl.py", 'IBKR_TITEL = "IBKR "', 'IBKR_TITEL = ""'),
("IBKR Meldeweiche aus", "abendkontrolle_wsl.py", "melde(IBKR_TITEL + titel if ibkr else titel,", "melde(titel,"),
# Seit Paket E9 (Task 726): --sicherung. Der Treiber faehrt dafuer nur seine Faelle zur Sicherung.
("Sicherung letzter Listeneintrag statt juengster Tag", "abendkontrolle_wsl.py",
" return max(tage) if tage else None", " return tage[-1] if tage else None", "--nur-sicherung"),
("Sicherung Vortag statt Kalender", "abendkontrolle_wsl.py",
" vorher = juengster_handelstag(lies_kalender_spanne(start, ende, argumente.kalender, Path(argumente.projekt)))",
" vorher = ende", "--nur-sicherung"),
("Sicherung Kalenderform egal", "abendkontrolle_wsl.py",
' raise ValueError("Kalender mit einem Eintrag ohne date JJJJ-MM-TT")', " continue",
"--nur-sicherung"),
("Sicherung Fehler des Hosts ueberlesen", "abendkontrolle_wsl.py", ' if "fehler" in stand:', " if False:",
"--nur-sicherung"),
("Sicherung Stand ohne Typpruefung", "abendkontrolle_wsl.py", " if not isinstance(stand, dict):", " if False:",
"--nur-sicherung"),
("Sicherung eintraege ohne Typpruefung", "abendkontrolle_wsl.py",
' if "erster_tag" not in stand or not isinstance(stand.get("eintraege"), dict):',
' if "erster_tag" not in stand:', "--nur-sicherung"),
("Sicherung erster_tag ohne Formpruefung", "abendkontrolle_wsl.py",
" if not isinstance(erster, str) or not DATUM.fullmatch(erster):", " if False:", "--nur-sicherung"),
("Sicherung ohne erster_tag nur Hinweis", "abendkontrolle_wsl.py",
' k.befund("Sicherung nicht geprueft: auf dem Docker-Host fehlt erster-tag der Sicherung")',
' k.hinweis("Sicherung nicht geprueft: auf dem Docker-Host fehlt erster-tag der Sicherung")', "--nur-sicherung"),
("Sicherung vor erster_tag geprueft", "abendkontrolle_wsl.py", " if vorher < erster:", " if False:",
"--nur-sicherung"),
("Sicherung erster_tag selbst ausgenommen", "abendkontrolle_wsl.py", " if vorher < erster:",
" if vorher <= erster:", "--nur-sicherung"),
("Sicherung kein Handelstag still", "abendkontrolle_wsl.py",
' k.befund(f"Sicherung: kein Handelstag in den {SICHERUNG_FENSTER_TAGE} Tagen vor {tag} im Kalender")',
" pass", "--nur-sicherung"),
("Sicherung immer alpaca", "abendkontrolle_wsl.py",
' instanz = "ibkr" if argumente.teil == "ibkr" else "alpaca"', ' instanz = "alpaca"', "--nur-sicherung"),
("Sicherung fehlender Eintrag still", "abendkontrolle_wsl.py",
' k.befund(f"Sicherung {name} fehlt auf dem Docker-Host")', " pass", "--nur-sicherung"),
("Sicherung Summe ungeprueft", "abendkontrolle_wsl.py",
" if not isinstance(summe, str) or not SUMME.fullmatch(summe):", " if summe is None:", "--nur-sicherung"),
("Sicherung Bestaetigung ungeprueft", "abendkontrolle_wsl.py",
" elif not isinstance(bestaetigt, str) or not SUMME.fullmatch(bestaetigt):", " elif False:",
"--nur-sicherung"),
("Sicherung Vergleich aus", "abendkontrolle_wsl.py", " elif bestaetigt != summe:", " elif False:",
"--nur-sicherung"),
("Sicherung Schalter nicht verdrahtet", "abendkontrolle_wsl.py", " if argumente.sicherung:", " if False:",
"--nur-sicherung"),
# Seit der Plankritik Runde 1 (Task 726): Fenster, erster_tag, Archiv und die zwei Faelle ohne Mutanten.
("Sicherung Fenster 1 Tag", "abendkontrolle_wsl.py", "SICHERUNG_FENSTER_TAGE = 10", "SICHERUNG_FENSTER_TAGE = 1",
"--nur-sicherung"),
("Sicherung Fenster einen Tag kuerzer", "abendkontrolle_wsl.py",
" start = (geprueft - timedelta(days=SICHERUNG_FENSTER_TAGE)).isoformat()",
" start = (geprueft - timedelta(days=SICHERUNG_FENSTER_TAGE - 1)).isoformat()", "--nur-sicherung"),
("Sicherung Ende am Tag selbst", "abendkontrolle_wsl.py",
" ende = (geprueft - timedelta(days=1)).isoformat()", " ende = geprueft.isoformat()", "--nur-sicherung"),
("Sicherung Spanne der Datei aus", "abendkontrolle_wsl.py",
' return in_spanne(json.loads(Path(kalender_datei).read_text(encoding="utf-8")), start, ende)',
' return json.loads(Path(kalender_datei).read_text(encoding="utf-8"))', "--nur-sicherung"),
("Sicherung Spanne ohne Anfang", "abendkontrolle_wsl.py", ' or start <= eintrag["date"] <= ende',
' or eintrag["date"] <= ende', "--nur-sicherung"),
("Sicherung Spanne ohne Typpruefung", "abendkontrolle_wsl.py",
" if not isinstance(liste, list):\n return liste", " if False:\n return liste",
"--nur-sicherung"),
("Sicherung Kalender ohne Typpruefung", "abendkontrolle_wsl.py",
' if not isinstance(liste, list):\n raise ValueError("Kalender ist keine Liste")',
' if False:\n raise ValueError("Kalender ist keine Liste")', "--nur-sicherung"),
("Sicherung erster_tag nach dem Tag still", "abendkontrolle_wsl.py", " if erster > tag:", " if False:",
"--nur-sicherung"),
("Sicherung erster_tag am Tag selbst als Befund", "abendkontrolle_wsl.py", " if erster > tag:",
" if erster >= tag:", "--nur-sicherung"),
("Sicherung Archiv ungeprueft", "abendkontrolle_wsl.py", ' elif eintrag.get("archiv") is not True:',
" elif False:", "--nur-sicherung"),
("Sicherung Archiv nur false zaehlt", "abendkontrolle_wsl.py", ' elif eintrag.get("archiv") is not True:',
' elif eintrag.get("archiv") is False:', "--nur-sicherung"),
("Sicherung ohne Schalter geprueft", "abendkontrolle_wsl.py", " if argumente.sicherung:", " if True:",
"--nur-sicherung"),
("Sicherung unbekannter Schluessel als Fehler", "abendkontrolle_wsl.py", ' if "fehler" in stand:',
' if "fehler" in stand or set(stand) - {"erster_tag", "eintraege"}:', "--nur-sicherung"),
]
WIN_MUTANTEN = [
("keine Ergebnisdatei", "abendkontrolle.py", '(ablage / f"{tag}{endung}.json").write_text(',
@@ -109,9 +184,19 @@ TREIBER = {
"rueckweg_aapl.py": "rueckweg_aapl_angriffe.py",
}
zerleger = argparse.ArgumentParser(description=__doc__.splitlines()[0])
# Seit Paket E9: nur die Mutanten, deren Name den Text enthaelt.
zerleger.add_argument("--nur", help="nur Mutanten, deren Name diesen Text enthaelt")
argumente = zerleger.parse_args()
mutanten = WIN_MUTANTEN if sys.platform == "win32" else WSL_MUTANTEN
if argumente.nur is not None:
mutanten = [mutant for mutant in mutanten if argumente.nur in mutant[0]]
if not mutanten:
print(f"KEIN MUTANT mit {argumente.nur!r} im Namen")
sys.exit(2)
ueberlebt = []
for name, datei, alt, neu in mutanten:
# Ein fuenftes Element im Tupel geht als Argument an den Treiber (seit Paket E9).
for name, datei, alt, neu, *treiber_argumente in mutanten:
with tempfile.TemporaryDirectory() as tmp:
ziel = Path(tmp) / "pruefung"
shutil.copytree(HIER, ziel, ignore=shutil.ignore_patterns("__pycache__"))
@@ -122,7 +207,7 @@ for name, datei, alt, neu in mutanten:
ueberlebt.append(name)
continue
pfad.write_text(text.replace(alt, neu), encoding="utf-8", newline="\n")
lauf = subprocess.run([sys.executable, str(ziel / TREIBER[datei])], capture_output=True, text=True,
lauf = subprocess.run([sys.executable, str(ziel / TREIBER[datei]), *treiber_argumente], capture_output=True, text=True,
timeout=1800, encoding="utf-8", errors="replace", cwd=tmp)
faelle = [z for z in lauf.stdout.splitlines() if z.startswith("FEHL")]
rot = lauf.returncode != 0