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 |
|
| [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 |
|
| [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 |
|
| [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
|
## Wann ADR, wann nicht
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user