gwoe-antragspruefer/app/protokoll_parsers/bb.py

119 lines
3.7 KiB
Python
Raw Normal View History

"""Brandenburg (BB) — Plenarprotokoll-Parser (#106 / #149, ADR 0009).
URL-Pattern (verifiziert WP8 Sitzung 22):
``https://www.parlamentsdokumentation.brandenburg.de/starweb/LBB/ELVIS/parladoku/w8/plpr/{n}.pdf``
**Wichtig:** parladoku-PDF-URL braucht Cookie-Session vom Portal. Erst
GET auf ``portal/browse.tt.html?wp=8`` zur Cookie-Akquise, dann mit
gesetztem Cookie die PDF-URL aufrufen. Im Auto-Ingest-Cron deshalb
ein eigener Block, der den BB-Cookie-Flow durchlaeuft.
## Anchor-Sprache (verifiziert WP8 Sitzung 22)
```
Wer dem zustimmt, den bitte ich um das Handzeichen. Ich bitte um die
Gegenprobe. Stimmenthaltungen? Damit ist der Antrag mehrheitlich
abgelehnt; es gab keine Enthaltungen.
```
Pattern (NRW-aehnlich):
- **Result-Anchor:** ``Damit ist [Subjekt] (mehrheitlich|einstimmig)? (angenommen|abgelehnt|überwiesen)``
- **Vote-Block:** Q+A im Reden-Stil (Handzeichen-only, ohne Fraktionen-Listing)
- "Wer dem zustimmt, ... Handzeichen"
- "Gegenprobe"
- "Enthaltungen?"
- **Drucksachen-Lookup:** ``Drucksache 8/N`` rueckwaerts vom Anchor
**Limitierung:** BB-Plenarprotokolle nennen die Fraktionen nicht
explizit Vote-Listen bleiben leer. ``einstimmig=True`` setzt
JA=alle WP8-Fraktionen als Approximation.
## Fraktions-Mapping WP8 (ab 2024)
WP8 Konstellation (2024-Wahl): SPD + BSW (Koalition), AfD + CDU + GRÜNE
(Opposition).
- ``SPD``, ``AfD``, ``CDU``, ``BSW``, ``GRÜNE``
"""
from __future__ import annotations
import re
from typing import Optional
try:
import fitz
except ImportError:
fitz = None
ALLE_FRAKTIONEN_BB = ["SPD", "AfD", "CDU", "BSW", "GRÜNE"]
# Result-Anchor: "Damit ist/sind [Subjekt] (modus)? (ergebnis)"
RESULT_ANCHOR_RE = re.compile(
r"Damit\s+(?:ist|sind)\s+(?:der|die|das|dieser?|dieses|beide|alle|auch)?\s*"
r"(?P<subject>Antrag|Alternativantrag|Änderungsantrag|Gesetzentwurf|"
r"Entschließungsantrag|Beschlussempfehlung|Tagesordnungspunkt|Anträge)?"
r"[^.]{0,200}?(?P<modus>einstimmig|mehrheitlich|mit\s+(?:großer\s+)?Mehrheit)?\s*"
r"(?P<ergebnis>angenommen|abgelehnt|überwiesen)",
re.DOTALL,
)
DS_RE_BB = re.compile(r"Drucksache\s+8/(\d{1,5})")
def _resolve_drucksache_bb(text: str, anchor_start: int) -> Optional[str]:
window_start = max(0, anchor_start - 1500)
window = text[window_start:anchor_start]
matches = list(DS_RE_BB.finditer(window))
if matches:
return f"8/{matches[-1].group(1)}"
return None
def _normalize_text(text: str) -> str:
text = re.sub(r"(?<=[a-zäöüß])-\s+(?=[a-zäöüß])", "", text)
return re.sub(r"\s+", " ", text)
def parse_protocol(pdf_path: str) -> list[dict]:
if fitz is None:
raise ImportError("PyMuPDF (fitz) ist erforderlich fuer den BB-Parser")
doc = fitz.open(pdf_path)
full = "".join(p.get_text() for p in doc)
doc.close()
full = _normalize_text(full)
results = []
for m in RESULT_ANCHOR_RE.finditer(full):
modus = (m.group("modus") or "").lower()
ergebnis = m.group("ergebnis")
ds = _resolve_drucksache_bb(full, m.start())
if not ds:
continue
einstimmig = "einstimmig" in modus
votes = {"ja": [], "nein": [], "enthaltung": []}
if einstimmig:
votes["ja"] = list(ALLE_FRAKTIONEN_BB)
results.append({
"drucksache": ds,
"ergebnis": ergebnis,
"einstimmig": einstimmig,
"kind": "direct",
"votes": votes,
"anchor_pos": m.start(),
})
seen = set()
deduped = []
for r in results:
key = (r["drucksache"], r["anchor_pos"])
if key in seen:
continue
seen.add(key)
deduped.append(r)
return deduped