gwoe-antragspruefer/tests/test_protokoll_parsers_sh.py

159 lines
5.6 KiB
Python
Raw Normal View History

"""Tests fuer app/protokoll_parsers/sh.py — SH Plenarprotokoll-Parser (#160).
Stichprobe-getestet gegen WP20 Sitzungen 115 + 116 (Schleswig-Holstein).
"""
from __future__ import annotations
import pytest
from app.protokoll_parsers.sh import (
_normalize_fraktionen_sh,
_normalize_text,
_parse_vote_block_sh,
_resolve_drucksache_sh,
RESULT_ANCHOR_RE,
ALLE_FRAKTIONEN_SH,
FRAKTIONEN_MAP_SH,
)
class TestNormalizeFraktionenSh:
def test_simple_cdu(self):
assert _normalize_fraktionen_sh("die CDU") == ["CDU"]
def test_buendnis_normalizes_to_gruene(self):
assert _normalize_fraktionen_sh("BÜNDNIS 90/DIE GRÜNEN") == ["GRÜNE"]
def test_die_gruenen_normalizes(self):
assert _normalize_fraktionen_sh("DIE GRÜNEN") == ["GRÜNE"]
def test_ssw(self):
assert _normalize_fraktionen_sh("die SSW-Fraktion") == ["SSW"]
def test_combined_fraktionen(self):
result = _normalize_fraktionen_sh(
"die Fraktionen von SPD, FDP und SSW"
)
assert set(result) == {"SPD", "FDP", "SSW"}
def test_koalition_phrase(self):
result = _normalize_fraktionen_sh(
"die Fraktionen von CDU und BÜNDNIS 90/DIE GRÜNEN"
)
assert set(result) == {"CDU", "GRÜNE"}
def test_empty(self):
assert _normalize_fraktionen_sh("") == []
def test_no_double_count_gruene(self):
# 'BÜNDNIS 90/DIE GRÜNEN' und 'GRÜNE' beide getroffen → nur 1× GRÜNE
result = _normalize_fraktionen_sh("BÜNDNIS 90/DIE GRÜNEN und GRÜNE")
assert result.count("GRÜNE") == 1
class TestNormalizeText:
def test_collapses_whitespace(self):
assert _normalize_text("a b\n\tc") == "a b c"
def test_repairs_soft_hyphenation(self):
# Deutsche Silbentrennung am Zeilenumbruch
assert _normalize_text("zustim- men") == "zustimmen"
def test_preserves_legitimate_hyphens(self):
# Schulgeld-Kosten ist ein Kompositum, kein Trennstrich
# → "Schulgeld-Kosten" mit Großbuchstaben nach Bindestrich → bleibt
assert _normalize_text("Schulgeld-Kosten") == "Schulgeld-Kosten"
class TestParseVoteBlockSh:
def test_complete_qa_block(self):
block = (
"Wer dem zustimmen will, den bitte ich um das Handzeichen. "
" Das sind die Fraktionen von SPD, FDP und SSW. "
"Wer stimmt dagegen? Das sind die Fraktionen von CDU "
"und BÜNDNIS 90/DIE GRÜNEN. Damit ist der Antrag abgelehnt."
)
votes = _parse_vote_block_sh(block)
assert set(votes["ja"]) == {"SPD", "FDP", "SSW"}
assert set(votes["nein"]) == {"CDU", "GRÜNE"}
assert votes["enthaltung"] == []
def test_block_with_enthaltung(self):
block = (
"Wer dem zustimmen will, den bitte ich um das Handzeichen. "
" Das sind die Fraktionen von FDP und SSW. "
"Wer stimmt dagegen? Das ist die CDU-Fraktion. "
"Wer enthält sich? Das sind die Fraktionen von "
"BÜNDNIS 90/DIE GRÜNEN und SPD. Damit ist der Antrag abgelehnt."
)
votes = _parse_vote_block_sh(block)
assert set(votes["ja"]) == {"FDP", "SSW"}
assert set(votes["nein"]) == {"CDU"}
assert set(votes["enthaltung"]) == {"GRÜNE", "SPD"}
class TestResolveDrucksacheSh:
def test_finds_drucksache_before_anchor(self):
text = (
"Drucksache 20/1234 ... Wer dem zustimmen will. Das ist die SPD. "
"Damit ist der Antrag abgelehnt."
)
anchor = text.index("Damit")
assert _resolve_drucksache_sh(text, anchor) == "20/1234"
def test_picks_most_recent_drucksache(self):
text = (
"Drucksache 20/1000 ... Drucksache 20/2000 wird abgestimmt. "
"Damit ist der Antrag abgelehnt."
)
anchor = text.index("Damit")
assert _resolve_drucksache_sh(text, anchor) == "20/2000"
def test_returns_none_when_no_ds(self):
assert _resolve_drucksache_sh("Damit ist abgelehnt.", 0) is None
class TestResultAnchorRegex:
def test_matches_antrag_abgelehnt(self):
m = RESULT_ANCHOR_RE.search("Damit ist der Antrag abgelehnt.")
assert m and m.group("ergebnis") == "abgelehnt"
def test_matches_mehrheitlich_angenommen(self):
m = RESULT_ANCHOR_RE.search(
"Damit ist der Antrag mehrheitlich angenommen."
)
assert m and m.group("modus") == "mehrheitlich"
assert m.group("ergebnis") == "angenommen"
def test_matches_trotzdem_mit_mehrheit(self):
m = RESULT_ANCHOR_RE.search(
"Damit ist der Antrag trotzdem mit Mehrheit angenommen."
)
assert m and m.group("ergebnis") == "angenommen"
def test_matches_ausschussueberweisung_einstimmig(self):
m = RESULT_ANCHOR_RE.search(
"Damit ist die Ausschussüberweisung einstimmig so beschlossen."
)
assert m
assert m.group("modus") == "einstimmig"
assert "beschlossen" in m.group("ergebnis").lower()
def test_no_match_random_text(self):
m = RESULT_ANCHOR_RE.search("Der Antrag wurde abgelehnt.")
# 'Damit ist' fehlt
assert m is None
class TestConstants:
def test_all_fraktionen_complete(self):
# WP20-SH: CDU+GRÜNE Koalition, SPD+FDP+SSW Opposition
assert set(ALLE_FRAKTIONEN_SH) == {"CDU", "GRÜNE", "SPD", "FDP", "SSW"}
def test_mapping_covers_all_fraktionen(self):
all_codes = set()
for _phrase, codes in FRAKTIONEN_MAP_SH:
all_codes.update(codes)
for f in ALLE_FRAKTIONEN_SH:
assert f in all_codes, f"Fraktion {f} fehlt im FRAKTIONEN_MAP_SH"