Files
mission-control-v2/backend/services/ausreisser.py
T
HitonabiandClaude Opus 5.5 2972cdfeca Später · 22 Ausreißer ohne feste Schwellen: Normalwert je Wochenstunde
- kern/ausreisser.py: 168 Fächer je Reihe (CPU, GPU, Speicher, Temperaturen, Netz), erst genau, ab acht Werten
  gleitend; auffällig ab 4 Streuungen und Mindestabstand, erst mit drei Wochen Erfahrung; Ausreißer gekappt gelernt
- services/ausreisser.py: Wächter beider Rollen, gelber Hinweis nach zwei auffälligen Stunden mit Normalwert und
  Wochenstunde im Satz; erster Lauf lernt aus den aufbewahrten acht Tagen
- Monitoring: Zeile mit Lernwoche bzw. was gerade auffällt; GET /api/ausreisser, /api/homelab/ausreisser

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:04:30 +02:00

69 lines
3.2 KiB
Python

"""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)]}