"""In-Memory-Status für den manuellen Health-Run des Portals. Pure Funktionen ohne DB und IO, damit die Zustandsübergänge unit-testbar sind. Der Status lebt nur im Prozess. Nach einem Neustart ist er wieder idle, ein dabei abgebrochener Lauf hinterlässt höchstens einen partiellen Stand in source_health_checks, den der nächste Lauf archiviert und überschreibt (identisch zum Verhalten des Monitor-Nachtjobs um 04:00 Uhr). """ from datetime import datetime def new_state() -> dict: return { "running": False, "started_at": None, "finished_at": None, "result": None, "error": None, } def _now_iso(now=None) -> str: return now if now is not None else datetime.now().isoformat(timespec="seconds") def try_start(state: dict, now=None) -> bool: """Setzt den Lauf auf running. False, wenn schon einer läuft.""" if state.get("running"): return False state["running"] = True state["started_at"] = _now_iso(now) state["finished_at"] = None state["result"] = None state["error"] = None return True def mark_finished(state: dict, result: dict, now=None) -> None: state["running"] = False state["finished_at"] = _now_iso(now) state["result"] = result def mark_failed(state: dict, error: str, now=None) -> None: state["running"] = False state["finished_at"] = _now_iso(now) state["error"] = error