docs(adr): 0013 — Programme + Legislaturen mit zeitpunktiger Bewertung
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>
This commit is contained in:
parent
d16cacc7fe
commit
89b82b1627
247
docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md
Normal file
247
docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md
Normal file
@ -0,0 +1,247 @@
|
||||
# 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 Geltungsdatum
|
||||
- `embeddings.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:
|
||||
|
||||
1. **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.
|
||||
2. **Sukzessionen verschluckt:** RP-Übergang Dreyer III (2021-05-18) →
|
||||
Schweitzer I (2024-07-10) ohne Wahl — beide in derselben WP18 — ließ
|
||||
sich nicht modellieren.
|
||||
3. **Zwei parallele Datenstrukturen:** `WAHLPROGRAMME` und
|
||||
`embeddings.PROGRAMME` enthielten 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.WAHLPROGRAMME` und `embeddings.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.PROGRAMME` bleibt 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
|
||||
|
||||
```python
|
||||
# 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]
|
||||
```
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
1. `wahlprogramme.WAHLPROGRAMME` — Wahlprogramme mit `regierungsbildung`
|
||||
und `regierungsende` (wurden im selben Refactor um Geltungsdaten
|
||||
erweitert)
|
||||
2. `embeddings.PROGRAMME` — Grundsatzprogramme mit `gueltig_ab`,
|
||||
`gueltig_bis`, plus optional `bundesland` (→
|
||||
`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
|
||||
|
||||
1. **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 ungleich `None`.
|
||||
2. **`analyzer.py` migrieren** auf
|
||||
`wahlprogramm_zum_zeitpunkt(bl, partei, antrag.datum)` — heute
|
||||
ohne Mehrwert, weil keine historischen Embeddings da. Wird mit (1)
|
||||
gemeinsam scharfgeschaltet.
|
||||
3. **`wahlprogramme.WAHLPROGRAMME` als 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 `WAHLPROGRAMME` und
|
||||
`embeddings.PROGRAMME` ist 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 die
|
||||
`programme.PROGRAMME` direkt inspizieren ohne vorherigen API-Call
|
||||
müssen explizit `_ensure_initialized()` aufrufen.
|
||||
|
||||
### Folgen für andere ADRs
|
||||
|
||||
- ADR 0001 (Citation-Binding): `partei`-Field aus `programme.PROGRAMME`
|
||||
liefert die Partei eindeutig — partei-skopierte Citation-Verification
|
||||
arbeitet jetzt damit, statt aus dem Programm-Namen zu parsen.
|
||||
- ADR 0002 (Adapter-Architektur): `programme.py` ist 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.
|
||||
@ -29,6 +29,7 @@ und Konsequenzen. Format inspiriert von [Michael Nygard](https://cognitect.com/b
|
||||
| [0010](0010-stimmverhalten-gwoe-aggregat.md) | Stimmverhalten × GWÖ-Bewertung als JOIN-Aggregat (Heuchelei + Konsistenz) | accepted | 2026-05-06 |
|
||||
| [0011](0011-aktuelle-themen-pm-generator.md) | Aktuelle-Themen-Dashboard mit PM-Generator (Persona-Prompt + Versionierung) | accepted | 2026-05-06 |
|
||||
| [0012](0012-debug-auth-token-bypass.md) | DEBUG_AUTH_TOKEN-Bypass für Diagnose-Sessions auf dev | accepted | 2026-05-06 |
|
||||
| [0013](0013-programme-legislaturen-zeitpunktige-bewertung.md) | Programme + Legislaturen mit zeitpunktiger Bewertung | accepted | 2026-05-08 |
|
||||
|
||||
## Wann ADR, wann nicht
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user