Files
claude-regelwerk/.vault-tools/trading-plattform-pruefung/abendkontrolle_wsl.py
T
Pascal MichelandClaude Opus 5 ea278dc24f Automatischer Push: 46 Datei(en) geändert
- .claude/agents/planer.md
- .claude/skills/plan-executer-pruefung/SKILL.md
- .claude/workflows/plankritik.js
- .claude/workflows/planung.js
- .claude/workflows/pruefung.js
- .claude/workflows/verbrauch.js
- .claude/workflows/welle-mit-pruefung.js
- .vault-tools/plan-kopie/LIESMICH.md
- .vault-tools/plan-kopie/plan_zeichen.py
- .vault-tools/plan-kopie/rot_nachweis.py
- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/abendkontrolle.py
- … und 34 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-25 04:00:02 +02:00

476 lines
23 KiB
Python

"""Abendkontrolle eines Paper-Handelstags: liest nur, urteilt im Code, meldet nur bei Befund.
Koordinator-Werkzeug, nicht fuers Repo. Laeuft in WSL im Venv des Projekts; gestartet von
abendkontrolle.py (Windows-Aufgabe "Trading Plattform - Abendkontrolle", werktags 22:45 Berlin und
bei jeder Anmeldung). Auftrag Pascal 2026-09-24: taegliche Abendkontrolle, dauerhaft, uebersteht
Neustarts und blockiert keine Updates von Claude Code oder Desktop -- deshalb ohne Claude-Prozess.
Entscheidungsliste (Frage -> Herkunft der Antwort):
1. Ist <tag> ein Handelstag? Quelle: GET /v2/calendar?start=<tag>&end=<tag>, gemessen am
2026-09-24: eine Liste von Objekten mit 'date' (JJJJ-MM-TT) und
'close' (HH:MM New York); Feiertage fehlen, das Wochenende ist
leer, verkuerzte Tage tragen ein frueheres 'close'. Antwort
nicht lesbar -> Befund, und die Fragen 2 bis 7 bleiben offen.
2. Lief ein scharfer Runner? Quelle: scripts/paper.py:554, Protokoll
artifacts/paper/<UTC-Stempel>-paper.jsonl. Der Start um 09:10
New York traegt das Datum des Handelstags (gilt, solange
max_wartezeit_h unter etwa 13 h bleibt; heute 8). Keines -> Befund.
3. Wie endete jeder Lauf? Quelle: scripts/paper.py:517, Zeile "Ende: <grund>, Position <n>,
Orders <n>, Killswitch <z>, Exit <n>" im Journal, und die Zeile
'ende' jedes Protokolls. Projektvorgabe: jeder Lauf endet mit
boersenschluss und Exit 0, der letzte mit Position 0. Ausgenommen
ist ein Lauf ausserhalb der Handelszeit (Ende wartezeit, im
Protokoll ohne Takt; gemessen am 2026-09-20 nach WSL-Neustarts):
nur ein Hinweis. Ein Lauf, der mittags mit abbruch endet, ist ein
Befund, auch wenn der naechste sauber schliesst. Keine Endezeile
im Journal -> Befund; "Failed with result" -> Befund.
4. Was sagt das Protokoll? Quelle: handelstag_auswertung.py (dieses Verzeichnis); jede
Zeile, die mit "BEFUND" beginnt, ist ein Befund; Exit ungleich 0
ebenfalls.
5. Ist das Konto glatt? Quelle: dieselbe Auswertung mit --broker, Zeile "offene
positionen beim broker jetzt: <n>". n != 0 -> Befund; fehlt die
Zeile -> Befund (die Frage blieb unbeantwortet).
6. Hat der Runner gehandelt? Projektvorgabe (Pascal 2026-09-24, Paket AF): an einem Handelstag
steht der Killswitch in mindestens einem Takt auf aktiv. Kein
solcher Takt -> Befund "Tag ohne Handel". Takte, die nach dem
ersten aktiven nicht mehr aktiv sind (Watchdog, Pascals Anhalten),
sind ein Hinweis: Der Runner hat dafuer schon selbst gemeldet.
Ereignisse auto_fortsetzen werden berichtet, nicht bewertet.
7. Liegt der Tagesbericht? Quelle: scripts/tagesauswertung.py:58 und :192,
data/paper/auswertungen/<tag>.md. Fehlt -> Befund.
8. Laufen Dashboard und Zeitgeber? Projektvorgabe (Pakete F und Z): trading-dashboard.service,
trading-paper.timer, trading-auswertung.timer "active"; ein
anderer Zustand wird nach DIENSTE_NOCHMAL_S einmal neu gelesen
(eine eben geweckte WSL startet ihre Dienste noch).
9. Wie steht der Killswitch? Quelle: Tabelle killswitch_zustand in data/registry.db
(trading/registry/registry.py:281). gesperrt -> Befund: ohne
"Freigeben" handelt der naechste Tag nicht. Ein Zustand
ausserhalb aktiv/angehalten/gesperrt -> Befund.
Jede Frage laeuft fuer sich: Scheitert eine (Sperre im Register, Journal nicht lesbar), ist das ein
Befund, und die anderen werden trotzdem beantwortet.
Meldung: bei mindestens einem Befund eine Meldung mit hoher Prioritaet ueber den Melder des Projekts.
Ob sie ankam, sagt der Rueckgabewert von Melder.melde (None oder der Grund, trading/paper/melder.py);
er steht in fakten.meldung. Kein Alarm entsteht, wenn WSL selbst nicht laeuft -- dann steht nur die
Zeile in der Tagesnotiz (benannte Luecke; der Totmann in Home Assistant, Paket Z2-7, deckt sie ab).
Nicht gesehen: die Guete der Kurse (steht im Bericht), Teilausfuehrungen einzeln, alles vor dem
Handelstag. Betraege gibt das Werkzeug nie aus; fremde Zeilen laufen durch maskiere().
Ein Fangnetz, kein Nachweis ueber den Handelstag.
Aufruf (WSL, Venv): python abendkontrolle_wsl.py --tag JJJJ-MM-TT [--ohne-meldung]
Nur eine Meldung schicken: python abendkontrolle_wsl.py --meldung "<titel>" "<text>"
Fuer Angriffsfaelle ohne Netz und ohne systemd: --projekt, --kalender, --journal, --dienste,
--ohne-broker, --ohne-kerzen.
Ausgabe: genau eine JSON-Zeile auf stdout. Exit 0 gelaufen (Befunde stehen in der Ausgabe),
2 abgebrochen (die bis dahin gesammelten Befunde stehen trotzdem in der Ausgabe).
"""
import argparse
import json
import os
import re
import sqlite3
import subprocess
import sys
import time
from datetime import date
from datetime import timedelta
from pathlib import Path
PROJEKT = "/mnt/d/Claude/Code Projekte/active - Trading Plattform"
GEHEIMNISSE = "/home/claude/.config/trading-plattform/secrets.toml"
AUSWERTUNG = Path(__file__).resolve().parent / "handelstag_auswertung.py"
# Nur fuer die Angriffsfaelle: ein Ersatz, der vorgegebene Zeilen druckt, statt ans Netz zu gehen.
AUSWERTUNG_ERSATZ = os.environ.get("ABENDKONTROLLE_AUSWERTUNG")
HANDEL_URL = "https://paper-api.alpaca.markets"
DIENSTE = ("trading-dashboard.service", "trading-paper.timer", "trading-auswertung.timer")
DIENSTE_NOCHMAL_S = 30
KILLSWITCH_ZUSTAENDE = ("aktiv", "angehalten", "gesperrt")
AUSWERTUNG_TIMEOUT_S = 900
ENDE = re.compile(
r"Ende: (?P<grund>[^,]+), Position (?P<position>[^,]+), Orders (?P<orders>[^,]+), "
r"Killswitch (?P<killswitch>[^,]+), Exit (?P<exit>-?\d+)"
)
BROKER_OFFEN = re.compile(r"offene positionen beim broker jetzt: (\d+)")
# Maske fuer alles, was aus fremder Ausgabe in Notiz und Meldung geht, in dieser Reihenfolge:
# - jede Zahl hinter einem Geldwort,
# - jede Zahl vor einer Waehrung,
# - jede Zahl mit mehr als zwei Nachkommastellen,
# - jede Zahl mit mindestens vier Stellen vor dem Komma und Nachkommastellen.
MASKEN = (
(re.compile(r"(?i)\b(kontowert|konto|startkapital|equity|cash|preis|kurs|usd|pnl)([^0-9-]{0,3})-?[0-9][0-9.,]*"),
lambda m: f"{m.group(1)}{m.group(2)}[x]"),
(re.compile(r"-?\d[\d.,]*\s?(?:USD|EUR|\$|€)"), lambda m: "[x]"),
(re.compile(r"-?\d[\d,]*\.\d{3,}"), lambda m: "[x]"),
(re.compile(r"-?\d{4,}[.,]\d{1,2}\b"), lambda m: "[x]"),
)
def maskiere(text: str) -> str:
for muster, ersatz in MASKEN:
text = muster.sub(ersatz, text)
return text
class Kontrolle:
def __init__(self) -> None:
self.befunde: list[str] = []
self.hinweise: list[str] = []
self.fakten: dict = {}
def befund(self, text: str) -> None:
self.befunde.append(maskiere(text))
def hinweis(self, text: str) -> None:
self.hinweise.append(maskiere(text))
def schritt(self, name: str, aufruf, *argumente):
"""Eine Frage fuer sich: Scheitert sie, ist das ein Befund, und der Rest laeuft weiter."""
try:
return aufruf(*argumente)
except Exception as ausnahme:
self.befund(f"{name} nicht geprueft ({type(ausnahme).__name__}: {str(ausnahme)[:160]})")
return None
# --- 1 Handelstag ---------------------------------------------------------------------------------
def lies_kalender(tag: str, kalender_datei: str | None, projekt: Path):
"""Die Liste aus /v2/calendar oder aus einer Datei (Angriffsfaelle)."""
if kalender_datei is not None:
return json.loads(Path(kalender_datei).read_text(encoding="utf-8"))
import requests # erst hier: Die Angriffsfaelle laufen ohne Netz
sys.path.insert(0, str(projekt))
sys.path.insert(0, str(AUSWERTUNG.parent))
from handelstag_auswertung import kopfzeilen # liest die Schluessel, gibt sie nie aus
kopf = kopfzeilen()
if kopf is None:
raise RuntimeError("Schluessel fuer Alpaca nicht lesbar")
antwort = requests.get(
f"{HANDEL_URL}/v2/calendar", headers=kopf, params={"start": tag, "end": tag}, timeout=30
)
antwort.raise_for_status()
return antwort.json()
def ist_handelstag(tag: str, liste) -> bool | None:
"""True, False, oder None, wenn die Antwort nicht die gemessene Form hat."""
if not isinstance(liste, list):
return None
for eintrag in liste:
if not isinstance(eintrag, dict) or not isinstance(eintrag.get("date"), str):
return None
return any(eintrag["date"] == tag for eintrag in liste)
# --- 2 Protokolle, 3 Ende je Lauf, 6 Handel --------------------------------------------------------
def protokolle(projekt: Path, tag: str) -> list[Path]:
return sorted((projekt / "artifacts" / "paper").glob(f"{tag}T*-paper.jsonl"))
def lies_protokoll(pfad: Path, k: Kontrolle) -> dict:
takte = aktiv = pausiert_danach = unlesbar = 0
auto = []
ende = None
# Dateiiteration statt splitlines: U+2028 in einem Text zerlegte sonst eine JSON-Zeile.
with pfad.open(encoding="utf-8", errors="replace") as datei:
for zeile in datei:
if not zeile.strip():
continue
try:
satz = json.loads(zeile)
except json.JSONDecodeError:
unlesbar += 1
continue
if not isinstance(satz, dict):
unlesbar += 1
continue
art = satz.get("art")
if art == "takt":
takte += 1
if satz.get("killswitch") == "aktiv":
aktiv += 1
elif aktiv:
pausiert_danach += 1
elif art == "ereignis" and satz.get("was") == "auto_fortsetzen":
auto.append(str(satz.get("text"))[:200])
elif art == "ende":
ende = {
schluessel: satz.get(schluessel)
for schluessel in ("grund", "fehler", "position", "positionen", "orders", "killswitch")
}
if unlesbar:
k.befund(f"{pfad.name}: {unlesbar} Zeile(n) nicht lesbar")
return {"datei": pfad.name, "takte": takte, "takte_aktiv": aktiv, "takte_pausiert_danach": pausiert_danach,
"auto_fortsetzen": auto, "ende": ende}
def ausserhalb_der_handelszeit(lauf: dict) -> bool:
"""Ein Lauf, der ohne einen einzigen Takt mit wartezeit endete (Start nach Boersenschluss)."""
return bool(lauf["ende"]) and lauf["ende"].get("grund") == "wartezeit" and lauf["takte"] == 0
def pruefe_laeufe(laeufe: list[dict], k: Kontrolle) -> None:
for lauf in laeufe:
ende = lauf["ende"]
if ende is None:
continue # die Auswertung meldet "keine Zeile 'ende'"
if ausserhalb_der_handelszeit(lauf):
k.hinweis(f"{lauf['datei']}: Lauf ausserhalb der Handelszeit (Ende wartezeit), nicht bewertet")
continue
if ende.get("grund") != "boersenschluss":
k.befund(f"{lauf['datei']}: Lauf endete mit {ende.get('grund')} nach {lauf['takte']} Takten")
if ende.get("fehler"):
k.befund(f"{lauf['datei']}: Lauf endete mit Fehler {str(ende.get('fehler'))[:120]}")
if lauf["takte_pausiert_danach"]:
k.hinweis(f"{lauf['datei']}: {lauf['takte_pausiert_danach']} Takte nach dem Fortsetzen nicht aktiv")
for text in lauf["auto_fortsetzen"]:
k.hinweis(f"auto_fortsetzen: {text}")
bewertet = [lauf for lauf in laeufe if not ausserhalb_der_handelszeit(lauf)]
if laeufe and not bewertet:
k.befund("kein Lauf des scharfen Runners in der Handelszeit, nur Laeufe nach Boersenschluss")
elif bewertet and sum(lauf["takte_aktiv"] for lauf in bewertet) == 0:
k.befund("Tag ohne Handel: in keinem Takt stand der Killswitch auf aktiv")
# --- 3 Journal -------------------------------------------------------------------------------------
def lies_journal(tag: str, journal_datei: str | None) -> str:
if journal_datei is not None:
return Path(journal_datei).read_text(encoding="utf-8", errors="replace")
morgen = (date.fromisoformat(tag) + timedelta(days=1)).isoformat()
lauf = subprocess.run(
["journalctl", "-u", "trading-paper.service", "--since", f"{tag} 00:00", "--until", f"{morgen} 06:00",
"-o", "cat", "--no-pager"],
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60,
)
if lauf.returncode != 0:
raise RuntimeError(f"journalctl Exit {lauf.returncode}")
return lauf.stdout
def pruefe_journal(text: str, k: Kontrolle) -> dict | None:
alle = [{schluessel: wert.strip() for schluessel, wert in m.groupdict().items()} for m in ENDE.finditer(text)]
# Ein Lauf ausserhalb der Handelszeit (Neustart nach Boersenschluss) endet mit wartezeit und Exit 3.
enden = [satz for satz in alle if satz["grund"] != "wartezeit"]
k.fakten["enden_im_journal"] = len(enden)
if len(alle) > len(enden):
k.hinweis(f"{len(alle) - len(enden)} Lauf/Laeufe ausserhalb der Handelszeit im Journal (Ende wartezeit)")
if "Failed with result" in text:
k.befund("Journal: trading-paper.service endete mit 'Failed with result'")
if not enden:
k.befund("Journal: keine Zeile 'Ende: …, Exit n' eines Handelslaufs von trading-paper.service an diesem Tag")
return None
# Jeder Lauf des Tages zaehlt: Ein Runner, der mittags mit einer Stoerung endete und neu gestartet
# wurde, ist auch dann ein Befund, wenn der zweite sauber schliesst.
for nummer, satz in enumerate(enden, start=1):
if satz["exit"] != "0":
k.befund(f"Runner (Lauf {nummer} von {len(enden)}) endete mit Exit {satz['exit']} (Grund {satz['grund']})")
if satz["grund"] != "boersenschluss":
k.befund(f"Runner (Lauf {nummer} von {len(enden)}) endete nicht mit boersenschluss, sondern mit {satz['grund']}")
letzte = enden[-1]
if letzte["position"] != "0":
k.befund(f"Runner endete mit Position {letzte['position']}")
return letzte
# --- 4 und 5 Auswertung ----------------------------------------------------------------------------
def fahre_auswertung(pfad: Path, projekt: Path, broker: bool, kerzen: bool, k: Kontrolle) -> None:
befehl = [sys.executable, AUSWERTUNG_ERSATZ or str(AUSWERTUNG), str(pfad)]
if broker:
befehl.append("--broker")
if kerzen:
befehl.append("--kerzen")
lauf = subprocess.run(
befehl, cwd=projekt, capture_output=True, text=True, encoding="utf-8", errors="replace",
timeout=AUSWERTUNG_TIMEOUT_S, env={**os.environ, "PYTHONIOENCODING": "utf-8"},
)
zeilen = lauf.stdout.splitlines() + lauf.stderr.splitlines()
for zeile in zeilen:
if zeile.strip().startswith("BEFUND") or zeile.startswith("VORAUSSETZUNG FEHLT"):
k.befund(f"{pfad.name}: {zeile.strip()}")
if lauf.returncode != 0:
k.befund(f"{pfad.name}: handelstag_auswertung.py endete mit Exit {lauf.returncode}")
if broker:
offen = [int(m.group(1)) for m in map(BROKER_OFFEN.search, zeilen) if m]
if not offen:
k.befund(f"{pfad.name}: keine Zeile zu offenen Positionen beim Broker -- Konto nicht geprueft")
else:
k.fakten["broker_offen"] = offen[-1]
if offen[-1] != 0:
k.befund(f"Beim Broker stehen {offen[-1]} offene Position(en)")
# --- 8 Dienste und 9 Killswitch -------------------------------------------------------------------
def lies_dienste(dienste_datei: str | None) -> dict[str, str]:
if dienste_datei is not None:
return json.loads(Path(dienste_datei).read_text(encoding="utf-8"))
def lesen():
ergebnis = {}
for dienst in DIENSTE:
lauf = subprocess.run(["systemctl", "is-active", dienst], capture_output=True, text=True, timeout=30)
ergebnis[dienst] = lauf.stdout.strip() or f"exit {lauf.returncode}"
return ergebnis
zustaende = lesen()
if any(wert != "active" for wert in zustaende.values()):
time.sleep(DIENSTE_NOCHMAL_S)
zustaende = lesen()
return zustaende
def pruefe_dienste(zustaende, k: Kontrolle) -> None:
if not isinstance(zustaende, dict):
k.befund("Dienste: Zustand nicht lesbar")
return
k.fakten["dienste"] = zustaende
for dienst in DIENSTE:
if zustaende.get(dienst) != "active":
k.befund(f"{dienst} ist nicht aktiv ({zustaende.get(dienst)})")
def pruefe_killswitch(projekt: Path, k: Kontrolle) -> None:
register = projekt / "data" / "registry.db"
if not register.is_file():
k.befund("Register data/registry.db fehlt")
return
with sqlite3.connect(f"file:{register}?mode=ro", uri=True, timeout=10) as verbindung:
zeile = verbindung.execute(
"SELECT zustand, seit, ausloeser FROM killswitch_zustand WHERE id = 1"
).fetchone()
if zeile is None:
k.befund("Register: kein Killswitch-Zustand")
return
k.fakten["killswitch"] = {"zustand": zeile[0], "seit": zeile[1], "ausloeser": zeile[2]}
if zeile[0] == "gesperrt":
k.befund("Killswitch steht auf gesperrt: Ohne 'Freigeben' handelt der naechste Tag nicht")
elif zeile[0] not in KILLSWITCH_ZUSTAENDE:
k.befund(f"Killswitch in unbekanntem Zustand {zeile[0]!r}")
# --- Meldung ---------------------------------------------------------------------------------------
def melde(titel: str, text: str, projekt: Path) -> str:
"""Eine Meldung mit hoher Prioritaet ueber den Melder des Projekts. Scheitert nie nach aussen.
Melder.melde wirft nicht: Es gibt None bei Erfolg und sonst den Grund zurueck (Typ der Ausnahme
oder "HTTP <status>"). Genau das steht im Ergebnis.
"""
try:
sys.path.insert(0, str(projekt))
os.chdir(projekt)
from trading.paper.melder import baue_melder
from trading.paper.meldungen import HOCH
from trading.paper.meldungen import Meldung
melder = baue_melder(config_pfad="config/paper.toml")
if not melder.aktiv:
return "nicht gesendet: Melder nicht aktiv"
fehler = melder.melde(Meldung(titel=titel, text=maskiere(text), prioritaet=HOCH, tags="warning"))
return "gesendet" if fehler is None else f"nicht gesendet: {fehler}"
except Exception as ausnahme: # die Kontrolle haengt nicht an der Meldung
return f"nicht gesendet: {type(ausnahme).__name__}"
# --- Ablauf ----------------------------------------------------------------------------------------
def kontrolliere(argumente: argparse.Namespace, k: Kontrolle) -> None:
projekt = Path(argumente.projekt)
tag = argumente.tag
date.fromisoformat(tag) # ein falsches Datum ist ein Bedienfehler und bricht ab
# handelstag_auswertung.py und der Melder lesen aus dem Arbeitsverzeichnis (Projektordner).
os.chdir(projekt)
zustaende = k.schritt("Dienste", lies_dienste, argumente.dienste)
if zustaende is not None:
pruefe_dienste(zustaende, k)
k.schritt("Killswitch", pruefe_killswitch, projekt, k)
liste = k.schritt("Kalender, und damit Lauf, Ende und Bericht,", lies_kalender, tag, argumente.kalender, projekt)
handelstag = None if liste is None else ist_handelstag(tag, liste)
k.fakten["handelstag"] = handelstag
if handelstag is None:
if liste is not None:
k.befund("Kalender in unerwarteter Form; Lauf, Ende und Bericht ungeprueft")
return
dateien = protokolle(projekt, tag)
if not handelstag:
if dateien:
k.hinweis(f"kein Handelstag, aber {len(dateien)} Protokoll(e) des scharfen Runners")
return
if not dateien:
k.befund("kein Protokoll des scharfen Runners an einem Handelstag -- der Runner lief nicht")
laeufe = [lauf for lauf in (k.schritt(f"Protokoll {pfad.name}", lies_protokoll, pfad, k) for pfad in dateien) if lauf]
k.fakten["laeufe"] = laeufe
pruefe_laeufe(laeufe, k)
text = k.schritt("Journal", lies_journal, tag, argumente.journal)
if text is not None:
k.fakten["ende"] = pruefe_journal(text, k)
ausserhalb = {lauf["datei"] for lauf in laeufe if ausserhalb_der_handelszeit(lauf)}
for pfad in dateien:
if pfad.name in ausserhalb:
continue
k.schritt(f"Auswertung {pfad.name}", fahre_auswertung, pfad, projekt,
not argumente.ohne_broker, not argumente.ohne_kerzen, k)
bericht = projekt / "data" / "paper" / "auswertungen" / f"{tag}.md"
k.fakten["bericht"] = bericht.is_file()
if not bericht.is_file():
k.befund(f"Tagesbericht data/paper/auswertungen/{tag}.md fehlt")
def parse_args(argv=None) -> argparse.Namespace:
zerleger = argparse.ArgumentParser(description=__doc__.splitlines()[0], allow_abbrev=False)
zerleger.add_argument("--tag")
zerleger.add_argument("--meldung", nargs=2, metavar=("TITEL", "TEXT"))
zerleger.add_argument("--projekt", default=PROJEKT)
zerleger.add_argument("--kalender")
zerleger.add_argument("--journal")
zerleger.add_argument("--dienste")
zerleger.add_argument("--ohne-broker", action="store_true")
zerleger.add_argument("--ohne-kerzen", action="store_true")
zerleger.add_argument("--ohne-meldung", action="store_true")
argumente = zerleger.parse_args(argv)
if bool(argumente.tag) == bool(argumente.meldung):
zerleger.error("genau eines von --tag und --meldung")
return argumente
def main(argv=None) -> int:
os.environ.setdefault("TRADING_GEHEIMNISSE", GEHEIMNISSE)
argumente = parse_args(argv)
if argumente.meldung:
print(json.dumps({"meldung": melde(*argumente.meldung, Path(argumente.projekt))}, ensure_ascii=False))
return 0
k = Kontrolle()
code = 0
try:
kontrolliere(argumente, k)
except Exception as ausnahme:
k.befund(f"Abendkontrolle abgebrochen: {type(ausnahme).__name__}: {ausnahme}")
code = 2
if k.befunde and not argumente.ohne_meldung:
text = "\n".join(f"- {befund}" for befund in k.befunde[:12])
k.fakten["meldung"] = melde(
f"Abendkontrolle {argumente.tag}: {len(k.befunde)} Befund(e)", text, Path(argumente.projekt)
)
print(json.dumps(
{"tag": argumente.tag, "befunde": k.befunde, "hinweise": k.hinweise, "fakten": k.fakten, "exit": code},
ensure_ascii=False, default=str,
))
return code
if __name__ == "__main__":
sys.exit(main())