gwoe-antragspruefer/docs/adr/0003-citation-property-tests.md
Dotty Dotter 45379a2639 #62 Phase 1+3: ADRs + Doku-Struktur in webapp/docs/
Architektur-Entscheidung aus Issue #62: Diátaxis-Framework für Doku-
Pflege ohne Drift. Pflege im Repo, ADRs immutable, Stale-Snapshots
explizit als Archiv markiert.

Phase 1 — Architecture Decision Records:

- docs/README.md — Diátaxis-Index, Erklärung was wo dokumentiert wird
- docs/adr/README.md — ADR-Workflow + Index
- docs/adr/template.md — Vorlage für neue ADRs
- docs/adr/0001-llm-citation-binding.md — Issue #60 Doppel-Fix-Story
  (A=ENUM-Anker, B=server-seitige Rekonstruktion, warum Option C verworfen)
- docs/adr/0002-adapter-architecture.md — ParlamentAdapter-Basisklasse
  + Registry, Klassen vs. Strategy vs. Modul-pro-Adapter
- docs/adr/0003-citation-property-tests.md — Sub-D Strategie, warum
  Property-Test gegen echte PDFs statt Schema-Tests oder Online-Verify
- docs/adr/0004-deployment-workflow.md — Docker-Compose + Volumes
  Standard-Workflow + SN-XML-Sonderpfad + Container-UTC-Gotcha

Phase 3 — Stale Doku archiviert:

- DOKUMENTATION.md (24.März, Skript-Architektur vor Webapp-Migrate)
  → docs/archive/DOKUMENTATION-2026-03-24.md
- STATUS-2026-03-28.md (Tagesstand-Snapshot)
  → docs/archive/STATUS-2026-03-28.md
- README.md (28.März, listet nur NRW-Adapter, vor 16 weiteren BLs)
  → docs/archive/README-2026-03-28.md
- docs/archive/README.md erklärt warum die Files da sind und warum
  niemand sie überschreiben oder ersetzen sollte

Plus neue Top-Level-README.md im Project-Root (außerhalb git, da
project-root kein Repo ist) als Folder-Index für den User.

CLAUDE.md ergänzt um Doku-Sektion mit Verweis auf docs/adr/.

Phase 2 (mkdocs Setup) folgt separat — braucht eine Docker-Image-
Erweiterung, die ich nicht autark einrollen will ohne Decision.

Tests: 194/194 grün (keine Code-Änderung).

Refs: #62
2026-04-10 01:38:03 +02:00

107 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 0003 — Sub-D Property-Verification: Zitate als Substring der zitierten PDF-Seite
| | |
|---|---|
| **Status** | accepted |
| **Datum** | 2026-04-10 |
| **Refs** | Issues #50, #54, #60; tests/integration/test_citations_substring.py |
## Kontext
Der LLM-Output enthält pro Assessment N Zitate, jedes mit `text`, `quelle`
(z.B. "GRÜNE NRW Wahlprogramm 2022, S. 58") und `url`. Wahrscheinlich
korrekt — aber wie verifizieren wir das, ohne jedes einzeln händisch
nachzuschlagen?
Die naheliegenden Test-Optionen sind alle unbefriedigend:
- **Mock-LLM-Tests**: prüfen das Schema, sagen aber nichts über die
inhaltliche Korrektheit.
- **Snapshot-Tests** der LLM-Outputs: drift mit jedem Modell-Update.
- **Manuelles Stichprobenchecken**: skaliert nicht über mehrere BLs.
## Optionen
### Option A — Schema-only Tests (was wir vorher hatten)
Pydantic validiert dass jedes Zitat die Felder `text`, `quelle`, `url` hat
und `url` mit `/static/referenzen/` beginnt. Erkennt syntaktische
Korruption, aber keine Halluzinationen.
### Option B — Property-Test gegen die echten PDFs
Pro Zitat in der Prod-DB:
1. `quelle` per Token-Coverage-Match auf den `PROGRAMME`-Eintrag mappen.
2. Seitennummer aus `quelle` extrahieren.
3. Per `fitz` die PDF-Seite lesen, Whitespace + Soft-Hyphen normalisieren.
4. `text` muss als Substring (oder 5-Wort-Anker) in der Seite vorkommen.
5. Bug-Klasse 17 (Cross-Bundesland-Zitat): das aufgelöste Programm muss
zum Bundesland des Antrags passen, oder ein Grundsatzprogramm sein.
**Vorteile:** prüft die einzige Eigenschaft die wirklich zählt — "war
das was zitiert wird auch wirklich da". Findet Halluzinationen direkt.
**Nachteile:** braucht eine lokale Kopie der `gwoe-antraege.db` und der
Wahlprogramm-PDFs. Test ist Pydantic-Schema-übergreifend (Integration,
nicht Unit). Skipped sauber wenn DB nicht gemounted ist.
### Option C — Online-Verifikation pro Assessment-Insert
Im `analyze_antrag`-Flow direkt nach LLM-Call jedes Zitat verifizieren
und bei Failure abbrechen oder retry.
**Vorteile:** kein "stale data in DB"-Risiko.
**Nachteile:** fügt Latenz und Komplexität in den Hot-Path. Die
Verifikation ist O(N×M), wo N=Zitate und M=Wahlprogramm-Pages.
## Entscheidung
**Option B als pytest-Integration-Test**`tests/integration/test_citations_substring.py`,
parametrisiert per `_load_recent_assessments(limit_per_bl=5)` × `_flat_zitate()`.
**Strict substring** als Default-Match (Whitespace + Soft-Hyphen normalisiert,
LLM-Truncation-Marker `...` toleriert), **5-Wort-Anker als Fallback** für
geringfügige Wort-Drift wie "LLM hat mittendrin gekürzt". Min-Length-Guard
von 20 Zeichen verhindert false-positive Matches auf trivialen Snippets.
Marker `pytestmark = pytest.mark.integration` — der Test läuft nicht in
der Default-Suite, sondern explizit per `pytest -m integration`. Skipped
wenn `webapp/data/gwoe-antraege.db` nicht existiert (Dev-Setup ohne
DB-Kopie).
Match-Helpers (`_normalize`, `_is_substring`, `_resolve_quelle_to_programm_id`,
`_extract_page_number`) sind eigene Unit-Tests in `TestHelpers` — die Match-
Logik selbst ist nicht-trivial und braucht ihre Eigenkontrolle.
## Konsequenzen
### Positiv
- **Findet Halluzinationen direkt**: Issue #60 wurde durch den ersten
Live-Lauf dieses Tests entdeckt (3 von 36 Citations failed), ohne
dass ein Mensch Wahlprogramm-PDFs aufmachen musste.
- **Re-runnable als Regression-Gate**: nach jedem Deploy einmal `pytest -m
integration` gegen die DB → 0 Failures = OK.
- **Test-Logik = Production-Logik**: ADR 0001 Option B (`reconstruct_zitate`)
nutzt **identische** Match-Heuristiken (`find_chunk_for_text`,
`_normalize_for_match`). Damit kann der Test nichts fangen, was die
Production nicht auch fangen würde, und umgekehrt — kein Test-/Prod-Drift.
### Negativ
- **Lokale DB-Kopie nötig**: vor jedem Sub-D-Run muss `data/gwoe-antraege.db`
vom Container gepullt werden. CI-Integration steht aus.
- **Test ist langsam-ish**: ~50 Citations × ein PDF-Open pro Programm ist
bei den ~30 indexierten Programmen ~250ms im Ganzen, nicht trivial aber
nicht prohibitiv.
- **Token-Coverage-Heuristik für Quelle-zu-Programm-Mapping** kann false-
positive bei sehr ähnlichen Programmen werden (z.B. CDU NRW 2022 vs. CDU
Niedersachsen 2022 — würde durch Bundesland-Bonus-Check abgefangen).
### Folgen für andere ADRs
- **ADR 0001** ist von ADR 0003 abhängig — wenn dieser Test entfernt würde,
hätte der LLM-Citation-Postprocess keinen Backstop und neue Halluzinations-
Bug-Klassen würden still durchrutschen.