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).
147 lines
5.2 KiB
Python
147 lines
5.2 KiB
Python
"""Tests fuer app/protokoll_parsers/be.py — Berliner Plenarprotokoll-Parser (#150).
|
||
|
||
Stichprobe-getestet gegen WP19 Sitzung 50 (Berlin).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from app.protokoll_parsers.be import (
|
||
_normalize_fraktionen_be,
|
||
_parse_vote_block_be,
|
||
_resolve_drucksache_be,
|
||
RESULT_ANCHOR_RE,
|
||
ALLE_FRAKTIONEN_BE,
|
||
FRAKTIONEN_MAP_BE,
|
||
)
|
||
|
||
|
||
class TestNormalizeFraktionenBe:
|
||
def test_simple_cdu(self):
|
||
assert _normalize_fraktionen_be("der CDU") == ["CDU"]
|
||
|
||
def test_buendnis_90_normalizes_to_gruene(self):
|
||
assert _normalize_fraktionen_be("Bündnis 90/Die Grünen") == ["GRÜNE"]
|
||
|
||
def test_die_linke(self):
|
||
assert _normalize_fraktionen_be("der Die Linke") == ["LINKE"]
|
||
|
||
def test_combined_phrase(self):
|
||
result = _normalize_fraktionen_be(
|
||
"die Fraktionen Bündnis 90/Die Grünen und Die Linke"
|
||
)
|
||
assert set(result) == {"GRÜNE", "LINKE"}
|
||
|
||
def test_three_fraktionen(self):
|
||
result = _normalize_fraktionen_be("die Fraktionen der CDU, SPD und AfD")
|
||
assert set(result) == {"CDU", "SPD", "AfD"}
|
||
|
||
def test_empty_returns_empty(self):
|
||
assert _normalize_fraktionen_be("") == []
|
||
|
||
def test_no_double_count(self):
|
||
# 'Die Linke' und 'Linke' beide im Text → nur 1× LINKE
|
||
result = _normalize_fraktionen_be("Die Linke und der Linken")
|
||
assert result.count("LINKE") == 1
|
||
|
||
|
||
class TestParseVoteBlockBe:
|
||
def test_complete_block(self):
|
||
block = (
|
||
"Wer den Antrag auf Drucksache 19/1589 annehmen möchte, den bitte "
|
||
"ich um das Handzeichen. – Das sind die Fraktionen Bündnis 90/Die "
|
||
"Grünen und Die Linke. Wer stimmt dagegen? – Das sind die Fraktionen "
|
||
"der CDU, SPD und AfD. Wer enthält sich, pro forma? – Das ist niemand. "
|
||
"Damit ist der Antrag abgelehnt."
|
||
)
|
||
votes = _parse_vote_block_be(block)
|
||
assert set(votes["ja"]) == {"GRÜNE", "LINKE"}
|
||
assert set(votes["nein"]) == {"CDU", "SPD", "AfD"}
|
||
assert votes["enthaltung"] == [] # 'niemand' → leer
|
||
|
||
def test_enthaltung_ignored_when_niemand(self):
|
||
block = (
|
||
"annehmen möchte, ... – Das sind die CDU. "
|
||
"Wer stimmt dagegen? – Das sind SPD. "
|
||
"Wer enthält sich? – Das ist niemand."
|
||
)
|
||
votes = _parse_vote_block_be(block)
|
||
assert votes["enthaltung"] == []
|
||
|
||
def test_enthaltung_with_real_fraktion(self):
|
||
block = (
|
||
"annehmen möchte, ... – Das sind CDU und SPD. "
|
||
"Wer stimmt dagegen? – AfD. "
|
||
"Wer enthält sich? – Die Linke."
|
||
)
|
||
votes = _parse_vote_block_be(block)
|
||
assert votes["enthaltung"] == ["LINKE"]
|
||
|
||
|
||
class TestResolveDrucksacheBe:
|
||
def test_finds_drucksache_before_anchor(self):
|
||
text = "Auf Drucksache 19/1234 ... Damit ist der Antrag angenommen."
|
||
anchor = text.index("Damit")
|
||
assert _resolve_drucksache_be(text, anchor) == "19/1234"
|
||
|
||
def test_with_dash_suffix(self):
|
||
"""Berliner Drucksachen koennen '-1', '-2' Suffixe haben fuer
|
||
Aenderungs-Versionen."""
|
||
text = "Aenderungsantrag auf Drucksache 19/1589-2 ... Damit ist abgelehnt."
|
||
anchor = text.index("Damit")
|
||
assert _resolve_drucksache_be(text, anchor) == "19/1589-2"
|
||
|
||
def test_returns_none_when_no_ds(self):
|
||
text = "Damit ist der Antrag abgelehnt."
|
||
assert _resolve_drucksache_be(text, 0) is None
|
||
|
||
|
||
class TestResultAnchorRegex:
|
||
def test_matches_antrag_angenommen(self):
|
||
text = "Damit ist der Antrag angenommen."
|
||
m = RESULT_ANCHOR_RE.search(text)
|
||
assert m
|
||
assert m.group("subject") == "Antrag"
|
||
assert m.group("ergebnis") == "angenommen"
|
||
|
||
def test_matches_aenderungsantrag_abgelehnt(self):
|
||
text = "Damit ist der Änderungsantrag abgelehnt."
|
||
m = RESULT_ANCHOR_RE.search(text)
|
||
assert m
|
||
assert m.group("subject") == "Änderungsantrag"
|
||
assert m.group("ergebnis") == "abgelehnt"
|
||
|
||
def test_matches_dieser_antrag_abgelehnt(self):
|
||
text = "Damit ist dieser Antrag abgelehnt."
|
||
m = RESULT_ANCHOR_RE.search(text)
|
||
assert m
|
||
|
||
def test_matches_auch_dieser_aenderungsantrag(self):
|
||
text = "Damit ist auch dieser Änderungsantrag abgelehnt."
|
||
m = RESULT_ANCHOR_RE.search(text)
|
||
assert m
|
||
|
||
def test_matches_gesetzentwurf(self):
|
||
text = "Damit ist der Gesetzentwurf angenommen."
|
||
m = RESULT_ANCHOR_RE.search(text)
|
||
assert m
|
||
|
||
def test_no_match_random_text(self):
|
||
text = "Der Bezirksbürgermeister hat eine Idee abgelehnt."
|
||
m = RESULT_ANCHOR_RE.search(text)
|
||
# 'Damit ist' fehlt — kein Match
|
||
assert m is None
|
||
|
||
|
||
class TestConstants:
|
||
def test_all_fraktionen_complete(self):
|
||
assert set(ALLE_FRAKTIONEN_BE) == {"CDU", "SPD", "GRÜNE", "LINKE", "AfD", "FDP"}
|
||
|
||
def test_mapping_covers_all_fraktionen(self):
|
||
"""Jede der 6 BE-Fraktionen sollte mindestens einen Phrase-Eintrag haben."""
|
||
all_codes = set()
|
||
for _phrase, codes in FRAKTIONEN_MAP_BE:
|
||
all_codes.update(codes)
|
||
for f in ALLE_FRAKTIONEN_BE:
|
||
assert f in all_codes, f"Fraktion {f} fehlt im FRAKTIONEN_MAP_BE"
|