gwoe-antragspruefer/tests/test_protokoll_parsers_be.py
Dotty Dotter c7d6ac7f5f 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

147 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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