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"
|