test(programme): Drift-Schutz zwischen WAHLPROGRAMME und PROGRAMME
ADR 0013 hatte als offene Folge "Doppelter Daten-Bestand zwischen WAHLPROGRAMME und embeddings.PROGRAMME ist nicht aufgelöst — Risk: stille Drift". Der invasive Compat-Shim (#222) ist aufwendig; bis dahin fängt eine neue Test-Klasse die Drift bidirektional ab: TestWahlprogrammeProgrammeConsistency (4 Tests): - Jeder WAHLPROGRAMME-Eintrag hat ein passendes aktuelles Programm in PROGRAMME (sonst liefert aktuelles_wahlprogramm None) - pdf-Dateinamen müssen übereinstimmen (file == pdf) - Partei-Kurzform muss übereinstimmen - Jedes aktuelle Wahlprogramm in PROGRAMME muss auch in WAHLPROGRAMME registriert sein (orphan-check andere Richtung) Drift-Funde dabei: - BIW (Bürger in Wut) HB war in PROGRAMME (biw-hb-2023, biw-hb-2019, biw-hb-2015), aber NICHT in WAHLPROGRAMME-HB. Bewertungs-Pipeline hätte BIW-Anträge gegen kein Wahlprogramm geprüft. Eintrag ergänzt: BÜRGER IN WUT — Programm Bürgerschaftswahl 2023 (26 Seiten). - Test test_hb_has_four_parteien → test_hb_has_five_parteien. 92/92 Programme-Tests grün.
This commit is contained in:
parent
27fd92c15f
commit
bf5400ae33
@ -121,12 +121,14 @@ WAHLPROGRAMME: dict[str, dict[str, dict]] = {
|
||||
"AfD": {"file": "afd-by-2023.pdf", "titel": "AfD Bayern Wahlprogramm 2023", "partei": "AfD Bayern", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-11-07", "regierungsende": None},
|
||||
},
|
||||
# Bremen — Bürgerschaftswahl 14.05.2023. Senat Bovenschulte II (SPD+GRÜNE+LINKE) vereidigt 04.07.2023.
|
||||
# AfD war wegen Listenstreit nicht zur Wahl zugelassen.
|
||||
# AfD war wegen Listenstreit nicht zur Wahl zugelassen — stattdessen ist
|
||||
# BIW (Bürger in Wut) als 6. Fraktion in der 21. Bürgerschaft.
|
||||
"HB": {
|
||||
"SPD": {"file": "spd-hb-2023.pdf", "titel": "SPD Bremen Wahlprogramm Bürgerschaftswahl 2023", "partei": "SPD Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-04", "regierungsende": None},
|
||||
"CDU": {"file": "cdu-hb-2023.pdf", "titel": "CDU Bremen Wahlprogramm Bürgerschaftswahl 2023", "partei": "CDU Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-04", "regierungsende": None},
|
||||
"GRÜNE": {"file": "gruene-hb-2023.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Bremen Wahlprogramm 2023", "partei": "BÜNDNIS 90/DIE GRÜNEN Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-04", "regierungsende": None},
|
||||
"LINKE": {"file": "linke-hb-2023.pdf", "titel": "DIE LINKE Bremen Wahlprogramm Bürgerschaftswahl 2023", "partei": "DIE LINKE Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-04", "regierungsende": None},
|
||||
"BIW": {"file": "biw-hb-2023.pdf", "titel": "BÜRGER IN WUT — Programm für die Bürgerschaftswahl 2023", "partei": "BIW Bremen", "jahr": 2023, "seiten": 26, "regierungsbildung": "2023-07-04", "regierungsende": None},
|
||||
},
|
||||
# Hessen — LTW 08.10.2023. Kabinett Rhein II (CDU+SPD) vereidigt 18.01.2024.
|
||||
"HE": {
|
||||
|
||||
@ -182,10 +182,11 @@ class TestParteienMitWahlprogramm:
|
||||
parteien = parteien_mit_wahlprogramm("BY")
|
||||
assert set(parteien) == {"CSU", "FREIE WÄHLER", "GRÜNE", "SPD", "AfD"}
|
||||
|
||||
def test_hb_has_four_parteien(self):
|
||||
def test_hb_has_five_parteien(self):
|
||||
# AfD war wegen Listenstreit nicht zur Bürgerschaftswahl 2023 zugelassen.
|
||||
# Stattdessen ist BIW (Bürger in Wut) als 6. Fraktion in der 21. WP.
|
||||
parteien = parteien_mit_wahlprogramm("HB")
|
||||
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "LINKE"}
|
||||
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "LINKE", "BIW"}
|
||||
|
||||
def test_he_has_five_parteien(self):
|
||||
parteien = parteien_mit_wahlprogramm("HE")
|
||||
@ -240,6 +241,95 @@ class TestEmbeddingsRegistryConsistency:
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Strikte Cross-Konsistenz mit programme.PROGRAMME (Drift-Schutz, ADR 0013)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestWahlprogrammeProgrammeConsistency:
|
||||
"""WAHLPROGRAMME (legacy, mit titel/seiten/regierung) und
|
||||
programme.PROGRAMME (zentrale Registry mit Geltungsdaten) speichern
|
||||
überlappende Felder. Diese Tests fangen stille Drift, bis #222 die
|
||||
Quelle vereinheitlicht (Compat-Shim).
|
||||
|
||||
Invarianten:
|
||||
- Für jedes (bl, partei) in WAHLPROGRAMME liefert
|
||||
``aktuelles_wahlprogramm(bl, partei)`` einen Eintrag (nicht None).
|
||||
- Der ``pdf``-Wert in PROGRAMME stimmt mit ``file`` in WAHLPROGRAMME
|
||||
überein.
|
||||
- Die ``partei``-Kurzform stimmt überein (PROGRAMME["partei"] ist die
|
||||
Kurzform; WAHLPROGRAMME-Key ist auch Kurzform).
|
||||
"""
|
||||
|
||||
def test_every_wahlprogramm_has_aktuelles_programm_match(self):
|
||||
from app.programme import aktuelles_wahlprogramm
|
||||
|
||||
mismatches = []
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei, info in parteien.items():
|
||||
prog = aktuelles_wahlprogramm(bl, partei)
|
||||
if prog is None:
|
||||
mismatches.append(
|
||||
f"{bl}/{partei}: aktuelles_wahlprogramm liefert None, "
|
||||
f"obwohl WAHLPROGRAMME-Eintrag {info['file']} existiert"
|
||||
)
|
||||
assert not mismatches, "\n ".join(mismatches)
|
||||
|
||||
def test_pdf_filenames_match_between_registries(self):
|
||||
from app.programme import aktuelles_wahlprogramm
|
||||
|
||||
drift = []
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei, info in parteien.items():
|
||||
prog = aktuelles_wahlprogramm(bl, partei)
|
||||
if prog is None:
|
||||
continue # vom Vortest abgedeckt
|
||||
wp_pdf = info["file"]
|
||||
pr_pdf = prog.get("pdf")
|
||||
if wp_pdf != pr_pdf:
|
||||
drift.append(f"{bl}/{partei}: WAHLPROGRAMME.file={wp_pdf!r} ≠ PROGRAMME.pdf={pr_pdf!r}")
|
||||
assert not drift, "Drift zwischen WAHLPROGRAMME und PROGRAMME:\n " + "\n ".join(drift)
|
||||
|
||||
def test_partei_kurzform_consistency(self):
|
||||
"""PROGRAMME["partei"] ist die Kurzform (z.B. 'CDU'), nicht
|
||||
die Langform ('CDU NRW'). Test-Sicherheitsnetz, falls jemand
|
||||
versehentlich die Langform reinträgt."""
|
||||
from app.programme import aktuelles_wahlprogramm
|
||||
|
||||
wrong = []
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei in parteien.keys():
|
||||
prog = aktuelles_wahlprogramm(bl, partei)
|
||||
if prog is None:
|
||||
continue
|
||||
if prog.get("partei") != partei:
|
||||
wrong.append(
|
||||
f"{bl}/{partei}: PROGRAMME.partei={prog.get('partei')!r} "
|
||||
f"≠ WAHLPROGRAMME-Key {partei!r}"
|
||||
)
|
||||
assert not wrong, "\n ".join(wrong)
|
||||
|
||||
def test_no_orphan_aktuelle_programme_in_registry(self):
|
||||
"""Die andere Richtung: jedes aktuelle Wahlprogramm in PROGRAMME
|
||||
(gueltig_bis IS NULL, typ='wahlprogramm') muss in WAHLPROGRAMME
|
||||
vorhanden sein. Sonst ist die Bewertungs-Pipeline blind dafür."""
|
||||
from app.programme import all_programme
|
||||
|
||||
orphans = []
|
||||
for prog in all_programme():
|
||||
if prog.get("typ") != "wahlprogramm":
|
||||
continue
|
||||
if prog.get("gueltig_bis") is not None:
|
||||
continue # historisches Programm
|
||||
bl = prog.get("bundesland")
|
||||
partei = prog.get("partei")
|
||||
if bl not in WAHLPROGRAMME:
|
||||
orphans.append(f"{prog['id']}: BL {bl} nicht in WAHLPROGRAMME")
|
||||
continue
|
||||
if partei not in WAHLPROGRAMME[bl]:
|
||||
orphans.append(f"{prog['id']}: {bl}/{partei} fehlt in WAHLPROGRAMME")
|
||||
assert not orphans, "Aktuelle Wahlprogramme in PROGRAMME ohne WAHLPROGRAMME-Eintrag:\n " + "\n ".join(orphans)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# load_wahlprogramm_text — Fallback-Pfade (#134 Coverage-Backfill)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Loading…
Reference in New Issue
Block a user