From 89b82b162778ad9409c61fc3d1347fe4b45972c1 Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Fri, 8 May 2026 01:04:04 +0200 Subject: [PATCH] =?UTF-8?q?docs(adr):=200013=20=E2=80=94=20Programme=20+?= =?UTF-8?q?=20Legislaturen=20mit=20zeitpunktiger=20Bewertung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- ...mme-legislaturen-zeitpunktige-bewertung.md | 247 ++++++++++++++++++ docs/adr/index.md | 1 + 2 files changed, 248 insertions(+) create mode 100644 docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md diff --git a/docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md b/docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md new file mode 100644 index 0000000..fab07d6 --- /dev/null +++ b/docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md @@ -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. diff --git a/docs/adr/index.md b/docs/adr/index.md index b92d741..c8f6229 100644 --- a/docs/adr/index.md +++ b/docs/adr/index.md @@ -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