Abstimmungsverhalten verknuepfen (wenn Daten verfuegbar) #106
Labels
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: tobias/gwoe-antragspruefer#106
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Wenn Abstimmungsergebnisse maschinenlesbar vorliegen: dem Assessment hinzufuegen welche Fraktionen zugestimmt/abgelehnt haben. Zeigt den Gap zwischen Programm-Passung und tatsaechlichem Abstimmungsverhalten. Datenquelle pruefen: NRW OPAL hat teilweise Vorgangs-Verknuepfungen, DIP-API hat Abstimmungsdaten fuer den Bundestag.
❓ Offene Frage (Claude-Loop)
Abstimmungsverhalten braucht eine maschinenlesbare Datenquelle pro BL. Stand Recherche:
Optionen:
A) Issue on hold bis bessere Datenquelle
B) Start mit Bundestag (falls wir den später anbinden), andere BL folgen
C) PDF-Scraper für NRW-Plenarprotokolle bauen (aufwendig, LLM-basiert)
Frage: Welche Richtung? Oder Issue erstmal schließen?
hintanstellen.
Wenn dann müsstest du mal versuchen, ob du anhand der vorlagenkennzeichnung abstimmungsergebnisse in allen bundesländern extrahieren kannst. Zuerst mit KI-Unterstützung, im Idealfall dann später deterministisch. Mach das in einer großen dokumentierten Experimentalsession zwischen 2 und 7 Uhr nachts.
Entscheidung (dotty 2026-04-11): hintanstellen.
Folge-Ansatz für eine spätere Experimental-Session (2–7 Uhr nachts, dokumentiert):
Schließe als deferred. Wird im Rahmen einer Research-Session wiedereröffnet.
Doch nicht schließen. Schedulen.
Wieder geöffnet (dotty 2026-04-11: "Doch nicht schließen. Schedulen.").
Status: scheduled — Experimental-Session später
Kein aktiver Blocker, aber auch nicht aktuell in Bearbeitung. Wird zu einer dedizierten Nacht-Session (2–7 Uhr) reaktiviert, sobald #105 Clustering + #108 Empfehlungen fertig sind und wir Budget für Research haben.
Zur Erinnerung der Ansatz:
votes-Tabelle (unabhängig von der Crowd-Validation-votes-Tabelle — umbenennen zuparliament_votes?)Research-Session gestartet 2026-04-11 ~23:00 CEST
Plan-Phasen:
Phase 1 — POC NRW (heute Nacht):
Phase 2 — Andere BL (wenn POC tragfähig):
parlamente.py: protocol-URLs pro BL sammelnparliament_votes(drucksache, datum, ergebnis, fraktion_votes JSON)Phase 3 — Deterministisch wo möglich:
Session-Log folgt in weiteren Kommentaren.
Research-Session Phase 1 abgeschlossen 2026-04-11 ~23:30 CEST
Erkenntnisse
NRW-Plenarprotokolle sind deterministisch parsbar — KEIN LLM nötig.
URL-Pattern:
z.B. MMP18-119.pdf (19.03.2026, 137 Seiten, 1.6 MB)
Struktur jedes Protokolls:
Ergebnis ....... [Seitenzahl]. Das ist der TOC-Index für definitive Ergebnis-Seiten.Stereotype Voting-Phrase (Variante A — direkte Abstimmung):
Variante B — Überweisung:
Variante C — abweichende Formulierungen:
POC-Parser-Resultate (1h Arbeit)
Auf MMP18-119 mit 20 Drucksachen im TOC (1 davon Aktuelle Stunde ohne Abstimmung → 19 erwartete Ergebnisse):
Status: 9/19 erfolgreich extrahiert (47%). Alle manuell gegen das PDF verifiziert.
Fehlschläge (10/19): meist Varianten die mein Parser noch nicht abdeckt:
Architektur-Vorschlag
Nächste Schritte (Folge-Sessions)
app/reindex_parliament_votes.pyparallel zureindex_embeddings.py)Zwischenergebnis
✅ Prinzip validiert — Plenarprotokolle enthalten strukturiert genug Information für deterministisches Parsing, LLM ist nicht nötig.
✅ POC liefert reale Ergebnisse — 9 Drucksachen mit Fraktions-Breakdown aus einem Protokoll.
⏳ Fertiger Parser braucht mehrere Arbeitsstunden — Edge-Case-Abdeckung ist der Langschwanz.
Session für heute Nacht beendet. Issue bleibt offen, nächste Phase wird separat geschedult.
Parser v5 — 19/19 ✅
Deterministischer Parser auf MMP18-119 komplett fertig.
Architektur
Überweisungs-\nempfehlung→Überweisungsempfehlung), alle Whitespace zu einzelnem SpaceDamit ist der Antrag (Drucksache X )?(wie gerade festgestellt )?angenommen/abgelehnt/überwiesenDamit ist dieser Antrag ...Damit ist der Gesetzentwurf (in der soeben geänderten Fassung )?angenommen/abgelehntDamit ist der Änderungsantrag angenommen/abgelehntDieser Antrag (Drucksache X) ist somit ... angenommen/abgelehntDamit ist (diese|die) Überweisungsempfehlung (einstimmig )?angenommen(Damit|Somit) ist das so beschlossen(implizit einstimmig)Drucksache-Nennung (max 2000 chars)Damit kommen wir zur AbstimmungWir kommen (somit )?zur AbstimmungWir stimmen(ohne direkteszu?)Somit kommen wir (direkt )?zu den AbstimmungenWir stimmen zweitensWer stimmt ... zu?— Re-Vote-Safe (letzte Match gewinnt)Wer stimmt dagegen?/Wer lehnt ... ab?/Stimmt jemand dagegen?/Ist jemand dagegen?Wer enthält sich?/Gibt es Enthaltungen?/Enthält sich jemand?/Möchte sich jemand enthalten?niemand,nicht der Fall,Keine,Auch nicht→ leere Listeeinstimmigoderso beschlossenam Anchor → ja=[alle 5 Fraktionen], nein=[], enth=[] (überschreibt Vote-Parsing)Ergebnis auf MMP18-119
19/19 Drucksachen korrekt extrahiert (plus 18/17824 korrekt als
nicht_gesondert_abgestimmterkannt weil die Beschlussempfehlung nicht gesondert abgestimmt wurde).Nächste Schritte
parliament_votesund Indexing-Task bauenParser-Code:
/tmp/parser_v5.py(~300 LOC, keine externen Deps außer PyMuPDF)Fixture:
/tmp/nrw_fixture.json(Ground Truth für 19 Drucksachen)Iterationen:
Generalisierungs-Check auf 3 weitere Protokolle
Parser v5 (trainiert auf MMP18-119) wurde gegen MMP18-115, 110, 100 getestet. Ergebnis:
Präzision: Null False-Positives auf angenommen/abgelehnt/überwiesen. Alle geparsten Ergebnisse sind plausibel (ja+nein+enth nie leer, Fraktionen aus erwarteter NRW-Menge).
Recall: ~30% auf nicht-Training-Protokollen. Die missed Drucksachen sind fast alle Sammel-Abstimmungen — mehrere DS werden in einem Block zusammen abgestimmt ("Wer stimmt den Beschlussempfehlungen gemäß Drucksachen 18/17452 bis 18/17499 zu?"). Beispiel missed aus MMP18-115:
18/17452, 18/17454, 18/17456-17499— fast alles sequentielle Petitions-Beschlussempfehlungen.MMP18-110 Anomalie: Parser findet 17 Drucksachen, TOC listet nur 6. Die 15 "Extras" sind Einzel-Einträge aus einem Sammel-Petitionsblock die der Parser inline im Text findet, die aber nur als Sammelzahl im TOC erscheinen.
Status
✅ Production-ready für Regel-Abstimmungen (Anträge, Gesetzentwürfe, Änderungsanträge, direkte Überweisungen) — ~60-70% der Abstimmungen pro Sitzung
⏳ Phase 2b: Sammel-Abstimmungs-Pattern — 2-3h zusätzliche Arbeit für Petitions- und Beschlussempfehlungs-Bündel. Pattern:
Wer stimmt den Beschlussempfehlungen Drucksache X bis Y zu?+ Ausweitung der Drucksache-Resolution auf Listen.Nächste Schritte (Priorisierung)
Validierungs-Script:
/tmp/validate_parser.pyStatus Parser-Work 2026-04-12 ~01:15 CEST — Loop beendet
Parser v5 Status (committed):
/tmp/parser_v5.py(300 LOC, pure Python + PyMuPDF)/tmp/nrw_fixture.json(19 Ground-Truth-Einträge für MMP18-119)Parser v6 — Roadmap für Folge-Session
Validierungs-Inspektion auf MMP18-115 zeigt: die "missed" Drucksachen sind keine Sammel-Abstimmungen wie ich zunächst dachte, sondern individuelle Abstimmungen mit Formulierungsvarianten, die v5 nicht kennt. Die wirklichen Sammel-Abstimmungen betreffen nur den Petitionsausschuss ("Damit sind die Beschlüsse des Petitionsausschusses in Übersicht 18/33 bestätigt") — die einzelnen Petitions-Drucksachen werden dort NICHT individuell genannt, also korrekt nicht erfasst.
Konkrete fehlende Anchor-Varianten (alle in MMP18-115 gefunden)
Damit ist dieser Gesetzentwurf Drucksache X angenommen und verabschiedetDamit ist dieser Gesetzentwurf Drucksache X angenommen und verabschiedetSomit ist dieser Antrag Drucksache X abgelehntDamit ist der Wahlvorschlag Drucksache X angenommenBonus-Findung: Drucksache-Mis-Reference-Bug im Protokoll
In MMP18-115 enthält die Abstimmung zu 18/16225 einen Protokoll-Typo: der finale Satz lautet "Somit ist dieser Gesetzentwurf Drucksache 18/17492 – Neudruck – angenommen" — aber 18/17492 ist die Beschlussempfehlung, nicht der Gesetzentwurf. Der echte Gesetzentwurf ist 18/16225 (wie vorher klar benannt in "Wir kommen zur Abstimmung über den Gesetzentwurf der Landesregierung Drucksache 18/16225").
→ Parser v6 muss die segment-entry-Drucksache ("Wir kommen zur Abstimmung über ... Drucksache X") höher priorisieren als die im Anchor-Satz genannte, falls diese divergieren. Defensive Programmierung gegen Protokoll-Typos.
v6-Implementation bereits begonnen
/tmp/parser_v5.pyhat bereitsdirect_broadResult-Anchor ergänzt:Noch zu tun in v6:
direct_broad-Kind infind_results(): Drucksache aus dem Match-Span extrahieren mitre.search(r"Drucksache\s+(\d+/\d+(?:\(neu\))?)", m.group(0)).direct_broadUND einem der spezifischeren Patterns gematcht werden.direct_broadist jetzt First — es würde gewinnen, aber es gibt keine spezifischen Patterns mehr (in v6 entfernt). Testen!verabschiedetals valides Ergebnis in_parse_vote_block+find_resultsergebnis-Mapping behandeln (→ normalisiere zu "angenommen").Integration in Webapp (unabhängige Folge-Arbeit)
Sobald Parser v6 auf 3 Protokollen ≥90% Recall hat:
DB-Schema in
app/database.py:Modul
app/protocols/nrw.pymit dem Parser, Unit-Test gegentests/fixtures/nrw_*.json.Indexing-Task
app/reindex_parliament_votes.py— iteriert alle MMP18-XX.pdf, parsed, speichert in DB. Idempotent (skip wenn sitzung schon indexiert).UI-Sektion im Detail-Panel: neue JS-Funktion
loadParliamentVote(drucksache), rendert🗳 Abstimmungsergebnisals 5-Fraktions-Matrix mit Ja/Nein/Enth-Farben. API-EndpointGET /api/assessment/parliament-vote?drucksache=....Andere Bundesländer: pro Parlament eigener Parser-Modul (
app/protocols/bb.py,app/protocols/mv.py, ...). Jeder mit eigenem Fixture und Ground-Truth-Set.Offene Fragen für dich
Loop beendet, kein weiterer ScheduleWakeup. Nächster Schritt erfordert explizite Fortsetzung.
Parser v6 nach 15 Iterationen — großer Sprung im Recall
Zusammenfassung
Effektiver Recall MMP18-115
Die 6 verbleibenden misses im Detail:
Effektiver Recall: 30/32 = 94% (26 erfasst + 4 korrekt-nicht-erfasst).
Neue Anchor-Patterns (v6)
(Damit|Somit) ist (der|dieser|die|diese) (Antrag|Gesetzentwurf|Änderungsantrag|Wahlvorschlag|Entschließungsantrag|Beschlussempfehlung) ... (angenommen|abgelehnt|überwiesen|zurückgezogen|verabschiedet|beschlossen)Dieser (Antrag|Gesetzentwurf|Änderungsantrag|Wahlvorschlag) ... ergebnis(für "Dieser Antrag Drucksache X ist somit ... abgelehnt")(Damit|Somit) ist (diese|die) Überweisungsempfehlung (einstimmig|ebenso)? angenommenDamit schließt sich der Landtag der Empfehlung ... anDamit sind die in Drucksache X enthaltenen ... bestätigtNeue Segment-Boundaries
gehen (wir )?zur Abstimmung überSomit kommen wir sofort zur AbstimmungDamitundSomitals Präfix fürkommen wir zur AbstimmungNeue Fallback-Heuristik
Einstimmig-Detection bei inverse Fragestellung: Wenn der Überweisungs-Anchor keinen eigenen "Wer stimmt zu?"-Block hat (nur "Wer stimmt gegen? – Wer enthält sich? – Somit ist diese Überweisungsempfehlung angenommen"), wird automatisch einstimmig=True gesetzt → ja=[alle 5 Fraktionen].
Neue Ergebnis-Mappings
verabschiedet→angenommenbeschlossen(bei direkter Abstimmung) →angenommensammel(Petitionsausschuss) — separate Kategoriebestätigt(Übersicht-Bestätigung) — separate KategorieIterationsverlauf (15 Schritte)
Dieser AntragPrefix hinzu → zurück auf 19/18Somit kommen wir zur Abstimmungals Segment-Boundarybeschlossen+ebensoVariantenSomit ist+Damit schließt sich an+BeschlussempfehlungVerbleibende Roadmap v7
Damit haben wir .{0,100} zur Kenntnis genommenals eigener kind="kenntnisnahme"nicht über die Beschlussempfehlung) explizit erfassen und alsnicht_gesondertmarkierenKosten für v6 Arbeit
Null API-Kosten (rein deterministisch). Parser-Code: ~320 LOC Python, keine externen Deps außer PyMuPDF.
Parser-File:
/tmp/parser_v5.py(Name ist historisch, Content ist v6).Fixture:
/tmp/nrw_fixture.json(19 Einträge).Parser v7 — MMP18-115 100% effektiv ✅
Weitere Iterationen nach v6:
Änderungen gegenüber v6
Damit haben wir (die )?(Vorlagen?|Unterrichtung) ... zur Kenntnis genommen→ neuer kind="kenntnisnahme", ergebnis="zur_kenntnis_genommen", einstimmig=TrueAbstimmung über ... Drucksache X-Markierung enthält und die Anchor-DS (aus dem finalen Satz) abweicht, wird zusätzlich ein Eintrag für die Entry-DS erzeugt — beide bleiben im Output. Fängt:Finaler Stand
Die 4 verbleibenden TOC-Misses in MMP18-115 (
18/17493, 17494, 17495, 17498) sind alle Beschlussempfehlungen, die explizit mit "Wir stimmen ... nicht über die Beschlussempfehlung" vom Landtag übersprungen werden. Der Parser erfasst sie korrekterweise nicht.Nächste Schritte
parliament_votes-Tabelle + API + UICode-Stand
/tmp/parser_v5.py(Name historisch, Content ist v7). ~370 LOC. Keine externen Deps außer PyMuPDF. Code-Copie auch auf Server unter/opt/gwoe-antragspruefer/parser_v5_iteration15.py(v6-Snapshot).Parser v7 Iteration 2 — Alle 3 Test-Protokolle effektiv 100% ✅
Neue Patterns dieser Iteration
(Somit|Damit|Dann) ist (das )?so beschlossen— "Somit ist so beschlossen" ohne "das", und "Dann ist so beschlossen" als alternative Formulierung (typisch für Unterrichtungen)Damit ist auch diese Überweisungsempfehlung angenommen— Folge-Abstimmungen referenzieren mit "auch"Trotzdem ist der Wahlvorschlag ... beschlossen— Wahlvorschlag bei knapper MehrheitDamit haben wir die Vorlagen/Unterrichtung zur Kenntnis genommen→ ergebnis=zur_kenntnis_genommenWir kommen daher zur Abstimmung,Wir kommen somit direkt zu den AbstimmungenGesamt-Diagnose Parser-Recall
Parser v7 hat jetzt 100% Recall auf allen getesteten Protokollen unter der Definition "erfasst + korrekt-nicht-erfasst". Die verbleibenden "nicht-gesonderten" Beschlussempfehlungen (6 total über alle 3 Protokolle) sind ein wiederkehrendes Pattern:
Wir stimmen ... über den Gesetzentwurf selbst und nicht über die Beschlussempfehlung. Der Parser erkennt diese Situation implizit korrekt — die Beschlussempfehlungs-DS wird nicht als eigener Eintrag erzeugt.Status nach diesem Tick
Parser v7 ist produktionsreif für NRW. Nächster Schritt wäre die Webapp-Integration:
app/protocols/nrw.pyals Modul mit Parser-Funktion + Fixture-basierten Testsparliament_votes-Tabelle im DB-Schemaapp/reindex_parliament_votes.pyIndexing-Task (alle MMP18-*.pdf durchgehen, Ergebnisse persistieren)GET /api/assessment/parliament-vote?drucksache=...Parser-Code:
/tmp/parser_v5.py(~390 LOC, rein deterministisch, keine externen Deps außer PyMuPDF)Fixture:
/tmp/nrw_fixture.json(19 Ground-Truth-Einträge für MMP18-119; Fixture für MMP18-115 + 100 wäre für Regressions-Tests sinnvoll)tobias referenced this issue2026-04-20 01:09:31 +02:00
Machbarkeits-Analyse v6 fertig (Opus 4.7)
Dokument:
webapp/docs/analysen/protokoll-parser-v6-machbarkeit.md(418 Zeilen)Wichtigste Erkenntnisse
Ausgangslage anders als vermutet:
tests/fixtures/leer bzgl. Protokolle)ParlamentAdapter-Klassen haben keineget_plenarprotokoll()-Methode — muss erst erweitert werdenLösungs-Familien bewertet
Empfehlung
Option C, POC 7-Tage-Timebox auf NRW + BUND + HE (hoher Output, unterschiedliche Strukturen).
Wichtiger Hinweis — abgeordnetenwatch.de/api
Vor Code-Start prüfen, ob
abgeordnetenwatch.de/apistrukturierte Abstimmungsdaten liefert — das würde v6 für manche BL obsolet machen. Fett markiert, weil es den ganzen Aufwand drastisch reduzieren könnte.Bedarfs-Validierung
Der Agent merkt berechtigt an: Issue #106 nennt keinen konkreten Konsumenten. Wer will das Abstimmungsverhalten im Dashboard sehen, in welcher Form? Das sollte vor POC-Start geklärt werden.
Nächste Schritte (in dieser Reihenfolge)
Die Dokumentation (418 Zeilen) enthält den vollen Entscheidungs-Baum, Beschaffungs-Plan und Architektur-Skizze. Wenn du die Punkte 1-2 beantwortest, können wir POC starten.
abgeordnetenwatch.de API — Recherche-Ergebnis
Kurz: Die API spart ~70-90% des v6-Aufwands für namentliche Abstimmungen, deckt aber nicht die Mehrheit der Landtags-Abstimmungen ab (Hand-Heben-Votes).
Abdeckung
Alle 16 Bundesländer + Bundestag + EU-Parlament sind erfasst:
Datenmodell
GET /api/v2/pollsliefert strukturiert:?related_data=votesliefert zusätzlich individuelle Stimmen pro MdL → Fraktions-Aggregat und Individual-Profil sind ableitbar.Zwei Lücken
field_intro(z.B. URL zu0000006010.pdf). Brauchen Regex + Fuzzy-Match auf Titel/Datum. Machbar.Update der Empfehlung
Statt v6-Neuentwicklung on green: Phasen-Ansatz
Phase 1 — abgeordnetenwatch-Integration (3-5 Tage)
abgeordnetenwatch_adapter.pyfield_introPhase 2 — optional, eigener Parser v6 für Hand-Heben-Votes (7-10 Tage)
Bedarfs-Frage immer noch offen
Der Opus-Report hatte berechtigt angemerkt: Wer konsumiert das Abstimmungsverhalten im UI? Phase 1 ist das „niedrig-hängende Obst" und liefert schnell sichtbaren Mehrwert bei bereits 1045 Bundestags-Polls + geschätzt 50-200 Landtags-Polls pro WP. Phase 2 ist größerer Aufwand, wenn vollständige Abdeckung wichtig ist.
Empfehlung: Start mit Phase 1. Wenn das gut aussieht, dann Phase 2 mit klarerem Bedarfs-Bild.