"""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"(?PAntrag|Alternativantrag|Änderungsantrag|Gesetzentwurf|" r"Entschließungsantrag|Beschlussempfehlung|Tagesordnungspunkt|Anträge)?" r"[^.]{0,200}?(?Peinstimmig|mehrheitlich|mit\s+(?:großer\s+)?Mehrheit)?\s*" r"(?Pangenommen|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