gwoe-antragspruefer/app/protokoll_parsers/__init__.py
Dotty Dotter 399dbc2639 feat(#163): TH-Parser produktiv — Thueringer Plenarprotokolle
Fuenfter produktiver Parser nach NRW + BUND + BE + HH.

URL-Pattern verifiziert (WP8 Sitzungen 1, 10, 20, 30, 40, 42):
  https://www.thueringer-landtag.de/uploads/tx_tltcalendar/protocols/Arbeitsfassung{n}.pdf

Anchor-Sprache (BE-aehnlich):
  Wer dem zustimmt, ... Das sind die Stimmen aus den Fraktionen der
  CDU, BSW, SPD und Die Linke. Wer stimmt gegen ...? Das sind die
  Stimmen aus der Fraktion der AfD. Damit ist [...] mehrheitlich
  angenommen.

Pattern:
- Result-Anchor: Damit ist [Subjekt] (mehrheitlich|einstimmig)?
  (angenommen|abgelehnt)
- Vote-Block: Wer dem zustimmt / Wer stimmt gegen / Wer enthaelt sich
- Drucksachen-Lookup: 'Drucksache 8/N' rueckwaerts

Fraktions-Mapping WP8 (ab Mai 2024): CDU, AfD, BSW, Linke, SPD
(WP7-Faktionen GRUENE/FDP fuer Backfill ebenfalls im Mapping).

Cron-PROTO_TARGETS um TH-WP8 erweitert. Stub-Test angepasst.
2026-04-29 01:11:58 +02:00

78 lines
2.6 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
from .be import parse_protocol as _parse_be
from .hh import parse_protocol as _parse_hh
from .th import parse_protocol as _parse_th
# 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,
"BE": _parse_be,
"HH": _parse_hh,
"TH": _parse_th,
}
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",
]