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
219 lines
16 KiB
Python
219 lines
16 KiB
Python
"""Mutanten der Abendkontrolle und des Rueckwegs: jeder muss seinen Angriffstreiber rot machen.
|
|
|
|
Koordinator-Werkzeug, nicht fuers Repo. Jeder Mutant laeuft auf einer Wegwerfkopie dieses Verzeichnisses.
|
|
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
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
HIER = Path(__file__).resolve().parent
|
|
|
|
WSL_MUTANTEN = [
|
|
("Exit-Vergleich verkehrt", "abendkontrolle_wsl.py", 'if satz["exit"] != "0":', 'if satz["exit"] == "9":'),
|
|
("Handelstag immer wahr", "abendkontrolle_wsl.py", 'return any(eintrag["date"] == tag for eintrag in liste)', "return True"),
|
|
("gesperrt nicht geprueft", "abendkontrolle_wsl.py", 'if zeile[0] == "gesperrt":', 'if zeile[0] == "nie":'),
|
|
("unbekannter Zustand still", "abendkontrolle_wsl.py", "elif zeile[0] not in KILLSWITCH_ZUSTAENDE:", "elif False:"),
|
|
("Broker-Schwelle", "abendkontrolle_wsl.py", "if offen[-1] != 0:", "if offen[-1] > 5:"),
|
|
("Probe zaehlt mit", "abendkontrolle_wsl.py", 'glob(f"{tag}T*-paper.jsonl")', 'glob(f"{tag}T*.jsonl")'),
|
|
("Maske aus", "abendkontrolle_wsl.py", " text = muster.sub(ersatz, text)", " pass"),
|
|
("nie-aktiv aus", "abendkontrolle_wsl.py", 'if satz.get("killswitch") == "aktiv":', "if True:"),
|
|
("Bericht nicht geprueft", "abendkontrolle_wsl.py", "if not bericht.is_file():\n k.befund", "if False:\n k.befund"),
|
|
("Failed-Zeile aus", "abendkontrolle_wsl.py", 'if "Failed with result" in text:', 'if "nie da" in text:'),
|
|
("nur letzter Lauf im Journal", "abendkontrolle_wsl.py", "for nummer, satz in enumerate(enden, start=1):",
|
|
"for nummer, satz in list(enumerate(enden, start=1))[-1:]:"),
|
|
("Melder-Rueckgabe verworfen", "abendkontrolle_wsl.py",
|
|
'return "gesendet" if fehler is None else f"nicht gesendet: {fehler}"', 'return "gesendet"'),
|
|
("Ende je Protokoll nicht bewertet", "abendkontrolle_wsl.py",
|
|
" if ende.get(\"grund\") != \"boersenschluss\":\n k.befund(f\"{lauf['datei']}",
|
|
" if False:\n k.befund(f\"{lauf['datei']}"),
|
|
("wartezeit im Journal mitgezaehlt", "abendkontrolle_wsl.py",
|
|
'enden = [satz for satz in alle if satz["grund"] != "wartezeit"]', "enden = alle"),
|
|
("Lauf nach Schluss als Handelslauf", "abendkontrolle_wsl.py",
|
|
'return bool(lauf["ende"]) and lauf["ende"].get("grund") == "wartezeit" and lauf["takte"] == 0', "return False"),
|
|
("Fragen nicht voneinander getrennt", "abendkontrolle_wsl.py",
|
|
" except Exception as ausnahme:\n self.befund(f\"{name} nicht geprueft",
|
|
" except ZeroDivisionError as ausnahme:\n self.befund(f\"{name} nicht geprueft"),
|
|
# Seit Paket IB2: der Teil ibkr.
|
|
("IBKR Ordner egal", "abendkontrolle_wsl.py",
|
|
" if not ordner.is_dir():\n k.befund(f\"Betriebsordner", " if False:\n k.befund(f\"Betriebsordner"),
|
|
("IBKR Marke egal", "abendkontrolle_wsl.py", ' if marke != "ibkr":', " if False:"),
|
|
("IBKR gesperrt egal", "abendkontrolle_wsl.py", 'if zustand == "gesperrt":', 'if zustand == "nie":'),
|
|
("IBKR Register nicht gelesen", "abendkontrolle_wsl.py",
|
|
' k.schritt("IBKR-Killswitch", pruefe_register_ibkr, ordner, k)', " pass"),
|
|
("IBKR Schlussposition egal", "abendkontrolle_wsl.py",
|
|
' elif ende.get("position") != 0:', " elif False:"),
|
|
("IBKR Lauf ohne ende still", "abendkontrolle_wsl.py",
|
|
' if ende is None:\n # Im Alpaca-Teil meldet das', ' if False:\n # Im Alpaca-Teil meldet das'),
|
|
("IBKR Zeitgeber zaehlt nicht", "abendkontrolle_wsl.py",
|
|
'if not dateien and zeitgeber is not None and zeitgeber != "active":', "if not dateien:"),
|
|
("IBKR nicht gestartet als Befund", "abendkontrolle_wsl.py", 'k.hinweis(f"{NICHT_GESTARTET} (', 'k.befund(f"{NICHT_GESTARTET} ('),
|
|
("IBKR Positionen egal", "abendkontrolle_wsl.py", " if positionen != 0:", " if positionen > 5:"),
|
|
("IBKR Orders egal", "abendkontrolle_wsl.py", " if orders != 0:", " if False:"),
|
|
("IBKR fehlende Kontozeile egal", "abendkontrolle_wsl.py", " if positionen is None or orders is None:", " if False:"),
|
|
("IBKR Kontonummer sichtbar", "abendkontrolle_wsl.py",
|
|
'return maskiere(KONTONUMMER.sub("[konto]", text))', "return maskiere(text)"),
|
|
("IBKR Handelsfenster egal", "abendkontrolle_wsl.py",
|
|
" return not IBKR_SPERRE_VON <= (ny.hour, ny.minute) < IBKR_SPERRE_BIS", " return True"),
|
|
("IBKR Journal mit Alpaca-Dienst", "abendkontrolle_wsl.py", "pruefe_journal(text, k, IBKR_DIENST)", "pruefe_journal(text, k)"),
|
|
("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(',
|
|
'(ablage / f"{tag}{endung}.nix").write_text('),
|
|
("Wochenende mit", "abendkontrolle.py", "if tag < erster_tag or tag.weekday() >= 5:", "if tag < erster_tag:"),
|
|
# Seit Paket IB2: der Teil ibkr.
|
|
("IBKR in die Alpaca-Datei", "abendkontrolle.py", 'IBKR_ENDUNG = "-ibkr"', 'IBKR_ENDUNG = ""'),
|
|
("IBKR mit Alpaca-Kopf", "abendkontrolle.py", ' "ibkr": "Abendkontrolle IBKR (automatisch)",',
|
|
' "ibkr": "Abendkontrolle Trading Plattform (automatisch)",'),
|
|
("IBKR nicht gestartet unsichtbar", "abendkontrolle.py", ' if fakten.get("nicht_gestartet") and not befunde:', " if False:"),
|
|
("IBKR ohne eigenen Starttag", "abendkontrolle.py", "erster_tag=IBKR_ERSTER_TAG, endung=IBKR_ENDUNG", "endung=IBKR_ENDUNG"),
|
|
("mit --ohne-wsl beide Teile", "abendkontrolle.py", '(["alpaca"] if argumente.ohne_wsl else list(TEILE))', "list(TEILE)"),
|
|
("IBKR-Zeile ohne Konto", "abendkontrolle.py", ' konto = fakten.get("konto") or {}', " konto = {}"),
|
|
("Teil ungeprueft", "abendkontrolle.py", 'if ergebnis.get("teil", "alpaca") != teil:', "if False:"),
|
|
("heute vor 22:30", "abendkontrolle.py", "if tag == jetzt.date() and jetzt.time() < FRUEHESTENS:", "if False:"),
|
|
("Befund ohne Markierung", "abendkontrolle.py", "**{len(befunde)} Befund(e)** #needs-review", "{len(befunde)} Befund(e)"),
|
|
("CRLF vergessen", "abendkontrolle.py", 'cr = "\\r" if any(z.endswith("\\r") for z in zeilen) else ""', 'cr = ""'),
|
|
("Formpruefung aus", "abendkontrolle.py",
|
|
'if not isinstance(ergebnis, dict) or not isinstance(ergebnis.get("befunde"), list):', "if False:"),
|
|
("Ueberschrift nur exakt", "abendkontrolle.py",
|
|
"if z == UEBERSCHRIFT or z.startswith(UEBERSCHRIFT + \" \")), None)", "if z == UEBERSCHRIFT), None)"),
|
|
("Abbruch gilt als erledigt", "abendkontrolle.py", 'if ergebnis.get("exit") != 2:', "if True:"),
|
|
("nicht atomar", "abendkontrolle.py", " os.replace(neu, notiz)", " notiz.write_bytes(text.encode(\"utf-8\"))"),
|
|
("Zeit egal", "rueckweg_aapl.py", "if jetzt < FRUEHESTENS:", "if False:"),
|
|
("Runner egal", "rueckweg_aapl.py", " if runner_aktiv(runner_vorgabe):", " if False and runner_aktiv(runner_vorgabe):"),
|
|
("unlesbar gilt als steht", "rueckweg_aapl.py",
|
|
'raise RuntimeError(f"Zustand des Runners nicht lesbar: {zustand!r}")', "return False"),
|
|
("schmutziger Ordner egal", "rueckweg_aapl.py", " else:\n if geaendert:", " else:\n if False:"),
|
|
("folge egal", "rueckweg_aapl.py", 'if MEHRERE not in ziel.read_text(encoding="utf-8"):', "if False:"),
|
|
("Ergebnisdatei egal", "rueckweg_aapl.py", 'if (ablage / "rueckweg.json").exists():', "if False:"),
|
|
("kein Commit", "rueckweg_aapl.py", '("add", ZIEL.as_posix()), ("commit", "-m", NACHRICHT)', '("add", ZIEL.as_posix()),'),
|
|
("halb getauscht gilt als fertig", "rueckweg_aapl.py",
|
|
" if not geaendert:\n return \"config/paper.toml ist schon",
|
|
" if True:\n return \"config/paper.toml ist schon"),
|
|
("Schluesselvergleich aus", "rueckweg_aapl.py", " if fehlend:", " if False:"),
|
|
("Zweig egal", "rueckweg_aapl.py", " if zweig != ZWEIG:", " if False:"),
|
|
("Wertevergleich aus", "rueckweg_aapl.py", " elif neu[name] != alt[name]:", " elif False:"),
|
|
("Gegenrichtung aus", "rueckweg_aapl.py", " elif name not in alt:", " elif False:"),
|
|
]
|
|
TREIBER = {
|
|
"abendkontrolle_wsl.py": "abendkontrolle_angriffe.py",
|
|
"abendkontrolle.py": "abendkontrolle_angriffe.py",
|
|
"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 = []
|
|
# 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__"))
|
|
pfad = ziel / datei
|
|
text = pfad.read_text(encoding="utf-8")
|
|
if text.count(alt) != 1:
|
|
print(f"ANKER {text.count(alt)}-mal: {name}")
|
|
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]), *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
|
|
print(f"{'ROT (gefangen)' if rot else 'GRUEN (UEBERLEBT)'} {name}: {[f[:110] for f in faelle[:2]]}")
|
|
if not rot:
|
|
ueberlebt.append(name)
|
|
print(f"ERGEBNIS: {len(ueberlebt)} ueberlebt von {len(mutanten)} {ueberlebt}")
|
|
sys.exit(1 if ueberlebt else 0)
|