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},
|
"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.
|
# 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": {
|
"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},
|
"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},
|
"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},
|
"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},
|
"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.
|
# Hessen — LTW 08.10.2023. Kabinett Rhein II (CDU+SPD) vereidigt 18.01.2024.
|
||||||
"HE": {
|
"HE": {
|
||||||
|
|||||||
@ -182,10 +182,11 @@ class TestParteienMitWahlprogramm:
|
|||||||
parteien = parteien_mit_wahlprogramm("BY")
|
parteien = parteien_mit_wahlprogramm("BY")
|
||||||
assert set(parteien) == {"CSU", "FREIE WÄHLER", "GRÜNE", "SPD", "AfD"}
|
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.
|
# 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")
|
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):
|
def test_he_has_five_parteien(self):
|
||||||
parteien = parteien_mit_wahlprogramm("HE")
|
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)
|
# load_wahlprogramm_text — Fallback-Pfade (#134 Coverage-Backfill)
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user