"""Ausreißer ohne feste Schwellen — die Prüfung des Wächters (Ausbauplan „Später“, Punkt 22, seit 26.09.2026). Beide Rollen: Die KI-Box lernt ihre eigene Messreihe („box“), der Homelab-Teil die des Proxmox-Hosts („pve“) und jedes Gasts („ct-104“, „vm-106“). Das Lernen und Vergleichen steht in kern/ausreisser.py; hier kommen Gerätenamen und die Sätze der Hinweise dazu. Der Wächter ruft pruefe_ausreisser() jede Minute; gerechnet wird nur, wenn eine Stunde neu abgeschlossen ist. Hinweise sind gelb — etwas Ungewöhnliches, kein Ausfall; „24 h stumm“ passt, wenn es gewollt ist. """ import time from kern import ausreisser as kern_ausreisser from kern import messreihen from kern.einstellungen import einstellungen TITEL = {"temp_cpu": "ist seit {n} Stunden ungewöhnlich warm", "temp_gpu": "ist seit {n} Stunden ungewöhnlich warm", "netz_rx": "hat seit {n} Stunden ungewöhnlich viel {was}", "netz_tx": "hat seit {n} Stunden ungewöhnlich viel {was}"} def quellen(jetzt: float) -> list[str]: if einstellungen().rolle == "box": return ["box"] return [q for q in messreihen.quellen(jetzt - 2 * 86400, jetzt + 1) if q == "pve" or q.startswith(("ct-", "vm-"))] def geraetename(quelle: str) -> str: if quelle == "box": return "Die KI-Box" if quelle == "pve": return "Der Proxmox-Host" try: from services.homelab import apps, kanal for g in ((kanal.bericht() or {}).get("bericht") or {}).get("gaeste") or []: if f"{'ct' if g.get('art') == 'lxc' else 'vm'}-{g.get('vmid')}" == quelle: app = apps.app_fuer((g.get("app") or {}).get("kennung"), g.get("name")) return app.name if app else str(g.get("name") or quelle) except Exception: pass return quelle def satz(a: dict) -> tuple[str, str]: anzeige, einheit, _, _ = kern_ausreisser.GROESSEN[a["name"]] muster = TITEL.get(a["name"], "braucht seit {n} Stunden ungewöhnlich viel {was}") titel = f"{geraetename(a['quelle'])} {muster.format(n=a['folge'], was=anzeige)}" text = (f"{kern_ausreisser.zahl_text(a['wert'], einheit)} statt sonst etwa " f"{kern_ausreisser.zahl_text(a['normal'], einheit)} {kern_ausreisser.fach_text(a['fach'])}. Ist das gewollt " "(ein Update, eine Sicherung, ein Test), schalte den Hinweis stumm; sonst im Monitoring nachsehen.") return titel, text def pruefe_ausreisser(jetzt: float | None = None) -> list: from services.waechter import Befund jetzt = time.time() if jetzt is None else jetzt daten = kern_ausreisser.lernen_und_pruefen(quellen(jetzt), jetzt) befunde = [] for a in kern_ausreisser.auffaelligkeiten(daten): titel, text = satz(a) befunde.append(Befund(id=f"ausreisser:{a['quelle']}:{a['name']}", stufe="gelb", titel=titel, text=text, quelle=a["quelle"], sofort=True)) return befunde def lage() -> dict: """Für GET /api/ausreisser bzw. /api/homelab/ausreisser: Lernstand und was gerade auffällt.""" daten = kern_ausreisser.lesen() return {**kern_ausreisser.lernstand(daten), "min_wochen": kern_ausreisser.MIN_WOCHEN, "auffaellig": [{**a, "titel": satz(a)[0]} for a in kern_ausreisser.auffaelligkeiten(daten)]}