Architektur-Refactor zur Vorbereitung BL-uebergreifender Parser: - app/protokoll_parser_nrw.py → app/protokoll_parsers/nrw.py - app/ingest_votes_nrw.py → app/ingest_votes.py (BL-uebergreifend) - Neue app/protokoll_parsers/__init__.py mit: - PROTOKOLL_PARSERS-Dict (BL-Code → Parser-Funktion, derzeit nur NRW) - parse_protocol(bundesland, pdf_path) als BL-uebergreifender Einstieg - supported_bundeslaender()-Helper - NotImplementedError mit hilfreicher Message bei unbekanntem BL CLI bekommt --supported-Flag fuer BL-Discovery: python -m app.ingest_votes --supported → 'NRW' ADR 0009 dokumentiert das Muster (Sub-Package + Funktions-Registry, analog zu ADR 0002 fuer ParlamentAdapter). Folge-BL bekommen je eine eigene Datei und einen Eintrag in PROTOKOLL_PARSERS — kein Refactoring der Bestands-Logik. Tests: - 7 neue Tests in test_protokoll_parsers.py fuer Registry und Dispatch - Bestehende NRW-Tests umbenannt zu test_protokoll_parsers_nrw.py, Imports angepasst — keine Verhaltens-Aenderung - Bestehende Ingest-Tests umbenannt zu test_ingest_votes.py 642 Tests gruen, kein Verhaltens-Drift.
4.7 KiB
0009 — Plenarprotokoll-Parser-Registry pro Bundesland
| Status | accepted |
| Datum | 2026-04-28 |
| Refs | #106, #126, ADR 0002 (Adapter-Pattern) |
Kontext
Der NRW-Plenarprotokoll-Parser (#106) ist deterministisch, anchor-basiert und erreicht 19/19 auf der MMP18-119-Fixture. Damit war die Architektur-Frage gelöst — aber nur fuer NRW. Andere Bundeslaender publizieren ihre Plenarprotokolle in fundamental anderen Formaten:
- Hessen: HTML mit semantischen Tags pro Beschluss
- Brandenburg: PDF mit Tabellen-Layout fuer Vote-Counts
- Mecklenburg-Vorpommern: ParLDok-XML-Export
- Berlin: PDF mit eigenem Formularkasten-Schema
- ...
Ein einziger Parser fuer alle BL ist nicht baubar. Die Reverse-Engineering- Arbeit pro Landtag ist substantiell und passiert phasenweise: zuerst NRW wegen der hohen Antragsdichte, danach BL fuer BL nach Bedarf.
Das Adapter-Pattern aus ADR 0002 (ParlamentAdapter) hat dieses Problem
fuer die Antrags-Suche bereits geloest. Plenarprotokoll-Parser ist die
naechste Familie mit derselben Form: pro BL eine eigene Implementierung,
ein gemeinsamer Aufruf-Vertrag, ein Registry-Lookup.
Optionen
Option A — Eine grosse Datei mit If-Else-Dispatch
Eine einzige app/protokoll_parser.py-Datei mit einem parse_protocol(bl, pdf),
das je nach BL andere Funktionen ruft. Vorteile: flach, einfach.
Nachteile: waechst zur 2000-LOC-Datei, BL-spezifische Reverse-Engineering-
Notizen und Helper-Functions vermischen sich, schlechte Test-Isolation.
Option B — OOP-Hierarchie mit ProtokollParserBase als ABC
Abstrakte Basisklasse mit parse(pdf_path) -> list[VoteResult],
konkrete Subklassen pro BL. Vorteile: typisierter Vertrag.
Nachteile: Boilerplate fuer Klassen-Definitionen ohne Mehrwert,
weil der NRW-Parser keine Instanz-State hat (alles def-Funktionen,
keine self.x).
Option C — Sub-Package mit Funktions-Registry (gewaehlt)
app/protokoll_parsers/ als Sub-Package, pro BL eine eigene Datei
(nrw.py, mv.py, he.py, ...) die mindestens
parse_protocol(pdf_path: str) -> list[dict] exportiert. Eine
PROTOKOLL_PARSERS-Dict in __init__.py mappt BL-Code → Funktion.
Das BL-uebergreifende parse_protocol(bl, pdf_path) macht den Lookup.
Vorteile:
- Konsistent mit dem
ADAPTERS-Dict inparlamente.py(ADR 0002) - BL-Code lebt in eigener Datei mit eigenen Helpern und Notizen
- Neue BL = neue Datei + ein Eintrag in
__init__.py, kein Refactoring - Tests pro BL in eigener Test-Datei (
tests/test_protokoll_parsers_<bl>.py) - Parser-Funktionen bleiben simpel, kein OOP-Overhead
Nachteile:
- Vertrag ist nur per Convention dokumentiert (nicht via Type-System
erzwingbar) — dafuer ein Schema-Test in
test_protokoll_parsers.pyals Sicherheitsnetz.
Entscheidung
Option C. Konkret:
app/protokoll_parsers/
├── __init__.py # Registry + parse_protocol(bl, pdf) + supported_bundeslaender()
├── nrw.py # NRW v5 (vorher app/protokoll_parser_nrw.py)
└── <bl>.py # je BL eine Datei, sobald implementiert
Vertrag fuer jeden Parser (verbindlich):
def parse_protocol(pdf_path: str) -> list[dict]:
"""Returns: [
{
"drucksache": str | None,
"ergebnis": str, # angenommen/abgelehnt/ueberwiesen/...
"einstimmig": bool,
"kind": str, # parser-intern, fuer Debug
"votes": {
"ja": list[str], # Fraktions-Codes (CDU, SPD, GRUENE, ...)
"nein": list[str],
"enthaltung": list[str],
},
},
...
]"""
Naming: Datei-Stem = lowercase BL-Code (nrw.py, mv.py, ...).
Registry-Key = uppercase BL-Code ("NRW", "MV").
Konsumenten rufen parse_protocol(bundesland, pdf_path) aus dem
Sub-Package, nicht direkt eine BL-Datei.
Konsequenzen
Positiv
- Folge-BL-Implementierungen ohne Refactoring der Bestands-Logik.
- Reverse-Engineering-Notizen leben pro BL in einer Datei statt verteilt ueber eine Mega-Datei.
- Der
supported_bundeslaender()-Helper macht in CLI und UI sofort sichtbar, wo Daten verfuegbar sind und wo nicht. - Neue Adapter-Test-Files folgen demselben Schema (
test_protokoll_parsers_<bl>.py).
Negativ
- Schema-Vertrag nur per Convention (kein TypedDict). Dafuer ein
Smoke-Test in
tests/test_protokoll_parsers.py, der pro registriertem Parser die Result-Keys pruefen wird, sobald >1 Implementation existiert.
Folgen fuer andere ADRs
- ADR 0002 (Adapter-Pattern) bleibt gueltig; dieses ADR ueberbruckt es nicht, sondern wendet das gleiche Muster auf eine zweite Adapter-Familie an.
- Folge-Issues (HE/BB/MV/BE/...) sind reine Implementation-Tickets ohne Architektur-Diskussion — der Vertrag ist hier festgelegt.