Neue Admin-Sektion für das OCR-Pipeline-Monitoring. Zwei Seiten: Die Übersicht zeigt Systemstatus, globale Block-Statistiken und alle Modelle (global + sendergebunden) in einer Tabelle auf einen Blick. Die Modell-Detailseite zeigt die aktuellen Trainingsmetriken und die vollständige Trainingshistorie für ein einzelnes Modell, mit Aktions-Buttons direkt über der Tabelle. Tägliche Nutzung — Statistiken und aktive Läufe sind daher immer sofort sichtbar.
Die Übersichtsseite öffnet sich beim Klick auf „OCR" in der EntityNav. Kein Split-Panel — OCR ist kein Entity-Editor. Der volle Bereich rechts der EntityNav gehört dem Dashboard. Vertikaler Fluss: Health-Bar → Statistiken → Modelltabelle → Globale Aktionen. Beim Klick auf eine Tabellenzeile wird die Modell-Detailseite geöffnet.
global_kurrent-Zeile hat einen leicht bläulichen Hintergrund (bg-[#F8F8FC]) und ist unabhängig von Sortierung immer die erste Zeile. Sie wird anhand personId === null identifiziert./api/ocr/jobs/{jobId}/progress-Kanal wie in der Dokumentenansicht. RUNNING-Tabellenzeilen erhalten zusätzlich bg-amber-50. Die Buttons „Training Erkennung" und „Training Segmentierung" werden disabled + opacity-50 wenn ein Lauf aktiv ist.<th> mit title="Zeichenfehlerrate (Character Error Rate)" und aria-label für Screenreader. Kleinere Werte sind besser — ein Pfeil in der Implementierung ist optional.Auf kleinen Bildschirmen stapeln sich die drei Stat-Karten einspaltig. Die Modelltabelle zeigt nur Name und Status-Pill (keine Metriken-Spalten — zu schmal). Aktions-Buttons werden vollbreit und vertikal gestapelt.
Die Detailseite öffnet sich beim Klick auf eine Zeile der Übersichtstabelle. Sie folgt demselben vertikalen Muster: Metric-Cards oben, darunter die vollständige Trainingshistorie nur für dieses Modell. Für Sender-Modelle gibt es einen Link zur Personen-Detailseite. Zwei Varianten: Sender-Modell (mit Personen-Link) und globales Modell (ohne).
↑ / ↓ von X % (vorher)). Grün für Verbesserung, rot für Verschlechterung. Berechnung: letzter DONE-Lauf vs. vorletzter DONE-Lauf im nach completedAt sortierten Array. Entfällt wenn weniger als zwei DONE-Läufe vorhanden.errorMessage-Feld — leicht rosa hinterlegt, kein Modal nötig. Ist errorMessage null oder leer, entfällt die zweite Zeile.SenderModelService angelegt und gelöscht. Die Detailseite bietet bewusst keinen manuellen Lösch-Button. Beim globalen Modell fehlt der „→ Person"-Link, da personId === null.Die vier Metric-Cards arrangieren sich auf Mobile in einem 2×2-Raster. Aktions-Buttons nehmen die volle Breite ein. Die Trainingshistorie zeigt auf kleinen Bildschirmen nur Status, Datum und Genauigkeit — ZFR, Blöcke und Epochs entfallen.
Exakte Tailwind-Klassen und Pixelwerte für beide Seiten. Neue Dateien sind mit Neu markiert.
| Element | Tailwind-Klassen | Real px / Wert | Hinweis |
|---|---|---|---|
| Routen & Dateien | |||
| Übersicht | src/routes/admin/ocr/+page.sveltesrc/routes/admin/ocr/+page.server.ts |
GET /api/ocr/training-info |
Neu |
| Global-Detail | src/routes/admin/ocr/global/+page.svelte |
GET /api/ocr/training-info (gefiltert: personId === null) |
Neu |
| Sender-Detail | src/routes/admin/ocr/[personId]/+page.svelte |
GET /api/ocr/training-info (gefiltert nach personId) |
Neu |
| EntityNav-Eintrag | Vorhandene EntityNav-Komponente um OCR-Eintrag ergänzen |
Neue Sektion „System" mit Separator davor | Bestehende Datei ändern |
| Seitenstruktur (beide Seiten) | |||
| OCR Content Area | flex-1 flex flex-col overflow-hidden min-w-0 bg-surface |
Kein Tree-Panel, kein Edit-Panel — volle Breite nach EntityNav | OCR ist kein Entity-Editor; enthält kein Split-Layout |
| Page Header Bar | flex items-center gap-2 px-4 py-2.5 border-b border-line bg-white shrink-0 h-10 |
h: 40px | Enthält Back-Link (nur Detail), Titel, optionale Status-Pill |
| Scrollbarer Seiteninhalt | flex-1 overflow-y-auto p-4 flex flex-col gap-3 |
gap: 12px zwischen Sektionen | — |
| Back-Link (Detail) | text-[11px] text-gray-400 hover:text-ink transition-colors shrink-0 |
— | goto('/admin/ocr') oder <a href="/admin/ocr"> |
| Health Bar (Übersicht) | |||
| Health Bar Container | flex items-center gap-3 px-4 py-2 bg-white border border-line rounded-sm |
h: 36px | Neu |
| Service-Pill (online) | inline-flex items-center gap-1 px-2 py-0.5 rounded-sm text-[10px] font-bold uppercase tracking-wide bg-green-50 text-green-700 border border-green-200 |
— | Offline: bg-red-50 text-red-700 border-red-200 |
| Running-Pill | bg-amber-50 text-amber-700 border border-amber-200 (gleiche Basis) |
— | Nur sichtbar wenn status === 'RUNNING'. Icon ⟳ via animate-spin |
| Queued-Badge | text-[10px] text-amber-800 bg-amber-100 border border-amber-200 px-2 py-0.5 rounded-sm font-bold |
— | Nur sichtbar wenn mind. 1 Lauf QUEUED. Text: „⏳ N in Warteschlange" |
| Stat Cards (Übersicht) | |||
| Grid (Desktop) | grid grid-cols-1 sm:grid-cols-3 gap-3 |
Mobile: 1 Spalte; ab 640px: 3 Spalten | Neu |
| Stat Card | bg-white border border-line rounded-sm px-4 py-3 |
padding: 12px 16px · kein Hover (nicht klickbar) | — |
| Stat-Zahl | font-serif text-3xl font-bold text-ink leading-none |
30px, Merriweather · deutsche Tausend-Trennung via n.toLocaleString('de-DE') |
— |
| Stat-Label | text-[10px] font-bold uppercase tracking-widest text-gray-400 mt-1 |
10px | — |
| Stat-Subtext | text-[9px] text-gray-300 mt-0.5 |
9px · optional | — |
| Modell-Tabelle (Übersicht) | |||
| Tabellen-Container | bg-white border border-line rounded-sm overflow-hidden |
— | Neu — kein <table>, sondern CSS-Grid-Rows |
| Tabellenkopf | bg-ink grid px-4 py-2 grid-cols-[2fr_80px_80px_80px_80px_100px] |
— | Kopfzellen: text-[9px] font-bold uppercase tracking-wide text-brand-mint |
| Tabellenzeile | grid px-4 py-2.5 border-b border-line items-center cursor-pointer hover:bg-surface transition-colors grid-cols-[2fr_80px_80px_80px_80px_100px] |
Klick → goto('/admin/ocr/{id}') |
Mobile: Tabelle horizontal scrollbar (overflow-x-auto) oder vereinfachte 2-Spalten-Liste |
| Global-Zeile | Zusätzlich bg-[#F8F8FC] |
Immer erste Zeile (personId === null) |
— |
| Sender-Name | text-sm font-medium text-gray-700 |
14px · darunter modelName: text-[10px] text-gray-400 mt-0.5 |
Name aus personNames[run.personId] des training-info-Endpoints |
| Metrik-Wert (gut) | text-green-700 font-semibold |
Accuracy ≥ 95 % oder niedrigste CER | — |
| Metrik-Wert (Fehler) | text-red-600 font-semibold |
— | Zeigt „—" bei fehlenden Werten (FAILED/RUNNING) |
| Status Pills (alle Seiten) | |||
| Basis-Klassen | inline-flex items-center gap-1 px-2 py-0.5 rounded-sm text-[10px] font-bold uppercase tracking-wide border |
— | Icon als Textzeichen vor Label (✓ ⟳ ✕ ⏳) |
| DONE | bg-green-50 text-green-700 border-green-200 |
— | — |
| RUNNING | bg-amber-50 text-amber-700 border-amber-200 |
— | Icon ⟳ in eigenem <span class="animate-spin"> |
| FAILED | bg-red-50 text-red-700 border-red-200 |
— | — |
| QUEUED | bg-yellow-50 text-yellow-800 border-yellow-200 |
— | — |
| Metric Cards (Detail) | |||
| Grid | grid grid-cols-2 sm:grid-cols-4 gap-3 |
Mobile: 2×2 · Desktop: 4×1 | Neu |
| Metric Card | bg-white border border-line rounded-sm px-4 py-3 text-center |
— | — |
| Zahl (positiv) | font-serif text-3xl font-bold leading-none text-green-700 |
Accuracy ≥ 95 % | — |
| Zahl (neutral) | font-serif text-3xl font-bold leading-none text-ink |
Standard | — |
| Delta-Zeile | text-[10px] mt-1 + text-green-600 / text-red-600 / text-gray-400 |
10px | Format: „↑ von 94,8 % (vorher)". Entfällt bei < 2 DONE-Läufen. |
| Trainingshistorie (Detail) | |||
| Tabellen-Container | bg-white border border-line rounded-sm overflow-hidden |
— | — |
| Tabellenkopf | bg-ink grid px-4 py-2 grid-cols-[100px_1fr_80px_80px_80px_80px] |
— | Mobile: grid-cols-[80px_1fr_80px] (Status · Datum · Genauigkeit) |
| RUNNING-Zeile | bg-amber-50 zusätzlich |
— | Leere Metrik-Zellen zeigen „—" |
| FAILED-Zeile | bg-red-50 (sehr hell, ~#fff8f8) |
— | Fehlertext folgt als zweite Sub-Zeile: text-[11px] text-red-600 px-4 pb-2 col-span-full |
| Buttons & Aktionen | |||
| Primär-Button | inline-flex items-center h-9 px-4 bg-ink text-white rounded-sm text-xs font-bold uppercase tracking-wide hover:opacity-90 transition-opacity |
h: 36px (44px auf Mobile) | Disabled wenn Lauf aktiv: opacity-50 cursor-not-allowed pointer-events-none |
| Outline-Button | inline-flex items-center h-9 px-4 border border-ink text-ink rounded-sm text-xs font-bold uppercase tracking-wide hover:bg-surface transition-colors |
— | Export: window.location.href = '/api/ocr/training-data/export' |
| Personen-Link | text-sm text-ink underline underline-offset-2 hover:text-brand-mint transition-colors |
— | Nur auf Sender-Detailseite. Ziel: /persons/{personId}. Entfällt bei personId === null. |
| API-Aufrufe | |||
| Übersicht laden | GET /api/ocr/training-info |
Felder: availableBlocks · totalOcrBlocks · availableDocuments · availableSegBlocks · ocrServiceAvailable · runs · personNames |
Im +page.server.ts laden; SenderModel-Liste zusätzlich via eigenem Endpunkt oder aus runs aggregieren |
| Training starten | POST /api/ocr/train / POST /api/ocr/segtrain |
Response: OcrTrainingRun |
Sofort in Health-Bar und Tabelle anzeigen; SSE-Kanal öffnen |
| Live-Updates | EventSource('/api/ocr/jobs/{jobId}/progress') |
Server-Sent Events | Health-Bar + Status-Pills live aktualisieren; onDestroy schließen |