# Admin — OCR: Übersicht & Modell-Detail

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.

Route: /admin/ocr Route: /admin/ocr/global Route: /admin/ocr/[personId] Neue EntityNav-Sektion: System → OCR Breakpoints: 320 / 640 / 1024 / 1440 API: GET /api/ocr/training-info · POST /api/ocr/train · POST /api/ocr/segtrain

Leonie Voss · UI/UX Design Lead · Familienarchiv · 2026-04-17

## 1 — Übersicht: Desktop (≥1024px)

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.

Desktop lg (1280px) — kein aktiver Lauf
FAMILIENARCHIV Dokumente Personen Admin
OCR-Administration
● Service online
Kein aktiver Trainingsauftrag
Letzter Lauf: sender_anna_raddatz · vor 2 Std.
1.842
Eligible Blocks
Kurrent-Erkennung
23
Dokumente
mit annotierten Blöcken
310
Seg. Blocks
Segmentierungstraining
Alle Modelle
Modell / Absender Genauigkeit ZFR ① Blöcke Letzter Lauf Status
global_kurrent
Globales Erkennungsmodell
95,1 % 2,3 % 1.842 vor 1 Tag ✓ Fertig
Anna Raddatz
sender_anna_raddatz
98,3 % 1,7 % 128 vor 2 Std. ✓ Fertig
Franz Kaufmann
sender_franz_kaufmann
— — 45 vor 3 Tagen ✕ Fehler
Marie Schulz
sender_marie_schulz
96,7 % 1,9 % 203 vor 3 Tagen ✓ Fertig
Johann Raddatz
sender_johann_raddatz
94,2 % 2,9 % 87 vor 1 Woche ✓ Fertig
+ 3 weitere Modelle
————
Globale Aktionen
Training Erkennung Training Segmentierung Erkennungsdaten exportieren Segmentierungsdaten exportieren
Desktop lg — mit aktivem Lauf (Health-Bar + Tabellenzeile)
FAMILIENARCHIV Dokumente Personen Admin
OCR-Administration
● Service online
⟳ sender_anna_raddatz · läuft seit 4 Min.
⏳ 1 in Warteschlange
Alle Modelle
Modell / AbsenderGenauigkeitZFRBlöckeLetzter LaufStatus
global_kurrent
Globales Erkennungsmodell
95,1 %2,3 %1.842 vor 1 Tag✓ Fertig
Anna Raddatz
sender_anna_raddatz
98,3 %1,7 %128 gerade jetzt⟳ Läuft
Marie Schulz
sender_marie_schulz
——203 —⏳ Warteschlange
1
Globales Modell immer an Position 0. Die `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.
2
Health-Bar als Echtzeit-Primärindikator. Der Status (RUNNING, QUEUED, idle) wird via SSE aktualisiert — derselbe `/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.
3
① ZFR = Zeichenfehlerrate (CER). Abkürzung im `<th>` mit `title="Zeichenfehlerrate (Character Error Rate)"` und `aria-label` für Screenreader. Kleinere Werte sind besser — ein Pfeil in der Implementierung ist optional.

## 2 — Übersicht: Mobile (320px)

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.

320px
09:41●●●●
FAMILIENARCHIV
OCR-Administration
● Online Kein aktiver Lauf
1.842
Eligible Blocks
23
Dokumente
310
Seg. Blocks
Alle Modelle
global_kurrent
95,1 % · 1.842 Bl.
✓
Anna Raddatz
98,3 % · 128 Bl.
✓
Franz Kaufmann
Fehler · 45 Bl.
✕
Marie Schulz
96,7 % · 203 Bl.
✓
+ 4 weitere…
Training Erkennung Training Segmentierung Erkennungsdaten exportieren

## 3 — Modell-Detail: Desktop (≥1024px)

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).

Desktop lg — sender_anna_raddatz (Lauf aktiv)
FAMILIENARCHIV Dokumente Personen Admin
← OCR sender_anna_raddatz · Anna Raddatz ⟳ Läuft
98,3 %
Genauigkeit
↑ von 97,1 % (vorher)
1,7 %
Zeichenfehlerrate
↓ von 2,2 % (vorher)
128
Training-Blöcke
beim letzten Lauf
50
Epochs
letztes Training
Trainingshistorie Jetzt neu trainieren Training-Daten exportieren → Anna Raddatz
Status Zeitpunkt Genauigkeit ZFR Blöcke Epochs
⟳ Läuft gerade jetzt — — 128 —
✓ Fertig vor 2 Std. 98,3 % 1,7 % 128 50
✓ Fertig vor 3 Tagen 97,1 % 2,2 % 92 50
✓ Fertig vor 1 Woche 96,4 % 2,8 % 74 40
✕ Fehler vor 3 Wochen — — 60 —
Fehler: Connection timeout nach 120 s — OCR-Service nicht erreichbar
Desktop lg — global_kurrent (kein Personen-Link)
FAMILIENARCHIV Dokumente Personen Admin
← OCR global_kurrent · Globales Erkennungsmodell ✓ Fertig
95,1 %
Genauigkeit
↑ von 94,8 % (vorher)
2,3 %
Zeichenfehlerrate
↓ von 2,7 % (vorher)
1.842
Training-Blöcke
beim letzten Lauf
100
Epochs
letztes Training
Trainingshistorie Jetzt neu trainieren Training-Daten exportieren
StatusZeitpunktGenauigkeitZFRBlöckeEpochs
✓ Fertig vor 1 Tag 95,1 %2,3 %1.842100
✓ Fertig vor 5 Tagen 94,8 %2,7 %1.650100
✓ Fertig vor 2 Wochen 93,2 %3,1 %1.420100
4
Delta-Zeile in Metric-Cards. Unter jedem Metrikwert erscheint ein Vergleich zum vorherigen erfolgreichen Lauf (`↑ / ↓ 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.
5
FAILED-Fehlertext inline. Bei einer FAILED-Zeile erscheint direkt darunter eine zweite Zeile mit dem `errorMessage`-Feld — leicht rosa hinterlegt, kein Modal nötig. Ist `errorMessage` null oder leer, entfällt die zweite Zeile.
6
Keine Danger-Zone. Sender-Modelle werden programmatisch durch `SenderModelService` angelegt und gelöscht. Die Detailseite bietet bewusst keinen manuellen Lösch-Button. Beim globalen Modell fehlt der „→ Person"-Link, da `personId === null`.

## 4 — Modell-Detail: Mobile (320px)

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.

320px — sender_anna_raddatz
09:41●●●●
FAMILIENARCHIV
← OCR
sender_anna_raddatz
Anna Raddatz
⟳
98,3 %
Genauigkeit
↑ von 97,1 %
1,7 %
ZFR
↓ von 2,2 %
128
Blöcke
letzter Lauf
50
Epochs
letzter Lauf
Jetzt neu trainieren Training-Daten exportieren → Anna Raddatz
Trainingshistorie
⟳ gerade jetzt · 128 Bl.
✓ vor 2 Std. 98,3 %
✓ vor 3 Tagen 97,1 %
✓ vor 1 Woche 96,4 %
✕ vor 3 Wochen · Timeout

## 5 — Implementation Reference

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.svelte` `src/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