gwoe-antragspruefer/app/protokoll_parsers/__init__.py
Dotty Dotter 22a2b63c35 feat(#148): BUND-Parser produktiv — Bundestags-XML-Plenarprotokolle
Erster vollwertiger Plenarprotokoll-Parser nach NRW. Quelle:
https://dserver.bundestag.de/btp/{wp}/{wp}{n:03}.xml

Anchor-Sprache (verifiziert WP20 Sitzungen 30 + 100):
  'Die Beschlussempfehlung ist mit den Stimmen der Koalitionsfraktionen
   gegen die Stimmen der CDU/CSU-Fraktion bei Enthaltung der AfD-Fraktion
   angenommen.'

Pattern:
- Subjekt: Beschlussempfehlung | Ueberweisungsvorschlag | Antrag | Gesetzentwurf
- Vote-Block: 'mit den Stimmen X / gegen die Stimmen Y / bei Enthaltung Z'
- Ergebnis: 'angenommen' oder 'abgelehnt'
- Drucksache rueckwaerts vom Anchor (1500 chars Window)
- Kind 'ueberweisung' invertiert ergebnis zu 'ueberwiesen'

Fraktions-Mapping (WP20 = Ampel):
- 'Koalitionsfraktionen' → SPD + GRÜNE + FDP
- 'Oppositionsfraktionen' → CDU/CSU + AfD + LINKE
- 'CDU/CSU-Fraktion', 'Fraktion Bündnis 90/Die Grünen', etc.

WP21 (ab 2025) braucht eigenes Mapping-Update.

26 Tests in test_protokoll_parsers_bund.py (Vote-Block-Parsing, Anchor-
Erkennung, Drucksachen-Lookup, End-to-End mit Mock-XML).

Cron + Ingest-CLI:
- PROTO_TARGETS-Format erweitert um PROTOKOLL_ID_PREFIX und {n3}-
  Placeholder fuer 3-stellig zero-gepaddetes BT-Schema (BTP20-N)
- ingest_votes URL-Suffix dynamisch (PDF vs XML) statt hardcoded .pdf
- Eintrag in PROTOKOLL_PARSERS (NRW + BUND)
- Stub-Test angepasst: BUND raus aus STUB_BL_CODES

889 Tests gruen (787 → 889, +102 fuer Phase-2 Stubs+Tests+BUND).
2026-04-28 23:21:39 +02:00

72 lines
2.4 KiB
Python

"""BL-uebergreifende Plenarprotokoll-Abstimmungsparser (#126).
Architektur (vgl. ADR 0009): pro Bundesland eine Modul-Datei
``app/protokoll_parsers/<bl-code>.py``, die mindestens eine Funktion
``parse_protocol(pdf_path: str) -> list[dict]`` exportiert. Die Registry
``PROTOKOLL_PARSERS`` mappt BL-Code → Parser-Funktion.
Erwartetes Result-Schema pro Eintrag in der Liste::
{
"drucksache": str | None, # z.B. "18/1234"; None bei nicht aufloesbar
"ergebnis": str, # angenommen | abgelehnt | ueberwiesen | ...
"einstimmig": bool, # explizit als einstimmig markiert
"kind": str, # parser-intern, fuer Debug
"votes": { # fraktions-Listen pro Vote-Kategorie
"ja": list[str],
"nein": list[str],
"enthaltung": list[str],
},
}
NRW ist die Referenz-Implementierung. Folge-BL (HE/BB/MV/BE/...) bekommen
eigene Module mit demselben Funktions-Vertrag — neue Eintraege in der
Registry sind reine Tippelarbeit, das Reverse-Engineering pro Landtag
ist die eigentliche Arbeit.
"""
from __future__ import annotations
from typing import Callable
from .nrw import parse_protocol as _parse_nrw
from .bund import parse_protocol as _parse_bund
# Typ-Alias fuer Lesbarkeit; Parser-Signatur ist bewusst minimal.
ProtokollParser = Callable[[str], list[dict]]
PROTOKOLL_PARSERS: dict[str, ProtokollParser] = {
"NRW": _parse_nrw,
"BUND": _parse_bund,
}
def parse_protocol(bundesland: str, pdf_path: str) -> list[dict]:
"""BL-uebergreifender Einstieg. Sucht den Parser in der Registry.
Raises:
NotImplementedError: wenn fuer das Bundesland (noch) kein Parser
registriert ist. Folge-Issue: BL-Adapter ergaenzen mit einem
eigenen Modul plus Eintrag hier.
"""
parser = PROTOKOLL_PARSERS.get(bundesland)
if parser is None:
supported = ", ".join(sorted(PROTOKOLL_PARSERS)) or "(keine)"
raise NotImplementedError(
f"Kein Plenarprotokoll-Parser fuer {bundesland!r}. "
f"Unterstuetzt: {supported}. Siehe #126."
)
return parser(pdf_path)
def supported_bundeslaender() -> list[str]:
"""Liste der BL-Codes mit registrierten Parsern."""
return sorted(PROTOKOLL_PARSERS)
__all__ = [
"ProtokollParser",
"PROTOKOLL_PARSERS",
"parse_protocol",
"supported_bundeslaender",
]