Welle 2 · Befundliste mit Ablauf: neu, gesehen, 24 h stumm; Puffer gegen Flackern

Jeder Hinweis hat jetzt einen Zustand (neu, gesehen, stumm bis …) mit Knöpfen in beiden Cockpits; das Homelab-Cockpit
nutzt dieselbe Liste wie die KI-Box (Knöpfe gehen an den Homelab-Teil, neue Route). Stumm heißt: keine
Telegram-Erinnerungen, unten in der Liste, keine Warnleuchte — verschärft er sich von gelb auf rot, gilt stumm nicht
mehr. Ein Hinweis ist erst erledigt, wenn sein Befund drei Takte am Stück fehlt: Kurze Wackler bringen kein „Erledigt“
und gleich danach keinen neuen Alarm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 17:54:56 +02:00
co-authored by Claude Opus 5.5
parent 45f11bc3dc
commit 995a0f34ee
13 changed files with 306 additions and 49 deletions
+95
View File
@@ -0,0 +1,95 @@
"""Befundliste mit Ablauf (Ausbauplan Welle 2, Punkt 10, 25.09.2026): Puffer gegen Flackern, Zustand je Hinweis
(neu, gesehen, stumm bis …) und dass Kritisches trotz „stumm“ durchkommt."""
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from services import waechter
@pytest.fixture
def wache(tmp_path, monkeypatch):
monkeypatch.setattr(waechter, "STORE_PATH", tmp_path / "waechter.json")
monkeypatch.setattr(waechter, "ZUSTAND_PATH", tmp_path / "zustand.json")
monkeypatch.setattr(waechter, "_stand", {"hinweise": {}, "kandidaten": {}, "verlauf": [], "auto": {}, "stand": 0.0})
monkeypatch.setattr(waechter, "_update_laeuft", lambda: False)
monkeypatch.setattr(waechter, "FAIL_AFTER", 2)
meldungen: list[str] = []
monkeypatch.setattr(waechter, "_telegram", lambda betreff, text: meldungen.append(f"{betreff} {text}"))
befund = {"stufe": "rot", "da": True}
def pruefung():
if not befund["da"]:
return []
return [waechter.Befund(id="kern:engine", stufe=befund["stufe"], titel="Der Motor antwortet nicht", text="…",
quelle="engine")]
monkeypatch.setattr(waechter, "PRUEFUNGEN", (pruefung,))
return befund, meldungen
def test_kurzer_wackler_bringt_kein_erledigt_und_keinen_neuen_alarm(wache):
befund, meldungen = wache
waechter.takt()
waechter.takt()
seit = waechter._stand["hinweise"]["kern:engine"]["seit"]
assert len(meldungen) == 1
befund["da"] = False
waechter.takt() # einen Takt weg …
befund["da"] = True
waechter.takt() # … und wieder da: derselbe Hinweis, keine Meldung
assert waechter._stand["hinweise"]["kern:engine"]["seit"] == seit and len(meldungen) == 1
assert [v["art"] for v in waechter._stand["verlauf"]] == ["neu"]
def test_zustand_neu_gesehen_stumm(wache):
befund, meldungen = wache
befund["stufe"] = "gelb"
waechter.takt()
waechter.takt()
(h,) = waechter.lese_stand()["hinweise"]
assert h["zustand"] == "neu" and "fehlt" not in h
assert waechter.fuehre_aktion_aus("kern:engine", "gesehen") == {"ok": True, "text": "Als gesehen markiert."}
assert waechter.lese_stand()["hinweise"][0]["zustand"] == "gesehen"
antwort = waechter.fuehre_aktion_aus("kern:engine", "stumm")
assert antwort["ok"] and antwort["text"].startswith("Stumm für 24 Stunden")
h = waechter.lese_stand()["hinweise"][0]
assert h["zustand"] == "stumm" and h["stumm_bis"] > h["zuletzt"]
# Wird er rot, gilt stumm nicht mehr: Kritisches meldet sich trotzdem.
befund["stufe"] = "rot"
waechter.takt()
assert waechter.lese_stand()["hinweise"][0]["zustand"] == "gesehen"
assert len(meldungen) == 1 and "Der Motor antwortet nicht" in meldungen[0]
assert waechter.fuehre_aktion_aus("kern:engine", "laut")["text"] == "Meldet sich wieder."
def test_stumm_haelt_die_erinnerung_zurueck(wache, monkeypatch):
_, meldungen = wache
waechter.takt()
waechter.takt()
assert len(meldungen) == 1
waechter.fuehre_aktion_aus("kern:engine", "stumm")
monkeypatch.setattr(waechter, "REMIND_S", 0) # jede Erinnerung wäre fällig …
waechter.takt()
assert len(meldungen) == 1 # … aber stumm
waechter.fuehre_aktion_aus("kern:engine", "laut")
waechter.takt()
assert len(meldungen) == 2 and "Immer noch" in meldungen[1]
def test_stumme_stehen_unten_und_der_knopf_geht_auch_im_homelab(wache, monkeypatch):
from routers import homelab
befund, _ = wache
befund["stufe"] = "gelb"
waechter.takt()
waechter.takt()
waechter._stand["hinweise"]["zweiter"] = {"id": "zweiter", "stufe": "gelb", "titel": "B", "text": "", "quelle": "x",
"seit": 1.0, "zuletzt": 2.0, "aktionen": []}
waechter._speichere()
waechter.fuehre_aktion_aus("zweiter", "stumm")
assert [h["id"] for h in waechter.lese_stand()["hinweise"]] == ["kern:engine", "zweiter"]
app = FastAPI()
app.include_router(homelab.router)
client = TestClient(app)
assert client.post("/api/homelab/hinweise/kern:engine/aktion/stumm").json()["ok"] is True
assert client.post("/api/homelab/hinweise/weg/aktion/stumm").status_code == 409
+2 -1
View File
@@ -156,7 +156,8 @@ def test_waechter_meldet_den_partner_erst_nach_geduld(monkeypatch, tmp_path):
assert len(meldungen) == 1 and "Proxmox-PC (http://pve:9001) ist nicht erreichbar" in meldungen[0]
erreichbar[0] = True
waechter.takt()
for _ in range(waechter.ERLEDIGT_NACH): # Puffer gegen Flackern (seit 25.09.2026)
waechter.takt()
assert waechter._stand["hinweise"] == {}
assert meldungen[-1] == "Erledigt: Homelab antwortet nicht."
+4
View File
@@ -95,7 +95,11 @@ def test_befunde_werden_erst_nach_fail_after_takten_zum_hinweis(monkeypatch, tmp
assert "kern:engine" in waechter._stand["hinweise"]
assert meldungen == ["[Box-Problem]"]
# Seit 25.09.2026 mit Puffer: erst nach ERLEDIGT_NACH Takten ohne Befund ist der Hinweis erledigt.
defekt[0] = False
for _ in range(waechter.ERLEDIGT_NACH - 1):
waechter.takt()
assert "kern:engine" in waechter._stand["hinweise"] and meldungen == ["[Box-Problem]"]
waechter.takt()
assert waechter._stand["hinweise"] == {}
assert meldungen == ["[Box-Problem]", "[Box wieder ok]"]