2026-04-28 08:37:31 +02:00
|
|
|
"""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
|
2026-04-28 23:21:39 +02:00
|
|
|
from .bund import parse_protocol as _parse_bund
|
feat(#150): BE-Parser produktiv — Berliner Abgeordnetenhaus-Plenarprotokolle
Dritter vollwertiger Plenarprotokoll-Parser nach NRW + BUND.
URL-Pattern verifiziert (WP19 Sitzungen 1, 10, 50, 80, 100):
https://www.parlament-berlin.de/ados/{wp}/IIIPlen/protokoll/plen{wp}-{n:03}-pp.pdf
Anchor-Sprache (NRW-aehnlich, mit Berliner-Eigenheit 'pro forma'):
Wer den Antrag auf Drucksache 19/X annehmen moechte, ... – Das sind
die Fraktionen Buendnis 90/Die Gruenen und Die Linke.
Wer stimmt dagegen? – Das sind die Fraktionen der CDU, SPD und AfD.
Wer enthaelt sich, pro forma? – Das ist niemand.
Damit ist der Antrag abgelehnt.
Pattern:
- Result-Anchor: Damit ist [Antrag/Aenderungsantrag/Gesetzentwurf/...]
(angenommen|abgelehnt)
- Vote-Block: 3 Q+A-Paare im Reden-Stil (annehmen moechte / dagegen /
enthaelt sich)
- Drucksachen-Lookup: 'Drucksache 19/N(-suffix)' rueckwaerts (1500-char Fenster)
Fraktions-Mapping WP19:
- Buendnis 90/Die Gruenen → GRÜNE
- Die Linke → LINKE
- CDU, SPD, AfD, FDP
21 Tests in test_protokoll_parsers_be.py.
Cron-PROTO_TARGETS erweitert um BE WP19 (~80 Sitzungen).
Stub-Test angepasst.
905 Tests gruen (889 → 905, +16 fuer BE).
2026-04-29 00:37:47 +02:00
|
|
|
from .be import parse_protocol as _parse_be
|
2026-04-29 00:57:58 +02:00
|
|
|
from .hh import parse_protocol as _parse_hh
|
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
|
|
|
from .th import parse_protocol as _parse_th
|
feat(#154): HE-Parser produktiv — Hessen Beschlussprotokoll (Status-Only)
Hessen publiziert nur Beschlussprotokolle (Tagesordnung + Status), KEINE
Wortprotokolle mit Vote-Block. Daher minimaler Parser:
- Drucksache + Status (angenommen/abgelehnt/ueberwiesen)
- Vote-Listen bleiben leer (HE hat keine Fraktions-Detail)
URL-Pattern (verifiziert WP21 Sitzungen 61-63):
http://starweb.hessen.de/cache/hessen/landtag/Plenum/{wp}/Beschlussprotokoll_PL_{n}_{datum}.pdf
Datum-Teil DD-MM-YYYY → URL-Vorhersage unmoeglich, Auto-Ingest braucht
Index-Scrape via starweb.hessen.de/starweb/LIS/Pd_Eingang.htm (analog HH).
Status-Mapping:
- "angenommen" → ergebnis="angenommen"
- "Abgelehnt" → ergebnis="abgelehnt"
- "Nach (Aussprache|Lesung) an [Ausschuss]" → ergebnis="ueberwiesen"
- "Entgegengenommen", "Abgehalten", "Zur Kenntnis genommen" → uebersprungen
Tests: PROTOKOLL_PARSERS-Set jetzt {NRW, BUND, BE, HH, TH, HE}. STUB_BL_CODES
auf 11 BL reduziert (BB, BW, BY, HB, LSA, MV, NI, RP, SH, SL, SN bleiben).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 01:19:02 +02:00
|
|
|
from .he import parse_protocol as _parse_he
|
2026-04-29 01:29:06 +02:00
|
|
|
from .sh import parse_protocol as _parse_sh
|
2026-04-28 08:37:31 +02:00
|
|
|
|
|
|
|
|
# Typ-Alias fuer Lesbarkeit; Parser-Signatur ist bewusst minimal.
|
|
|
|
|
ProtokollParser = Callable[[str], list[dict]]
|
|
|
|
|
|
|
|
|
|
PROTOKOLL_PARSERS: dict[str, ProtokollParser] = {
|
|
|
|
|
"NRW": _parse_nrw,
|
2026-04-28 23:21:39 +02:00
|
|
|
"BUND": _parse_bund,
|
feat(#150): BE-Parser produktiv — Berliner Abgeordnetenhaus-Plenarprotokolle
Dritter vollwertiger Plenarprotokoll-Parser nach NRW + BUND.
URL-Pattern verifiziert (WP19 Sitzungen 1, 10, 50, 80, 100):
https://www.parlament-berlin.de/ados/{wp}/IIIPlen/protokoll/plen{wp}-{n:03}-pp.pdf
Anchor-Sprache (NRW-aehnlich, mit Berliner-Eigenheit 'pro forma'):
Wer den Antrag auf Drucksache 19/X annehmen moechte, ... – Das sind
die Fraktionen Buendnis 90/Die Gruenen und Die Linke.
Wer stimmt dagegen? – Das sind die Fraktionen der CDU, SPD und AfD.
Wer enthaelt sich, pro forma? – Das ist niemand.
Damit ist der Antrag abgelehnt.
Pattern:
- Result-Anchor: Damit ist [Antrag/Aenderungsantrag/Gesetzentwurf/...]
(angenommen|abgelehnt)
- Vote-Block: 3 Q+A-Paare im Reden-Stil (annehmen moechte / dagegen /
enthaelt sich)
- Drucksachen-Lookup: 'Drucksache 19/N(-suffix)' rueckwaerts (1500-char Fenster)
Fraktions-Mapping WP19:
- Buendnis 90/Die Gruenen → GRÜNE
- Die Linke → LINKE
- CDU, SPD, AfD, FDP
21 Tests in test_protokoll_parsers_be.py.
Cron-PROTO_TARGETS erweitert um BE WP19 (~80 Sitzungen).
Stub-Test angepasst.
905 Tests gruen (889 → 905, +16 fuer BE).
2026-04-29 00:37:47 +02:00
|
|
|
"BE": _parse_be,
|
2026-04-29 00:57:58 +02:00
|
|
|
"HH": _parse_hh,
|
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
|
|
|
"TH": _parse_th,
|
feat(#154): HE-Parser produktiv — Hessen Beschlussprotokoll (Status-Only)
Hessen publiziert nur Beschlussprotokolle (Tagesordnung + Status), KEINE
Wortprotokolle mit Vote-Block. Daher minimaler Parser:
- Drucksache + Status (angenommen/abgelehnt/ueberwiesen)
- Vote-Listen bleiben leer (HE hat keine Fraktions-Detail)
URL-Pattern (verifiziert WP21 Sitzungen 61-63):
http://starweb.hessen.de/cache/hessen/landtag/Plenum/{wp}/Beschlussprotokoll_PL_{n}_{datum}.pdf
Datum-Teil DD-MM-YYYY → URL-Vorhersage unmoeglich, Auto-Ingest braucht
Index-Scrape via starweb.hessen.de/starweb/LIS/Pd_Eingang.htm (analog HH).
Status-Mapping:
- "angenommen" → ergebnis="angenommen"
- "Abgelehnt" → ergebnis="abgelehnt"
- "Nach (Aussprache|Lesung) an [Ausschuss]" → ergebnis="ueberwiesen"
- "Entgegengenommen", "Abgehalten", "Zur Kenntnis genommen" → uebersprungen
Tests: PROTOKOLL_PARSERS-Set jetzt {NRW, BUND, BE, HH, TH, HE}. STUB_BL_CODES
auf 11 BL reduziert (BB, BW, BY, HB, LSA, MV, NI, RP, SH, SL, SN bleiben).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 01:19:02 +02:00
|
|
|
"HE": _parse_he,
|
2026-04-29 01:29:06 +02:00
|
|
|
"SH": _parse_sh,
|
2026-04-28 08:37:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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",
|
|
|
|
|
]
|