19/2650 ist eine echte BE-Drucksache (GRÜNE A100-Antrag) aber außerhalb
des Top-Result-Windows von BE PARDOK — der Server-side ETYPF-Filter ist
bei BE deaktiviert (document_type=None) und der client-side Filter
verwirft die meisten Schriftlichen Anfragen, sodass die Pagination der
verbleibenden Anträge nicht zuverlässig zu 19/2650 reicht.
19/2606 ist die Top-3-Antrag-Drucksache aus aktueller search() — als
GRÜNE-Antrag mit Title 'Menstruation enttabuisieren' deutlich
identifizierbar und im Window stabil.
Refs: #61
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TH 8/1594 wurde durch den TH-Adapter-Patch in #61 ausgefiltert (kein
PDF freigegeben). Sample auf 8/3133 (Notfallversorgung, datum 2026-03-18,
AfD) aktualisiert — die hat einen freigegebenen PDF-Link.
BE 19/3107 ist außerhalb des 200-result-Windows von
PortalaAdapter.get_document gewandert. Sample auf 19/2650 (A100,
datum 2025-09-09, GRÜNE) aktualisiert.
Refs: #61
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Live-Run von Sub-Issue B im Container hat zwei Test-False-Positives in
ground_truth.py aufgedeckt, die nichts mit Adapter-Bugs zu tun haben:
- BW: PDF-URL kodiert den Underscore als %5F (`17%5F10323.pdf`), nicht
als nacktes `_`. pdf_url_substring auf `17%5f10323` aktualisiert.
- RP: PDFs werden von `dokumente.landtag.rlp.de` ausgeliefert (nicht
von `opal.rlp.de` — das ist nur das Suchfrontend). Substring auf die
Drucksachen-Nummer im Pfad (`11250-18`) umgestellt — robust gegen
weiteren URL-Schema-Drift.
176 Unit-Tests bleiben grün.
Refs: #52, #59 (Sub-B Live-Verifikation)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Erster Live-Run von Sub-Issue D gegen die Prod-DB im Container hat 15 von
39 Citation-Tests fehlschlagen lassen. Detail-Analyse: 12 davon waren
Test-False-Positives (zwei Schichten von Brittleness im Test selbst), 3
sind echte LLM-Halluzinationen.
Drei Härtungen am Test-Resolver, damit er nur noch echte Halluzinationen
fängt:
1. **PDF-Bindestrich-Bridging in `_normalize`**:
PyMuPDF zerlegt Wörter über Zeilenumbrüche mit `-\n`. Nach unserer
Whitespace-Normalisierung wird daraus `- `, sodass aus
"Investitionsoffensive" im LLM-Snippet das PDF "investiti- onsoffensive"
gegenübersteht. Neue Regex `_RE_HYPHEN_BREAK` bridged das in einem
Konvergenz-Loop, damit auch mehrere aufeinanderfolgende Wort-Wraps
sauber verschmelzen.
2. **Token-Coverage-Resolver in `_resolve_quelle_to_programm_id`**:
Zwei-stufig — erst die alte strict-substring-Strategie (deckt
Adapter-konformes LLM-Output), dann ein Token-Coverage-Fallback. Der
zerlegt jeden PROGRAMME-Namen in (Partei + Bundesland + Jahr) mit
Aliasen (GRÜNE/Bündnis 90, LSA/Sachsen-Anhalt, …) und akzeptiert
eine Quelle, wenn alle drei Tokens in irgendeiner Reihenfolge in der
Quelle vorkommen. Fängt damit z.B. "Landtagswahlprogramm 2021 BÜNDNIS
90/DIE GRÜNEN Sachsen-Anhalt" → `gruene-lsa-2021`, ohne dass die LLM
den exakten Adapter-Label-Wortlaut treffen muss.
3. **Anker-Match-Fallback in `_is_substring`**:
Ein 200-Zeichen-Snippet, das nur in einem Wort kürzt, scheitert sonst
am Volltext-Substring-Check. Neuer Anker-Match zerlegt den Snippet
in 5-Wort-Sequenzen und akzeptiert, wenn mindestens eine wortwörtlich
im Seitentext steht. Erfundene Snippets haben keine 5-Wort-Sequenz,
die wortwörtlich im PDF steht — die false-negative-Rate für echte
Halluzinationen bleibt damit bei 0.
Live-Run nach dem Patch: **15 → 3 Failures** (39 Cases, 24 → 36 grüne).
Die verbleibenden 3 sind echte LLM-Bugs:
- 18/9605 NRW GRÜNE S.58 ('Wahlalter auf 16/14 absenken') — Snippet
und PDF-Seite zeigen komplett andere Themen, das LLM hat die Seite
oder den Snippet erfunden
- 18/18100 NRW B90/Grüne S.36 (Grundsatzprogramm 2020, Plattform-
Regulierung)
- 8/6645 LSA SPD S.37 ('Wir Sozialdemokratinnen ächten ...') — PDF
S.37 enthält dort Zweitstudiengebühren-Text
Diese drei werden als separates LLM-Bug-Issue erfasst.
13 Helper-Unit-Tests bleiben grün.
Refs: #54, #59 (Sub-D Live-Verifikation)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vier Sub-Issues unter Umbrella #50 — opt-in via 'pytest -m integration',
Default-Suite (77 Unit-Tests) bleibt unberührt.
- Sub-Issue A (#51): test_adapters_live.py — pro aktivem BL Reachability,
Drucksache-ID-Format, Type-Filter, Datum-/Fraktion-Plausibilität,
PDF-Link-HEAD-Probe (slow). NI als xfail (Login-Wall).
- Sub-Issue B (#52): test_frontend_xref.py + ground_truth.py — pro BL
ein manuell kuratiertes Frontend-Sample (Drucksache + Title-Substring +
Fraktionen + Datum + PDF-URL), gegen das adapter.get_document() gespiegelt
wird. Fängt Bug-Klasse 14 (Cross-Bundesland-Match).
- Sub-Issue C (#53): test_wahlprogramme_indexed.py — Indexing-Status pro
aktivem BL aus embeddings.db, PDF-Inhalts-Plausibilität (14 Marker +
Wahlperioden-Horizont), expliziter Anti-Marker für Bug-Klasse 8
(CDU-BE 2021 vs 2026 PDF-Tausch durch abgeordnetenwatch).
- Sub-Issue D (#54): test_citations_substring.py — Property-Verification:
jedes vom LLM zitierte Snippet muss als (whitespace-normalisierter)
Substring auf der angegebenen PDF-Seite vorhanden sein. Strict-Match
mit Truncation-Marker-Toleranz, kein Fuzzy. Liest reale Assessments
aus gwoe-antraege.db. Fängt Bug-Klassen 7/10/17 (Halluzination).
Architektur: separates tests/integration/ Verzeichnis mit eigenem
conftest.py, das die Stubs der Unit-Suite (fitz/bs4/openai/pydantic_settings)
gezielt entfernt und auf echte Module umstellt — mit Fallback-Skip via
pytest.require_module wenn lokale Dev-Maschine die Prod-Deps nicht hat.
206 neue Integration-Tests, 13 Helper-Unit-Tests. 77 Unit-Tests bleiben grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>