diff --git a/docs-src/services/klausur-service/OCR-Kombi-Pipeline.md b/docs-src/services/klausur-service/OCR-Kombi-Pipeline.md new file mode 100644 index 0000000..c323c05 --- /dev/null +++ b/docs-src/services/klausur-service/OCR-Kombi-Pipeline.md @@ -0,0 +1,253 @@ +# OCR Kombi Pipeline - Modulare 11-Schritt-Architektur + +**Version:** 1.0.0 +**Status:** Phase 1 implementiert (Grundgeruest + DB) +**URL:** https://macmini:3002/ai/ocr-kombi + +## Uebersicht + +Die OCR Kombi Pipeline ist der Nachfolger des OCR-Overlay-Monolithen (`/ai/ocr-overlay`). +Sie zerlegt den OCR-Prozess in **11 modulare Schritte** mit je einer eigenen Komponente +pro Frontend- und Backend-Datei. Ziel: schnelles Debugging, klare Verantwortlichkeiten, +Multi-Page-Dokument-Unterstuetzung. + +**Primaerer Modus:** Kombi (PaddleOCR + Tesseract) — der einzige Modus, den der User nutzt. + +### Warum ein Refactor? + +| Problem (alt) | Loesung (neu) | +|----------------|---------------| +| `page.tsx` = 751-Zeilen-Monolith mit 3 Modi | `page.tsx` = ~140-Zeilen-Orchestrator, je 1 Datei pro Step | +| Upload, Orientierung und Page-Split in einem Step | 3 separate Steps mit eigener Logik | +| Keine Multi-Page-Dokument-Unterstuetzung | `document_group_id` + `page_number` auf DB-Ebene | +| OCR intransparent (eine Blackbox) | 3-Phasen-Fortschritt + Engine-Attribution pro Wort (geplant) | +| `grid_editor_api.py` = 1801 Zeilen | 4 Module + Orchestrator (geplant) | + +--- + +## Pipeline-Schritte + +| # | Step | Frontend | Backend | Beschreibung | +|---|------|----------|---------|--------------| +| 1 | Upload | `StepUpload.tsx` | `step_upload.py` | Bild/PDF hochladen, Titel, Kategorie. Multi-Page-PDF → N Sessions | +| 2 | Orientierung | `StepOrientation.tsx` | (shared) | Rotation 90/180/270 erkennen + korrigieren | +| 3 | Seitentrennung | `StepPageSplit.tsx` | (shared) | Doppelseiten erkennen + splitten | +| 4 | Begradigung | `StepDeskew.tsx` | (shared) | Hough Lines + Word Alignment | +| 5 | Entzerrung | `StepDewarp.tsx` | (shared) | Shear-Korrektur (Vertikalkanten-Drift) | +| 6 | Zuschneiden | `StepContentCrop.tsx` | (shared) | Scanner-Raender entfernen (nach Begradigung!) | +| 7 | OCR | `StepOcr.tsx` | (shared) | Tesseract + PaddleOCR + Merge | +| 8 | Strukturerkennung | `StepStructure.tsx` | (shared) | Boxen, Zonen, Farben, Grafiken | +| 9 | Grid-Aufbau | `StepGridBuild.tsx` | (shared) | Strukturiertes Grid aus OCR + Struktur | +| 10 | Grid-Review | `StepGridReview.tsx` | (shared) | Excel-Editor, IPA, Silben, Korrekturen | +| 11 | Ground Truth | `StepGroundTruth.tsx` | (shared) | Validierung + GT-Markierung | + +!!! note "Crop nach Dewarp" + Seitentrennung (Step 3) passiert **vor** Begradigung — richtig, weil jede Haelfte + unabhaengig begradigt wird. Der Content-Crop (Step 6) bleibt **nach** Dewarp, + weil content-basierter Crop auf geradem Bild besser funktioniert. + +--- + +## Multi-Page-Dokument-Gruppierung + +### Problem + +Ein Lehrer scannt 10 Vokabelseiten als eine PDF-Datei. Im Endnutzer-Frontend soll das +ein zusammenhaengendes Dokument sein. Alle Seiten muessen spaeter zu gemeinsamen +Lern-Units verarbeitet werden koennen. + +### Loesung: `document_group_id` + `page_number` + +Zwei neue Felder auf `ocr_pipeline_sessions` (Migration `009_add_document_group.sql`): + +```sql +ALTER TABLE ocr_pipeline_sessions + ADD COLUMN IF NOT EXISTS document_group_id UUID, + ADD COLUMN IF NOT EXISTS page_number INT; +``` + +| Upload-Typ | document_group_id | page_number | +|------------|-------------------|-------------| +| Einzelbild | neues UUID | 1 | +| Multi-Page-PDF (10 Seiten) | gleiches UUID fuer alle 10 | 1..10 | +| Doppelseiten-Split von S. 3 | gleiches UUID | neue S. 3 + S. 4, Rest umkontiert | + +### Benennung + +Upload-Titel "Vokabeln Unit 3" erzeugt: + +- "Vokabeln Unit 3 — S. 1" +- "Vokabeln Unit 3 — S. 2" +- ... +- "Vokabeln Unit 3 — S. 10" + +### Session-Liste im Admin + +Gruppierte Anzeige: Ein Dokument-Header ("Vokabeln Unit 3, 10 Seiten") mit aufklappbaren +Einzel-Sessions darunter. Jede Session hat eigenen Pipeline-Status. + +--- + +## API-Endpoints + +### Neue Endpoints (OCR Kombi) + +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| POST | `/api/v1/ocr-kombi/upload` | Upload: Einzelbild oder Multi-Page-PDF | +| GET | `/api/v1/ocr-kombi/documents/{group_id}` | Alle Sessions einer Dokumentgruppe | + +### Bestehende Endpoints (wiederverwendet) + +Die Kombi-Pipeline nutzt alle bestehenden Endpoints aus `/api/v1/ocr-pipeline/`: + +| Methode | Pfad | Step | +|---------|------|------| +| POST | `/sessions` | Upload (Legacy, Einzelbild) | +| POST | `/sessions/{id}/orientation` | Orientierung | +| POST | `/sessions/{id}/page-split` | Seitentrennung | +| POST | `/sessions/{id}/deskew` | Begradigung | +| POST | `/sessions/{id}/dewarp` | Entzerrung | +| POST | `/sessions/{id}/crop` | Zuschneiden | +| POST | `/sessions/{id}/paddle-kombi` | OCR (Kombi) | +| POST | `/sessions/{id}/detect-structure` | Strukturerkennung | +| POST | `/sessions/{id}/build-grid` | Grid-Aufbau | +| POST | `/sessions/{id}/save-grid` | Grid speichern | +| GET | `/sessions/{id}/grid-editor` | Grid laden | +| POST | `/sessions/{id}/mark-ground-truth` | GT markieren | + +--- + +## Dateistruktur + +### Frontend + +``` +admin-lehrer/app/(admin)/ai/ocr-kombi/ +├── page.tsx # ~140 Zeilen, Orchestrator mit Suspense-Boundary +├── types.ts # KOMBI_V2_STEPS (11 Steps), DocumentGroup-Types, OCR-Transparenz-Types +└── useKombiPipeline.ts # Hook: Session-State, Step-Navigation, Dokument-Gruppierung + +admin-lehrer/components/ocr-kombi/ +├── KombiStepper.tsx # 11-Step-Indikator (kompakt, scrollbar) +├── SessionList.tsx # Gruppierte Session-Liste (Dokumentgruppen aufklappbar) +├── SessionHeader.tsx # Aktive Session: Name + Kategorie + GT-Badge +├── StepUpload.tsx # Drag-Drop + Titel + Kategorie-Auswahl +├── StepOrientation.tsx # Wrapper → shared StepOrientation +├── StepPageSplit.tsx # Doppelseiten-Erkennung + Auto-Advance +├── StepDeskew.tsx # Wrapper → shared StepDeskew +├── StepDewarp.tsx # Wrapper → shared StepDewarp +├── StepContentCrop.tsx # Wrapper → shared StepCrop +├── StepOcr.tsx # Wrapper → PaddleDirectStep (kombi endpoint) +├── StepStructure.tsx # Wrapper → shared StepStructureDetection +├── StepGridBuild.tsx # Auto-Trigger build-grid + Ergebnis-Anzeige +├── StepGridReview.tsx # Wrapper → shared StepGridReview (mit saveRef) +└── StepGroundTruth.tsx # GT-Markierung mit Auto-Save +``` + +### Backend + +``` +klausur-service/backend/ocr_kombi/ +├── __init__.py +├── router.py # Composite Router (/api/v1/ocr-kombi) +└── step_upload.py # Multi-Page-PDF → N Sessions + document_group_id +``` + +### Shared (wiederverwendet) + +Die Kombi-Pipeline nutzt alle bestehenden Backend-Module: + +- `orientation_crop_api.py` — Orientierung, Page-Split, Crop +- `ocr_pipeline_api.py` — Deskew, Dewarp +- `ocr_pipeline_ocr_merge.py` — PaddleOCR + Tesseract Merge +- `grid_editor_api.py` — Grid-Aufbau + Editor +- `ocr_pipeline_session_store.py` — DB-Layer (erweitert um document_group_id) +- Alle `cv_*.py` — CV-Algorithmen + +### Migration + +``` +migrations/009_add_document_group.sql # document_group_id UUID + page_number INT + Index +``` + +--- + +## Implementierungsphasen + +| Phase | Status | Beschreibung | +|-------|--------|--------------| +| **1: Grundgeruest + DB** | Implementiert | DB-Migration, Types, Hook, Stepper, SessionList, page.tsx, Navigation, Backend-Router | +| **2: Vorverarbeitungs-Steps** | Geplant | Multi-Page-PDF-Upload, Orientierung ohne Upload, Seitentrennung mit document_group_id | +| **3: OCR-Transparenz** | Geplant | 3-Phasen-Fortschritt, Engine-Attribution pro Wort, Farbkodierung | +| **4: Grid-Pipeline aufteilen** | Geplant | grid_editor_api.py → 4 Module + Orchestrator | +| **5: Restliche Steps** | Geplant | Structure, GridBuild, GridReview, GroundTruth voll integriert | +| **6: Features migrieren** | Spaeter | LLM-Review-Streaming, Labeling-Mode, Bild-Generierung | +| **7: Aufraeumen** | Spaeter | /ai/ocr-overlay und /ai/ocr-pipeline loeschen | + +--- + +## OCR-Transparenz (Phase 3, geplant) + +### 3-Phasen-Fortschritt in Step 7 + +1. "Tesseract laeuft..." (Fortschrittsbalken) +2. "PaddleOCR laeuft..." (Fortschrittsbalken) +3. "Merge laeuft..." (Fortschrittsbalken) + +### Engine-Attribution pro Wort + +Vergleichsansicht mit Farbkodierung: + +| Farbe | Bedeutung | +|-------|-----------| +| Gruen | Beide Engines einig | +| Blau | Nur PaddleOCR | +| Orange | Nur Tesseract | +| Gelb | Konflikt, PaddleOCR gewaehlt | +| Rot | Konflikt, Tesseract gewaehlt | + +### Geplanter Endpoint + +``` +POST /sessions/{id}/ocr-kombi-transparent +→ { raw_tesseract, raw_paddle, merged, engine_source_per_word } +``` + +--- + +## Grid-Pipeline-Aufteilung (Phase 4, geplant) + +`grid_editor_api.py` (1801 Zeilen) wird aufgeteilt in: + +| Modul | Inhalt | ~Zeilen | +|-------|--------|---------| +| `grid_build_filters.py` | Margin, Footer, Header, Exclude, Grafik-Filter | ~200 | +| `grid_build_zones.py` | Box-Detect, Page-Zones, Vert-Dividers | ~250 | +| `grid_build_columns.py` | Spalten-Clustering + Union-Merge + Zone-Grids | ~300 | +| `grid_build_postprocess.py` | Row/Cell-Postprocessing, IPA, Farben, Dictionary | ~500 | + +`grid_editor_api.py` wird zum schlanken Orchestrator. + +--- + +## Verhaeltnis zu bestehenden Pipelines + +| Pipeline | Route | Status | Beschreibung | +|----------|-------|--------|--------------| +| **OCR Kombi** | `/ai/ocr-kombi` | Aktiv (neu) | Modulare 11-Schritt-Pipeline | +| OCR Overlay | `/ai/ocr-overlay` | Legacy | 751-Zeilen-Monolith, 3 Modi | +| OCR Pipeline | `/ai/ocr-pipeline` | Legacy | Volle Pipeline mit Spalten | +| OCR Compare | `/ai/ocr-compare` | Eigenstaendig | Methoden-Vergleich | + +Die alte OCR Overlay (`/ai/ocr-overlay`) bleibt waehrend des gesamten Umbaus parallel nutzbar +fuer A/B-Tests. Sobald die Kombi-Pipeline feature-complete ist, werden die alten Pipelines +in Phase 7 entfernt. + +--- + +## Aenderungshistorie + +| Datum | Version | Aenderung | +|-------|---------|-----------| +| 2026-03-26 | 1.0.0 | **Phase 1:** Grundgeruest mit 11-Step-Architektur, DB-Migration (document_group_id, page_number), Backend-Router mit Multi-Page-Upload, Frontend mit SessionList (gruppiert), KombiStepper, 13 Step-Komponenten, useKombiPipeline Hook, Navigation | diff --git a/docs-src/services/klausur-service/OCR-Pipeline.md b/docs-src/services/klausur-service/OCR-Pipeline.md index cdf600a..987f633 100644 --- a/docs-src/services/klausur-service/OCR-Pipeline.md +++ b/docs-src/services/klausur-service/OCR-Pipeline.md @@ -1,6 +1,6 @@ # OCR Pipeline - Schrittweise Seitenrekonstruktion -**Version:** 5.0.0 +**Version:** 5.1.0 **Status:** Produktiv (Schritte 1–10 + Grid Editor + Regression Framework) **URL:** https://macmini:3002/ai/ocr-pipeline @@ -149,7 +149,9 @@ klausur-service/backend/ ├── ocr_pipeline_api.py # FastAPI Router (Schritte 2-10) ├── orientation_crop_api.py # FastAPI Router (Schritte 1 + 4) ├── grid_editor_api.py # Grid Editor: build-grid, save-grid, grid-editor +├── grid_editor_helpers.py # Footer-Filterung, Seitenzahl-Extraktion ├── cv_ocr_engines.py # OCR-Engines, IPA-Korrektur, Britfone-Woerterbuch +├── cv_syllable_detect.py # Deutsche Silbentrennung (Silben:DE Modus) ├── cv_box_detect.py # Box-Erkennung + Zonen-Aufteilung ├── cv_graphic_detect.py # Grafik-/Bilderkennung (Region-basiert) ├── cv_color_detect.py # Farbtext-Erkennung (HSV-Analyse) @@ -1081,6 +1083,8 @@ Rekonstruktion fuer Vokabelseiten mit komplexen Layouts (Bilder, Ueberschriften, | Datei | Beschreibung | |-------|--------------| | `grid_editor_api.py` | `_build_grid_core()` Pipeline, alle Steps | +| `grid_editor_helpers.py` | `_filter_footer_words()` → Seitenzahl-Extraktion, Footer-Filterung | +| `cv_syllable_detect.py` | Deutsche Silbentrennung mit IPA-Kompatibilitaet | | `cv_ocr_engines.py` | IPA-Korrektur, Britfone-Woerterbuch, Garbled-IPA-Erkennung | | `cv_vocab_types.py` | `PageZone` (mit `image_overlays`), `ColumnGeometry` | | `tests/test_grid_editor_api.py` | 27 Tests | @@ -1106,9 +1110,15 @@ Kombi-Wortdaten ├─ Step 4: Farb-Annotation │ → detect_word_colors(): HSV-Farbanalyse aller word_boxes │ + ├─ Step 4b2: Per-Cell Artifact Filter + │ → Einzel-Wort-Zellen mit ≤2 Zeichen und conf < 65 entfernen + │ ├─ Step 4c: Oversized Word Box Removal │ → word_boxes > 3x Median entfernen (Grafik-Artefakte) │ + ├─ Step 4d2: Connector Column Normalization + │ → Dominante Kurzwoerter in schmalen Spalten normalisieren + │ ├─ Step 5: Overlay-Wort-Filter │ → Woerter innerhalb image_overlays entfernen │ @@ -1197,6 +1207,38 @@ des Headwords der vorherigen Zeile). Diese werden von PaddleOCR als garbled Text 4. Schlaegt IPA im Britfone-Woerterbuch nach 5. Beruecksichtigt alle Wortteile (z.B. "close sth. down" → `[klˈəʊz dˈaʊn]`) +### Per-Cell Artifact Filter (Step 4b2) + +Entfernt OCR-Rauschen auf Zellebene: Zellen mit genau einer `word_box`, maximal 2 Zeichen +und Confidence unter 65 werden als Artefakte klassifiziert und entfernt. + +**Konstanten:** + +| Parameter | Wert | Beschreibung | +|-----------|------|--------------| +| `_ARTIFACT_MAX_LEN` | 2 | Maximale Textlaenge fuer Artefakt-Verdacht | +| `_ARTIFACT_CONF_THRESHOLD` | 65 | Confidence-Schwelle (darunter = Artefakt) | + +**Sicherheit:** Einzelne Zeichen mit hoher Confidence (z.B. rote `!`-Marker mit conf=98) +werden **nicht** entfernt, da ihre Confidence ueber dem Schwellwert liegt. + +**Typische Artefakte:** `(as)` conf=55, `u)` conf=44 — OCR-Noise aus Seitenraendern +oder Schatten. + +### Connector Column Normalization (Step 4d2) + +Erkennt schmale Spalten mit einem dominanten Kurzwort (z.B. "oder", "and", "bzw.") +und normalisiert OCR-Fehler bei denen das dominante Wort mit Rauschen versehen wurde. + +**Algorithmus:** + +1. Pro Spalte: Zaehle Textvorkommen aller Zellen +2. Pruefe ob ein dominantes Wort existiert (≥ 60% der Zellen, max 10 Zeichen) +3. Fuer Zellen die mit dem dominanten Wort **beginnen** und max 2 Zeichen laenger sind: + Normalisiere auf das dominante Wort + +**Beispiel:** Spalte mit "oder" in 80% der Zellen → `"oderb"` wird zu `"oder"` normalisiert. + ### Compound Word IPA Decomposition (Step 5e) Zusammengesetzte Woerter wie "schoolbag" oder "blackbird" haben oft keinen eigenen @@ -1253,6 +1295,69 @@ Admin-UI fuer effiziente Massenpruefung von Sessions: Admin-UI: [/ai/ocr-ground-truth](https://macmini:3002/ai/ocr-ground-truth) +### Page Number Extraction + +Die Footer-Filterung (`_filter_footer_words` in `grid_editor_helpers.py`) erkennt +Seitenzahlen in den untersten 5% des Bildes und gibt sie als Metadaten zurueck, +statt sie stillschweigend zu entfernen. + +**Algorithmus:** + +1. Woerter in den untersten 5% des Bildes identifizieren +2. Wenn ≤ 3 Woerter mit ≤ 10 Zeichen Gesamtlaenge: Als Seitenzahl extrahieren +3. Rueckgabe als `PageNumber`-Objekt: `{text, y_pct, number?}` +4. Ziffern werden separat als `number` (Integer) extrahiert + +**Datentyp:** + +```typescript +interface PageNumber { + text: string // Roh-OCR-Text (z.B. "u)233") + y_pct: number // Vertikale Position in Prozent + number?: number // Extrahierte Zahl (z.B. 233) +} +``` + +**Frontend-Anzeige:** + +In der Summary-Leiste (GridEditor + StepGridReview) als Badge: `S. 233`. +Zeigt bevorzugt `page_number.number` (saubere Zahl), Fallback auf `page_number.text`. + +**Zweck:** Spaetere Zusammenfuehrung aufeinanderfolgender Seiten im Kundenfrontend. + +### Footer-Zeilen-Erkennung (Verbesserung) + +Die Footer-Erkennung wurde um zwei Pruefungen erweitert, um falsch-positive +Footer-Markierungen bei Content-Zeilen zu verhindern: + +| Pruefung | Bedingung | Grund | +|----------|-----------|-------| +| Komma-Check | `',' in text` → kein Footer | Content-Saetze enthalten Kommas, Seitenzahlen nicht | +| Laengen-Check | `len(text) > 20` → kein Footer | Seitenzahlen sind kurz, Content-Zeilen lang | + +**Vorher:** `"Uhrzeit, Vergangenheit, Zukunft"` wurde als Footer markiert. +**Nachher:** Nur tatsaechliche Seitenzahlen (kurz, ohne Kommas) werden als Footer erkannt. + +### Silben + IPA Kombination (Fix) + +**Datei:** `cv_syllable_detect.py` + +Wenn beide Modi (Silben:DE und IPA) aktiviert sind, blockierte der `_IPA_RE`-Guard +die Silbentrennung, weil programmatisch eingefuegte IPA-Klammern (z.B. `[bɪltʃøn]`) +IPA-Zeichen enthalten. + +**Loesung:** Vor der IPA-Pruefung wird Bracket-Content entfernt: + +```python +# Bracket-Content strippen, da programmatisch eingefuegt +text_no_brackets = re.sub(r'\[[^\]]*\]', '', text) +if _IPA_RE.search(text_no_brackets): + return text # Echte IPA im Fliesstext → keine Silbentrennung +``` + +So wird `"Bild·chen [bɪltʃøn]"` korrekt silbifiziert: Die Silbenpunkte bleiben erhalten, +und die IPA in Klammern wird nicht als Blockiergrund gewertet. + ### `en_col_type` Erkennung Die Erkennung der Englisch-Headword-Spalte nutzt **Bracket-IPA-Pattern-Count** @@ -1620,6 +1725,8 @@ Die Ergebnisse fliessen in Schritt 5 (Spaltenerkennung) und den Grid Editor ein. | Datum | Version | Aenderung | |-------|---------|----------| +| 2026-03-26 | 5.2.0 | **OCR Kombi Pipeline:** Neuer modularer Nachfolger als 11-Schritt-Architektur unter `/ai/ocr-kombi`. Eigene Dokumentation: [OCR Kombi Pipeline](OCR-Kombi-Pipeline.md). Phase 1 (Grundgeruest + DB) implementiert: DB-Migration (`document_group_id`, `page_number`), Frontend-Orchestrator, 13 Step-Komponenten, Backend-Router mit Multi-Page-Upload. | +| 2026-03-26 | 5.1.0 | **Grid Quality & Metadata:** Per-Cell Artifact Filter (Step 4b2: ≤2 Zeichen + conf < 65), Connector Column Normalization (Step 4d2: dominante Kurzwoerter), Footer-Erkennung verbessert (Komma/Laengen-Check), Seitenzahl-Extraktion als Metadaten (`page_number` Feld im Grid-Result), Frontend-Anzeige in Summary-Leiste. Silben+IPA-Kombination gefixt (Bracket-Content vor IPA-Guard strippen). | | 2026-03-23 | 5.0.0 | **Phase 1 Sprint 1:** Compound-IPA-Zerlegung (`_decompose_compound`), Trailing-Garbled-Fragment-Entfernung (Multi-Wort-Headwords), Regression Framework mit DB-Persistenz + History + Shell-Script, Ground-Truth Review Workflow UI, Page-Crop Determinismus verifiziert. Admin-Seiten: `/ai/ocr-regression`, `/ai/ocr-ground-truth`. | | 2026-03-20 | 4.7.0 | Grid Editor: Zone Merging ueber Bilder (`image_overlays`), Heading Detection (Farbe + Hoehe), Ghost-Filter (borderless-aware), Oversized Word Box Removal, IPA Phonetic Correction (Britfone), IPA Continuation Detection, `en_col_type` via Bracket-Count. 27 Tests. | | 2026-03-16 | 4.6.0 | Strukturerkennung (Schritt 8): Region-basierte Grafikerkennung (`cv_graphic_detect.py`) mit Zwei-Pass-Verfahren (Farbregionen + schwarze Illustrationen), Wort-Ueberlappungs-Filter, Box/Zonen/Farb-Analyse. Schritt laeuft nach Worterkennung. | diff --git a/mkdocs.yml b/mkdocs.yml index eb205af..49d83ec 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -70,6 +70,7 @@ nav: - BYOEH Developer Guide: services/klausur-service/BYOEH-Developer-Guide.md - NiBiS Pipeline: services/klausur-service/NiBiS-Ingestion-Pipeline.md - OCR Pipeline: services/klausur-service/OCR-Pipeline.md + - OCR Kombi Pipeline: services/klausur-service/OCR-Kombi-Pipeline.md - TrOCR ONNX: services/klausur-service/TrOCR-ONNX.md - OCR Labeling: services/klausur-service/OCR-Labeling-Spec.md - OCR Vergleich: services/klausur-service/OCR-Compare.md