waechter: Werkzeugfehler eines Jobs bis zum naechsten Lauf ausblenden
Heute stand ein schon behobener web_extract-Fehler des News-Jobs bis zum naechsten Lauf (morgen 07:00) im Cockpit. Neuer Knopf "Ausblenden bis zum naechsten Lauf": MC2 merkt sich den Lauf in /srv/models/mc2-quittiert.json, der Waechter blendet genau diesen Lauf aus. Hat der naechste Lauf wieder Fehler, erscheint der Hinweis erneut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4cd856bd34
commit
b0c9549a29
@@ -58,6 +58,9 @@ AUTO_MAX_PRO_STUNDE = int(os.environ.get("MC_WAECHTER_AUTO_MAX", "2"))
|
|||||||
DISK_ROT_PCT = float(os.environ.get("MC_WAECHTER_DISK_ROT", "90"))
|
DISK_ROT_PCT = float(os.environ.get("MC_WAECHTER_DISK_ROT", "90"))
|
||||||
DISK_GELB_PCT = float(os.environ.get("MC_WAECHTER_DISK_GELB", "80"))
|
DISK_GELB_PCT = float(os.environ.get("MC_WAECHTER_DISK_GELB", "80"))
|
||||||
STORE_PATH = Path(os.environ.get("MC_WAECHTER_STORE", str(MODELS_DIR / "mc2-waechter.json")))
|
STORE_PATH = Path(os.environ.get("MC_WAECHTER_STORE", str(MODELS_DIR / "mc2-waechter.json")))
|
||||||
|
# „Ausblenden bis zum nächsten Lauf“ (24.09.2026): Hinweis-ID → Start des Laufs, der ausgeblendet ist.
|
||||||
|
# Schreibt nur MC2 (Knopf), der Steward liest. Ein neuer Lauf macht den Eintrag wirkungslos.
|
||||||
|
QUITTIERT_PATH = Path(os.environ.get("MC_WAECHTER_QUITTIERT", str(MODELS_DIR / "mc2-quittiert.json")))
|
||||||
VERLAUF_MAX = 200
|
VERLAUF_MAX = 200
|
||||||
# Trockenlauf (Probelauf neben dem echten Betrieb): prüft und führt Hinweise, meldet aber
|
# Trockenlauf (Probelauf neben dem echten Betrieb): prüft und führt Hinweise, meldet aber
|
||||||
# nichts an Telegram/Lucy und repariert nichts selbst — das macht weiter der echte Wächter.
|
# nichts an Telegram/Lucy und repariert nichts selbst — das macht weiter der echte Wächter.
|
||||||
@@ -305,6 +308,24 @@ def _errors_log_ende(max_bytes: int = 400_000) -> list[str]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _quittiert() -> dict[str, str]:
|
||||||
|
try:
|
||||||
|
daten = json.loads(QUITTIERT_PATH.read_text(encoding="utf-8"))
|
||||||
|
return daten if isinstance(daten, dict) else {}
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def quittieren(hinweis_id: str, lauf: str) -> None:
|
||||||
|
"""Hinweis bis zum nächsten Lauf ausblenden (atomar schreiben, alte Einträge begrenzen)."""
|
||||||
|
daten = _quittiert()
|
||||||
|
daten[hinweis_id] = lauf
|
||||||
|
daten = dict(list(daten.items())[-50:])
|
||||||
|
tmp = QUITTIERT_PATH.with_suffix(".json.tmp")
|
||||||
|
tmp.write_text(json.dumps(daten, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||||
|
os.replace(tmp, QUITTIERT_PATH)
|
||||||
|
|
||||||
|
|
||||||
def pruefe_hermes_jobs() -> list[Befund]:
|
def pruefe_hermes_jobs() -> list[Befund]:
|
||||||
befunde: list[Befund] = []
|
befunde: list[Befund] = []
|
||||||
jobs = _hermes_jobs()
|
jobs = _hermes_jobs()
|
||||||
@@ -340,13 +361,15 @@ def pruefe_hermes_jobs() -> list[Befund]:
|
|||||||
if alter > 26 * 3600:
|
if alter > 26 * 3600:
|
||||||
continue # nur der aktuelle Lauf zählt
|
continue # nur der aktuelle Lauf zählt
|
||||||
anzahl, beispiel = werkzeugfehler_im_lauf(log_zeilen, jid, lauf["started_at"])
|
anzahl, beispiel = werkzeugfehler_im_lauf(log_zeilen, jid, lauf["started_at"])
|
||||||
if anzahl:
|
befund_id = f"job:{jid}:werkzeug"
|
||||||
|
if anzahl and _quittiert().get(befund_id) != lauf["started_at"]:
|
||||||
befunde.append(Befund(
|
befunde.append(Befund(
|
||||||
id=f"job:{jid}:werkzeug", stufe="gelb",
|
id=befund_id, stufe="gelb",
|
||||||
titel=f"Job „{name}“: {anzahl} Werkzeugfehler im letzten Lauf",
|
titel=f"Job „{name}“: {anzahl} Werkzeugfehler im letzten Lauf",
|
||||||
text=beispiel or "Einzelne Werkzeuge meldeten Fehler, der Lauf selbst kam durch.",
|
text=beispiel or "Einzelne Werkzeuge meldeten Fehler, der Lauf selbst kam durch.",
|
||||||
quelle=f"hermes-cron:{jid}",
|
quelle=f"hermes-cron:{jid}",
|
||||||
aktionen=[_aktion("protokoll", "Protokoll", job=jid)],
|
aktionen=[_aktion("protokoll", "Protokoll", job=jid),
|
||||||
|
_aktion("ausblenden", "Ausblenden bis zum nächsten Lauf", lauf=lauf["started_at"])],
|
||||||
sofort=True))
|
sofort=True))
|
||||||
return befunde
|
return befunde
|
||||||
|
|
||||||
@@ -595,6 +618,10 @@ def fuehre_aktion_aus(hinweis_id: str, aktion_id: str) -> dict:
|
|||||||
if not update_verlauf.freigeben(aktion["baustein"]):
|
if not update_verlauf.freigeben(aktion["baustein"]):
|
||||||
return {"ok": False, "detail": "Der Baustein war schon freigegeben."}
|
return {"ok": False, "detail": "Der Baustein war schon freigegeben."}
|
||||||
return {"ok": True, "text": update_verlauf.FREIGEGEBEN_TEXT}
|
return {"ok": True, "text": update_verlauf.FREIGEGEBEN_TEXT}
|
||||||
|
if aktion_id == "ausblenden":
|
||||||
|
quittieren(hinweis_id, str(aktion.get("lauf", "")))
|
||||||
|
return {"ok": True, "text": "Ausgeblendet. Der Hinweis verschwindet in spätestens einer Minute "
|
||||||
|
"und kommt nur wieder, wenn der nächste Lauf erneut Fehler hat."}
|
||||||
if aktion_id == "job-wiederholen":
|
if aktion_id == "job-wiederholen":
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(["hermes", "cron", "run", aktion["job"]],
|
r = subprocess.run(["hermes", "cron", "run", aktion["job"]],
|
||||||
|
|||||||
@@ -139,3 +139,28 @@ def test_abgestuerzte_pruefung_wird_ein_gelber_befund(monkeypatch, tmp_path):
|
|||||||
hinweise = waechter._stand["hinweise"]
|
hinweise = waechter._stand["hinweise"]
|
||||||
assert "pruefung:pruefe_hermes_jobs" in hinweise
|
assert "pruefung:pruefe_hermes_jobs" in hinweise
|
||||||
assert "neues Format" in hinweise["pruefung:pruefe_hermes_jobs"]["text"]
|
assert "neues Format" in hinweise["pruefung:pruefe_hermes_jobs"]["text"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_ausblenden_gilt_bis_zum_naechsten_lauf(monkeypatch, tmp_path):
|
||||||
|
"""24.09.2026: Ein behobener Werkzeugfehler (z. B. web_extract im News-Job) soll sich
|
||||||
|
ausblenden lassen — kommt aber wieder, sobald ein neuer Lauf erneut Fehler hat."""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
monkeypatch.setattr(waechter, "QUITTIERT_PATH", tmp_path / "mc2-quittiert.json")
|
||||||
|
heute = (datetime.now() - timedelta(hours=2)).isoformat(timespec="seconds")
|
||||||
|
lauf = {"started_at": heute}
|
||||||
|
monkeypatch.setattr(waechter, "_hermes_jobs", lambda: [{"id": "news", "name": "Daily News Report",
|
||||||
|
"enabled": True, "last_status": "ok"}])
|
||||||
|
monkeypatch.setattr(waechter, "_letzter_lauf", lambda jid: lauf)
|
||||||
|
monkeypatch.setattr(waechter, "_errors_log_ende", lambda: [])
|
||||||
|
monkeypatch.setattr(waechter, "werkzeugfehler_im_lauf", lambda zeilen, jid, start: (2, "web_extract: Fehler"))
|
||||||
|
|
||||||
|
befunde = waechter.pruefe_hermes_jobs()
|
||||||
|
assert [b.id for b in befunde] == ["job:news:werkzeug"]
|
||||||
|
assert any(a["id"] == "ausblenden" and a["lauf"] == heute for a in befunde[0].aktionen)
|
||||||
|
|
||||||
|
waechter.quittieren("job:news:werkzeug", heute)
|
||||||
|
assert waechter.pruefe_hermes_jobs() == []
|
||||||
|
|
||||||
|
lauf["started_at"] = (datetime.now() - timedelta(minutes=5)).isoformat(timespec="seconds")
|
||||||
|
assert [b.id for b in waechter.pruefe_hermes_jobs()] == ["job:news:werkzeug"]
|
||||||
|
|||||||
Reference in New Issue
Block a user