gwoe-antragspruefer/docs/adr/0009-protokoll-parser-registry.md

128 lines
4.7 KiB
Markdown
Raw Normal View History

# 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 in `parlamente.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.py`
als 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):
```python
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.