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>
This commit is contained in:
Hitonabi
2026-09-26 10:04:30 +02:00
co-authored by Claude Opus 5.5
parent 904598031a
commit 2972cdfeca
32 changed files with 414 additions and 27 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{i as e,r as t}from"./dist-aE7UmubC.js";import{G as n,K as r}from"./index-CuG_RlEw.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-DG7u1mB-.js";var l=t(),u=e();function d(e){let t=(0,l.c)(8),{onSchliessen:d}=e,f;t[0]===d?f=t[1]:(f=e=>!e&&d(),t[0]=d,t[1]=f);let p;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(p=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(n,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=p):p=t[2];let m;t[3]===d?m=t[4]:(m=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-7 border-white/[0.06] bg-[#0a0d14] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[p,(0,u.jsx)(r,{onGewaehlt:d})]}),t[3]=d,t[4]=m);let h;return t[5]!==f||t[6]!==m?(h=(0,u.jsx)(c,{open:!0,onOpenChange:f,children:m}),t[5]=f,t[6]=m,t[7]=h):h=t[7],h}export{d as MenueSchublade};
import{i as e,r as t}from"./dist-aE7UmubC.js";import{G as n,K as r}from"./index-Cdq4iteU.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-DG7u1mB-.js";var l=t(),u=e();function d(e){let t=(0,l.c)(8),{onSchliessen:d}=e,f;t[0]===d?f=t[1]:(f=e=>!e&&d(),t[0]=d,t[1]=f);let p;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(p=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(n,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=p):p=t[2];let m;t[3]===d?m=t[4]:(m=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-7 border-white/[0.06] bg-[#0a0d14] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[p,(0,u.jsx)(r,{onGewaehlt:d})]}),t[3]=d,t[4]=m);let h;return t[5]!==f||t[6]!==m?(h=(0,u.jsx)(c,{open:!0,onOpenChange:f,children:m}),t[5]=f,t[6]=m,t[7]=h):h=t[7],h}export{d as MenueSchublade};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -9,11 +9,11 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<title>Homelab Orchestrator</title>
<script type="module" crossorigin src="/assets/index-CuG_RlEw.js"></script>
<script type="module" crossorigin src="/assets/index-Cdq4iteU.js"></script>
<link rel="modulepreload" crossorigin href="/assets/dist-aE7UmubC.js">
<link rel="modulepreload" crossorigin href="/assets/button-BHVKhtQ6.js">
<link rel="modulepreload" crossorigin href="/assets/dist-CakcBaML.js">
<link rel="modulepreload" crossorigin href="/assets/abfragen-CDCgpAto.js">
<link rel="modulepreload" crossorigin href="/assets/abfragen-DNMpmgyt.js">
<link rel="modulepreload" crossorigin href="/assets/useSelector-DBNxxw17.js">
<link rel="modulepreload" crossorigin href="/assets/useMatch-DDVpPKJI.js">
<link rel="modulepreload" crossorigin href="/assets/useRouterState-DJr4hrdj.js">
+11 -1
View File
@@ -11,7 +11,7 @@ import { stromVerbunden } from "./strom"
import type {
Ablauf, AktionsErgebnis, Altrest, AufraeumKandidat, BoxMesswerte, EinstellungenStand, Hinweis, HomelabEinstellungen,
HomelabLauf, HomelabMesswerte, HomelabSpeicher, HomelabStand, HomelabZertifikate, NeuesGeraet, NeuerStack, NeuWahl, DockerLage, Neuerungen, HomelabWartung, WartungsRegel,
LucyAutonomie, LucyStufe, Rueckfragen, Wochenbericht, Platte, HomelabPlatten, Instanz, InstanzMitStand, Job, Modell, Nutzung, PartnerStatus,
LucyAutonomie, LucyStufe, Rueckfragen, Wochenbericht, Platte, HomelabPlatten, AusreisserLage, Instanz, InstanzMitStand, Job, Modell, Nutzung, PartnerStatus,
ProtokollEintrag, Radar, SammelPlanSchritt, Sammellauf, Sicherung, Start, TelegramTest, UpdateDetails, ZielAktion,
ZielStand, Zeitraum,
} from "./typen"
@@ -512,6 +512,16 @@ export const useNeuerungen = (zid: string, baustein: string, aktiv: boolean) =>
retry: false,
})
/** Ausreißer ohne feste Schwellen (seit 26.09.2026): Lernstand und was gerade auffällt, je Bereich. */
export const useAusreisser = (bereich: "box" | "homelab", aktiv = true) =>
useQuery({
queryKey: ["ausreisser", bereich],
queryFn: () => api<AusreisserLage>(bereich === "box" ? "/api/ausreisser" : "/api/homelab/ausreisser"),
enabled: aktiv,
refetchInterval: 600_000,
retry: false,
})
/** Plattengesundheit (seit 26.09.2026): die KI-Box selbst und das Homelab (Proxmox-Host, NAS über SNMP). */
export const useBoxPlatten = () =>
useQuery({ queryKey: ["platten"], queryFn: () => api<{ platten: Platte[] }>("/api/platten"), refetchInterval: 600_000, retry: false })
+11
View File
@@ -915,3 +915,14 @@ export interface HomelabPlatten {
platten: Platte[]
nas: { eingerichtet: boolean; ok: boolean; detail: string | null; system: string | null }
}
// --- Ausreißer ohne feste Schwellen (GET /api/ausreisser, /api/homelab/ausreisser; seit 26.09.2026) ---------------
export interface AusreisserLage {
reihen: number
wochen: number
lernt: boolean
min_wochen: number
stunde: number | null
auffaellig: { quelle: string; name: string; folge: number; wert: number; normal: number; fach: number; titel: string }[]
}
+15 -1
View File
@@ -6,7 +6,7 @@ import { Geraetesymbol } from "@/components/cockpit/Geraetesymbol"
import { Panel } from "@/components/cockpit/Panel"
import { Kennzahl } from "@/components/diagramme/Kennzahl"
import { Verlauf } from "@/components/diagramme/Verlauf"
import { useBoxMesswerte, useHomelabMesswerte, useInstanz, usePartner } from "@/lib/abfragen"
import { useAusreisser, useBoxMesswerte, useHomelabMesswerte, useInstanz, usePartner } from "@/lib/abfragen"
import { geraetebild } from "@/lib/geraetebild"
import { laufzeit, letzterWert, zustandBei } from "@/lib/messung"
import { useLetzteMetrik } from "@/lib/strom"
@@ -51,6 +51,18 @@ const zeitraumText = (z: Zeitraum) => ZEITRAEUME.find((x) => x.id === z)?.text ?
// --- KI-Box -------------------------------------------------------------------------------------------
/** Ausreißer ohne feste Schwellen (seit 26.09.2026): lernt noch — oder was gerade ungewöhnlich ist. */
export function AusreisserZeile({ bereich, aktiv = true }: { bereich: "box" | "homelab"; aktiv?: boolean }) {
const a = useAusreisser(bereich, aktiv).data
if (!a || a.reihen === 0) return null
const text = a.lernt
? `Ungewöhnliches meldet der Orchestrator, sobald er je Wochenstunde ${a.min_wochen} Wochen Erfahrung hat — er lernt gerade (Woche ${a.wochen} von ${a.min_wochen}).`
: a.auffaellig.length
? `Gerade ungewöhnlich: ${a.auffaellig.map((x) => x.titel).join("; ")}.`
: "Nichts Ungewöhnliches — verglichen mit den üblichen Werten dieser Wochenstunde."
return <p className={cn("m-0 text-sm", !a.lernt && a.auffaellig.length ? "text-bernstein" : "text-text-3")}>{text}</p>
}
export function BoxMonitoringSeite() {
const [zeitraum, setZeitraum] = useState<Zeitraum>("1h")
const mess = useBoxMesswerte(zeitraum)
@@ -65,6 +77,7 @@ export function BoxMonitoringSeite() {
<ZeitraumWahl wert={zeitraum} onWahl={setZeitraum} farbe="box" />
<span className="text-sm text-text-3">Zahlen live, Verläufe im Minutentakt</span>
</div>
<AusreisserZeile bereich="box" />
<div className="grid grid-cols-2 gap-2.5 sm:grid-cols-3 xl:grid-cols-6">
<Kennzahl label="CPU" wert={wert(live?.cpu, "cpu")} einheit="prozent" max={100} zustand={zustandBei(wert(live?.cpu, "cpu"), 85, 95)} zeitraumText={zeitraumText(zeitraum)} />
<Kennzahl label="Speicher" wert={wert(live?.ram, "ram")} einheit="prozent" max={100} zustand={zustandBei(wert(live?.ram, "ram"), 85, 95)} zeitraumText={zeitraumText(zeitraum)} />
@@ -140,6 +153,7 @@ export function HomelabMonitoringSeite() {
<ZeitraumWahl wert={zeitraum} onWahl={setZeitraum} farbe="homelab" />
<span className="text-sm text-text-3">Werte im Minutentakt vom Proxmox-Host</span>
</div>
<AusreisserZeile bereich="homelab" aktiv={aktiv} />
{host && (
<Panel titel="Proxmox-Host" rechts={<span className="ziffern text-sm text-text-3">läuft {laufzeit(h.uptime_s)}{h.load1 != null ? ` · Last ${h.load1.toFixed(2).replace(".", ",")}` : ""}</span>}>
<div className="grid grid-cols-2 gap-2.5 sm:grid-cols-4">