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
|
|
|
|
"""Berlin (BE) — Plenarprotokoll-Parser (#106 / #150, ADR 0009).
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
PDF-basierter Parser fuer Berliner Abgeordnetenhaus-Plenarprotokolle.
|
|
|
|
|
|
Quelle: ``https://www.parlament-berlin.de/ados/{wp}/IIIPlen/protokoll/plen{wp}-{n:03}-pp.pdf``
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
## Anchor-Sprache (verifiziert WP19 Sitzung 50)
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
Berliner Abstimmungs-Sprache ist NRW-aehnlich, mit eigenem
|
|
|
|
|
|
Sprach-Stil:
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
```
|
|
|
|
|
|
Wer den Antrag auf Drucksache 19/1589 annehmen moechte, den bitte
|
|
|
|
|
|
ich jetzt um das Handzeichen. – Das sind die Fraktionen Bündnis 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.
|
|
|
|
|
|
```
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
## Pattern-Erkennung
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
- **Result-Anchor:** ``Damit ist [Antrag/Aenderungsantrag/...] (angenommen|abgelehnt)``
|
|
|
|
|
|
- **Vote-Block:** drei Q+A-Paare im Reden-Stil
|
|
|
|
|
|
- JA: ``annehmen moechte ... – Das sind [PHRASE]``
|
|
|
|
|
|
- NEIN: ``Wer stimmt dagegen? – Das sind [PHRASE]``
|
|
|
|
|
|
- ENTH: ``Wer enthaelt sich(, pro forma)? – [PHRASE]``
|
|
|
|
|
|
- **Drucksachen-Lookup:** ``auf Drucksache 19/N`` rueckwaerts vom Anchor
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
## Fraktions-Mapping WP19
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
- ``Buendnis 90/Die Gruenen`` → GRÜNE
|
|
|
|
|
|
- ``Die Linke`` → LINKE
|
|
|
|
|
|
- ``CDU``, ``SPD``, ``AfD``, ``FDP``
|
|
|
|
|
|
- ``fraktionsloser Abgeordneter`` → ignoriert (Einzelpersonen)
|
2026-04-28 23:09:07 +02:00
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
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
|
|
|
|
import re
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
import fitz # PyMuPDF
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
fitz = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ALLE_FRAKTIONEN_BE = ["CDU", "SPD", "GRÜNE", "LINKE", "AfD", "FDP"]
|
|
|
|
|
|
|
|
|
|
|
|
# Reihenfolge: längere Aliasse zuerst.
|
|
|
|
|
|
FRAKTIONEN_MAP_BE = [
|
|
|
|
|
|
("Bündnis 90/Die Grünen", ["GRÜNE"]),
|
|
|
|
|
|
("Bündnisses 90/Die Grünen", ["GRÜNE"]),
|
|
|
|
|
|
("Bündnis 90", ["GRÜNE"]),
|
|
|
|
|
|
("Die Linke", ["LINKE"]),
|
|
|
|
|
|
("der Linken", ["LINKE"]),
|
|
|
|
|
|
("Linke", ["LINKE"]),
|
|
|
|
|
|
("CDU", ["CDU"]),
|
|
|
|
|
|
("SPD", ["SPD"]),
|
|
|
|
|
|
("AfD", ["AfD"]),
|
|
|
|
|
|
("FDP", ["FDP"]),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_fraktionen_be(text: str) -> list[str]:
|
|
|
|
|
|
"""Extrahiere BE-Fraktions-Codes aus einer Phrase."""
|
|
|
|
|
|
found = set()
|
|
|
|
|
|
remaining = text
|
|
|
|
|
|
for phrase, codes in FRAKTIONEN_MAP_BE:
|
|
|
|
|
|
if phrase in remaining:
|
|
|
|
|
|
for c in codes:
|
|
|
|
|
|
found.add(c)
|
|
|
|
|
|
remaining = remaining.replace(phrase, " ")
|
|
|
|
|
|
return sorted(found)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Result-Anchor: "Damit ist [Subjekt] (angenommen|abgelehnt)"
|
|
|
|
|
|
RESULT_ANCHOR_RE = re.compile(
|
|
|
|
|
|
r"Damit ist\s+(?:auch\s+)?(?:dieser?|die|das|der)\s+"
|
|
|
|
|
|
r"(?P<subject>Antrag|Änderungsantrag|Gesetzesvorlage|Gesetzentwurf|"
|
|
|
|
|
|
r"Entschließungsantrag|Beschlussempfehlung)"
|
|
|
|
|
|
r"[^.]{0,200}?(?P<ergebnis>angenommen|abgelehnt)\.",
|
|
|
|
|
|
re.DOTALL,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Vote-Sub-Patterns: 3 Reden-Q+A-Paare. Wir akzeptieren Punkte zwischen
|
|
|
|
|
|
# der Frage und dem Vote-Block ("möchte. – Das sind ..."), und stoppen am
|
|
|
|
|
|
# nächsten Q-Marker oder Damit-Anchor.
|
|
|
|
|
|
JA_RE = re.compile(
|
|
|
|
|
|
r"annehmen m(?:ö|oe)chte[^?]{0,200}?[–-]\s+(?:Das sind\s+|Das ist\s+)?"
|
|
|
|
|
|
r"(?P<ja>[^?]+?)(?=Wer stimmt dagegen|Wer enthält sich|Wer enthaelt sich|Damit ist)",
|
|
|
|
|
|
re.DOTALL,
|
|
|
|
|
|
)
|
|
|
|
|
|
NEIN_RE = re.compile(
|
|
|
|
|
|
r"Wer stimmt dagegen\?[^?]{0,40}?[–-]\s+(?:Das sind\s+|Das ist\s+)?"
|
|
|
|
|
|
r"(?P<nein>[^?]+?)(?=Wer enthält sich|Wer enthaelt sich|Damit ist)",
|
|
|
|
|
|
re.DOTALL,
|
|
|
|
|
|
)
|
|
|
|
|
|
ENTH_RE = re.compile(
|
|
|
|
|
|
r"Wer enth(?:ä|ae)lt sich(?:,\s+pro forma)?\?[^?]{0,40}?[–-]\s+"
|
|
|
|
|
|
r"(?P<enth>[^?.]+?)(?=Damit ist|Wer|\.)",
|
|
|
|
|
|
re.DOTALL,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
DS_RE_BE = re.compile(r"Drucksache\s+(\d{1,2}/\d{2,5}(?:-\d+)?)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_vote_block_be(block: str) -> dict:
|
|
|
|
|
|
"""Parst BE-Vote-Block aus dem Text vor einem Damit-Anchor."""
|
|
|
|
|
|
votes = {"ja": [], "nein": [], "enthaltung": []}
|
|
|
|
|
|
|
|
|
|
|
|
ja_m = JA_RE.search(block)
|
|
|
|
|
|
if ja_m:
|
|
|
|
|
|
votes["ja"] = _normalize_fraktionen_be(ja_m.group("ja"))
|
|
|
|
|
|
|
|
|
|
|
|
nein_m = NEIN_RE.search(block)
|
|
|
|
|
|
if nein_m:
|
|
|
|
|
|
votes["nein"] = _normalize_fraktionen_be(nein_m.group("nein"))
|
|
|
|
|
|
|
|
|
|
|
|
enth_m = ENTH_RE.search(block)
|
|
|
|
|
|
if enth_m:
|
|
|
|
|
|
enth_text = enth_m.group("enth")
|
|
|
|
|
|
# 'niemand' → leere Liste (Berliner Idiom)
|
|
|
|
|
|
if "niemand" in enth_text.lower() or "ist nicht der Fall" in enth_text:
|
|
|
|
|
|
votes["enthaltung"] = []
|
|
|
|
|
|
else:
|
|
|
|
|
|
votes["enthaltung"] = _normalize_fraktionen_be(enth_text)
|
|
|
|
|
|
|
|
|
|
|
|
return votes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_drucksache_be(text: str, anchor_start: int) -> Optional[str]:
|
|
|
|
|
|
"""Rueckwaerts vom Anchor die Drucksache finden (1500-Zeichen Window)."""
|
|
|
|
|
|
window_start = max(0, anchor_start - 1500)
|
|
|
|
|
|
window = text[window_start:anchor_start]
|
|
|
|
|
|
matches = list(DS_RE_BE.finditer(window))
|
|
|
|
|
|
if matches:
|
|
|
|
|
|
return matches[-1].group(1)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_text(text: str) -> str:
|
|
|
|
|
|
"""Whitespace-Normalisierung wie NRW-Parser."""
|
|
|
|
|
|
text = re.sub(r"\s+", " ", text)
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_protocol(pdf_path: str) -> list[dict]:
|
|
|
|
|
|
"""Parst ein Berliner Plenarprotokoll-PDF und liefert Vote-Records."""
|
|
|
|
|
|
if fitz is None:
|
|
|
|
|
|
raise ImportError("PyMuPDF (fitz) ist erforderlich fuer den BE-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):
|
|
|
|
|
|
ergebnis = m.group("ergebnis")
|
|
|
|
|
|
|
|
|
|
|
|
# Vote-Block: 1500 Zeichen vor dem Anchor
|
|
|
|
|
|
block_start = max(0, m.start() - 1500)
|
|
|
|
|
|
block = full[block_start:m.end()]
|
|
|
|
|
|
|
|
|
|
|
|
ds = _resolve_drucksache_be(full, m.start())
|
|
|
|
|
|
if not ds:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
votes = _parse_vote_block_be(block)
|
|
|
|
|
|
|
|
|
|
|
|
einstimmig = (
|
|
|
|
|
|
len(votes["ja"]) >= 5
|
|
|
|
|
|
and not votes["nein"]
|
|
|
|
|
|
and not votes["enthaltung"]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
results.append({
|
|
|
|
|
|
"drucksache": ds,
|
|
|
|
|
|
"ergebnis": ergebnis,
|
|
|
|
|
|
"einstimmig": einstimmig,
|
|
|
|
|
|
"kind": "direct",
|
|
|
|
|
|
"votes": votes,
|
|
|
|
|
|
"anchor_pos": m.start(),
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# Dedup ueber anchor_pos
|
|
|
|
|
|
seen = set()
|
|
|
|
|
|
deduped = []
|
|
|
|
|
|
for r in results:
|
|
|
|
|
|
if r["anchor_pos"] in seen:
|
|
|
|
|
|
continue
|
|
|
|
|
|
seen.add(r["anchor_pos"])
|
|
|
|
|
|
deduped.append(r)
|
2026-04-28 23:09:07 +02:00
|
|
|
|
|
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
|
|
|
|
return deduped
|