Architektur-Doku zum Schema-Refactor der letzten Session: - Begruendung fuer programme.py + legislaturen.py - Optionen-Vergleich (zentrale Registry vs. Liste-im-Schema vs. Status quo) - Migrationsweg via _migrate_from_legacy() und Lazy-Init - Datenstand (86 Wahlprogramme + 12 Grundsatzprogramme + 56 Legislaturen + 70 Regierungen) - Offene Folgearbeiten: historische Wahlprogramme indizieren, analyzer.py-Migration, wahlprogramme.py-Compat-Shim Index aktualisiert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.7 KiB
0013 — Programme + Legislaturen mit zeitpunktiger Bewertung
| Status | accepted |
| Datum | 2026-05-08 |
| Refs | Commits 991d1eb, b5d2bb2, 4e7f7da, a80ac17, c7861cf, d16cacc |
Kontext
Bis zur 21. Wahlperiode des Bundestags (Mai 2025) wurde im GWÖ-Antragsprüfer ein flaches Schema gefahren:
wahlprogramme.WAHLPROGRAMME[bundesland][partei]→ genau ein Wahlprogramm pro Partei pro BL, ohne explizites Geltungsdatumembeddings.PROGRAMME→ flacher Indexer-Lookup, ohne Datierung- Antrags-Bewertung: implizit gegen das jeweils im Schema hinterlegte Programm — egal welches Datum der Antrag trägt
Drei Probleme wurden über die Zeit sichtbar:
- Keine historische Korrektheit: Anträge aus z.B. 2018 (NRW WP17, Kabinett Laschet I) wurden gegen das aktuelle WP18-Programm (Kabinett Wüst II) bewertet, weil das alte Programm gar nicht im Schema stand.
- Sukzessionen verschluckt: RP-Übergang Dreyer III (2021-05-18) → Schweitzer I (2024-07-10) ohne Wahl — beide in derselben WP18 — ließ sich nicht modellieren.
- Zwei parallele Datenstrukturen:
WAHLPROGRAMMEundembeddings.PROGRAMMEenthielten teils dieselben Daten in leicht unterschiedlicher Form. Inkonsistenzen waren ein wiederkehrendes Bug-Muster (#175 Citation-Cross-Attribution war ein Symptom davon).
Hinzu kam die User-Anforderung im Mai 2026 (BTW 2025): die 19 BUND-Anträge sollten gegen die BTW-2025-Wahlprogramme der 8 im 21. Bundestag vertretenen Parteien bewertet werden, nicht gegen die 6 Bundesgrundsatzprogramme. Außerdem sollen historische Vorgänger und Landesgrundsatzprogramme (CSU 2023, CDU NRW 2015, CDU SN 2023, CDU LSA 2023, SSW SH 2016, FW Bund) im System verfügbar sein.
Optionen
Option A — Eine zentrale Programm-Registry mit Geltungsdaten
Alle politischen Programm-Dokumente (Wahlprogramme, Bundes- und
Landesgrundsatzprogramme) leben in einem einzigen Dict mit explizitem
gueltig_ab / gueltig_bis. Helper-Funktionen liefern „welches
Programm galt zum Zeitpunkt X". Legislaturen + Regierungen sind ein
zweites, ergänzendes Modul für die zeitliche Einordnung.
Vorteile:
- Einzige Source of Truth, keine doppelten Datenbestände.
- Historische Bewertung möglich, sobald historische Programme indiziert sind.
- Sukzessionen wie Dreyer III → Schweitzer I sauber modellierbar.
- API-Symmetrie:
wahlprogramm_zum_zeitpunkt(bl, partei, datum),regierung_zum_zeitpunkt(bl, datum),legislatur_zum_zeitpunkt(...).
Nachteile:
- Eingriff in zwei bestehende Module
(
wahlprogramme.WAHLPROGRAMMEundembeddings.PROGRAMME) bzw. Migrationspfad nötig. - Tests gegen die alten Konstanten (39 Tests in
test_wahlprogramme.py) müssen ggf. angepasst werden.
Option B — WAHLPROGRAMME als Liste statt Single-Dict
Pro (bundesland, partei) eine Liste aller Versionen, älteste zuerst.
Helper liest die Liste und sucht das passende Element.
Vorteile:
- Minimal-invasive Erweiterung des bestehenden Schemas.
- Backward-Kompatibilität mit
WAHLPROGRAMME[bl][partei]-Lookups durch[-1]-Slicing möglich.
Nachteile:
- Landesgrundsatzprogramme passen nicht ins Schema (haben kein klassisches Wahlprogramm-Profil mit Wahltag).
embeddings.PROGRAMMEbleibt zweite Datenstruktur — keine Konsolidierung.- Sukzessionen ohne Wahl (Dreyer III → Schweitzer I) sind ein Sonderfall, der schlecht zu „Liste pro Wahl" passt.
Option C — Status quo mit kleinen Erweiterungen
WAHLPROGRAMME bekommt ein regierungsbildung-Feld, sonst nichts. Keine
historischen Programme, keine Landesgrundsatzprogramme.
Vorteile:
- Kleinster Aufwand. Tests bleiben grün.
Nachteile:
- Adressiert keines der drei Probleme oben. Erste Option wenn jemand später historische Bewertung will: nochmal rausreißen.
Entscheidung
Option A wurde gewählt und vollständig umgesetzt:
Schemata
# app/programme.py
class Programm(TypedDict):
id: str # "cdu-nrw-2022", "csu-grundsatz", …
titel: str
name: str # voll-qualifiziert für Citation
typ: Literal[
"wahlprogramm",
"grundsatzprogramm-bund",
"grundsatzprogramm-land",
]
partei: str # normalisiert: CDU, GRÜNE, FREIE WÄHLER, …
bundesland: Optional[str] # None bei reinen Bundesprogrammen
beschluss: Optional[str] # ISO-Datum (Grundsatzprogramme)
wahl: Optional[str] # ISO-Datum (Wahlprogramme)
wp: Optional[int] # Legislatur-Nr (Wahlprogramme)
gueltig_ab: str # bei Wahl: regierungsbildung;
# bei Grundsatz: beschluss
gueltig_bis: Optional[str] # None = aktuell
pdf: str
seiten: int
hinweis: Optional[str] # freier Text (Sonderfälle, BSW)
PROGRAMME: dict[str, Programm] = {} # gefüllt via _migrate_from_legacy()
# Helper
get_programm(programm_id) -> Optional[Programm]
aktuelles_wahlprogramm(bl, partei) -> Optional[Programm]
wahlprogramm_zum_zeitpunkt(bl, partei, datum) -> Optional[Programm]
grundsatzprogramm_zum_zeitpunkt(partei, datum, bundesland=None) -> Optional[Programm]
parteien_mit_wahlprogramm(bl) -> list[str]
alle_versionen(bl, partei) -> list[Programm]
# app/legislaturen.py
class Legislatur(TypedDict):
bundesland: str
wp: int
wahltermin: str
konstituierung: str
ende: Optional[str]
class Regierung(TypedDict):
bundesland: str
name: str # "Wüst II", "Schweitzer I", "Merz I", …
wp: int
parteien: list[str]
ministerpraesident: str
von: str # Vereidigung
bis: Optional[str] # Vereidigung der Nachfolge oder Ende WP
LEGISLATUREN: list[Legislatur] # 56 Einträge für 16 BL + Bund
REGIERUNGEN: list[Regierung] # 70 Einträge
# Helper
legislatur_zum_zeitpunkt(bl, datum) -> Optional[Legislatur]
regierung_zum_zeitpunkt(bl, datum) -> Optional[Regierung]
aktuelle_legislatur(bl), aktuelle_regierung(bl)
regierungen_einer_wp(bl, wp) -> list[Regierung]
Migrationsweg
programme.py ist neue SoT. Das alte WAHLPROGRAMME-Dict wird nicht
gelöscht (würde 19 Konsumenten brechen — analyzer, find_relevant_quotes,
embeddings, etc.). Stattdessen pflegt die Lazy-Init-Funktion
_migrate_from_legacy() die PROGRAMME-Registry beim ersten Helper-Aufruf
aus zwei Quellen:
wahlprogramme.WAHLPROGRAMME— Wahlprogramme mitregierungsbildungundregierungsende(wurden im selben Refactor um Geltungsdaten erweitert)embeddings.PROGRAMME— Grundsatzprogramme mitgueltig_ab,gueltig_bis, plus optionalbundesland(→grundsatzprogramm-land)
embeddings.PROGRAMME bleibt als Indexer-Registry (Reindex-Skript liest
es direkt für DashScope-Embeddings). Compat-Shim für WAHLPROGRAMME als
View über programme.PROGRAMME ist offen — nicht dringend, weil keine
Datenduplikation entsteht (beide Strukturen sind heute kohärent).
Daten-Stand
Im Repo bei diesem ADR (2026-05-08):
- 16 BL + Bund × bis zu 8 Parteien × Wahlprogramme = 86 indizierte Wahlprogramme (alle aktuell)
- 12 Bundes- und Landes-Grundsatzprogramme indiziert (CDU, SPD, GRÜNE, FDP, AfD, LINKE, CSU 2023, CDU NRW 2015, CDU Sachsen 2023, CDU Sachsen-Anhalt 2023, SSW SH 2016, FREIE WÄHLER Bund)
- 56 Legislaturen + 70 Regierungen historisch erfasst (mind. 3 vergangene WP pro BL, plus Bund 18–21)
Was zu tun bleibt
- Historische Wahlprogramme indizieren (~50 PDFs für 3 Vorperioden
pro BL). Erst dann liefert
wahlprogramm_zum_zeitpunkt(...)für Anträge aus älteren Legislaturen ein Ergebnis ungleichNone. analyzer.pymigrieren aufwahlprogramm_zum_zeitpunkt(bl, partei, antrag.datum)— heute ohne Mehrwert, weil keine historischen Embeddings da. Wird mit (1) gemeinsam scharfgeschaltet.wahlprogramme.WAHLPROGRAMMEals Compat-Shim umbauen — Code- Hygiene. Nicht dringend, beide Strukturen koexistieren ohne Drift.
Konsequenzen
Positiv
- Antrag-Detail zeigt jetzt einen Bewertungs-Kontext-Block (Wahlperiode, Regierung zur Antragszeit, Wahlprogramm pro Antragsteller-Fraktion mit PDF-Link + Geltungsdatum + LLM-Modell + Bewertungs-Snapshot-Datum). Macht transparent, gegen welchen Stand bewertet wurde.
- BUND-Anträge der 21. WP werden gegen die BTW-2025-Wahlprogramme bewertet — vorher mussten sie ein Bundes-Grundsatzprogramm reflektieren, das mit dem Tagesgeschäft im Bundestag nur lose zusammenhing.
- Sukzessionen wie RP Dreyer III → Schweitzer I, BUND Scholz I (Ampel) → Scholz I (geschäftsführend) → Merz I, oder das Kemmerich-Kabinett (TH 2020-02) sind im Schema ohne Sonderfall darstellbar.
- 88 neue/erweiterte Tests sichern Konsistenz: alle Bundesländer haben genau eine aktuelle Regierung, alle Regierungs-Intervalle pro BL sind ohne Lücke aneinandergereiht, alle Programm-IDs sind eindeutig.
Negativ
- Doppelter Daten-Bestand zwischen
WAHLPROGRAMMEundembeddings.PROGRAMMEist nicht aufgelöst — wenn man eines ändert, muss man das andere nachpflegen. Risk: stille Drift. _migrate_from_legacy()läuft beim ersten Helper-Aufruf. In Tests dieprogramme.PROGRAMMEdirekt inspizieren ohne vorherigen API-Call müssen explizit_ensure_initialized()aufrufen.
Folgen für andere ADRs
- ADR 0001 (Citation-Binding):
partei-Field ausprogramme.PROGRAMMEliefert die Partei eindeutig — partei-skopierte Citation-Verification arbeitet jetzt damit, statt aus dem Programm-Namen zu parsen. - ADR 0002 (Adapter-Architektur):
programme.pyist eine reine Daten-Modul-Schicht ohne IO, hängt sich nicht an Adapter dran. - Keine Auswirkung auf ADR 0006 (Embedding-Modell-Migration v3→v4) oder ADR 0008 (DDD-Lightweight) — orthogonal.