feat: Programme + Legislaturen-Registry mit historisch korrekter Geltung
Neue Module: - app/programme.py: zentrale Programm-Registry (alle Wahl- und Grundsatz- programme in einem Index), mit Geltungsdaten gueltig_ab/gueltig_bis und Helpern wahlprogramm_zum_zeitpunkt(), grundsatzprogramm_zum_zeitpunkt(), alle_versionen(). Skelett fuer 6 zusaetzliche Eintraege (CSU 2023, CDU NRW 2015, CDU SN 2023, CDU LSA 2023, SSW SH 2016, FREIE WAEHLER) vorbereitet — PDFs folgen. - app/legislaturen.py: 56 Legislaturen + 70 Regierungen fuer 16 BL + Bund. Helper legislatur_zum_zeitpunkt(), regierung_zum_zeitpunkt(), regierungen_einer_wp() fuer historisch korrekte Antrags-Bewertung (z.B. Kemmerich-28-Tage-Kabinett, RP-Uebergang Dreyer III -> Schweitzer I, BUND Scholz-Ampel -> geschaeftsfuehrend -> Merz I). - tests/test_legislaturen.py: 20 Tests zu Konsistenz + Historie. Datenbasis: 8 BTW-2025-Wahlprogramme (CDU, CSU, SPD, GRUENE, FDP, AfD, LINKE, BSW) als PDFs hinzugefuegt. SHA-256-Pinning in app/wahlprogramm-shas.lock.json (separat). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7793705486
commit
991d1eb903
313
app/legislaturen.py
Normal file
313
app/legislaturen.py
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
"""Wahlperioden + Regierungen pro Bundesland — historisch + aktuell.
|
||||||
|
|
||||||
|
Diese Datei beantwortet zwei Fragen pro Bundesland:
|
||||||
|
|
||||||
|
1. **Welche Legislatur lief zum Zeitpunkt X?** (``legislatur_zum_zeitpunkt``)
|
||||||
|
2. **Welche Regierung war zum Zeitpunkt X im Amt?** (``regierung_zum_zeitpunkt``)
|
||||||
|
|
||||||
|
Die Strukturen sind ergänzend zu ``app/programme.py``: Programme verweisen
|
||||||
|
über ``wp`` (Legislatur-Nummer) bzw. ``gueltig_ab`` (= Vereidigung der
|
||||||
|
Regierung) auf die hier gelisteten Einträge.
|
||||||
|
|
||||||
|
Sonderfälle, die das Schema unterstützt:
|
||||||
|
- Mehrere Regierungen in einer Legislatur (z.B. RP: Dreyer III → Schweitzer I)
|
||||||
|
- Vorgezogene Wahlen / verkürzte Legislaturen (z.B. NRW WP15 2010-2012)
|
||||||
|
- BUND als 17. Eintrag (technisch kein BL, aber Schema-konform)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional, TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Typ
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
class Legislatur(TypedDict, total=False):
|
||||||
|
"""Eine Wahlperiode eines deutschen Parlaments."""
|
||||||
|
bundesland: str # BL-Code (NRW, BUND, …)
|
||||||
|
wp: int # Wahlperioden-Nummer
|
||||||
|
wahltermin: str # ISO YYYY-MM-DD
|
||||||
|
konstituierung: str # ISO YYYY-MM-DD (= Beginn der WP)
|
||||||
|
ende: Optional[str] # ISO oder None bei aktueller WP
|
||||||
|
|
||||||
|
|
||||||
|
class Regierung(TypedDict, total=False):
|
||||||
|
"""Eine Landes- oder Bundesregierung (Kabinett).
|
||||||
|
|
||||||
|
Eine Legislatur kann mehrere Regierungen enthalten (z.B. wenn mitten in
|
||||||
|
der WP der/die Ministerpräsident:in zurücktritt). Bei diesen wird ``bis``
|
||||||
|
der Vorgängerin = ``von`` der Nachfolgerin gesetzt.
|
||||||
|
"""
|
||||||
|
bundesland: str
|
||||||
|
name: str # "Wüst II", "Schweitzer I", "Merz I", …
|
||||||
|
wp: int # Legislatur-Nummer
|
||||||
|
parteien: list[str] # Koalitionspartner (sortiert nach Größe)
|
||||||
|
ministerpraesident: str # MP / Bundeskanzler:in
|
||||||
|
von: str # ISO — Vereidigung
|
||||||
|
bis: Optional[str] # ISO — Vereidigung der Nachfolge oder Ende der WP
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Daten-Container
|
||||||
|
# Werden vom Daten-Modul ``legislaturen_data.py`` befüllt sobald die
|
||||||
|
# Recherche fertig ist. Hier bleiben sie initial leer und werden später
|
||||||
|
# importiert / erweitert.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
LEGISLATUREN: list[Legislatur] = [
|
||||||
|
# ─── Baden-Württemberg ───────────────────────────────────────────────
|
||||||
|
{"bundesland": "BW", "wp": 15, "wahltermin": "2011-03-27", "konstituierung": "2011-05-11", "ende": "2016-05-11"},
|
||||||
|
{"bundesland": "BW", "wp": 16, "wahltermin": "2016-03-13", "konstituierung": "2016-05-11", "ende": "2021-05-11"},
|
||||||
|
{"bundesland": "BW", "wp": 17, "wahltermin": "2021-03-14", "konstituierung": "2021-05-11", "ende": None},
|
||||||
|
# ─── Bayern ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "BY", "wp": 17, "wahltermin": "2013-09-15", "konstituierung": "2013-10-07", "ende": "2018-11-05"},
|
||||||
|
{"bundesland": "BY", "wp": 18, "wahltermin": "2018-10-14", "konstituierung": "2018-11-05", "ende": "2023-10-30"},
|
||||||
|
{"bundesland": "BY", "wp": 19, "wahltermin": "2023-10-08", "konstituierung": "2023-10-30", "ende": None},
|
||||||
|
# ─── Berlin ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "BE", "wp": 17, "wahltermin": "2011-09-18", "konstituierung": "2011-10-27", "ende": "2016-10-27"},
|
||||||
|
{"bundesland": "BE", "wp": 18, "wahltermin": "2016-09-18", "konstituierung": "2016-10-27", "ende": "2021-11-04"},
|
||||||
|
# WP19: Originalwahl 2021-09-26; Wiederholung 2023-02-12 (BVerfG). Formal weiter WP19.
|
||||||
|
{"bundesland": "BE", "wp": 19, "wahltermin": "2021-09-26", "konstituierung": "2021-11-04", "ende": None},
|
||||||
|
# ─── Brandenburg ─────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "BB", "wp": 6, "wahltermin": "2014-09-14", "konstituierung": "2014-10-21", "ende": "2019-09-25"},
|
||||||
|
{"bundesland": "BB", "wp": 7, "wahltermin": "2019-09-01", "konstituierung": "2019-09-25", "ende": "2024-10-23"},
|
||||||
|
{"bundesland": "BB", "wp": 8, "wahltermin": "2024-09-22", "konstituierung": "2024-10-23", "ende": None},
|
||||||
|
# ─── Bremen ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "HB", "wp": 18, "wahltermin": "2011-05-22", "konstituierung": "2011-06-07", "ende": "2015-06-15"},
|
||||||
|
{"bundesland": "HB", "wp": 19, "wahltermin": "2015-05-10", "konstituierung": "2015-06-15", "ende": "2019-07-04"},
|
||||||
|
{"bundesland": "HB", "wp": 20, "wahltermin": "2019-05-26", "konstituierung": "2019-07-04", "ende": "2023-07-04"},
|
||||||
|
{"bundesland": "HB", "wp": 21, "wahltermin": "2023-05-14", "konstituierung": "2023-07-04", "ende": None},
|
||||||
|
# ─── Hamburg ─────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "HH", "wp": 21, "wahltermin": "2015-02-15", "konstituierung": "2015-03-18", "ende": "2020-03-18"},
|
||||||
|
{"bundesland": "HH", "wp": 22, "wahltermin": "2020-02-23", "konstituierung": "2020-03-18", "ende": "2025-03-19"},
|
||||||
|
{"bundesland": "HH", "wp": 23, "wahltermin": "2025-03-02", "konstituierung": "2025-03-19", "ende": None},
|
||||||
|
# ─── Hessen ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "HE", "wp": 19, "wahltermin": "2013-09-22", "konstituierung": "2014-01-18", "ende": "2019-01-18"},
|
||||||
|
{"bundesland": "HE", "wp": 20, "wahltermin": "2018-10-28", "konstituierung": "2019-01-18", "ende": "2024-01-18"},
|
||||||
|
{"bundesland": "HE", "wp": 21, "wahltermin": "2023-10-08", "konstituierung": "2024-01-18", "ende": None},
|
||||||
|
# ─── Mecklenburg-Vorpommern ──────────────────────────────────────────
|
||||||
|
{"bundesland": "MV", "wp": 6, "wahltermin": "2011-09-04", "konstituierung": "2011-10-04", "ende": "2016-10-04"},
|
||||||
|
{"bundesland": "MV", "wp": 7, "wahltermin": "2016-09-04", "konstituierung": "2016-10-04", "ende": "2021-10-26"},
|
||||||
|
{"bundesland": "MV", "wp": 8, "wahltermin": "2021-09-26", "konstituierung": "2021-10-26", "ende": None},
|
||||||
|
# ─── Niedersachsen ───────────────────────────────────────────────────
|
||||||
|
{"bundesland": "NI", "wp": 17, "wahltermin": "2013-01-20", "konstituierung": "2013-02-19", "ende": "2017-11-14"},
|
||||||
|
{"bundesland": "NI", "wp": 18, "wahltermin": "2017-10-15", "konstituierung": "2017-11-14", "ende": "2022-11-08"},
|
||||||
|
{"bundesland": "NI", "wp": 19, "wahltermin": "2022-10-09", "konstituierung": "2022-11-08", "ende": None},
|
||||||
|
# ─── Nordrhein-Westfalen ─────────────────────────────────────────────
|
||||||
|
{"bundesland": "NRW", "wp": 15, "wahltermin": "2010-05-09", "konstituierung": "2010-06-09", "ende": "2012-06-12"},
|
||||||
|
{"bundesland": "NRW", "wp": 16, "wahltermin": "2012-05-13", "konstituierung": "2012-06-12", "ende": "2017-06-01"},
|
||||||
|
{"bundesland": "NRW", "wp": 17, "wahltermin": "2017-05-14", "konstituierung": "2017-06-01", "ende": "2022-06-01"},
|
||||||
|
{"bundesland": "NRW", "wp": 18, "wahltermin": "2022-05-15", "konstituierung": "2022-06-01", "ende": None},
|
||||||
|
# ─── Rheinland-Pfalz ─────────────────────────────────────────────────
|
||||||
|
{"bundesland": "RP", "wp": 16, "wahltermin": "2011-03-27", "konstituierung": "2011-05-18", "ende": "2016-05-18"},
|
||||||
|
{"bundesland": "RP", "wp": 17, "wahltermin": "2016-03-13", "konstituierung": "2016-05-18", "ende": "2021-05-18"},
|
||||||
|
{"bundesland": "RP", "wp": 18, "wahltermin": "2021-03-14", "konstituierung": "2021-05-18", "ende": None},
|
||||||
|
# ─── Saarland ────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "SL", "wp": 14, "wahltermin": "2009-08-30", "konstituierung": "2009-09-30", "ende": "2012-04-25"},
|
||||||
|
{"bundesland": "SL", "wp": 15, "wahltermin": "2012-03-25", "konstituierung": "2012-04-25", "ende": "2017-05-09"},
|
||||||
|
{"bundesland": "SL", "wp": 16, "wahltermin": "2017-03-26", "konstituierung": "2017-05-09", "ende": "2022-04-25"},
|
||||||
|
{"bundesland": "SL", "wp": 17, "wahltermin": "2022-03-27", "konstituierung": "2022-04-25", "ende": None},
|
||||||
|
# ─── Sachsen ─────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "SN", "wp": 5, "wahltermin": "2009-08-30", "konstituierung": "2009-09-29", "ende": "2014-09-29"},
|
||||||
|
{"bundesland": "SN", "wp": 6, "wahltermin": "2014-08-31", "konstituierung": "2014-09-29", "ende": "2019-10-01"},
|
||||||
|
{"bundesland": "SN", "wp": 7, "wahltermin": "2019-09-01", "konstituierung": "2019-10-01", "ende": "2024-10-01"},
|
||||||
|
{"bundesland": "SN", "wp": 8, "wahltermin": "2024-09-01", "konstituierung": "2024-10-01", "ende": None},
|
||||||
|
# ─── Sachsen-Anhalt ──────────────────────────────────────────────────
|
||||||
|
{"bundesland": "LSA", "wp": 6, "wahltermin": "2011-03-20", "konstituierung": "2011-04-19", "ende": "2016-04-13"},
|
||||||
|
{"bundesland": "LSA", "wp": 7, "wahltermin": "2016-03-13", "konstituierung": "2016-04-13", "ende": "2021-07-06"},
|
||||||
|
{"bundesland": "LSA", "wp": 8, "wahltermin": "2021-06-06", "konstituierung": "2021-07-06", "ende": None},
|
||||||
|
# ─── Schleswig-Holstein ──────────────────────────────────────────────
|
||||||
|
{"bundesland": "SH", "wp": 18, "wahltermin": "2012-05-06", "konstituierung": "2012-06-05", "ende": "2017-06-06"},
|
||||||
|
{"bundesland": "SH", "wp": 19, "wahltermin": "2017-05-07", "konstituierung": "2017-06-06", "ende": "2022-06-07"},
|
||||||
|
{"bundesland": "SH", "wp": 20, "wahltermin": "2022-05-08", "konstituierung": "2022-06-07", "ende": None},
|
||||||
|
# ─── Thüringen ───────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "TH", "wp": 6, "wahltermin": "2014-09-14", "konstituierung": "2014-10-14", "ende": "2019-11-26"},
|
||||||
|
{"bundesland": "TH", "wp": 7, "wahltermin": "2019-10-27", "konstituierung": "2019-11-26", "ende": "2024-09-26"},
|
||||||
|
{"bundesland": "TH", "wp": 8, "wahltermin": "2024-09-01", "konstituierung": "2024-09-26", "ende": None},
|
||||||
|
# ─── Bund (Deutscher Bundestag) ──────────────────────────────────────
|
||||||
|
{"bundesland": "BUND", "wp": 18, "wahltermin": "2013-09-22", "konstituierung": "2013-10-22", "ende": "2017-10-24"},
|
||||||
|
{"bundesland": "BUND", "wp": 19, "wahltermin": "2017-09-24", "konstituierung": "2017-10-24", "ende": "2021-10-26"},
|
||||||
|
{"bundesland": "BUND", "wp": 20, "wahltermin": "2021-09-26", "konstituierung": "2021-10-26", "ende": "2025-03-25"},
|
||||||
|
{"bundesland": "BUND", "wp": 21, "wahltermin": "2025-02-23", "konstituierung": "2025-03-25", "ende": None},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
REGIERUNGEN: list[Regierung] = [
|
||||||
|
# ─── Baden-Württemberg ───────────────────────────────────────────────
|
||||||
|
{"bundesland": "BW", "name": "Kretschmann I", "wp": 15, "parteien": ["GRÜNE", "SPD"], "ministerpraesident": "Winfried Kretschmann", "von": "2011-05-12", "bis": "2016-05-12"},
|
||||||
|
{"bundesland": "BW", "name": "Kretschmann II", "wp": 16, "parteien": ["GRÜNE", "CDU"], "ministerpraesident": "Winfried Kretschmann", "von": "2016-05-12", "bis": "2021-05-12"},
|
||||||
|
{"bundesland": "BW", "name": "Kretschmann III", "wp": 17, "parteien": ["GRÜNE", "CDU"], "ministerpraesident": "Winfried Kretschmann", "von": "2021-05-12", "bis": None},
|
||||||
|
# ─── Bayern ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "BY", "name": "Seehofer II", "wp": 17, "parteien": ["CSU"], "ministerpraesident": "Horst Seehofer", "von": "2013-10-10", "bis": "2018-03-21"},
|
||||||
|
{"bundesland": "BY", "name": "Söder I", "wp": 17, "parteien": ["CSU"], "ministerpraesident": "Markus Söder", "von": "2018-03-21", "bis": "2018-11-12"},
|
||||||
|
{"bundesland": "BY", "name": "Söder II", "wp": 18, "parteien": ["CSU", "FREIE WÄHLER"], "ministerpraesident": "Markus Söder", "von": "2018-11-12", "bis": "2023-11-07"},
|
||||||
|
{"bundesland": "BY", "name": "Söder III", "wp": 19, "parteien": ["CSU", "FREIE WÄHLER"], "ministerpraesident": "Markus Söder", "von": "2023-11-07", "bis": None},
|
||||||
|
# ─── Berlin ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "BE", "name": "Wowereit IV", "wp": 17, "parteien": ["SPD", "CDU"], "ministerpraesident": "Klaus Wowereit", "von": "2011-11-24", "bis": "2014-12-11"},
|
||||||
|
{"bundesland": "BE", "name": "Müller I", "wp": 17, "parteien": ["SPD", "CDU"], "ministerpraesident": "Michael Müller", "von": "2014-12-11", "bis": "2016-12-08"},
|
||||||
|
{"bundesland": "BE", "name": "Müller II", "wp": 18, "parteien": ["SPD", "LINKE", "GRÜNE"], "ministerpraesident": "Michael Müller", "von": "2016-12-08", "bis": "2021-12-21"},
|
||||||
|
{"bundesland": "BE", "name": "Giffey I", "wp": 19, "parteien": ["SPD", "GRÜNE", "LINKE"], "ministerpraesident": "Franziska Giffey", "von": "2021-12-21", "bis": "2023-04-27"},
|
||||||
|
{"bundesland": "BE", "name": "Wegner I", "wp": 19, "parteien": ["CDU", "SPD"], "ministerpraesident": "Kai Wegner", "von": "2023-04-27", "bis": None},
|
||||||
|
# ─── Brandenburg ─────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "BB", "name": "Woidke I", "wp": 6, "parteien": ["SPD", "LINKE"], "ministerpraesident": "Dietmar Woidke", "von": "2014-11-05", "bis": "2019-11-20"},
|
||||||
|
{"bundesland": "BB", "name": "Woidke II", "wp": 7, "parteien": ["SPD", "CDU", "GRÜNE"], "ministerpraesident": "Dietmar Woidke", "von": "2019-11-20", "bis": "2024-12-11"},
|
||||||
|
{"bundesland": "BB", "name": "Woidke III", "wp": 8, "parteien": ["SPD", "BSW"], "ministerpraesident": "Dietmar Woidke", "von": "2024-12-11", "bis": None},
|
||||||
|
# ─── Bremen ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "HB", "name": "Böhrnsen III", "wp": 18, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Jens Böhrnsen", "von": "2011-06-29", "bis": "2015-07-15"},
|
||||||
|
{"bundesland": "HB", "name": "Sieling I", "wp": 19, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Carsten Sieling", "von": "2015-07-15", "bis": "2019-08-15"},
|
||||||
|
{"bundesland": "HB", "name": "Bovenschulte I", "wp": 20, "parteien": ["SPD", "GRÜNE", "LINKE"], "ministerpraesident": "Andreas Bovenschulte", "von": "2019-08-15", "bis": "2023-07-05"},
|
||||||
|
{"bundesland": "HB", "name": "Bovenschulte II", "wp": 21, "parteien": ["SPD", "GRÜNE", "LINKE"], "ministerpraesident": "Andreas Bovenschulte", "von": "2023-07-05", "bis": None},
|
||||||
|
# ─── Hamburg ─────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "HH", "name": "Scholz II", "wp": 21, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Olaf Scholz", "von": "2015-04-15", "bis": "2018-03-28"},
|
||||||
|
{"bundesland": "HH", "name": "Tschentscher I", "wp": 21, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Peter Tschentscher", "von": "2018-03-28", "bis": "2020-06-10"},
|
||||||
|
{"bundesland": "HH", "name": "Tschentscher II", "wp": 22, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Peter Tschentscher", "von": "2020-06-10", "bis": "2025-05-07"},
|
||||||
|
{"bundesland": "HH", "name": "Tschentscher III", "wp": 23, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Peter Tschentscher", "von": "2025-05-07", "bis": None},
|
||||||
|
# ─── Hessen ──────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "HE", "name": "Bouffier II", "wp": 19, "parteien": ["CDU", "GRÜNE"], "ministerpraesident": "Volker Bouffier", "von": "2014-01-18", "bis": "2019-01-18"},
|
||||||
|
{"bundesland": "HE", "name": "Bouffier III", "wp": 20, "parteien": ["CDU", "GRÜNE"], "ministerpraesident": "Volker Bouffier", "von": "2019-01-18", "bis": "2022-05-31"},
|
||||||
|
{"bundesland": "HE", "name": "Rhein I", "wp": 20, "parteien": ["CDU", "GRÜNE"], "ministerpraesident": "Boris Rhein", "von": "2022-05-31", "bis": "2024-01-18"},
|
||||||
|
{"bundesland": "HE", "name": "Rhein II", "wp": 21, "parteien": ["CDU", "SPD"], "ministerpraesident": "Boris Rhein", "von": "2024-01-18", "bis": None},
|
||||||
|
# ─── Mecklenburg-Vorpommern ──────────────────────────────────────────
|
||||||
|
{"bundesland": "MV", "name": "Sellering II", "wp": 6, "parteien": ["SPD", "CDU"], "ministerpraesident": "Erwin Sellering", "von": "2011-10-25", "bis": "2016-11-01"},
|
||||||
|
{"bundesland": "MV", "name": "Sellering III", "wp": 7, "parteien": ["SPD", "CDU"], "ministerpraesident": "Erwin Sellering", "von": "2016-11-01", "bis": "2017-07-04"},
|
||||||
|
{"bundesland": "MV", "name": "Schwesig I", "wp": 7, "parteien": ["SPD", "CDU"], "ministerpraesident": "Manuela Schwesig", "von": "2017-07-04", "bis": "2021-11-15"},
|
||||||
|
{"bundesland": "MV", "name": "Schwesig II", "wp": 8, "parteien": ["SPD", "LINKE"], "ministerpraesident": "Manuela Schwesig", "von": "2021-11-15", "bis": None},
|
||||||
|
# ─── Niedersachsen ───────────────────────────────────────────────────
|
||||||
|
{"bundesland": "NI", "name": "Weil I", "wp": 17, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Stephan Weil", "von": "2013-02-19", "bis": "2017-11-22"},
|
||||||
|
{"bundesland": "NI", "name": "Weil II", "wp": 18, "parteien": ["SPD", "CDU"], "ministerpraesident": "Stephan Weil", "von": "2017-11-22", "bis": "2022-11-08"},
|
||||||
|
{"bundesland": "NI", "name": "Weil III", "wp": 19, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Stephan Weil", "von": "2022-11-08", "bis": None},
|
||||||
|
# ─── Nordrhein-Westfalen ─────────────────────────────────────────────
|
||||||
|
{"bundesland": "NRW", "name": "Kraft I", "wp": 15, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Hannelore Kraft", "von": "2010-07-15", "bis": "2012-06-20"},
|
||||||
|
{"bundesland": "NRW", "name": "Kraft II", "wp": 16, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Hannelore Kraft", "von": "2012-06-20", "bis": "2017-06-30"},
|
||||||
|
{"bundesland": "NRW", "name": "Laschet I", "wp": 17, "parteien": ["CDU", "FDP"], "ministerpraesident": "Armin Laschet", "von": "2017-06-30", "bis": "2021-10-27"},
|
||||||
|
{"bundesland": "NRW", "name": "Wüst I", "wp": 17, "parteien": ["CDU", "FDP"], "ministerpraesident": "Hendrik Wüst", "von": "2021-10-27", "bis": "2022-06-29"},
|
||||||
|
{"bundesland": "NRW", "name": "Wüst II", "wp": 18, "parteien": ["CDU", "GRÜNE"], "ministerpraesident": "Hendrik Wüst", "von": "2022-06-29", "bis": None},
|
||||||
|
# ─── Rheinland-Pfalz ─────────────────────────────────────────────────
|
||||||
|
{"bundesland": "RP", "name": "Beck V", "wp": 16, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Kurt Beck", "von": "2011-05-18", "bis": "2013-01-16"},
|
||||||
|
{"bundesland": "RP", "name": "Dreyer I", "wp": 16, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Malu Dreyer", "von": "2013-01-16", "bis": "2016-05-18"},
|
||||||
|
{"bundesland": "RP", "name": "Dreyer II", "wp": 17, "parteien": ["SPD", "GRÜNE", "FDP"], "ministerpraesident": "Malu Dreyer", "von": "2016-05-18", "bis": "2021-05-18"},
|
||||||
|
{"bundesland": "RP", "name": "Dreyer III", "wp": 18, "parteien": ["SPD", "GRÜNE", "FDP"], "ministerpraesident": "Malu Dreyer", "von": "2021-05-18", "bis": "2024-07-10"},
|
||||||
|
{"bundesland": "RP", "name": "Schweitzer I", "wp": 18, "parteien": ["SPD", "GRÜNE", "FDP"], "ministerpraesident": "Alexander Schweitzer", "von": "2024-07-10", "bis": None},
|
||||||
|
# ─── Saarland ────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "SL", "name": "Müller III", "wp": 14, "parteien": ["CDU", "FDP", "GRÜNE"], "ministerpraesident": "Peter Müller", "von": "2009-11-10", "bis": "2011-08-10"},
|
||||||
|
{"bundesland": "SL", "name": "Kramp-Karrenbauer I", "wp": 14, "parteien": ["CDU", "FDP", "GRÜNE"], "ministerpraesident": "Annegret Kramp-Karrenbauer", "von": "2011-08-10", "bis": "2012-05-09"},
|
||||||
|
{"bundesland": "SL", "name": "Kramp-Karrenbauer II", "wp": 15, "parteien": ["CDU", "SPD"], "ministerpraesident": "Annegret Kramp-Karrenbauer", "von": "2012-05-09", "bis": "2017-05-17"},
|
||||||
|
{"bundesland": "SL", "name": "Kramp-Karrenbauer III", "wp": 16, "parteien": ["CDU", "SPD"], "ministerpraesident": "Annegret Kramp-Karrenbauer", "von": "2017-05-17", "bis": "2018-02-28"},
|
||||||
|
{"bundesland": "SL", "name": "Hans I", "wp": 16, "parteien": ["CDU", "SPD"], "ministerpraesident": "Tobias Hans", "von": "2018-02-28", "bis": "2022-04-25"},
|
||||||
|
{"bundesland": "SL", "name": "Rehlinger I", "wp": 17, "parteien": ["SPD"], "ministerpraesident": "Anke Rehlinger", "von": "2022-04-25", "bis": None},
|
||||||
|
# ─── Sachsen ─────────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "SN", "name": "Tillich I", "wp": 5, "parteien": ["CDU", "FDP"], "ministerpraesident": "Stanislaw Tillich", "von": "2009-09-29", "bis": "2014-11-13"},
|
||||||
|
{"bundesland": "SN", "name": "Tillich II", "wp": 6, "parteien": ["CDU", "SPD"], "ministerpraesident": "Stanislaw Tillich", "von": "2014-11-13", "bis": "2017-12-13"},
|
||||||
|
{"bundesland": "SN", "name": "Kretschmer I", "wp": 6, "parteien": ["CDU", "SPD"], "ministerpraesident": "Michael Kretschmer", "von": "2017-12-13", "bis": "2019-12-20"},
|
||||||
|
{"bundesland": "SN", "name": "Kretschmer II", "wp": 7, "parteien": ["CDU", "GRÜNE", "SPD"], "ministerpraesident": "Michael Kretschmer", "von": "2019-12-20", "bis": "2024-12-18"},
|
||||||
|
{"bundesland": "SN", "name": "Kretschmer III", "wp": 8, "parteien": ["CDU", "SPD"], "ministerpraesident": "Michael Kretschmer", "von": "2024-12-18", "bis": None},
|
||||||
|
# ─── Sachsen-Anhalt ──────────────────────────────────────────────────
|
||||||
|
{"bundesland": "LSA", "name": "Haseloff I", "wp": 6, "parteien": ["CDU", "SPD"], "ministerpraesident": "Reiner Haseloff", "von": "2011-04-19", "bis": "2016-04-25"},
|
||||||
|
{"bundesland": "LSA", "name": "Haseloff II", "wp": 7, "parteien": ["CDU", "SPD", "GRÜNE"], "ministerpraesident": "Reiner Haseloff", "von": "2016-04-25", "bis": "2021-09-16"},
|
||||||
|
{"bundesland": "LSA", "name": "Haseloff III", "wp": 8, "parteien": ["CDU", "SPD", "FDP"], "ministerpraesident": "Reiner Haseloff", "von": "2021-09-16", "bis": None},
|
||||||
|
# ─── Schleswig-Holstein ──────────────────────────────────────────────
|
||||||
|
{"bundesland": "SH", "name": "Albig I", "wp": 18, "parteien": ["SPD", "GRÜNE", "SSW"], "ministerpraesident": "Torsten Albig", "von": "2012-06-12", "bis": "2017-06-28"},
|
||||||
|
{"bundesland": "SH", "name": "Günther I", "wp": 19, "parteien": ["CDU", "GRÜNE", "FDP"], "ministerpraesident": "Daniel Günther", "von": "2017-06-28", "bis": "2022-06-29"},
|
||||||
|
{"bundesland": "SH", "name": "Günther II", "wp": 20, "parteien": ["CDU", "GRÜNE"], "ministerpraesident": "Daniel Günther", "von": "2022-06-29", "bis": None},
|
||||||
|
# ─── Thüringen ───────────────────────────────────────────────────────
|
||||||
|
{"bundesland": "TH", "name": "Ramelow I", "wp": 6, "parteien": ["LINKE", "SPD", "GRÜNE"], "ministerpraesident": "Bodo Ramelow", "von": "2014-12-05", "bis": "2020-02-05"},
|
||||||
|
{"bundesland": "TH", "name": "Kemmerich I", "wp": 7, "parteien": ["FDP"], "ministerpraesident": "Thomas Kemmerich", "von": "2020-02-05", "bis": "2020-03-04"},
|
||||||
|
{"bundesland": "TH", "name": "Ramelow II", "wp": 7, "parteien": ["LINKE", "SPD", "GRÜNE"], "ministerpraesident": "Bodo Ramelow", "von": "2020-03-04", "bis": "2024-12-12"},
|
||||||
|
{"bundesland": "TH", "name": "Voigt I", "wp": 8, "parteien": ["CDU", "BSW", "SPD"], "ministerpraesident": "Mario Voigt", "von": "2024-12-12", "bis": None},
|
||||||
|
# ─── Bund (Deutscher Bundestag) ──────────────────────────────────────
|
||||||
|
{"bundesland": "BUND", "name": "Merkel III", "wp": 18, "parteien": ["CDU", "CSU", "SPD"], "ministerpraesident": "Angela Merkel", "von": "2013-12-17", "bis": "2018-03-14"},
|
||||||
|
{"bundesland": "BUND", "name": "Merkel IV", "wp": 19, "parteien": ["CDU", "CSU", "SPD"], "ministerpraesident": "Angela Merkel", "von": "2018-03-14", "bis": "2021-12-08"},
|
||||||
|
{"bundesland": "BUND", "name": "Scholz I", "wp": 20, "parteien": ["SPD", "GRÜNE", "FDP"], "ministerpraesident": "Olaf Scholz", "von": "2021-12-08", "bis": "2024-11-06"},
|
||||||
|
{"bundesland": "BUND", "name": "Scholz I (geschäftsführend)", "wp": 20, "parteien": ["SPD", "GRÜNE"], "ministerpraesident": "Olaf Scholz", "von": "2024-11-06", "bis": "2025-05-06"},
|
||||||
|
{"bundesland": "BUND", "name": "Merz I", "wp": 21, "parteien": ["CDU", "CSU", "SPD"], "ministerpraesident": "Friedrich Merz", "von": "2025-05-06", "bis": None},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Helper-API
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _datum_in_range(datum: str, von: str, bis: Optional[str]) -> bool:
|
||||||
|
if datum < von:
|
||||||
|
return False
|
||||||
|
if bis is None:
|
||||||
|
return True
|
||||||
|
return datum < bis
|
||||||
|
|
||||||
|
|
||||||
|
def legislatur_zum_zeitpunkt(
|
||||||
|
bundesland: str, datum: str,
|
||||||
|
) -> Optional[Legislatur]:
|
||||||
|
"""Welche Wahlperiode lief im Bundesland zum gegebenen Datum?"""
|
||||||
|
for leg in LEGISLATUREN:
|
||||||
|
if leg["bundesland"] != bundesland:
|
||||||
|
continue
|
||||||
|
if _datum_in_range(datum, leg["konstituierung"], leg.get("ende")):
|
||||||
|
return leg
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def regierung_zum_zeitpunkt(
|
||||||
|
bundesland: str, datum: str,
|
||||||
|
) -> Optional[Regierung]:
|
||||||
|
"""Welche Regierung war im Bundesland zum gegebenen Datum im Amt?"""
|
||||||
|
for reg in REGIERUNGEN:
|
||||||
|
if reg["bundesland"] != bundesland:
|
||||||
|
continue
|
||||||
|
if _datum_in_range(datum, reg["von"], reg.get("bis")):
|
||||||
|
return reg
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def aktuelle_regierung(bundesland: str) -> Optional[Regierung]:
|
||||||
|
"""Aktuelle Regierung des Bundeslands (bis=None)."""
|
||||||
|
for reg in REGIERUNGEN:
|
||||||
|
if reg["bundesland"] == bundesland and reg.get("bis") is None:
|
||||||
|
return reg
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def aktuelle_legislatur(bundesland: str) -> Optional[Legislatur]:
|
||||||
|
"""Laufende Wahlperiode (ende=None)."""
|
||||||
|
for leg in LEGISLATUREN:
|
||||||
|
if leg["bundesland"] == bundesland and leg.get("ende") is None:
|
||||||
|
return leg
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def regierungen_einer_wp(bundesland: str, wp: int) -> list[Regierung]:
|
||||||
|
"""Alle Regierungen einer bestimmten Legislatur, chronologisch."""
|
||||||
|
regs = [
|
||||||
|
r for r in REGIERUNGEN
|
||||||
|
if r["bundesland"] == bundesland and r["wp"] == wp
|
||||||
|
]
|
||||||
|
regs.sort(key=lambda r: r["von"])
|
||||||
|
return regs
|
||||||
|
|
||||||
|
|
||||||
|
def parteien_in_regierung_zum_zeitpunkt(
|
||||||
|
bundesland: str, datum: str,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Welche Parteien waren zum Zeitpunkt in der Regierung?"""
|
||||||
|
reg = regierung_zum_zeitpunkt(bundesland, datum)
|
||||||
|
if reg is None:
|
||||||
|
return []
|
||||||
|
return list(reg.get("parteien", []))
|
||||||
|
|
||||||
|
|
||||||
|
def alle_legislaturen(bundesland: str) -> list[Legislatur]:
|
||||||
|
"""Alle Wahlperioden eines Bundeslands, sortiert nach WP-Nummer."""
|
||||||
|
legs = [leg for leg in LEGISLATUREN if leg["bundesland"] == bundesland]
|
||||||
|
legs.sort(key=lambda l: l["wp"])
|
||||||
|
return legs
|
||||||
|
|
||||||
|
|
||||||
|
def alle_regierungen(bundesland: str) -> list[Regierung]:
|
||||||
|
"""Alle Regierungen eines Bundeslands, chronologisch nach von."""
|
||||||
|
regs = [r for r in REGIERUNGEN if r["bundesland"] == bundesland]
|
||||||
|
regs.sort(key=lambda r: r["von"])
|
||||||
|
return regs
|
||||||
487
app/programme.py
Normal file
487
app/programme.py
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
"""Zentrale Programm-Registry — alle politischen Programm-Dokumente
|
||||||
|
(Wahlprogramme, Bundes-Grundsatzprogramme, Landes-Grundsatzprogramme),
|
||||||
|
historisch und aktuell.
|
||||||
|
|
||||||
|
Single Source of Truth für:
|
||||||
|
- ``embeddings.py`` (Indexer liest die PDFs aus dieser Liste)
|
||||||
|
- ``wahlprogramme.py`` (Compat-Shim, leitet die alte BL/Partei-API hierher)
|
||||||
|
- ``analyzer.py`` (sucht das zum Antrag passende Wahlprogramm)
|
||||||
|
- UI (zeigt Geltungszeitraum + zugeordnete Regierung pro Programm)
|
||||||
|
|
||||||
|
Siehe ``app/legislaturen.py`` für Wahlperioden + Regierungen.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Literal, Optional, TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Typ
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
ProgrammTyp = Literal[
|
||||||
|
"wahlprogramm", # zur Wahl beschlossen, gilt für 1 Legislatur
|
||||||
|
"grundsatzprogramm-bund", # bundesweites Grundsatzprogramm
|
||||||
|
"grundsatzprogramm-land", # landesspezifisches Grundsatzprogramm
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Programm(TypedDict, total=False):
|
||||||
|
"""Single source of truth für ein politisches Programm-Dokument.
|
||||||
|
|
||||||
|
Pflichtfelder: id, titel, name, typ, partei, gueltig_ab, pdf, seiten.
|
||||||
|
Optional: bundesland, beschluss, wahl, wp, gueltig_bis, hinweis.
|
||||||
|
"""
|
||||||
|
id: str # eindeutiger Schlüssel, z.B. "cdu-nrw-2022"
|
||||||
|
titel: str # offizieller Titel ("Machen, worauf es ankommt")
|
||||||
|
name: str # voll-qualifiziert für Citation (z.B. "CDU NRW Wahlprogramm 2022")
|
||||||
|
typ: ProgrammTyp
|
||||||
|
partei: str # normalisierter Schlüssel (CDU, GRÜNE, FREIE WÄHLER, BSW, …)
|
||||||
|
bundesland: Optional[str] # BL-Code; None nur bei reinen Bundesgrundsatzprogrammen
|
||||||
|
beschluss: Optional[str] # ISO YYYY-MM-DD; bei grundsatz: Parteitags-Beschluss
|
||||||
|
wahl: Optional[str] # ISO YYYY-MM-DD; nur typ=wahlprogramm: Wahltag
|
||||||
|
wp: Optional[int] # Legislatur-Nummer; nur typ=wahlprogramm
|
||||||
|
gueltig_ab: str # ISO; bei wahl: regierungsbildung; bei grundsatz: beschluss
|
||||||
|
gueltig_bis: Optional[str] # ISO; None = Programm ist aktuell gültig
|
||||||
|
pdf: str # Dateiname in static/referenzen/
|
||||||
|
seiten: int
|
||||||
|
hinweis: Optional[str] # freier Text, z.B. "BSW hat kein Grundsatzprogramm — Wahlprogramm dient als Hauptquelle"
|
||||||
|
|
||||||
|
|
||||||
|
REFERENZEN_PATH = Path(__file__).parent / "static" / "referenzen"
|
||||||
|
KONTEXT_PATH = Path(__file__).parent / "kontext"
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Programm-Registry
|
||||||
|
# Die Daten werden aus der bisherigen ``embeddings.PROGRAMME`` und
|
||||||
|
# ``wahlprogramme.WAHLPROGRAMME`` migriert. Historische Wahlprogramme +
|
||||||
|
# Landesgrundsatzprogramme werden ergänzt sobald die Recherche fertig ist.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
# Aktuelle Programme — gefüllt durch ``_register_initial_data()`` weiter unten,
|
||||||
|
# damit der Migrations-Pfad an einer Stelle zu sehen ist.
|
||||||
|
PROGRAMME: dict[str, Programm] = {}
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Helper-API
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _date_in_range(datum: str, ab: str, bis: Optional[str]) -> bool:
|
||||||
|
"""Liefert True, wenn ``datum`` (ISO) in [ab, bis) liegt."""
|
||||||
|
if datum < ab:
|
||||||
|
return False
|
||||||
|
if bis is None:
|
||||||
|
return True
|
||||||
|
return datum < bis
|
||||||
|
|
||||||
|
|
||||||
|
def get_programm(programm_id: str) -> Optional[Programm]:
|
||||||
|
"""Lookup nach ID."""
|
||||||
|
return PROGRAMME.get(programm_id)
|
||||||
|
|
||||||
|
|
||||||
|
def aktuelles_wahlprogramm(bundesland: str, partei: str) -> Optional[Programm]:
|
||||||
|
"""Aktuell gültiges Wahlprogramm einer Partei in einem Bundesland.
|
||||||
|
|
||||||
|
Es kann nur eines aktuell sein (gueltig_bis=None und typ=wahlprogramm).
|
||||||
|
"""
|
||||||
|
for prog in PROGRAMME.values():
|
||||||
|
if (
|
||||||
|
prog.get("typ") == "wahlprogramm"
|
||||||
|
and prog.get("bundesland") == bundesland
|
||||||
|
and prog.get("partei") == partei
|
||||||
|
and prog.get("gueltig_bis") is None
|
||||||
|
):
|
||||||
|
return prog
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def wahlprogramm_zum_zeitpunkt(
|
||||||
|
bundesland: str, partei: str, datum: str,
|
||||||
|
) -> Optional[Programm]:
|
||||||
|
"""Welches Wahlprogramm dieser Partei galt im Bundesland am gegebenen Datum?
|
||||||
|
|
||||||
|
``datum`` ist ISO-Datum. Es wird das Programm zurückgegeben, dessen
|
||||||
|
Geltungszeitraum [gueltig_ab, gueltig_bis) das Datum enthält.
|
||||||
|
|
||||||
|
Rückgabe ``None``, wenn die Partei zu dem Zeitpunkt nicht im Schema
|
||||||
|
erfasst ist (oder das Bundesland nicht).
|
||||||
|
"""
|
||||||
|
for prog in PROGRAMME.values():
|
||||||
|
if (
|
||||||
|
prog.get("typ") == "wahlprogramm"
|
||||||
|
and prog.get("bundesland") == bundesland
|
||||||
|
and prog.get("partei") == partei
|
||||||
|
and _date_in_range(datum, prog["gueltig_ab"], prog.get("gueltig_bis"))
|
||||||
|
):
|
||||||
|
return prog
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def grundsatzprogramm_zum_zeitpunkt(
|
||||||
|
partei: str,
|
||||||
|
datum: str,
|
||||||
|
bundesland: Optional[str] = None,
|
||||||
|
) -> Optional[Programm]:
|
||||||
|
"""Welches Grundsatzprogramm der Partei galt am gegebenen Datum?
|
||||||
|
|
||||||
|
Wenn ``bundesland`` gesetzt ist, wird zuerst nach einem
|
||||||
|
Landes-Grundsatzprogramm gesucht; falls keines existiert, fällt die
|
||||||
|
Suche auf das Bundes-Grundsatzprogramm zurück.
|
||||||
|
|
||||||
|
Wenn ``bundesland`` ``None`` ist, wird nur nach Bundes-Grundsatz gesucht.
|
||||||
|
"""
|
||||||
|
if bundesland is not None:
|
||||||
|
# Erst Land suchen
|
||||||
|
for prog in PROGRAMME.values():
|
||||||
|
if (
|
||||||
|
prog.get("typ") == "grundsatzprogramm-land"
|
||||||
|
and prog.get("partei") == partei
|
||||||
|
and prog.get("bundesland") == bundesland
|
||||||
|
and _date_in_range(datum, prog["gueltig_ab"], prog.get("gueltig_bis"))
|
||||||
|
):
|
||||||
|
return prog
|
||||||
|
# Bund als Fallback / oder primär
|
||||||
|
for prog in PROGRAMME.values():
|
||||||
|
if (
|
||||||
|
prog.get("typ") == "grundsatzprogramm-bund"
|
||||||
|
and prog.get("partei") == partei
|
||||||
|
and _date_in_range(datum, prog["gueltig_ab"], prog.get("gueltig_bis"))
|
||||||
|
):
|
||||||
|
return prog
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parteien_mit_wahlprogramm(bundesland: str) -> list[str]:
|
||||||
|
"""Parteien mit einem aktuell gültigen Wahlprogramm in dem Bundesland.
|
||||||
|
|
||||||
|
Reihenfolge: nach Eintrags-Reihenfolge in PROGRAMME (deterministic).
|
||||||
|
"""
|
||||||
|
seen: list[str] = []
|
||||||
|
for prog in PROGRAMME.values():
|
||||||
|
if (
|
||||||
|
prog.get("typ") == "wahlprogramm"
|
||||||
|
and prog.get("bundesland") == bundesland
|
||||||
|
and prog.get("gueltig_bis") is None
|
||||||
|
):
|
||||||
|
partei = prog["partei"]
|
||||||
|
if partei not in seen:
|
||||||
|
seen.append(partei)
|
||||||
|
return seen
|
||||||
|
|
||||||
|
|
||||||
|
def alle_versionen(bundesland: str, partei: str) -> list[Programm]:
|
||||||
|
"""Alle Wahlprogramm-Versionen dieser Partei im Bundesland, sortiert
|
||||||
|
nach ``gueltig_ab`` aufsteigend."""
|
||||||
|
versions = [
|
||||||
|
prog for prog in PROGRAMME.values()
|
||||||
|
if prog.get("typ") == "wahlprogramm"
|
||||||
|
and prog.get("bundesland") == bundesland
|
||||||
|
and prog.get("partei") == partei
|
||||||
|
]
|
||||||
|
versions.sort(key=lambda p: p["gueltig_ab"])
|
||||||
|
return versions
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Initiale Daten — wird über separate Daten-Datei eingespielt.
|
||||||
|
# Während des Migrationsfensters wird ``embeddings.PROGRAMME`` und
|
||||||
|
# ``wahlprogramme.WAHLPROGRAMME`` automatisch in PROGRAMME überführt.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _register(prog: Programm) -> None:
|
||||||
|
"""Add a Programm to the registry, validating uniqueness of id."""
|
||||||
|
if prog["id"] in PROGRAMME:
|
||||||
|
raise ValueError(f"Programm-id collision: {prog['id']}")
|
||||||
|
PROGRAMME[prog["id"]] = prog
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Zusätzliche Programme — historische Vorgänger und Landesgrundsatzprogramme,
|
||||||
|
# die nicht aus WAHLPROGRAMME/embeddings.PROGRAMME migriert werden können.
|
||||||
|
#
|
||||||
|
# PDFs werden separat beschafft und hier eingetragen sobald verfügbar.
|
||||||
|
# Einträge ohne existierende PDF-Datei können aktiv sein (z.B. für reine
|
||||||
|
# Metadaten-Anzeige), aber sie werden in der Embeddings-Indizierung
|
||||||
|
# übersprungen und ``test_every_registered_pdf_exists`` filtert sie via
|
||||||
|
# ``hinweis="pdf-pending"`` Marker aus.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
_ADDITIONAL_PROGRAMME: list[Programm] = [
|
||||||
|
# ─── CSU Bayern: aktualisiertes Grundsatzprogramm 2023 ───────────────
|
||||||
|
# "Die Ordnung 2016" (in embeddings.PROGRAMME als Bundes-Grundsatz)
|
||||||
|
# wird durch das 2023er ersetzt — wir tragen 2023 als aktuelles ein und
|
||||||
|
# setzen das 2016er auf gueltig_bis. Funktionell ist es ein Landes-
|
||||||
|
# grundsatzprogramm, weil CSU nur in Bayern existiert.
|
||||||
|
{
|
||||||
|
"id": "csu-grundsatz-2023",
|
||||||
|
"titel": "Für ein neues Miteinander — Grundsatzprogramm der CSU",
|
||||||
|
"name": "CSU Grundsatzprogramm 2023",
|
||||||
|
"typ": "grundsatzprogramm-land",
|
||||||
|
"partei": "CSU",
|
||||||
|
"bundesland": "BY",
|
||||||
|
"beschluss": "2023-05-06",
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": "2023-05-06",
|
||||||
|
"gueltig_bis": None,
|
||||||
|
"pdf": "csu-grundsatz-2023.pdf",
|
||||||
|
"seiten": 0, # nach Download via PyMuPDF setzen
|
||||||
|
"hinweis": "pdf-pending",
|
||||||
|
},
|
||||||
|
# ─── CDU NRW Landesgrundsatzprogramm 2015 ─────────────────────────────
|
||||||
|
{
|
||||||
|
"id": "cdu-grundsatz-nrw-2015",
|
||||||
|
"titel": "Aufstieg, Sicherheit, Perspektive — Das Nordrhein-Westfalen-Programm",
|
||||||
|
"name": "CDU NRW Grundsatzprogramm 2015",
|
||||||
|
"typ": "grundsatzprogramm-land",
|
||||||
|
"partei": "CDU",
|
||||||
|
"bundesland": "NRW",
|
||||||
|
"beschluss": "2015-06-13",
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": "2015-06-13",
|
||||||
|
"gueltig_bis": None,
|
||||||
|
"pdf": "cdu-grundsatz-nrw-2015.pdf",
|
||||||
|
"seiten": 0,
|
||||||
|
"hinweis": "pdf-pending",
|
||||||
|
},
|
||||||
|
# ─── CDU Sachsen Landesgrundsatzprogramm 2023 ─────────────────────────
|
||||||
|
{
|
||||||
|
"id": "cdu-grundsatz-sn-2023",
|
||||||
|
"titel": "Zukunftsplan für Sachsen",
|
||||||
|
"name": "CDU Sachsen Grundsatzprogramm 2023",
|
||||||
|
"typ": "grundsatzprogramm-land",
|
||||||
|
"partei": "CDU",
|
||||||
|
"bundesland": "SN",
|
||||||
|
"beschluss": "2023-11-20",
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": "2023-11-20",
|
||||||
|
"gueltig_bis": None,
|
||||||
|
"pdf": "cdu-grundsatz-sn-2023.pdf",
|
||||||
|
"seiten": 0,
|
||||||
|
"hinweis": "pdf-pending",
|
||||||
|
},
|
||||||
|
# ─── CDU Sachsen-Anhalt Landesgrundsatzprogramm 2023 ──────────────────
|
||||||
|
{
|
||||||
|
"id": "cdu-grundsatz-lsa-2023",
|
||||||
|
"titel": "Sachsen-Anhalt. Unsere Verantwortung. Unsere Zukunft.",
|
||||||
|
"name": "CDU Sachsen-Anhalt Grundsatzprogramm 2023",
|
||||||
|
"typ": "grundsatzprogramm-land",
|
||||||
|
"partei": "CDU",
|
||||||
|
"bundesland": "LSA",
|
||||||
|
"beschluss": "2023-09-30",
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": "2023-09-30",
|
||||||
|
"gueltig_bis": None,
|
||||||
|
"pdf": "cdu-grundsatz-lsa-2023.pdf",
|
||||||
|
"seiten": 0,
|
||||||
|
"hinweis": "pdf-pending",
|
||||||
|
},
|
||||||
|
# ─── SSW Schleswig-Holstein Rahmenprogramm 2016 ────────────────────────
|
||||||
|
# SSW ist ausschließlich in SH aktiv (Minderheitenpartei der dänisch-
|
||||||
|
# friesischen Volksgruppe). Das Rahmenprogramm tritt funktional an die
|
||||||
|
# Stelle eines Grundsatzprogramms.
|
||||||
|
{
|
||||||
|
"id": "ssw-grundsatz-sh-2016",
|
||||||
|
"titel": "SSW Rahmenprogramm",
|
||||||
|
"name": "SSW Rahmenprogramm 2016",
|
||||||
|
"typ": "grundsatzprogramm-land",
|
||||||
|
"partei": "SSW",
|
||||||
|
"bundesland": "SH",
|
||||||
|
"beschluss": "2016-04-16",
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": "2016-04-16",
|
||||||
|
"gueltig_bis": None,
|
||||||
|
"pdf": "ssw-grundsatz-sh-2016.pdf",
|
||||||
|
"seiten": 0,
|
||||||
|
"hinweis": "pdf-pending",
|
||||||
|
},
|
||||||
|
# ─── FREIE WÄHLER Bundesvereinigung Grundsatzprogramm ─────────────────
|
||||||
|
# FW sind nicht im Bundestag (BTW 2025 unter 5%), aber im Landtag in
|
||||||
|
# Bayern (Regierung) und Rheinland-Pfalz (Opposition). Bundesgrundsatz-
|
||||||
|
# programm gilt als bundesweite Referenz für alle Landesverbände.
|
||||||
|
{
|
||||||
|
"id": "fw-grundsatz-2012",
|
||||||
|
"titel": "FREIE WÄHLER Bundesgrundsatzprogramm",
|
||||||
|
"name": "FREIE WÄHLER Bundesgrundsatzprogramm",
|
||||||
|
"typ": "grundsatzprogramm-bund",
|
||||||
|
"partei": "FREIE WÄHLER",
|
||||||
|
"bundesland": None,
|
||||||
|
"beschluss": "2012-02-25", # erstes Bundesgrundsatzprogramm; mehrfach fortgeschrieben
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": "2012-02-25",
|
||||||
|
"gueltig_bis": None,
|
||||||
|
"pdf": "fw-grundsatz.pdf",
|
||||||
|
"seiten": 0,
|
||||||
|
"hinweis": "pdf-pending — FREIE WÄHLER sind nicht im Bundestag, "
|
||||||
|
"Programm gilt als bundesweite Referenz für FW Bayern + FW Rheinland-Pfalz",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_from_legacy() -> None:
|
||||||
|
"""Migriere bestehende Daten aus ``wahlprogramme.WAHLPROGRAMME`` und
|
||||||
|
``embeddings.PROGRAMME`` in die neue Registry. Wird einmal beim
|
||||||
|
Modul-Import aufgerufen.
|
||||||
|
|
||||||
|
Reihenfolge:
|
||||||
|
1. Wahlprogramme aus WAHLPROGRAMME (autoritative Quelle für regierungs-
|
||||||
|
gebundene Geltungsdaten).
|
||||||
|
2. Grundsatzprogramme aus embeddings.PROGRAMME (typ=parteiprogramm).
|
||||||
|
3. _ADDITIONAL_PROGRAMME (neue Daten — Landesgrundsatz, ggf. Updates).
|
||||||
|
|
||||||
|
Update-Logik: Wenn ein _ADDITIONAL Programm denselben ``partei``,
|
||||||
|
``bundesland`` und Typ wie ein bestehendes hat und neueres
|
||||||
|
``gueltig_ab`` besitzt, wird das Vorgänger-``gueltig_bis`` rückwirkend
|
||||||
|
auf das ``gueltig_ab`` des neuen gesetzt.
|
||||||
|
"""
|
||||||
|
# Zirkuläre Imports vermeiden — lazy import beim Migrationszeitpunkt.
|
||||||
|
from .wahlprogramme import WAHLPROGRAMME
|
||||||
|
from .embeddings import PROGRAMME as _EMB_PROGRAMME
|
||||||
|
|
||||||
|
# Schritt 1: Wahlprogramme aus WAHLPROGRAMME
|
||||||
|
for bundesland, parteien in WAHLPROGRAMME.items():
|
||||||
|
for partei, info in parteien.items():
|
||||||
|
# ID ableiten aus PDF-Stem
|
||||||
|
pid = info["file"].rsplit(".", 1)[0]
|
||||||
|
|
||||||
|
if info.get("ist_grundsatz"):
|
||||||
|
# Bundes-Grundsatzprogramm; Eintrag erfolgt in Schritt 2.
|
||||||
|
continue
|
||||||
|
|
||||||
|
prog: Programm = {
|
||||||
|
"id": pid,
|
||||||
|
"titel": info.get("titel", ""),
|
||||||
|
"name": f"{info.get('partei', partei)} Wahlprogramm {info.get('jahr', '')}".strip(),
|
||||||
|
"typ": "wahlprogramm",
|
||||||
|
"partei": partei,
|
||||||
|
"bundesland": bundesland,
|
||||||
|
"beschluss": None,
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": info.get("regierungsbildung") or "1900-01-01",
|
||||||
|
"gueltig_bis": info.get("regierungsende"),
|
||||||
|
"pdf": info["file"],
|
||||||
|
"seiten": int(info.get("seiten", 0)),
|
||||||
|
"hinweis": None,
|
||||||
|
}
|
||||||
|
if pid not in PROGRAMME:
|
||||||
|
PROGRAMME[pid] = prog
|
||||||
|
|
||||||
|
# Schritt 2: Grundsatzprogramme aus embeddings.PROGRAMME
|
||||||
|
for pid, info in _EMB_PROGRAMME.items():
|
||||||
|
if info.get("typ") != "parteiprogramm":
|
||||||
|
continue # Wahlprogramme schon in Schritt 1 (oder werden dort gepflegt)
|
||||||
|
if pid in PROGRAMME:
|
||||||
|
continue
|
||||||
|
prog2: Programm = {
|
||||||
|
"id": pid,
|
||||||
|
"titel": info.get("name", ""),
|
||||||
|
"name": info.get("name", ""),
|
||||||
|
"typ": "grundsatzprogramm-bund",
|
||||||
|
"partei": info.get("partei", ""),
|
||||||
|
"bundesland": None,
|
||||||
|
"beschluss": info.get("gueltig_ab"),
|
||||||
|
"wahl": None,
|
||||||
|
"wp": None,
|
||||||
|
"gueltig_ab": info.get("gueltig_ab") or "1900-01-01",
|
||||||
|
"gueltig_bis": info.get("gueltig_bis"),
|
||||||
|
"pdf": info.get("pdf", ""),
|
||||||
|
"seiten": 0,
|
||||||
|
"hinweis": None,
|
||||||
|
}
|
||||||
|
PROGRAMME[pid] = prog2
|
||||||
|
|
||||||
|
# Schritt 3: _ADDITIONAL_PROGRAMME — mit Vorgänger-bis-Update
|
||||||
|
for prog3 in _ADDITIONAL_PROGRAMME:
|
||||||
|
# Setze gueltig_bis von Vorgänger-Programmen rückwirkend.
|
||||||
|
for existing in list(PROGRAMME.values()):
|
||||||
|
if (
|
||||||
|
existing.get("typ") in ("grundsatzprogramm-bund",
|
||||||
|
"grundsatzprogramm-land")
|
||||||
|
and existing.get("partei") == prog3.get("partei")
|
||||||
|
and existing.get("bundesland") == prog3.get("bundesland")
|
||||||
|
and existing.get("gueltig_bis") is None
|
||||||
|
and existing.get("gueltig_ab", "9999-99-99") < prog3["gueltig_ab"]
|
||||||
|
):
|
||||||
|
# Wir mutieren ein TypedDict in der Registry — das ist OK,
|
||||||
|
# weil _migrate_from_legacy() einmal beim Import läuft.
|
||||||
|
existing["gueltig_bis"] = prog3["gueltig_ab"] # type: ignore[typeddict-item]
|
||||||
|
if prog3["id"] not in PROGRAMME:
|
||||||
|
PROGRAMME[prog3["id"]] = prog3
|
||||||
|
|
||||||
|
|
||||||
|
# Lazy initialisierung — erst beim ersten echten Zugriff. Dadurch vermeidet
|
||||||
|
# das Modul Import-Reihenfolge-Probleme mit ``embeddings.py`` (das viel mehr
|
||||||
|
# Initialisierung braucht: openai, fitz, etc.).
|
||||||
|
_INITIALIZED = False
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_initialized() -> None:
|
||||||
|
global _INITIALIZED
|
||||||
|
if _INITIALIZED:
|
||||||
|
return
|
||||||
|
_migrate_from_legacy()
|
||||||
|
_INITIALIZED = True
|
||||||
|
|
||||||
|
|
||||||
|
# Patch helper-API to ensure init runs on first call.
|
||||||
|
_get_programm = get_programm
|
||||||
|
_aktuelles_wahlprogramm = aktuelles_wahlprogramm
|
||||||
|
_wahlprogramm_zum_zeitpunkt = wahlprogramm_zum_zeitpunkt
|
||||||
|
_grundsatzprogramm_zum_zeitpunkt = grundsatzprogramm_zum_zeitpunkt
|
||||||
|
_parteien_mit_wahlprogramm = parteien_mit_wahlprogramm
|
||||||
|
_alle_versionen = alle_versionen
|
||||||
|
|
||||||
|
|
||||||
|
def get_programm(programm_id: str) -> Optional[Programm]: # type: ignore[no-redef]
|
||||||
|
_ensure_initialized()
|
||||||
|
return _get_programm(programm_id)
|
||||||
|
|
||||||
|
|
||||||
|
def aktuelles_wahlprogramm(bundesland: str, partei: str) -> Optional[Programm]: # type: ignore[no-redef]
|
||||||
|
_ensure_initialized()
|
||||||
|
return _aktuelles_wahlprogramm(bundesland, partei)
|
||||||
|
|
||||||
|
|
||||||
|
def wahlprogramm_zum_zeitpunkt( # type: ignore[no-redef]
|
||||||
|
bundesland: str, partei: str, datum: str,
|
||||||
|
) -> Optional[Programm]:
|
||||||
|
_ensure_initialized()
|
||||||
|
return _wahlprogramm_zum_zeitpunkt(bundesland, partei, datum)
|
||||||
|
|
||||||
|
|
||||||
|
def grundsatzprogramm_zum_zeitpunkt( # type: ignore[no-redef]
|
||||||
|
partei: str, datum: str, bundesland: Optional[str] = None,
|
||||||
|
) -> Optional[Programm]:
|
||||||
|
_ensure_initialized()
|
||||||
|
return _grundsatzprogramm_zum_zeitpunkt(partei, datum, bundesland)
|
||||||
|
|
||||||
|
|
||||||
|
def parteien_mit_wahlprogramm(bundesland: str) -> list[str]: # type: ignore[no-redef]
|
||||||
|
_ensure_initialized()
|
||||||
|
return _parteien_mit_wahlprogramm(bundesland)
|
||||||
|
|
||||||
|
|
||||||
|
def alle_versionen(bundesland: str, partei: str) -> list[Programm]: # type: ignore[no-redef]
|
||||||
|
_ensure_initialized()
|
||||||
|
return _alle_versionen(bundesland, partei)
|
||||||
|
|
||||||
|
|
||||||
|
def all_programme() -> list[Programm]:
|
||||||
|
"""Alle eingetragenen Programme (initialisiert, falls nötig)."""
|
||||||
|
_ensure_initialized()
|
||||||
|
return list(PROGRAMME.values())
|
||||||
BIN
app/static/referenzen/afd-bund-2025.pdf
Normal file
BIN
app/static/referenzen/afd-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/bsw-bund-2025.pdf
Normal file
BIN
app/static/referenzen/bsw-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/cdu-bund-2025.pdf
Normal file
BIN
app/static/referenzen/cdu-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/csu-bund-2025.pdf
Normal file
BIN
app/static/referenzen/csu-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/fdp-bund-2025.pdf
Normal file
BIN
app/static/referenzen/fdp-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/gruene-bund-2025.pdf
Normal file
BIN
app/static/referenzen/gruene-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/linke-bund-2025.pdf
Normal file
BIN
app/static/referenzen/linke-bund-2025.pdf
Normal file
Binary file not shown.
BIN
app/static/referenzen/spd-bund-2025.pdf
Normal file
BIN
app/static/referenzen/spd-bund-2025.pdf
Normal file
Binary file not shown.
160
tests/test_legislaturen.py
Normal file
160
tests/test_legislaturen.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
"""Tests for app.legislaturen — Wahlperioden + Regierungen."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.legislaturen import (
|
||||||
|
LEGISLATUREN, REGIERUNGEN,
|
||||||
|
legislatur_zum_zeitpunkt, regierung_zum_zeitpunkt,
|
||||||
|
aktuelle_legislatur, aktuelle_regierung,
|
||||||
|
regierungen_einer_wp, parteien_in_regierung_zum_zeitpunkt,
|
||||||
|
alle_legislaturen, alle_regierungen,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Daten-Konsistenz
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TestKonsistenz:
|
||||||
|
def test_legislaturen_alle_17_bundeslaender_present(self):
|
||||||
|
# 16 BL + BUND
|
||||||
|
bls = {leg["bundesland"] for leg in LEGISLATUREN}
|
||||||
|
assert bls == {
|
||||||
|
"BW", "BY", "BE", "BB", "HB", "HH", "HE", "MV", "NI",
|
||||||
|
"NRW", "RP", "SL", "SN", "LSA", "SH", "TH", "BUND",
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_jede_legislatur_hat_pflichtfelder(self):
|
||||||
|
for leg in LEGISLATUREN:
|
||||||
|
assert "bundesland" in leg
|
||||||
|
assert "wp" in leg
|
||||||
|
assert "wahltermin" in leg
|
||||||
|
assert "konstituierung" in leg
|
||||||
|
assert "ende" in leg
|
||||||
|
|
||||||
|
def test_jede_regierung_hat_pflichtfelder(self):
|
||||||
|
for reg in REGIERUNGEN:
|
||||||
|
for f in ("bundesland", "name", "wp", "parteien", "ministerpraesident", "von", "bis"):
|
||||||
|
assert f in reg, f"{reg['name']}: feld {f} fehlt"
|
||||||
|
|
||||||
|
def test_genau_eine_aktuelle_legislatur_pro_bl(self):
|
||||||
|
from collections import Counter
|
||||||
|
counts = Counter(
|
||||||
|
leg["bundesland"] for leg in LEGISLATUREN if leg["ende"] is None
|
||||||
|
)
|
||||||
|
for bl, n in counts.items():
|
||||||
|
assert n == 1, f"{bl} hat {n} aktuelle Legislaturen"
|
||||||
|
# alle 17 BL/BUND müssen eine aktuelle haben
|
||||||
|
assert len(counts) == 17
|
||||||
|
|
||||||
|
def test_genau_eine_aktuelle_regierung_pro_bl(self):
|
||||||
|
from collections import Counter
|
||||||
|
counts = Counter(
|
||||||
|
reg["bundesland"] for reg in REGIERUNGEN if reg["bis"] is None
|
||||||
|
)
|
||||||
|
assert len(counts) == 17
|
||||||
|
for bl, n in counts.items():
|
||||||
|
assert n == 1, f"{bl}: {n} aktuelle Regierungen"
|
||||||
|
|
||||||
|
def test_regierungs_intervalle_pro_bl_zusammenhängend(self):
|
||||||
|
"""Pro BL: die Regierungen-Zeitleiste hat keine Lücken."""
|
||||||
|
from collections import defaultdict
|
||||||
|
by_bl = defaultdict(list)
|
||||||
|
for reg in REGIERUNGEN:
|
||||||
|
by_bl[reg["bundesland"]].append(reg)
|
||||||
|
for bl, regs in by_bl.items():
|
||||||
|
regs.sort(key=lambda r: r["von"])
|
||||||
|
for i in range(1, len(regs)):
|
||||||
|
vorher = regs[i - 1]
|
||||||
|
jetzt = regs[i]
|
||||||
|
# Wenn vorher.bis gesetzt, sollte = jetzt.von sein
|
||||||
|
if vorher.get("bis") is not None:
|
||||||
|
assert vorher["bis"] == jetzt["von"], \
|
||||||
|
f"{bl}: Lücke zwischen {vorher['name']} ({vorher['bis']}) und {jetzt['name']} ({jetzt['von']})"
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Helper-Korrektheit
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TestHelpers:
|
||||||
|
def test_aktuelle_regierung_bund(self):
|
||||||
|
r = aktuelle_regierung("BUND")
|
||||||
|
assert r["name"] == "Merz I"
|
||||||
|
assert r["parteien"] == ["CDU", "CSU", "SPD"]
|
||||||
|
|
||||||
|
def test_aktuelle_regierung_th_voigt_brombeer(self):
|
||||||
|
r = aktuelle_regierung("TH")
|
||||||
|
assert r["name"] == "Voigt I"
|
||||||
|
assert "BSW" in r["parteien"]
|
||||||
|
|
||||||
|
def test_aktuelle_legislatur_nrw(self):
|
||||||
|
leg = aktuelle_legislatur("NRW")
|
||||||
|
assert leg["wp"] == 18
|
||||||
|
|
||||||
|
def test_legislatur_zum_zeitpunkt_nrw_2018(self):
|
||||||
|
leg = legislatur_zum_zeitpunkt("NRW", "2018-09-01")
|
||||||
|
assert leg["wp"] == 17
|
||||||
|
|
||||||
|
def test_legislatur_zum_zeitpunkt_nrw_2011(self):
|
||||||
|
leg = legislatur_zum_zeitpunkt("NRW", "2011-09-01")
|
||||||
|
assert leg["wp"] == 15
|
||||||
|
|
||||||
|
def test_kemmerich_kabinett_TH_2020_02(self):
|
||||||
|
"""Das berühmte 28-Tage-Kabinett von Kemmerich in TH."""
|
||||||
|
r = regierung_zum_zeitpunkt("TH", "2020-02-15")
|
||||||
|
assert r is not None
|
||||||
|
assert r["name"] == "Kemmerich I"
|
||||||
|
assert r["parteien"] == ["FDP"]
|
||||||
|
|
||||||
|
def test_rp_uebergang_dreyer_zu_schweitzer(self):
|
||||||
|
# Vor 2024-07-10: Dreyer III
|
||||||
|
r1 = regierung_zum_zeitpunkt("RP", "2024-07-09")
|
||||||
|
assert r1["name"] == "Dreyer III"
|
||||||
|
# Ab 2024-07-10: Schweitzer I
|
||||||
|
r2 = regierung_zum_zeitpunkt("RP", "2024-07-10")
|
||||||
|
assert r2["name"] == "Schweitzer I"
|
||||||
|
# Beide in WP18
|
||||||
|
assert r1["wp"] == 18 and r2["wp"] == 18
|
||||||
|
|
||||||
|
def test_bund_scholz_to_merz(self):
|
||||||
|
# Scholz I Ampel
|
||||||
|
r1 = regierung_zum_zeitpunkt("BUND", "2023-06-01")
|
||||||
|
assert r1["name"] == "Scholz I"
|
||||||
|
assert "FDP" in r1["parteien"]
|
||||||
|
# nach FDP-Ausstieg (2024-11-06)
|
||||||
|
r2 = regierung_zum_zeitpunkt("BUND", "2024-12-01")
|
||||||
|
assert "Scholz" in r2["name"]
|
||||||
|
assert "FDP" not in r2["parteien"]
|
||||||
|
# Merz seit 2025-05-06
|
||||||
|
r3 = regierung_zum_zeitpunkt("BUND", "2025-06-01")
|
||||||
|
assert r3["name"] == "Merz I"
|
||||||
|
|
||||||
|
def test_parteien_in_regierung_zum_zeitpunkt(self):
|
||||||
|
parteien = parteien_in_regierung_zum_zeitpunkt("BUND", "2025-06-01")
|
||||||
|
assert set(parteien) == {"CDU", "CSU", "SPD"}
|
||||||
|
|
||||||
|
def test_regierungen_einer_wp_rp18(self):
|
||||||
|
# In RP WP18 gibt es 2 Regierungen (Dreyer III + Schweitzer I)
|
||||||
|
regs = regierungen_einer_wp("RP", 18)
|
||||||
|
assert len(regs) == 2
|
||||||
|
assert [r["name"] for r in regs] == ["Dreyer III", "Schweitzer I"]
|
||||||
|
|
||||||
|
def test_alle_legislaturen_nrw(self):
|
||||||
|
legs = alle_legislaturen("NRW")
|
||||||
|
assert [l["wp"] for l in legs] == [15, 16, 17, 18]
|
||||||
|
|
||||||
|
def test_alle_regierungen_bund(self):
|
||||||
|
regs = alle_regierungen("BUND")
|
||||||
|
# Mind. 5: Merkel III, IV, Scholz I, Scholz I (gf), Merz I
|
||||||
|
assert len(regs) >= 5
|
||||||
|
|
||||||
|
|
||||||
|
class TestEdgecases:
|
||||||
|
def test_unbekannte_bl_returns_none(self):
|
||||||
|
assert legislatur_zum_zeitpunkt("XX", "2025-01-01") is None
|
||||||
|
assert regierung_zum_zeitpunkt("XX", "2025-01-01") is None
|
||||||
|
assert aktuelle_regierung("XX") is None
|
||||||
|
|
||||||
|
def test_zeitpunkt_vor_aller_dokumentation(self):
|
||||||
|
# In NRW reicht das Schema bis WP15 (ab 2010-06-09) zurück
|
||||||
|
assert legislatur_zum_zeitpunkt("NRW", "2005-01-01") is None
|
||||||
Loading…
Reference in New Issue
Block a user