gwoe-antragspruefer/docs/analysen/protokoll-parser-v6-machbarkeit.md
Dotty Dotter 2dec009b5c docs+ops: ADRs 0006/0008, DDD-Bewertung, Zugriffsrechte, Smoke-Test, Cron-Scripts
ADRs:
- 0006 Embedding-Modell-Migration v3->v4 (#123)
- 0008 DDD-Lightweight-Migration (#136)

Analysen:
- ddd-bewertung.md (1237 Zeilen) — vollstaendige DDD-Analyse mit Tages-Roadmap
- protokoll-parser-v6-machbarkeit.md (418 Zeilen) — #106 Phase 2 Vorbereitung

Reference:
- zugriffsrechte.md — 63 Routes x 3 User-Status, UI-Sichtbarkeits-Matrix

Ops:
- scripts/deploy.sh — mit Uptime-Kuma-Wartungsmodus (#149)
- scripts/run-digest.sh — taeglicher Mail-Digest-Cron
- scripts/run-monitoring-scan.sh — Monitoring-Scan-Cron (noch nicht aktiv)
- scripts/smoke-test.sh — Gesamt-Funktionspruefung
- pytest.ini: integration/slow/e2e Markers, addopts not-integration

Tests/integration/: Live-Adapter-Tests + Frontend-XRef + Citation-Substring
                    + Wahlprogramm-Indexed (4 Live-Test-Suites, marker-opt-in)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00

17 KiB
Raw Permalink Blame History

Plenarprotokoll-Parser v6 — Machbarkeits-Analyse

Status Exploration
Datum 2026-04-20
Refs Issue #106 (Abstimmungsverhalten), #126 (BL-übergreifender Parser), MEMORY reference_nrw_protokoll_parser.md
Autor Claude Opus 4.7 (1M) — strukturelle Exploration, kein Code

Zweck

Strukturierte Bewertung, ob ein Parser v6 für das Abstimmungsverhalten in Plenarprotokollen sinnvoll ist — und wenn ja, in welcher Architektur. Entscheidungsgrundlage, keine Implementierungs-Vorgabe.

1. Bestandsaufnahme: v5 heute

1.1 Wo lebt der Parser?

Befund: Im Produktionscode (webapp/app/) existiert kein protokoll_parser.py oder vergleichbares Modul. Grep über alle app/*.py auf parse_protokoll, Plenarprotokoll, MMP, Wer stimmt, Damit ist der Antrag liefert null Treffer in Produktions-Python — nur ein inzidentelles Plenarprotokoll im Kommentar zu parlamente.py:986.

Der v5-Parser ist also POC-Code aus einer früheren Session, der nicht ins Repo gelangt ist. Die einzige belastbare Referenz ist die Memory-Datei ~/.claude/projects/…gwoe…/memory/reference_nrw_protokoll_parser.md (95 Zeilen, 12 Apr. 2026).

Konsequenz: v6 ist kein Refactor, sondern ein Neuaufbau. Das reduziert Altlast-Risiko, bedeutet aber auch: es gibt keine Baseline-Test-Fixtures im Repo, keine bestehenden Call-Sites, die zu brechen wären. Saubere grüne Wiese.

1.2 Was v5 laut Memory kann

Aus reference_nrw_protokoll_parser.md (zur Orientierung, nicht als Autorität — Memory ist 8 Tage alt):

Input: PDF-Text von landtag.nrw.de/.../MMP{WP}-{N}.pdf, nach Worttrennungs-Auflösung (-\s*\n\s*"") und Whitespace-Normalisierung.

Anchor-Phrasen für Ergebnis-Klassifikation:

Damit ist der Antrag [Drucksache X] angenommen|abgelehnt
Damit ist der Gesetzentwurf [Drucksache X] angenommen|abgelehnt
Damit ist dieser Antrag Drucksache X angenommen|abgelehnt
Damit ist (diese|die) Überweisungsempfehlung … angenommen  → überwiesen
Somit ist dieser Antrag Drucksache X abgelehnt

Segment-Boundaries (trennen Abstimmungen im selben TOP):

Damit kommen wir zur Abstimmung
Wir kommen (somit )?zur Abstimmung
Wir stimmen (?!zu)
Somit kommen wir (direkt )?zu den Abstimmungen

Vote-Pattern (Ja/Nein/Enthaltung):

  • Wer stimmt … zu? Das (ist|sind) [Fraktionen] → Ja-Stimmer
  • Wer stimmt dagegen? / Wer lehnt … ab? → Nein-Stimmer
  • Wer enthält sich? / Gibt es Enthaltungen? → Enthaltungen

Negations-Antworten (= leere Liste): niemand, Keine Gegenstimmen, nicht der Fall, Auch nicht.

Sonderfälle:

  • Re-Vote (Präsident unterbricht): „Vielleicht sind sich dann alle einig." → letzte Instanz im Segment zählt.
  • Beschlussempfehlung-vs-Gesetzentwurf: abgestimmt wird über den Gesetzentwurf, die Empfehlungs-DS ist nicht_gesondert_abgestimmt.
  • Protokoll-Typo (MMP18-115): Anchor nennt falsche DS → Segment-Entry-DS hat Vorrang (v6-Forderung).
  • Petitions-Sammelüberweisung (Drucksache 18/33 = Übersicht) → Einzelpetitionen haben kein Ergebnis.

1.3 Validierungs-Stand v5

Protokoll Treffer Bemerkung
MMP18-119 (Training) 19/19 100 % Precision und Recall
MMP18-115 10/32 fehlende Anchor-Varianten
MMP18-110 17/6 Sammel-Petitions-Einzelansprache (False Positives)
MMP18-100 8/25 ähnlich wie MMP18-115

Interpretation: v5 ist overfit auf MMP18-119. Präzision bleibt hoch (keine falschen Abstimmungs-Ergebnisse), aber der Recall-Einbruch auf ~30-50 % schon innerhalb NRW zeigt: die Anchor-Liste deckt nicht einmal NRW-interne Variation ab. Cross-BL wird drastisch schlechter.

2. Cross-BL-Strukturanalyse

2.1 Sample-Verfügbarkeit im Repo

Befund: Keine Plenarprotokoll-Samples im Repo.

Geprüfte Pfade:

  • webapp/tests/fixtures/ → leer (nur Sub-Dir-Marker, keine Dateien)
  • webapp/app/static/ → nur Wahlprogramm-PDFs
  • webapp/data/ → nur gwoe-antraege.db (SQLite)
  • antraege/ (Projekt-Root) → 80+ PDFs, aber alle Drucksachen (Anträge), keine MMP-Protokolle
  • TEMP/ → HAR-Captures der Dokumentations-Frontends, keine PDFs

Keine einzige MMP*.pdf oder PlPr*.pdf im gesamten Projekt-Tree.

2.2 Strukturelle Annahmen aus Doku-Systemen

Ohne Samples ist eine echte Struktur-Analyse nicht möglich. Aus app/bundeslaender.py und app/parlamente.py lassen sich nur Plausibilitäts-Hypothesen ableiten:

Familie BL Protokoll-Quirk (Hypothese)
OPAL NRW MMP-PDF, stereotyp (v5-Referenz)
StarWeb/portala HE, SH, BB, RP, LSA, BE Plenarprotokolle typischerweise als PDF mit Zweispalten-Layout
ParlDok MV, HH, TH eigene Redaktionsrichtlinie pro LT
PARiS HB Bürgerschaft, Kleinst-Parlament
Eigensystem BY, SL, SN je eigene Typografie
DIP (BUND) BUND Plenarprotokolle als strukturiertes XML verfügbar — Ausnahme!

Wichtig: Die Adapter in parlamente.py liefern bisher Drucksachen (Anträge), nicht Plenarprotokolle. Für Protokoll-Download müssten die Adapter erweitert werden — das ist Teil des Scopes von #106.

2.3 Fraktions-Namen-Normalisierung — vorhanden

app/parteien.py:187 bietet normalize_partei(raw, *, bundesland=None), das laut Docstring die vier Adapter-eigenen _normalize_fraktion() ersetzt. Das ist entscheidend, weil Protokolle Fraktionen in ihrer LT-spezifischen Schreibweise nennen (BÜNDNIS 90/DIE GRÜNEN, GRÜNE, B'90/Grüne). Die Komponente ist da und getestet — v6 muss sie nicht neu bauen.

3. Lösungs-Ansätze

3.1 Option A — Rule-based expand (v5 pro BL)

Pro Bundesland eine abstimmung_rules_<bl>.yaml mit BL-spezifischen Anchors, Segment-Boundaries, Vote-Patterns. Ein generischer Engine- Kern lädt die Rules und parst.

Pro:

  • Deterministisch, reproduzierbar, offline.
  • Null Laufzeit-Kosten, Millisekunden pro Protokoll.
  • Präzision bleibt bei 100 % (nur bekannte Anchors matchen).

Contra:

  • Recall-Problem überträgt sich linear auf 17 BL. v5 schafft in NRW schon nur 30-50 % auf unbekannten Protokollen — 17-fach skaliert heißt: pro BL eigene Overfit-Runde.
  • Format-Drift zwischen Wahlperioden (neues Redaktionsteam, neuer Stilführer) bricht still.
  • Wartungs-Aufwand: geschätzt 2-3 Tage Reverse-Engineering pro BL, plus ~0,5 Tage Nachpflege pro WP-Wechsel.

Aufwand: 2-3 Tage pro BL × 17 = 34-51 Personentage bis Vollabdeckung. Pro BL-Nachpflege alle 4-5 Jahre.

Risiko: Stille Recall-Einbrüche, die nur durch Ground-Truth-Tests entdeckt werden. Ohne kontinuierliches Fixture-Update degradiert die Datenqualität.

3.2 Option B — LLM-Extraction pro Seite

Ein strukturiertes Prompt nimmt eine Protokoll-Seite (oder einen 10-Seiten-Block) und liefert JSON:

{
  "abstimmungen": [
    {
      "drucksache": "18/17492",
      "titel": "…",
      "ergebnis": "angenommen|abgelehnt|überwiesen",
      "ja": ["CDU", "GRÜNE"],
      "nein": ["SPD", "AfD", "FDP"],
      "enthaltung": [],
      "namentlich": false
    }
  ]
}

Pro:

  • Ein Prompt für alle 17 BL, Format-Drift-robust.
  • Nutzt bestehende LLM-Pipeline (analyzer.py, Qwen-Plus).
  • Neue BL kosten null Entwicklungs-Zeit nach Grund-Integration.
  • Zitat-Binding-Pattern aus ADR 0001 direkt übertragbar (Post-LLM-Match gegen Protokoll-Text, genau wie bei Wahlprogramm-Zitaten).

Contra:

  • Kosten: ein Protokoll hat 120-200 Seiten. Bei 10 Seiten pro Prompt-Call: ~15-20 Calls × ~0,03 €/Call bei Qwen-Plus = 0,45-0,60 € pro Protokoll. NRW-WP18 hat ~170 Plenarsitzungen → ~85 € allein für NRW, ~1500 € für alle 17 BL × 1 WP. Erträglich, aber nicht trivial.
  • Laufzeit: 15-20 Calls × 10 s = 2,5-3 min pro Protokoll.
  • LLM-Halluzinationen: ADR 0001 zeigt, dass Zitate cross-mixen. Hier: Gefahr, dass LLM ein Abstimmungs-Ergebnis halluziniert, das im Protokoll nicht vorkommt. Citation-Binding gegen Protokoll-Text ist Pflicht.

Aufwand: Grund-Integration 3-5 Tage (Prompt-Engineering + Citation- Binding analog ADR 0001 + Cache-Layer, damit dasselbe Protokoll nicht zweimal abgefragt wird). Pro BL null zusätzlich.

Risiko: LLM-Halluzinationen bei stillem Citation-Binding-Bug; Kosten-Explosion wenn alle WPs voll nachgefahren werden.

3.3 Option C — Hybrid: Rules-Pre-Filter + LLM-Strukturierung

Stufe 1 (Rules): Ein universelles Regex-Set (wenige, stabile Anchors) segmentiert das Protokoll in Abstimmungs-Kandidat-Blöcke. Kandidat: Text zwischen zwei Abstimmung|stimmen ab|stimmen … zu- Vorkommen.

Stufe 2 (LLM): Jeder Kandidat-Block (~500-2000 Tokens) wird an den LLM gegeben mit dem Prompt „Extrahiere Drucksache, Ergebnis, Ja/Nein/ Enthaltung-Fraktionen. Nur was im Text steht."

Stufe 3 (Verifier): Jede extrahierte Fraktion wird gegen normalize_partei() geprüft und gegen BUNDESLAENDER[bl] .landtagsfraktionen gefiltert. Halluzinierte Fraktionen (nicht im LT vertreten) werden gedroppt oder loggen einen Warn.

Pro:

  • Kosten ~80 % niedriger als Option B (nur Kandidat-Blöcke, nicht ganze Seiten).
  • BL-übergreifend, weil Stufe 1 nur grobe Anchors braucht (stimmen, Abstimmung als Lemma-Formen sind in allen dt. Parlamenten üblich).
  • Verifier fängt die meisten Halluzinationen ab.
  • Skaliert auf neue BL mit nahezu null Zusatz-Aufwand.

Contra:

  • Drei Stufen = drei Stellen, an denen Bugs sein können.
  • Stufe 1 kann Kandidaten übersehen (stille Recall-Lücke). Debuggable über „kein Kandidat gefunden"-Log.
  • Komplexer zu testen als reine Rules oder reines LLM.

Aufwand: 5-7 Tage Grund-Integration. Pro BL: 0,5 Tag (einmal Kalibrierung der Stufe-1-Anchors mit einem Protokoll-Sample).

Risiko: Mittel. Komplexität ist höher, aber jede Stufe isoliert testbar. Analog zur LLM-Citation-Binding-Architektur aus ADR 0001.

3.4 Bewertungs-Matrix

Kriterium A (Rules) B (LLM-voll) C (Hybrid)
Precision (erwartet) 95-100 % 85-95 % 90-98 %
Recall (erwartet) 30-60 % pro BL 85-95 % 80-92 %
Entwicklung (Tage) 34-51 3-5 5-7
Laufzeit-Kosten/Prot. ~0 € ~0,50 € ~0,10 €
Skaliert auf neue BL nein (linear) ja (gratis) ja (halbgratis)
Format-Drift-Robustheit niedrig hoch mittel-hoch
Debug-Transparenz hoch niedrig mittel
Offline-fähig ja nein nein

4. Entscheidungs-Baum

flowchart TD
    Start([Issue #106: Abstimmungsverhalten]) --> Samples{Plenarprotokoll-<br/>Samples<br/>beschaffbar?}
    Samples -->|nein| Stop1[STOP:<br/>Sample-Beschaffung zuerst]
    Samples -->|ja, ≥3 BL| Scope{Scope<br/>klar?}
    Scope -->|nur NRW, kurzfristig| OptA[Option A<br/>v5 härten auf NRW]
    Scope -->|alle 17 BL| OptFamily{Kosten-Bereitschaft<br/>für LLM-Calls?}
    OptFamily -->|nein, offline nötig| OptA_scaled[Option A:<br/>Rule-Engine pro BL<br/>34-51 Tage]
    OptFamily -->|ja, 1500 €/WP OK| OptC[Option C Hybrid:<br/>5-7 Tage Grund<br/>+ 0,5 Tag/BL]
    OptC --> POC[POC auf 2 BL:<br/>NRW + HE<br/>Ground-Truth-Set je 3 Prot.]
    POC --> Eval{Recall<br/>≥ 80 % auf HE?}
    Eval -->|ja| Rollout[Rollout auf 17 BL]
    Eval -->|nein| Rethink[LLM-Prompt iterieren<br/>oder auf B eskalieren]
    OptA_scaled --> NRW_first[NRW zuerst,<br/>dann nach Output-Ranking]

5. Empfehlung

5.1 Familie

Option C (Hybrid) als Primärweg. Begründung:

  1. Die bestehende Architektur passt: analyzer.py nutzt schon LLM, ADR 0001 liefert das Binding-Pattern, parteien.py:normalize_partei gibt den Verifier.
  2. Option A skaliert nicht auf 17 BL ohne Personen-Aufwand, den das Projekt realistisch nicht hat (siehe Issue-Backlog).
  3. Option B hat das Halluzinations-Risiko und Kosten ohne Gewinn gegenüber C.

5.2 Minimum-Viable-Scope

  1. Protokoll-Download-Erweiterung der Adapter: get_plenarprotokoll (wp, sitzung) als optionale Methode. Zuerst nur NRW + 1 weiteres BL (Vorschlag: BUND, weil dort XML statt PDF → erleichtert Text-Extraktion).
  2. Stufe-1-Anchors als einzige YAML-Datei (nicht pro BL), mit den ~5 stabilsten Formulierungen (stimmen ab, Abstimmung über, wer stimmt zu, wer ist dagegen, wer enthält sich).
  3. Stufe-2-Prompt mit Zitat-Binding-Anforderung (jede Fraktion muss als Exakt-Substring im Kandidat-Block vorkommen).
  4. Stufe-3-Verifier via normalize_partei und BUNDESLAENDER[bl].landtagsfraktionen.
  5. Persistenz: neue Tabelle abstimmungen (drucksache, wp, sitzung, ergebnis, ja_fraktionen, nein_fraktionen, enthaltung_fraktionen, source_url, extracted_at, extractor_version).

5.3 Abbruch-Kriterien

  • Stop 1: Wenn POC auf 2 BL nach 7 Tagen unter 70 % Recall bleibt, ist C nicht erreicht. Fallback: B (voller Seiten-Scan) oder Parken.
  • Stop 2: Wenn Nutzer-Interview zeigt, dass Abstimmungsverhalten pro Drucksache keinen Dashboard-Mehrwert gegenüber dem bereits aktiven Score hat, ist v6 unpriorisiert. Issue #106 hängt seit April offen — die Frage „braucht das jemand?" ist vor Implementierung zu klären.
  • Stop 3: Wenn eine offene Datenquelle (z.B. abgeordnetenwatch.de /api) Abstimmungsverhalten bereits strukturiert ausliefert, ist ein eigener Parser obsolet → Evaluations-Pflicht vor Bau.

5.4 BL-Priorisierung

Nach CSV-Export /api/auswertungen/export.csv und Wichtigkeit:

  1. NRW — größter Output, v5 vorhanden
  2. BUND — XML statt PDF, einfachste Implementierung, hoher Impact
  3. HE, BY, BW — große Flächenländer, hohe Drucksachen-Zahl
  4. BE, HH, HB — Stadtstaaten, geringer Protokoll-Umfang, guter Cross-Check für den Hybrid
  5. Rest nach Output-Ranking aus Dashboard

6. Sample-Beschaffungs-Plan

Da im Repo keine Plenarprotokolle liegen, für den POC zuerst manuell ziehen. Pro BL 1 aktuelles Protokoll:

BL Quelle Format
NRW landtag.nrw.de/portal/WWW/dokumentenarchiv/Dokument/MMP{WP}-{N}.pdf PDF
BUND bundestag.de/services/opendata (XML-Feed) XML
BY bayern.landtag.de/parlament/dokumente/plenarprotokolle PDF
BW landtag-bw.de/home/dokumente/plenarprotokolle.html PDF
BE parlament-berlin.de/dokumente/open-data (XML verfügbar) XML/PDF
BB parlamentsdokumentation.brandenburg.de (portala) PDF
HB paris.bremische-buergerschaft.de/starweb/paris PDF
HH buergerschaft-hh.de/parldok PDF
HE starweb.hessen.de/portal PDF
MV dokumentation.landtag-mv.de (ParlDok) PDF
NI nilas.niedersachsen.de (HAR-Capture nötig, siehe MEMORY) PDF
RP opal.rlp.de (portala) PDF
SL landtag-saar.de (Umbraco) PDF
SN edas.landtag.sachsen.de (XML-Export, siehe project_sn_xml_export.md) XML
LSA padoka.landtag.sachsen-anhalt.de (PARDOK) PDF
SH lissh.lvn.parlanet.de/cgi-bin/starfinder/0 PDF
TH parldok.thueringer-landtag.de PDF

Speicherung unter webapp/tests/fixtures/protokolle/<bl>/ (git-lfs oder außerhalb des Repos, je nach Dateigröße). Pro Protokoll eine <protokoll>.expected.json mit Ground-Truth-Abstimmungen als Fixture.

Minimum für POC: NRW (MMP18-119 als Regression), BUND (XML als Gegenprobe), HE (StarWeb als Cross-BL). Drei Protokolle × ~20-30 Abstimmungen = ~60-90 Ground-Truth-Items für Precision/Recall.

7. Offene Fragen für Tobias

  1. Bedarfs-Validierung: Wer konsumiert das Abstimmungsverhalten konkret im Dashboard? Gibt es eine UI-Spec dafür, oder ist das gedachte Feature? Issue #106 nennt keine konkreten Konsumenten.
  2. LLM-Kosten: Ist ~1500 € Budget für die 17-BL-Erstbefüllung einer WP vertretbar? Oder muss der Rollout auf eine WP pro Jahr gedeckelt werden?
  3. Alternative Datenquelle: Wurde abgeordnetenwatch.de/api geprüft? Die liefern auf Bundesebene strukturierte Abstimmungsdaten und haben teilweise Landesabdeckung. Wenn ja, ist v6 für diese BL obsolet.
  4. v5-POC-Code: Existiert der Code noch auf der Festplatte (in einem früheren Session-Worktree)? Ein Fundstück würde 1-2 Tage Re-Implementations-Arbeit sparen.

8. Nächster Schritt (empfohlen)

  1. Vor jeder Zeile Code: Beantworte Fragen 1 und 3 oben. Das entscheidet, ob v6 überhaupt startet.
  2. Wenn ja: POC auf NRW + BUND + HE mit Option C, Sample-Zug manuell. 7 Tage Timebox.
  3. Wenn POC erfolgreich (Recall ≥ 80 %): ADR für v6 schreiben (analog 0001/0002), Rollout-Plan.
  4. Wenn POC scheitert: Zurück zu Option A nur für NRW, v5-Recall-Lücken schließen, #106 auf NRW-only beschränken und #126 schließen als „nicht wirtschaftlich".

9. Verifikations-Checkliste

  • v5-Code lokal gesucht → nicht im Repo
  • MEMORY-Referenz gelesen (95 Zeilen) — v5-Pattern dokumentiert
  • Sample-Inventar: null Protokoll-Samples im Repo
  • BL-Adapter-Architektur (ADR 0002) verstanden — ParlamentAdapter hat aktuell keine get_plenarprotokoll-Methode
  • Fraktions-Normalisierung existiert (parteien.py:187 normalize_partei)
  • Kosten-Schätzung für Option B/C kalkuliert
  • Drei Optionen bewertet, Matrix + Mermaid-Diagramm
  • Abbruch-Kriterien formuliert
  • Sample-Beschaffungs-Plan für alle 17 BL

Nicht in diesem Dokument: Konkreter Code, Prompt-Vorlagen, ADR-Text für v6. Alles nächste Phase nach Go/No-Go-Entscheidung von Tobias.