refactor(programme): WAHLPROGRAMME → programme.PROGRAMME konsolidiert (#222)

Schließt #222. Entfernt die Doppelung zwischen ``wahlprogramme.WAHLPROGRAMME``
und ``programme.PROGRAMME``. Single source of truth ist jetzt
``programme.PROGRAMME`` als Literal mit allen 287 Programmen
(Wahlprogramme + Bundes- + Landes-Grundsatzprogramme, historisch + aktuell).

Schema schmaler — Felder ohne Konsumenten entfallen:
- ``regierungsbildung`` / ``regierungsende`` → gehören zu
  ``legislaturen.REGIERUNGEN``. Verbindung Programm→Regierung läuft jetzt
  über ``legislaturen.regierung_zum_zeitpunkt(bl, datum)``.
- ``partei`` (Langform "CDU NRW") → ableitbar aus partei + bundesland.
- ``jahr`` → ableitbar aus ``gueltig_ab[:4]``.
- ``beschluss`` / ``wahl`` / ``hinweis`` → keine App-Konsumenten.

Felder im neuen Schema: id, typ, partei, bundesland, wp, gueltig_ab,
gueltig_bis, name, titel (Slogan, optional), pdf, seiten.

Daten-Migration einmalig via ``tools/build_programme_literal.py``:
- Basis: bisherige embeddings.PROGRAMME (alle 287 IDs + gueltig_ab/bis)
- titel aus WAHLPROGRAMME für die ~80 aktuellen Wahlprogramme +
  Land-Grundsatzprogramm-Slogans (ehem. _ARCHIVED_SKELETONS)
- seiten via ``fitz.open(p).page_count`` für alle 287 PDFs

Aufrufer migriert:
- app/main.py:4055 — ``aktuelles_wahlprogramm(bl, partei).pdf``
- app/wahlprogramm_check.py — ``parteien_mit_wahlprogramm(bl)``
- app/redline_utils.py — Reverse-Lookup über ``all_programme()``
- app/wahlprogramm_fetch.py (3 Stellen) — ``aktuelles_wahlprogramm()``
- tests/test_redline_parser.py — Programm-Lookup statt WAHLPROGRAMME

``wahlprogramme.py`` schrumpft auf den Such-Code: Keyword-Fallback +
PDF-Text-Loader + ein dünner ``get_wahlprogramm``-Compat-Adapter zu
``programme.aktuelles_wahlprogramm``.

Drei Helper gelöscht (keine App-Konsumenten):
``regierungsbildung_for``, ``regierungsende_for``, ``regierung_aktuell``.
Wer das Datum der Regierungsbildung will, fragt
``legislaturen.aktuelle_regierung(bl).get('von')``.

Test-Suite: 1217 grün (vorher 1244, Differenz 27 = entfernte
regierungs-Helper-Tests + obsolete WAHLPROGRAMME-Strukturtests).
This commit is contained in:
Dotty Dotter 2026-05-09 00:37:35 +02:00
parent 7d507f81f4
commit bd591b9246
10 changed files with 920 additions and 995 deletions

View File

@ -4055,7 +4055,7 @@ async def api_admin_wahlprogramm_fetch(
wird sie nicht überschrieben stattdessen wird ein 409-Fehler zurückgegeben.
"""
from .wahlprogramm_fetch import fetch_and_verify, suggest_candidates
from .wahlprogramme import WAHLPROGRAMME
from .programme import aktuelles_wahlprogramm
body = await request.json()
bl = body.get("bl", "").strip().upper()
@ -4075,10 +4075,10 @@ async def api_admin_wahlprogramm_fetch(
)
url = candidates[0]["url"]
wp_info = WAHLPROGRAMME.get(bl, {}).get(partei)
if wp_info:
wp_info = aktuelles_wahlprogramm(bl, partei)
if wp_info and wp_info.get("pdf"):
from pathlib import Path as _Path
dest = _Path(__file__).parent / "static" / "referenzen" / wp_info["file"]
dest = _Path(__file__).parent / "static" / "referenzen" / wp_info["pdf"]
else:
from pathlib import Path as _Path
dest = _Path(__file__).parent / "static" / "referenzen" / f"{partei.lower()}-{bl.lower()}-neu.pdf"

View File

@ -4,11 +4,13 @@ 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.
Siehe ``app/legislaturen.py`` für Wahlperioden + Regierungen Programm-
Daten beschreiben das Dokument selbst, nicht die Regierung, die aus ihm
hervorging. Die Verbindung läuft über
``legislaturen.regierung_zum_zeitpunkt(bundesland, datum)``.
"""
from __future__ import annotations
@ -17,7 +19,7 @@ from typing import Literal, Optional, TypedDict
# ─────────────────────────────────────────────────────────────────────────────
# Typ
# Schema
# ─────────────────────────────────────────────────────────────────────────────
@ -31,23 +33,27 @@ ProgrammTyp = Literal[
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.
Pflichtfelder: id, typ, partei, gueltig_ab, name, pdf.
Optional: bundesland, wp, gueltig_bis, titel, seiten.
Was hier bewusst NICHT drin ist:
- ``regierungsbildung`` / ``regierungsende`` gehört zu
``legislaturen.REGIERUNGEN``. Verbindung ProgrammRegierung läuft
über ``legislaturen.regierung_zum_zeitpunkt(bl, antrag_datum)``.
- ``partei`` Langform ("CDU NRW") ableitbar via partei + bundesland.
- ``jahr`` ``int(gueltig_ab[:4])`` reicht.
"""
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
partei: str # kanonisch (CDU, BiW, BÜNDNIS 90/DIE GRÜNEN, …)
bundesland: Optional[str] # BL-Code; None nur bei Bundesgrundsatzprogrammen
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
gueltig_ab: str # ISO YYYY-MM-DD; bei wahl: Wahltag
gueltig_bis: Optional[str] # ISO; None = aktuell gültig
name: str # voll-qualifiziert für Citation, z.B. "CDU NRW Wahlprogramm 2022"
titel: Optional[str] # Slogan ("Machen, worauf es ankommt"); None wenn nicht erfasst
pdf: str # Dateiname in static/referenzen/
seiten: int
hinweis: Optional[str] # freier Text, z.B. "BSW hat kein Grundsatzprogramm — Wahlprogramm dient als Hauptquelle"
seiten: Optional[int] # PDF-Seitenzahl
REFERENZEN_PATH = Path(__file__).parent / "static" / "referenzen"
@ -55,16 +61,348 @@ 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.
# Daten — alle 287 Programme (historisch + aktuell), sortiert nach
# (typ, bundesland, gueltig_ab, partei). Auto-generiert aus der bisherigen
# embeddings.PROGRAMME + WAHLPROGRAMME-Lazy-Migration via fitz für seiten.
# Generator: tools/build_programme_literal.py (siehe Commit-Historie).
# ─────────────────────────────────────────────────────────────────────────────
# Aktuelle Programme — gefüllt durch ``_register_initial_data()`` weiter unten,
# damit der Migrations-Pfad an einer Stelle zu sehen ist.
PROGRAMME: dict[str, Programm] = {}
PROGRAMME: dict[str, Programm] = {
# ─── grundsatzprogramm-bund · BUND ───
"spd-grundsatz": {"id": "spd-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "SPD", "bundesland": None, "wp": None, "gueltig_ab": "2007-10-28", "gueltig_bis": None, "name": "SPD Hamburger Programm 2007", "titel": None, "pdf": "spd-grundsatzprogramm.pdf", "seiten": 79},
"linke-grundsatz": {"id": "linke-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "LINKE", "bundesland": None, "wp": None, "gueltig_ab": "2011-10-23", "gueltig_bis": None, "name": "DIE LINKE Erfurter Programm 2011", "titel": None, "pdf": "linke-grundsatzprogramm.pdf", "seiten": 58},
"fw-grundsatz": {"id": "fw-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "FREIE WÄHLER", "bundesland": None, "wp": None, "gueltig_ab": "2012-02-25", "gueltig_bis": None, "name": "FREIE WÄHLER Bundesgrundsatzprogramm", "titel": None, "pdf": "fw-grundsatz.pdf", "seiten": 46},
"fdp-grundsatz": {"id": "fdp-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "FDP", "bundesland": None, "wp": None, "gueltig_ab": "2012-04-22", "gueltig_bis": None, "name": "FDP Karlsruher Freiheitsthesen 2012", "titel": None, "pdf": "fdp-grundsatzprogramm.pdf", "seiten": 118},
"afd-grundsatz": {"id": "afd-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "AfD", "bundesland": None, "wp": None, "gueltig_ab": "2016-05-01", "gueltig_bis": None, "name": "AfD Grundsatzprogramm 2016", "titel": None, "pdf": "afd-grundsatzprogramm.pdf", "seiten": 96},
"gruene-grundsatz": {"id": "gruene-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "GRÜNE", "bundesland": None, "wp": None, "gueltig_ab": "2020-11-22", "gueltig_bis": None, "name": "Grüne Grundsatzprogramm 2020", "titel": None, "pdf": "gruene-grundsatzprogramm.pdf", "seiten": 136},
"cdu-grundsatz": {"id": "cdu-grundsatz", "typ": "grundsatzprogramm-bund", "partei": "CDU", "bundesland": None, "wp": None, "gueltig_ab": "2024-05-07", "gueltig_bis": None, "name": "CDU Grundsatzprogramm 2024", "titel": None, "pdf": "cdu-grundsatzprogramm.pdf", "seiten": 82},
# ─── grundsatzprogramm-land · BY ───
"csu-grundsatz": {"id": "csu-grundsatz", "typ": "grundsatzprogramm-land", "partei": "CSU", "bundesland": "BY", "wp": None, "gueltig_ab": "2023-05-06", "gueltig_bis": None, "name": "CSU Grundsatzprogramm 2023", "titel": "Für ein neues Miteinander — Grundsatzprogramm der CSU", "pdf": "csu-grundsatz-2023.pdf", "seiten": 55},
# ─── grundsatzprogramm-land · LSA ───
"cdu-grundsatz-lsa": {"id": "cdu-grundsatz-lsa", "typ": "grundsatzprogramm-land", "partei": "CDU", "bundesland": "LSA", "wp": None, "gueltig_ab": "2023-09-30", "gueltig_bis": None, "name": "CDU Sachsen-Anhalt Grundsatzprogramm 2023", "titel": "Sachsen-Anhalt. Unsere Verantwortung. Unsere Zukunft.", "pdf": "cdu-grundsatz-lsa-2023.pdf", "seiten": 70},
# ─── grundsatzprogramm-land · NRW ───
"cdu-grundsatz-nrw": {"id": "cdu-grundsatz-nrw", "typ": "grundsatzprogramm-land", "partei": "CDU", "bundesland": "NRW", "wp": None, "gueltig_ab": "2015-06-13", "gueltig_bis": None, "name": "CDU NRW Grundsatzprogramm 2015", "titel": "Aufstieg, Sicherheit, Perspektive — Das Nordrhein-Westfalen-Programm", "pdf": "cdu-grundsatz-nrw-2015.pdf", "seiten": 48},
# ─── grundsatzprogramm-land · SH ───
"ssw-grundsatz": {"id": "ssw-grundsatz", "typ": "grundsatzprogramm-land", "partei": "SSW", "bundesland": "SH", "wp": None, "gueltig_ab": "2016-04-16", "gueltig_bis": None, "name": "SSW Rahmenprogramm 2016", "titel": "SSW Rahmenprogramm", "pdf": "ssw-grundsatz-sh-2016.pdf", "seiten": 34},
# ─── grundsatzprogramm-land · SN ───
"cdu-grundsatz-sn": {"id": "cdu-grundsatz-sn", "typ": "grundsatzprogramm-land", "partei": "CDU", "bundesland": "SN", "wp": None, "gueltig_ab": "2023-11-20", "gueltig_bis": None, "name": "CDU Sachsen Grundsatzprogramm 2023", "titel": "Zukunftsplan für Sachsen", "pdf": "cdu-grundsatz-sn-2023.pdf", "seiten": 54},
# ─── wahlprogramm · BB ───
"afd-bb-2014": {"id": "afd-bb-2014", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BB", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-09-01", "name": "AfD BB Wahlprogramm 2014", "titel": None, "pdf": "afd-bb-2014.pdf", "seiten": 37},
"bvb-fw-bb-2014": {"id": "bvb-fw-bb-2014", "typ": "wahlprogramm", "partei": "BVB/FREIE WÄHLER", "bundesland": "BB", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-09-01", "name": "BVB/FREIE WÄHLER BB Wahlprogramm 2014", "titel": None, "pdf": "bvb-fw-bb-2014.pdf", "seiten": 39},
"cdu-bb-2014": {"id": "cdu-bb-2014", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BB", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-09-01", "name": "CDU BB Wahlprogramm 2014", "titel": None, "pdf": "cdu-bb-2014.pdf", "seiten": 20},
"gruene-bb-2014": {"id": "gruene-bb-2014", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BB", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-09-01", "name": "Grüne BB Wahlprogramm 2014", "titel": None, "pdf": "gruene-bb-2014.pdf", "seiten": 150},
"linke-bb-2014": {"id": "linke-bb-2014", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BB", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-09-01", "name": "DIE LINKE BB Wahlprogramm 2014", "titel": None, "pdf": "linke-bb-2014.pdf", "seiten": 60},
"spd-bb-2014": {"id": "spd-bb-2014", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BB", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-09-01", "name": "SPD BB Wahlprogramm 2014", "titel": None, "pdf": "spd-bb-2014.pdf", "seiten": 26},
"afd-bb-2019": {"id": "afd-bb-2019", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BB", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-22", "name": "AfD BB Wahlprogramm 2019", "titel": None, "pdf": "afd-bb-2019.pdf", "seiten": 42},
"bvb-fw-bb-2019": {"id": "bvb-fw-bb-2019", "typ": "wahlprogramm", "partei": "BVB/FREIE WÄHLER", "bundesland": "BB", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-22", "name": "BVB/FREIE WÄHLER BB Wahlprogramm 2019", "titel": None, "pdf": "bvb-fw-bb-2019.pdf", "seiten": 37},
"cdu-bb-2019": {"id": "cdu-bb-2019", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BB", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-22", "name": "CDU BB Wahlprogramm 2019", "titel": None, "pdf": "cdu-bb-2019.pdf", "seiten": 50},
"gruene-bb-2019": {"id": "gruene-bb-2019", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BB", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-22", "name": "Grüne BB Wahlprogramm 2019", "titel": None, "pdf": "gruene-bb-2019.pdf", "seiten": 106},
"linke-bb-2019": {"id": "linke-bb-2019", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BB", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-22", "name": "DIE LINKE BB Wahlprogramm 2019", "titel": None, "pdf": "linke-bb-2019.pdf", "seiten": 100},
"spd-bb-2019": {"id": "spd-bb-2019", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BB", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-22", "name": "SPD BB Wahlprogramm 2019", "titel": None, "pdf": "spd-bb-2019.pdf", "seiten": 47},
"afd-bb-2024": {"id": "afd-bb-2024", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BB", "wp": 8, "gueltig_ab": "2024-09-22", "gueltig_bis": None, "name": "AfD Brandenburg Wahlprogramm 2024", "titel": None, "pdf": "afd-bb-2024.pdf", "seiten": 100},
"bsw-bb-2024": {"id": "bsw-bb-2024", "typ": "wahlprogramm", "partei": "BSW", "bundesland": "BB", "wp": 8, "gueltig_ab": "2024-09-22", "gueltig_bis": None, "name": "BSW Brandenburg Wahlprogramm 2024", "titel": None, "pdf": "bsw-bb-2024.pdf", "seiten": 50},
"cdu-bb-2024": {"id": "cdu-bb-2024", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BB", "wp": 8, "gueltig_ab": "2024-09-22", "gueltig_bis": None, "name": "CDU Brandenburg Wahlprogramm 2024", "titel": None, "pdf": "cdu-bb-2024.pdf", "seiten": 100},
"spd-bb-2024": {"id": "spd-bb-2024", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BB", "wp": 8, "gueltig_ab": "2024-09-22", "gueltig_bis": None, "name": "SPD Brandenburg Wahlprogramm 2024", "titel": None, "pdf": "spd-bb-2024.pdf", "seiten": 100},
# ─── wahlprogramm · BE ───
"cdu-be-2011": {"id": "cdu-be-2011", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BE", "wp": 17, "gueltig_ab": "2011-09-18", "gueltig_bis": "2016-09-18", "name": "CDU BE Wahlprogramm 2011", "titel": None, "pdf": "cdu-be-2011.pdf", "seiten": 41},
"gruene-be-2011": {"id": "gruene-be-2011", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BE", "wp": 17, "gueltig_ab": "2011-09-18", "gueltig_bis": "2016-09-18", "name": "Grüne BE Wahlprogramm 2011", "titel": None, "pdf": "gruene-be-2011.pdf", "seiten": 120},
"linke-be-2011": {"id": "linke-be-2011", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BE", "wp": 17, "gueltig_ab": "2011-09-18", "gueltig_bis": "2016-09-18", "name": "DIE LINKE BE Wahlprogramm 2011", "titel": None, "pdf": "linke-be-2011.pdf", "seiten": 72},
"piraten-be-2011": {"id": "piraten-be-2011", "typ": "wahlprogramm", "partei": "PIRATEN", "bundesland": "BE", "wp": 17, "gueltig_ab": "2011-09-18", "gueltig_bis": "2016-09-18", "name": "PIRATEN BE Wahlprogramm 2011", "titel": None, "pdf": "piraten-be-2011.pdf", "seiten": 51},
"spd-be-2011": {"id": "spd-be-2011", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BE", "wp": 17, "gueltig_ab": "2011-09-18", "gueltig_bis": "2016-09-18", "name": "SPD BE Wahlprogramm 2011", "titel": None, "pdf": "spd-be-2011.pdf", "seiten": 60},
"afd-be-2016": {"id": "afd-be-2016", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BE", "wp": 18, "gueltig_ab": "2016-09-18", "gueltig_bis": "2021-09-26", "name": "AfD BE Wahlprogramm 2016", "titel": None, "pdf": "afd-be-2016.pdf", "seiten": 40},
"cdu-be-2016": {"id": "cdu-be-2016", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BE", "wp": 18, "gueltig_ab": "2016-09-18", "gueltig_bis": "2021-09-26", "name": "CDU BE Wahlprogramm 2016", "titel": None, "pdf": "cdu-be-2016.pdf", "seiten": 49},
"fdp-be-2016": {"id": "fdp-be-2016", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BE", "wp": 18, "gueltig_ab": "2016-09-18", "gueltig_bis": "2021-09-26", "name": "FDP BE Wahlprogramm 2016", "titel": None, "pdf": "fdp-be-2016.pdf", "seiten": 57},
"gruene-be-2016": {"id": "gruene-be-2016", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BE", "wp": 18, "gueltig_ab": "2016-09-18", "gueltig_bis": "2021-09-26", "name": "Grüne BE Wahlprogramm 2016", "titel": None, "pdf": "gruene-be-2016.pdf", "seiten": 104},
"linke-be-2016": {"id": "linke-be-2016", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BE", "wp": 18, "gueltig_ab": "2016-09-18", "gueltig_bis": "2021-09-26", "name": "DIE LINKE BE Wahlprogramm 2016", "titel": None, "pdf": "linke-be-2016.pdf", "seiten": 100},
"spd-be-2016": {"id": "spd-be-2016", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BE", "wp": 18, "gueltig_ab": "2016-09-18", "gueltig_bis": "2021-09-26", "name": "SPD BE Wahlprogramm 2016", "titel": None, "pdf": "spd-be-2016.pdf", "seiten": 99},
"afd-be-2023": {"id": "afd-be-2023", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BE", "wp": 19, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "AfD Berlin Wahlprogramm 2021", "titel": "Wahlprogramm der AfD Berlin für die Wahl des Abgeordnetenhauses am 26. September 2021", "pdf": "afd-be-2023.pdf", "seiten": 166},
"cdu-be-2023": {"id": "cdu-be-2023", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BE", "wp": 19, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "CDU Berlin Wahlprogramm 2021", "titel": "Unser Berlin. Mehr geht nur gemeinsam. — Berlin-Plan der CDU Berlin 20212026", "pdf": "cdu-be-2023.pdf", "seiten": 135},
"gruene-be-2023": {"id": "gruene-be-2023", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BE", "wp": 19, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Berlin Wahlprogramm 2021", "titel": "Unser Plan für Berlin — Landeswahlprogramm BÜNDNIS 90/DIE GRÜNEN Berlin 2021", "pdf": "gruene-be-2023.pdf", "seiten": 280},
"linke-be-2023": {"id": "linke-be-2023", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BE", "wp": 19, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "DIE LINKE Berlin Wahlprogramm 2021", "titel": "rot. radikal. realistisch. — Unser Programm für die soziale Stadt", "pdf": "linke-be-2023.pdf", "seiten": 130},
"spd-be-2023": {"id": "spd-be-2023", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BE", "wp": 19, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "SPD Berlin Wahlprogramm 2021", "titel": "Ganz sicher Berlin — Wahlprogramm der SPD Berlin zur Abgeordnetenhauswahl 2021", "pdf": "spd-be-2023.pdf", "seiten": 86},
# ─── wahlprogramm · BUND ───
"cdu-bund-2013": {"id": "cdu-bund-2013", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BUND", "wp": 18, "gueltig_ab": "2013-09-22", "gueltig_bis": "2017-09-24", "name": "CDU BUND Wahlprogramm 2013", "titel": None, "pdf": "cdu-bund-2013.pdf", "seiten": 81},
"csu-bund-2013": {"id": "csu-bund-2013", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BUND", "wp": 18, "gueltig_ab": "2013-09-22", "gueltig_bis": "2017-09-24", "name": "CSU BUND Wahlprogramm 2013", "titel": None, "pdf": "csu-bund-2013.pdf", "seiten": 41},
"gruene-bund-2013": {"id": "gruene-bund-2013", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BUND", "wp": 18, "gueltig_ab": "2013-09-22", "gueltig_bis": "2017-09-24", "name": "Grüne BUND Wahlprogramm 2013", "titel": None, "pdf": "gruene-bund-2013.pdf", "seiten": 337},
"linke-bund-2013": {"id": "linke-bund-2013", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BUND", "wp": 18, "gueltig_ab": "2013-09-22", "gueltig_bis": "2017-09-24", "name": "DIE LINKE BUND Wahlprogramm 2013", "titel": None, "pdf": "linke-bund-2013.pdf", "seiten": 93},
"spd-bund-2013": {"id": "spd-bund-2013", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BUND", "wp": 18, "gueltig_ab": "2013-09-22", "gueltig_bis": "2017-09-24", "name": "SPD BUND Wahlprogramm 2013", "titel": None, "pdf": "spd-bund-2013.pdf", "seiten": 120},
"afd-bund-2017": {"id": "afd-bund-2017", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "AfD Bundestagswahlprogramm 2017", "titel": None, "pdf": "afd-bund-2017.pdf", "seiten": 76},
"cdu-bund-2017": {"id": "cdu-bund-2017", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "CDU/CSU Wahlprogramm BTW 2017", "titel": None, "pdf": "cdu-bund-2017.pdf", "seiten": 76},
"csu-bund-2017": {"id": "csu-bund-2017", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "CSU Bayernplan 2017", "titel": None, "pdf": "csu-bund-2017.pdf", "seiten": 31},
"fdp-bund-2017": {"id": "fdp-bund-2017", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "FDP Wahlprogramm BTW 2017", "titel": None, "pdf": "fdp-bund-2017.pdf", "seiten": 158},
"gruene-bund-2017": {"id": "gruene-bund-2017", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "Grüne Bundestagswahlprogramm 2017", "titel": None, "pdf": "gruene-bund-2017.pdf", "seiten": 248},
"linke-bund-2017": {"id": "linke-bund-2017", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "DIE LINKE Bundestagswahlprogramm 2017", "titel": None, "pdf": "linke-bund-2017.pdf", "seiten": 144},
"spd-bund-2017": {"id": "spd-bund-2017", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BUND", "wp": 19, "gueltig_ab": "2017-09-24", "gueltig_bis": "2021-09-26", "name": "SPD Zukunftsprogramm BTW 2017", "titel": None, "pdf": "spd-bund-2017.pdf", "seiten": 116},
"afd-bund-2021": {"id": "afd-bund-2021", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "AfD Bundestagswahlprogramm 2021", "titel": None, "pdf": "afd-bund-2021.pdf", "seiten": 210},
"cdu-bund-2021": {"id": "cdu-bund-2021", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "CDU/CSU Programm fuer Stabilitaet und Erneuerung 2021", "titel": None, "pdf": "cdu-bund-2021.pdf", "seiten": 140},
"csu-bund-2021": {"id": "csu-bund-2021", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "CSU-Programm Bundestagswahl 2021", "titel": None, "pdf": "csu-bund-2021.pdf", "seiten": 18},
"fdp-bund-2021": {"id": "fdp-bund-2021", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "FDP Wahlprogramm Bundestagswahl 2021", "titel": None, "pdf": "fdp-bund-2021.pdf", "seiten": 68},
"gruene-bund-2021": {"id": "gruene-bund-2021", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "Gruene Bundestagswahlprogramm 2021", "titel": None, "pdf": "gruene-bund-2021.pdf", "seiten": 272},
"linke-bund-2021": {"id": "linke-bund-2021", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "DIE LINKE Bundestagswahlprogramm 2021", "titel": None, "pdf": "linke-bund-2021.pdf", "seiten": 168},
"spd-bund-2021": {"id": "spd-bund-2021", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BUND", "wp": 20, "gueltig_ab": "2021-09-26", "gueltig_bis": "2025-02-23", "name": "SPD Zukunftsprogramm Bundestagswahl 2021", "titel": None, "pdf": "spd-bund-2021.pdf", "seiten": 66},
"afd-bund-2025": {"id": "afd-bund-2025", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "AfD Wahlprogramm 2025", "titel": "Zeit für Deutschland — AfD Bundestagswahlprogramm 2025", "pdf": "afd-bund-2025.pdf", "seiten": 177},
"bsw-bund-2025": {"id": "bsw-bund-2025", "typ": "wahlprogramm", "partei": "BSW", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "BSW Wahlprogramm 2025", "titel": "Unser Land verdient mehr — BSW Wahlprogramm BTW 2025", "pdf": "bsw-bund-2025.pdf", "seiten": 45},
"cdu-bund-2025": {"id": "cdu-bund-2025", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "CDU Wahlprogramm 2025", "titel": "Politikwechsel für Deutschland — Wahlprogramm CDU/CSU BTW 2025", "pdf": "cdu-bund-2025.pdf", "seiten": 82},
"csu-bund-2025": {"id": "csu-bund-2025", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "CSU Wahlprogramm 2025", "titel": "Politikwechsel für Deutschland — Wahlprogramm CDU/CSU BTW 2025 (CSU)", "pdf": "csu-bund-2025.pdf", "seiten": 81},
"fdp-bund-2025": {"id": "fdp-bund-2025", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "FDP Wahlprogramm 2025", "titel": "Alles lässt sich ändern — FDP Wahlprogramm BTW 2025", "pdf": "fdp-bund-2025.pdf", "seiten": 52},
"gruene-bund-2025": {"id": "gruene-bund-2025", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Wahlprogramm 2025", "titel": "Zusammen wachsen — Regierungsprogramm BÜNDNIS 90/DIE GRÜNEN BTW 2025", "pdf": "gruene-bund-2025.pdf", "seiten": 160},
"linke-bund-2025": {"id": "linke-bund-2025", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "DIE LINKE Wahlprogramm 2025", "titel": "Alle wollen regieren. Wir wollen verändern. — DIE LINKE Wahlprogramm BTW 2025", "pdf": "linke-bund-2025.pdf", "seiten": 60},
"spd-bund-2025": {"id": "spd-bund-2025", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BUND", "wp": 21, "gueltig_ab": "2025-02-23", "gueltig_bis": None, "name": "SPD Wahlprogramm 2025", "titel": "Mehr für Dich. Besser für Deutschland. — SPD Regierungsprogramm BTW 2025", "pdf": "spd-bund-2025.pdf", "seiten": 68},
# ─── wahlprogramm · BW ───
"cdu-bw-2011": {"id": "cdu-bw-2011", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BW", "wp": 15, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-03-13", "name": "CDU BW Wahlprogramm 2011", "titel": None, "pdf": "cdu-bw-2011.pdf", "seiten": 57},
"fdp-bw-2011": {"id": "fdp-bw-2011", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BW", "wp": 15, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-03-13", "name": "FDP BW Wahlprogramm 2011", "titel": None, "pdf": "fdp-bw-2011.pdf", "seiten": 137},
"gruene-bw-2011": {"id": "gruene-bw-2011", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BW", "wp": 15, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-03-13", "name": "Grüne BW Wahlprogramm 2011", "titel": None, "pdf": "gruene-bw-2011.pdf", "seiten": 241},
"spd-bw-2011": {"id": "spd-bw-2011", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BW", "wp": 15, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-03-13", "name": "SPD BW Wahlprogramm 2011", "titel": None, "pdf": "spd-bw-2011.pdf", "seiten": 65},
"afd-bw-2016": {"id": "afd-bw-2016", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BW", "wp": 16, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "AfD BW Wahlprogramm 2016", "titel": None, "pdf": "afd-bw-2016.pdf", "seiten": 64},
"cdu-bw-2016": {"id": "cdu-bw-2016", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BW", "wp": 16, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "CDU BW Wahlprogramm 2016", "titel": None, "pdf": "cdu-bw-2016.pdf", "seiten": 156},
"fdp-bw-2016": {"id": "fdp-bw-2016", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BW", "wp": 16, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "FDP BW Wahlprogramm 2016", "titel": None, "pdf": "fdp-bw-2016.pdf", "seiten": 124},
"gruene-bw-2016": {"id": "gruene-bw-2016", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BW", "wp": 16, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "Grüne BW Wahlprogramm 2016", "titel": None, "pdf": "gruene-bw-2016.pdf", "seiten": 249},
"spd-bw-2016": {"id": "spd-bw-2016", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BW", "wp": 16, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "SPD BW Wahlprogramm 2016", "titel": None, "pdf": "spd-bw-2016.pdf", "seiten": 41},
"afd-bw-2021": {"id": "afd-bw-2021", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BW", "wp": 17, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "AfD Baden-Württemberg Wahlprogramm 2021", "titel": "AfD Baden-Württemberg Landtagswahlprogramm 2021", "pdf": "afd-bw-2021.pdf", "seiten": 100},
"cdu-bw-2021": {"id": "cdu-bw-2021", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BW", "wp": 17, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "CDU Baden-Württemberg Wahlprogramm 2021", "titel": "Neue Ideen für eine neue Zeit — Regierungsprogramm der CDU Baden-Württemberg zur Landtagswahl 2021", "pdf": "cdu-bw-2021.pdf", "seiten": 100},
"fdp-bw-2021": {"id": "fdp-bw-2021", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BW", "wp": 17, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "FDP Baden-Württemberg Wahlprogramm 2021", "titel": "FDP Baden-Württemberg Landtagswahlprogramm 2021", "pdf": "fdp-bw-2021.pdf", "seiten": 100},
"gruene-bw-2021": {"id": "gruene-bw-2021", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BW", "wp": 17, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Baden-Württemberg Wahlprogramm 2021", "titel": "Wachsen wir über uns hinaus — Landtagswahlprogramm BÜNDNIS 90/DIE GRÜNEN Baden-Württemberg 2021", "pdf": "gruene-bw-2021.pdf", "seiten": 100},
"spd-bw-2021": {"id": "spd-bw-2021", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BW", "wp": 17, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "SPD Baden-Württemberg Wahlprogramm 2021", "titel": "SPD Baden-Württemberg Wahlprogramm zur Landtagswahl 2021", "pdf": "spd-bw-2021.pdf", "seiten": 100},
# ─── wahlprogramm · BY ───
"csu-by-2013": {"id": "csu-by-2013", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BY", "wp": 17, "gueltig_ab": "2013-09-15", "gueltig_bis": "2018-10-14", "name": "CSU BY Wahlprogramm 2013", "titel": None, "pdf": "csu-by-2013.pdf", "seiten": 28},
"fw-by-2013": {"id": "fw-by-2013", "typ": "wahlprogramm", "partei": "FREIE WÄHLER", "bundesland": "BY", "wp": 17, "gueltig_ab": "2013-09-15", "gueltig_bis": "2018-10-14", "name": "FREIE WÄHLER BY Wahlprogramm 2013", "titel": None, "pdf": "fw-by-2013.pdf", "seiten": 67},
"gruene-by-2013": {"id": "gruene-by-2013", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BY", "wp": 17, "gueltig_ab": "2013-09-15", "gueltig_bis": "2018-10-14", "name": "Grüne BY Wahlprogramm 2013", "titel": None, "pdf": "gruene-by-2013.pdf", "seiten": 87},
"spd-by-2013": {"id": "spd-by-2013", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BY", "wp": 17, "gueltig_ab": "2013-09-15", "gueltig_bis": "2018-10-14", "name": "SPD BY Wahlprogramm 2013", "titel": None, "pdf": "spd-by-2013.pdf", "seiten": 150},
"afd-by-2018": {"id": "afd-by-2018", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BY", "wp": 18, "gueltig_ab": "2018-10-14", "gueltig_bis": "2023-10-08", "name": "AfD BY Wahlprogramm 2018", "titel": None, "pdf": "afd-by-2018.pdf", "seiten": 100},
"csu-by-2018": {"id": "csu-by-2018", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BY", "wp": 18, "gueltig_ab": "2018-10-14", "gueltig_bis": "2023-10-08", "name": "CSU BY Wahlprogramm 2018", "titel": None, "pdf": "csu-by-2018.pdf", "seiten": 24},
"fdp-by-2018": {"id": "fdp-by-2018", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BY", "wp": 18, "gueltig_ab": "2018-10-14", "gueltig_bis": "2023-10-08", "name": "FDP BY Wahlprogramm 2018", "titel": None, "pdf": "fdp-by-2018.pdf", "seiten": 78},
"fw-by-2018": {"id": "fw-by-2018", "typ": "wahlprogramm", "partei": "FREIE WÄHLER", "bundesland": "BY", "wp": 18, "gueltig_ab": "2018-10-14", "gueltig_bis": "2023-10-08", "name": "FREIE WÄHLER BY Wahlprogramm 2018", "titel": None, "pdf": "fw-by-2018.pdf", "seiten": 43},
"gruene-by-2018": {"id": "gruene-by-2018", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BY", "wp": 18, "gueltig_ab": "2018-10-14", "gueltig_bis": "2023-10-08", "name": "Grüne BY Wahlprogramm 2018", "titel": None, "pdf": "gruene-by-2018.pdf", "seiten": 107},
"spd-by-2018": {"id": "spd-by-2018", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BY", "wp": 18, "gueltig_ab": "2018-10-14", "gueltig_bis": "2023-10-08", "name": "SPD BY Wahlprogramm 2018", "titel": None, "pdf": "spd-by-2018.pdf", "seiten": 69},
"afd-by-2023": {"id": "afd-by-2023", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BY", "wp": 19, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "AfD Bayern Wahlprogramm 2023", "titel": None, "pdf": "afd-by-2023.pdf", "seiten": 100},
"csu-by-2023": {"id": "csu-by-2023", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BY", "wp": 19, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "CSU Bayern Wahlprogramm 2023", "titel": "CSU Bayern Bayernplan 2023", "pdf": "csu-by-2023.pdf", "seiten": 100},
"fw-by-2023": {"id": "fw-by-2023", "typ": "wahlprogramm", "partei": "FREIE WÄHLER", "bundesland": "BY", "wp": 19, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "FREIE WÄHLER Bayern Wahlprogramm 2023", "titel": None, "pdf": "fw-by-2023.pdf", "seiten": 80},
"gruene-by-2023": {"id": "gruene-by-2023", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BY", "wp": 19, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Bayern Wahlprogramm 2023", "titel": "BÜNDNIS 90/DIE GRÜNEN Bayern Regierungsprogramm 2023", "pdf": "gruene-by-2023.pdf", "seiten": 100},
"spd-by-2023": {"id": "spd-by-2023", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BY", "wp": 19, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "SPD Bayern Wahlprogramm 2023", "titel": "SPD Bayern Zukunftsprogramm 2023", "pdf": "spd-by-2023.pdf", "seiten": 100},
# ─── wahlprogramm · HB ───
"afd-hb-2015": {"id": "afd-hb-2015", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "AfD HB Wahlprogramm 2015", "titel": None, "pdf": "afd-hb-2015.pdf", "seiten": 49},
"biw-hb-2015": {"id": "biw-hb-2015", "typ": "wahlprogramm", "partei": "BiW", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "BIW Bremen HB Wahlprogramm 2015", "titel": None, "pdf": "biw-hb-2015.pdf", "seiten": 32},
"cdu-hb-2015": {"id": "cdu-hb-2015", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "CDU HB Wahlprogramm 2015", "titel": None, "pdf": "cdu-hb-2015.pdf", "seiten": 58},
"fdp-hb-2015": {"id": "fdp-hb-2015", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "FDP HB Wahlprogramm 2015", "titel": None, "pdf": "fdp-hb-2015.pdf", "seiten": 80},
"gruene-hb-2015": {"id": "gruene-hb-2015", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "Grüne HB Wahlprogramm 2015", "titel": None, "pdf": "gruene-hb-2015.pdf", "seiten": 140},
"linke-hb-2015": {"id": "linke-hb-2015", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "DIE LINKE HB Wahlprogramm 2015", "titel": None, "pdf": "linke-hb-2015.pdf", "seiten": 82},
"spd-hb-2015": {"id": "spd-hb-2015", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HB", "wp": 19, "gueltig_ab": "2015-05-10", "gueltig_bis": "2019-05-26", "name": "SPD HB Wahlprogramm 2015", "titel": None, "pdf": "spd-hb-2015.pdf", "seiten": 67},
"afd-hb-2019": {"id": "afd-hb-2019", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "AfD HB Wahlprogramm 2019", "titel": None, "pdf": "afd-hb-2019.pdf", "seiten": 27},
"biw-hb-2019": {"id": "biw-hb-2019", "typ": "wahlprogramm", "partei": "BiW", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "BIW Bremen Wahlprogramm 2019", "titel": None, "pdf": "biw-hb-2019.pdf", "seiten": 22},
"cdu-hb-2019": {"id": "cdu-hb-2019", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "CDU HB Wahlprogramm 2019", "titel": None, "pdf": "cdu-hb-2019.pdf", "seiten": 66},
"fdp-hb-2019": {"id": "fdp-hb-2019", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "FDP HB Wahlprogramm 2019", "titel": None, "pdf": "fdp-hb-2019.pdf", "seiten": 89},
"gruene-hb-2019": {"id": "gruene-hb-2019", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "Grüne HB Wahlprogramm 2019", "titel": None, "pdf": "gruene-hb-2019.pdf", "seiten": 190},
"linke-hb-2019": {"id": "linke-hb-2019", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "DIE LINKE HB Wahlprogramm 2019", "titel": None, "pdf": "linke-hb-2019.pdf", "seiten": 64},
"spd-hb-2019": {"id": "spd-hb-2019", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HB", "wp": 20, "gueltig_ab": "2019-05-26", "gueltig_bis": "2023-05-14", "name": "SPD HB Wahlprogramm 2019", "titel": None, "pdf": "spd-hb-2019.pdf", "seiten": 132},
"biw-hb-2023": {"id": "biw-hb-2023", "typ": "wahlprogramm", "partei": "BiW", "bundesland": "HB", "wp": 21, "gueltig_ab": "2023-05-14", "gueltig_bis": None, "name": "BiW Bremen Wahlprogramm 2023", "titel": "BÜRGER IN WUT — Programm für die Bürgerschaftswahl 2023", "pdf": "biw-hb-2023.pdf", "seiten": 26},
"cdu-hb-2023": {"id": "cdu-hb-2023", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HB", "wp": 21, "gueltig_ab": "2023-05-14", "gueltig_bis": None, "name": "CDU Bremen Wahlprogramm 2023", "titel": "CDU Bremen Wahlprogramm Bürgerschaftswahl 2023", "pdf": "cdu-hb-2023.pdf", "seiten": 100},
"gruene-hb-2023": {"id": "gruene-hb-2023", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HB", "wp": 21, "gueltig_ab": "2023-05-14", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Bremen Wahlprogramm 2023", "titel": None, "pdf": "gruene-hb-2023.pdf", "seiten": 100},
"linke-hb-2023": {"id": "linke-hb-2023", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HB", "wp": 21, "gueltig_ab": "2023-05-14", "gueltig_bis": None, "name": "DIE LINKE Bremen Wahlprogramm 2023", "titel": "DIE LINKE Bremen Wahlprogramm Bürgerschaftswahl 2023", "pdf": "linke-hb-2023.pdf", "seiten": 100},
"spd-hb-2023": {"id": "spd-hb-2023", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HB", "wp": 21, "gueltig_ab": "2023-05-14", "gueltig_bis": None, "name": "SPD Bremen Wahlprogramm 2023", "titel": "SPD Bremen Wahlprogramm Bürgerschaftswahl 2023", "pdf": "spd-hb-2023.pdf", "seiten": 100},
# ─── wahlprogramm · HE ───
"cdu-he-2013": {"id": "cdu-he-2013", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HE", "wp": 19, "gueltig_ab": "2013-09-22", "gueltig_bis": "2019-01-18", "name": "CDU HE Wahlprogramm 2013", "titel": None, "pdf": "cdu-he-2013.pdf", "seiten": 72},
"fdp-he-2013": {"id": "fdp-he-2013", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HE", "wp": 19, "gueltig_ab": "2013-09-22", "gueltig_bis": "2019-01-18", "name": "FDP HE Wahlprogramm 2013", "titel": None, "pdf": "fdp-he-2013.pdf", "seiten": 197},
"gruene-he-2013": {"id": "gruene-he-2013", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HE", "wp": 19, "gueltig_ab": "2013-09-22", "gueltig_bis": "2019-01-18", "name": "Grüne HE Wahlprogramm 2013", "titel": None, "pdf": "gruene-he-2013.pdf", "seiten": 110},
"linke-he-2013": {"id": "linke-he-2013", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HE", "wp": 19, "gueltig_ab": "2013-09-22", "gueltig_bis": "2019-01-18", "name": "DIE LINKE HE Wahlprogramm 2013", "titel": None, "pdf": "linke-he-2013.pdf", "seiten": 60},
"spd-he-2013": {"id": "spd-he-2013", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HE", "wp": 19, "gueltig_ab": "2013-09-22", "gueltig_bis": "2019-01-18", "name": "SPD HE Wahlprogramm 2013", "titel": None, "pdf": "spd-he-2013.pdf", "seiten": 112},
"afd-he-2018": {"id": "afd-he-2018", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HE", "wp": 20, "gueltig_ab": "2018-10-28", "gueltig_bis": "2023-10-08", "name": "AfD HE Wahlprogramm 2018", "titel": None, "pdf": "afd-he-2018.pdf", "seiten": 90},
"cdu-he-2018": {"id": "cdu-he-2018", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HE", "wp": 20, "gueltig_ab": "2018-10-28", "gueltig_bis": "2023-10-08", "name": "CDU HE Wahlprogramm 2018", "titel": None, "pdf": "cdu-he-2018.pdf", "seiten": 132},
"fdp-he-2018": {"id": "fdp-he-2018", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HE", "wp": 20, "gueltig_ab": "2018-10-28", "gueltig_bis": "2023-10-08", "name": "FDP HE Wahlprogramm 2018", "titel": None, "pdf": "fdp-he-2018.pdf", "seiten": 112},
"gruene-he-2018": {"id": "gruene-he-2018", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HE", "wp": 20, "gueltig_ab": "2018-10-28", "gueltig_bis": "2023-10-08", "name": "Grüne HE Wahlprogramm 2018", "titel": None, "pdf": "gruene-he-2018.pdf", "seiten": 140},
"linke-he-2018": {"id": "linke-he-2018", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HE", "wp": 20, "gueltig_ab": "2018-10-28", "gueltig_bis": "2023-10-08", "name": "DIE LINKE HE Wahlprogramm 2018", "titel": None, "pdf": "linke-he-2018.pdf", "seiten": 90},
"spd-he-2018": {"id": "spd-he-2018", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HE", "wp": 20, "gueltig_ab": "2018-10-28", "gueltig_bis": "2023-10-08", "name": "SPD HE Wahlprogramm 2018", "titel": None, "pdf": "spd-he-2018.pdf", "seiten": 175},
"afd-he-2023": {"id": "afd-he-2023", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HE", "wp": 21, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "AfD Hessen Wahlprogramm 2023", "titel": None, "pdf": "afd-he-2023.pdf", "seiten": 100},
"cdu-he-2023": {"id": "cdu-he-2023", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HE", "wp": 21, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "CDU Hessen Wahlprogramm 2023", "titel": "CDU Hessen Regierungsprogramm 2023", "pdf": "cdu-he-2023.pdf", "seiten": 100},
"fdp-he-2023": {"id": "fdp-he-2023", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HE", "wp": 21, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "FDP Hessen Wahlprogramm 2023", "titel": None, "pdf": "fdp-he-2023.pdf", "seiten": 100},
"gruene-he-2023": {"id": "gruene-he-2023", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HE", "wp": 21, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Hessen Wahlprogramm 2023", "titel": None, "pdf": "gruene-he-2023.pdf", "seiten": 100},
"spd-he-2023": {"id": "spd-he-2023", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HE", "wp": 21, "gueltig_ab": "2023-10-08", "gueltig_bis": None, "name": "SPD Hessen Wahlprogramm 2023", "titel": None, "pdf": "spd-he-2023.pdf", "seiten": 100},
# ─── wahlprogramm · HH ───
"afd-hh-2015": {"id": "afd-hh-2015", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HH", "wp": 21, "gueltig_ab": "2015-02-15", "gueltig_bis": "2020-02-23", "name": "AfD HH Wahlprogramm 2015", "titel": None, "pdf": "afd-hh-2015.pdf", "seiten": 28},
"cdu-hh-2015": {"id": "cdu-hh-2015", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HH", "wp": 21, "gueltig_ab": "2015-02-15", "gueltig_bis": "2020-02-23", "name": "CDU HH Wahlprogramm 2015", "titel": None, "pdf": "cdu-hh-2015.pdf", "seiten": 60},
"fdp-hh-2015": {"id": "fdp-hh-2015", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HH", "wp": 21, "gueltig_ab": "2015-02-15", "gueltig_bis": "2020-02-23", "name": "FDP HH Wahlprogramm 2015", "titel": None, "pdf": "fdp-hh-2015.pdf", "seiten": 63},
"gruene-hh-2015": {"id": "gruene-hh-2015", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HH", "wp": 21, "gueltig_ab": "2015-02-15", "gueltig_bis": "2020-02-23", "name": "Grüne HH Wahlprogramm 2015", "titel": None, "pdf": "gruene-hh-2015.pdf", "seiten": 237},
"linke-hh-2015": {"id": "linke-hh-2015", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HH", "wp": 21, "gueltig_ab": "2015-02-15", "gueltig_bis": "2020-02-23", "name": "DIE LINKE HH Wahlprogramm 2015", "titel": None, "pdf": "linke-hh-2015.pdf", "seiten": 44},
"spd-hh-2015": {"id": "spd-hh-2015", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HH", "wp": 21, "gueltig_ab": "2015-02-15", "gueltig_bis": "2020-02-23", "name": "SPD HH Wahlprogramm 2015", "titel": None, "pdf": "spd-hh-2015.pdf", "seiten": 70},
"afd-hh-2020": {"id": "afd-hh-2020", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HH", "wp": 22, "gueltig_ab": "2020-02-23", "gueltig_bis": "2025-03-02", "name": "AfD HH Wahlprogramm 2020", "titel": None, "pdf": "afd-hh-2020.pdf", "seiten": 40},
"cdu-hh-2020": {"id": "cdu-hh-2020", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HH", "wp": 22, "gueltig_ab": "2020-02-23", "gueltig_bis": "2025-03-02", "name": "CDU HH Wahlprogramm 2020", "titel": None, "pdf": "cdu-hh-2020.pdf", "seiten": 84},
"fdp-hh-2020": {"id": "fdp-hh-2020", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "HH", "wp": 22, "gueltig_ab": "2020-02-23", "gueltig_bis": "2025-03-02", "name": "FDP HH Wahlprogramm 2020", "titel": None, "pdf": "fdp-hh-2020.pdf", "seiten": 97},
"gruene-hh-2020": {"id": "gruene-hh-2020", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HH", "wp": 22, "gueltig_ab": "2020-02-23", "gueltig_bis": "2025-03-02", "name": "Grüne HH Wahlprogramm 2020", "titel": None, "pdf": "gruene-hh-2020.pdf", "seiten": 146},
"linke-hh-2020": {"id": "linke-hh-2020", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HH", "wp": 22, "gueltig_ab": "2020-02-23", "gueltig_bis": "2025-03-02", "name": "DIE LINKE HH Wahlprogramm 2020", "titel": None, "pdf": "linke-hh-2020.pdf", "seiten": 37},
"spd-hh-2020": {"id": "spd-hh-2020", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HH", "wp": 22, "gueltig_ab": "2020-02-23", "gueltig_bis": "2025-03-02", "name": "SPD HH Wahlprogramm 2020", "titel": None, "pdf": "spd-hh-2020.pdf", "seiten": 90},
"afd-hh-2025": {"id": "afd-hh-2025", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "HH", "wp": 23, "gueltig_ab": "2025-03-02", "gueltig_bis": None, "name": "AfD Hamburg Wahlprogramm 2025", "titel": "AfD Hamburg Wahlprogramm Bürgerschaftswahl 2025", "pdf": "afd-hh-2025.pdf", "seiten": 100},
"cdu-hh-2025": {"id": "cdu-hh-2025", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "HH", "wp": 23, "gueltig_ab": "2025-03-02", "gueltig_bis": None, "name": "CDU Hamburg Wahlprogramm 2025", "titel": "CDU Hamburg Wahlprogramm Bürgerschaftswahl 2025", "pdf": "cdu-hh-2025.pdf", "seiten": 100},
"gruene-hh-2025": {"id": "gruene-hh-2025", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "HH", "wp": 23, "gueltig_ab": "2025-03-02", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Hamburg Wahlprogramm 2025", "titel": "Gute Gründe für Grün — Regierungsprogramm BÜNDNIS 90/DIE GRÜNEN Hamburg 2025", "pdf": "gruene-hh-2025.pdf", "seiten": 100},
"linke-hh-2025": {"id": "linke-hh-2025", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "HH", "wp": 23, "gueltig_ab": "2025-03-02", "gueltig_bis": None, "name": "DIE LINKE Hamburg Wahlprogramm 2025", "titel": "DIE LINKE Hamburg Wahlprogramm Bürgerschaftswahl 2025", "pdf": "linke-hh-2025.pdf", "seiten": 100},
"spd-hh-2025": {"id": "spd-hh-2025", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "HH", "wp": 23, "gueltig_ab": "2025-03-02", "gueltig_bis": None, "name": "SPD Hamburg Wahlprogramm 2025", "titel": "SPD Hamburg Wahlprogramm Bürgerschaftswahl 2025", "pdf": "spd-hh-2025.pdf", "seiten": 100},
# ─── wahlprogramm · LSA ───
"cdu-lsa-2011": {"id": "cdu-lsa-2011", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "LSA", "wp": 6, "gueltig_ab": "2011-03-20", "gueltig_bis": "2016-04-13", "name": "CDU LSA Wahlprogramm 2011", "titel": None, "pdf": "cdu-lsa-2011.pdf", "seiten": 66},
"gruene-lsa-2011": {"id": "gruene-lsa-2011", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "LSA", "wp": 6, "gueltig_ab": "2011-03-20", "gueltig_bis": "2016-04-13", "name": "Grüne LSA Wahlprogramm 2011", "titel": None, "pdf": "gruene-lsa-2011.pdf", "seiten": 94},
"linke-lsa-2011": {"id": "linke-lsa-2011", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "LSA", "wp": 6, "gueltig_ab": "2011-03-20", "gueltig_bis": "2016-04-13", "name": "DIE LINKE LSA Wahlprogramm 2011", "titel": None, "pdf": "linke-lsa-2011.pdf", "seiten": 36},
"spd-lsa-2011": {"id": "spd-lsa-2011", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "LSA", "wp": 6, "gueltig_ab": "2011-03-20", "gueltig_bis": "2016-04-13", "name": "SPD LSA Wahlprogramm 2011", "titel": None, "pdf": "spd-lsa-2011.pdf", "seiten": 38},
"afd-lsa-2016": {"id": "afd-lsa-2016", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "LSA", "wp": 7, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-06-06", "name": "AfD LSA Wahlprogramm 2016", "titel": None, "pdf": "afd-lsa-2016.pdf", "seiten": 68},
"cdu-lsa-2016": {"id": "cdu-lsa-2016", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "LSA", "wp": 7, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-06-06", "name": "CDU LSA Wahlprogramm 2016", "titel": None, "pdf": "cdu-lsa-2016.pdf", "seiten": 64},
"fdp-lsa-2016": {"id": "fdp-lsa-2016", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "LSA", "wp": 7, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-06-06", "name": "FDP LSA Wahlprogramm 2016", "titel": None, "pdf": "fdp-lsa-2016.pdf", "seiten": 51},
"gruene-lsa-2016": {"id": "gruene-lsa-2016", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "LSA", "wp": 7, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-06-06", "name": "Grüne LSA Wahlprogramm 2016", "titel": None, "pdf": "gruene-lsa-2016.pdf", "seiten": 129},
"linke-lsa-2016": {"id": "linke-lsa-2016", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "LSA", "wp": 7, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-06-06", "name": "DIE LINKE LSA Wahlprogramm 2016", "titel": None, "pdf": "linke-lsa-2016.pdf", "seiten": 59},
"spd-lsa-2016": {"id": "spd-lsa-2016", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "LSA", "wp": 7, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-06-06", "name": "SPD LSA Wahlprogramm 2016", "titel": None, "pdf": "spd-lsa-2016.pdf", "seiten": 72},
"afd-lsa-2021": {"id": "afd-lsa-2021", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "LSA", "wp": 8, "gueltig_ab": "2021-06-06", "gueltig_bis": None, "name": "AfD Sachsen-Anhalt Wahlprogramm 2021", "titel": "Alles für unsere Heimat! Programm der AfD Sachsen-Anhalt zur Landtagswahl 2021", "pdf": "afd-lsa-2021.pdf", "seiten": 64},
"cdu-lsa-2021": {"id": "cdu-lsa-2021", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "LSA", "wp": 8, "gueltig_ab": "2021-06-06", "gueltig_bis": None, "name": "CDU Sachsen-Anhalt Wahlprogramm 2021", "titel": "Unsere Heimat. Unsere Verantwortung.", "pdf": "cdu-lsa-2021.pdf", "seiten": 82},
"fdp-lsa-2021": {"id": "fdp-lsa-2021", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "LSA", "wp": 8, "gueltig_ab": "2021-06-06", "gueltig_bis": None, "name": "FDP Sachsen-Anhalt Wahlprogramm 2021", "titel": "Wahlprogramm der FDP Sachsen-Anhalt zur Landtagswahl 2021", "pdf": "fdp-lsa-2021.pdf", "seiten": 76},
"gruene-lsa-2021": {"id": "gruene-lsa-2021", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "LSA", "wp": 8, "gueltig_ab": "2021-06-06", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Sachsen-Anhalt Wahlprogramm 2021", "titel": "Verlässlich für Sachsen-Anhalt", "pdf": "gruene-lsa-2021.pdf", "seiten": 164},
"linke-lsa-2021": {"id": "linke-lsa-2021", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "LSA", "wp": 8, "gueltig_ab": "2021-06-06", "gueltig_bis": None, "name": "DIE LINKE Sachsen-Anhalt Wahlprogramm 2021", "titel": "Wahlprogramm zur Landtagswahl 2021", "pdf": "linke-lsa-2021.pdf", "seiten": 88},
"spd-lsa-2021": {"id": "spd-lsa-2021", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "LSA", "wp": 8, "gueltig_ab": "2021-06-06", "gueltig_bis": None, "name": "SPD Sachsen-Anhalt Wahlprogramm 2021", "titel": "Zusammenhalt und neue Chancen. Politik fürs ganze Land", "pdf": "spd-lsa-2021.pdf", "seiten": 77},
# ─── wahlprogramm · MV ───
"cdu-mv-2011": {"id": "cdu-mv-2011", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "MV", "wp": 6, "gueltig_ab": "2011-09-04", "gueltig_bis": "2016-10-04", "name": "CDU MV Wahlprogramm 2011", "titel": None, "pdf": "cdu-mv-2011.pdf", "seiten": 34},
"gruene-mv-2011": {"id": "gruene-mv-2011", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "MV", "wp": 6, "gueltig_ab": "2011-09-04", "gueltig_bis": "2016-10-04", "name": "Grüne MV Wahlprogramm 2011", "titel": None, "pdf": "gruene-mv-2011.pdf", "seiten": 144},
"linke-mv-2011": {"id": "linke-mv-2011", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "MV", "wp": 6, "gueltig_ab": "2011-09-04", "gueltig_bis": "2016-10-04", "name": "DIE LINKE MV Wahlprogramm 2011", "titel": None, "pdf": "linke-mv-2011.pdf", "seiten": 33},
"spd-mv-2011": {"id": "spd-mv-2011", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "MV", "wp": 6, "gueltig_ab": "2011-09-04", "gueltig_bis": "2016-10-04", "name": "SPD MV Wahlprogramm 2011", "titel": None, "pdf": "spd-mv-2011.pdf", "seiten": 50},
"afd-mv-2016": {"id": "afd-mv-2016", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "MV", "wp": 7, "gueltig_ab": "2016-09-04", "gueltig_bis": "2021-09-26", "name": "AfD MV Wahlprogramm 2016", "titel": None, "pdf": "afd-mv-2016.pdf", "seiten": 22},
"cdu-mv-2016": {"id": "cdu-mv-2016", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "MV", "wp": 7, "gueltig_ab": "2016-09-04", "gueltig_bis": "2021-09-26", "name": "CDU MV Wahlprogramm 2016", "titel": None, "pdf": "cdu-mv-2016.pdf", "seiten": 27},
"gruene-mv-2016": {"id": "gruene-mv-2016", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "MV", "wp": 7, "gueltig_ab": "2016-09-04", "gueltig_bis": "2021-09-26", "name": "Grüne MV Wahlprogramm 2016", "titel": None, "pdf": "gruene-mv-2016.pdf", "seiten": 100},
"linke-mv-2016": {"id": "linke-mv-2016", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "MV", "wp": 7, "gueltig_ab": "2016-09-04", "gueltig_bis": "2021-09-26", "name": "DIE LINKE MV Wahlprogramm 2016", "titel": None, "pdf": "linke-mv-2016.pdf", "seiten": 49},
"spd-mv-2016": {"id": "spd-mv-2016", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "MV", "wp": 7, "gueltig_ab": "2016-09-04", "gueltig_bis": "2021-09-26", "name": "SPD MV Wahlprogramm 2016", "titel": None, "pdf": "spd-mv-2016.pdf", "seiten": 48},
"afd-mv-2021": {"id": "afd-mv-2021", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "MV", "wp": 8, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "AfD Mecklenburg-Vorpommern Wahlprogramm 2021", "titel": "Landeswahlprogramm der AfD Mecklenburg-Vorpommern 2021", "pdf": "afd-mv-2021.pdf", "seiten": 84},
"cdu-mv-2021": {"id": "cdu-mv-2021", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "MV", "wp": 8, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "CDU Mecklenburg-Vorpommern Wahlprogramm 2021", "titel": "Zusammen. Den Blick nach vorn. Gemeinsam die Zukunft meistern", "pdf": "cdu-mv-2021.pdf", "seiten": 56},
"fdp-mv-2021": {"id": "fdp-mv-2021", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "MV", "wp": 8, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "FDP Mecklenburg-Vorpommern Wahlprogramm 2021", "titel": "Wahlprogramm der Freien Demokraten Mecklenburg-Vorpommern zur Landtagswahl 2021", "pdf": "fdp-mv-2021.pdf", "seiten": 120},
"gruene-mv-2021": {"id": "gruene-mv-2021", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "MV", "wp": 8, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Mecklenburg-Vorpommern Wahlprogramm 2021", "titel": "Für Klima, Land und ein besseres Miteinander", "pdf": "gruene-mv-2021.pdf", "seiten": 88},
"linke-mv-2021": {"id": "linke-mv-2021", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "MV", "wp": 8, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "DIE LINKE Mecklenburg-Vorpommern Wahlprogramm 2021", "titel": "Das ist links! — Zukunftsprogramm für Mecklenburg-Vorpommern", "pdf": "linke-mv-2021.pdf", "seiten": 82},
"spd-mv-2021": {"id": "spd-mv-2021", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "MV", "wp": 8, "gueltig_ab": "2021-09-26", "gueltig_bis": None, "name": "SPD Mecklenburg-Vorpommern Wahlprogramm 2021", "titel": "Verantwortung für heute und morgen — Regierungsprogramm 20212026", "pdf": "spd-mv-2021.pdf", "seiten": 95},
# ─── wahlprogramm · NI ───
"cdu-ni-2013": {"id": "cdu-ni-2013", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "NI", "wp": 17, "gueltig_ab": "2013-01-20", "gueltig_bis": "2017-11-14", "name": "CDU NI Wahlprogramm 2013", "titel": None, "pdf": "cdu-ni-2013.pdf", "seiten": 86},
"fdp-ni-2013": {"id": "fdp-ni-2013", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "NI", "wp": 17, "gueltig_ab": "2013-01-20", "gueltig_bis": "2017-11-14", "name": "FDP NI Wahlprogramm 2013", "titel": None, "pdf": "fdp-ni-2013.pdf", "seiten": 65},
"gruene-ni-2013": {"id": "gruene-ni-2013", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "NI", "wp": 17, "gueltig_ab": "2013-01-20", "gueltig_bis": "2017-11-14", "name": "Grüne NI Wahlprogramm 2013", "titel": None, "pdf": "gruene-ni-2013.pdf", "seiten": 92},
"spd-ni-2013": {"id": "spd-ni-2013", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "NI", "wp": 17, "gueltig_ab": "2013-01-20", "gueltig_bis": "2017-11-14", "name": "SPD NI Wahlprogramm 2013", "titel": None, "pdf": "spd-ni-2013.pdf", "seiten": 70},
"afd-ni-2017": {"id": "afd-ni-2017", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "NI", "wp": 18, "gueltig_ab": "2017-10-15", "gueltig_bis": "2022-10-09", "name": "AfD NI Wahlprogramm 2017", "titel": None, "pdf": "afd-ni-2017.pdf", "seiten": 93},
"cdu-ni-2017": {"id": "cdu-ni-2017", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "NI", "wp": 18, "gueltig_ab": "2017-10-15", "gueltig_bis": "2022-10-09", "name": "CDU NI Wahlprogramm 2017", "titel": None, "pdf": "cdu-ni-2017.pdf", "seiten": 116},
"fdp-ni-2017": {"id": "fdp-ni-2017", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "NI", "wp": 18, "gueltig_ab": "2017-10-15", "gueltig_bis": "2022-10-09", "name": "FDP NI Wahlprogramm 2017", "titel": None, "pdf": "fdp-ni-2017.pdf", "seiten": 74},
"gruene-ni-2017": {"id": "gruene-ni-2017", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "NI", "wp": 18, "gueltig_ab": "2017-10-15", "gueltig_bis": "2022-10-09", "name": "Grüne NI Wahlprogramm 2017", "titel": None, "pdf": "gruene-ni-2017.pdf", "seiten": 101},
"spd-ni-2017": {"id": "spd-ni-2017", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "NI", "wp": 18, "gueltig_ab": "2017-10-15", "gueltig_bis": "2022-10-09", "name": "SPD NI Wahlprogramm 2017", "titel": None, "pdf": "spd-ni-2017.pdf", "seiten": 154},
"afd-ni-2022": {"id": "afd-ni-2022", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "NI", "wp": 19, "gueltig_ab": "2022-10-09", "gueltig_bis": None, "name": "AfD Niedersachsen Wahlprogramm 2022", "titel": None, "pdf": "afd-ni-2022.pdf", "seiten": 100},
"cdu-ni-2022": {"id": "cdu-ni-2022", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "NI", "wp": 19, "gueltig_ab": "2022-10-09", "gueltig_bis": None, "name": "CDU Niedersachsen Wahlprogramm 2022", "titel": "CDU Niedersachsen Regierungsprogramm 2022", "pdf": "cdu-ni-2022.pdf", "seiten": 100},
"gruene-ni-2022": {"id": "gruene-ni-2022", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "NI", "wp": 19, "gueltig_ab": "2022-10-09", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Niedersachsen Wahlprogramm 2022", "titel": None, "pdf": "gruene-ni-2022.pdf", "seiten": 100},
"spd-ni-2022": {"id": "spd-ni-2022", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "NI", "wp": 19, "gueltig_ab": "2022-10-09", "gueltig_bis": None, "name": "SPD Niedersachsen Wahlprogramm 2022", "titel": "SPD Niedersachsen Regierungsprogramm 2022", "pdf": "spd-ni-2022.pdf", "seiten": 100},
# ─── wahlprogramm · NRW ───
"cdu-nrw-2012": {"id": "cdu-nrw-2012", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "NRW", "wp": 16, "gueltig_ab": "2012-05-13", "gueltig_bis": "2017-06-01", "name": "CDU NRW Wahlprogramm 2012", "titel": None, "pdf": "cdu-nrw-2012.pdf", "seiten": 17},
"fdp-nrw-2012": {"id": "fdp-nrw-2012", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "NRW", "wp": 16, "gueltig_ab": "2012-05-13", "gueltig_bis": "2017-06-01", "name": "FDP NRW Wahlprogramm 2012", "titel": None, "pdf": "fdp-nrw-2012.pdf", "seiten": 6},
"gruene-nrw-2012": {"id": "gruene-nrw-2012", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "NRW", "wp": 16, "gueltig_ab": "2012-05-13", "gueltig_bis": "2017-06-01", "name": "Grüne NRW Zukunftsplan Update 2012", "titel": None, "pdf": "gruene-nrw-2012.pdf", "seiten": 52},
"piraten-nrw-2012": {"id": "piraten-nrw-2012", "typ": "wahlprogramm", "partei": "PIRATEN", "bundesland": "NRW", "wp": 16, "gueltig_ab": "2012-05-13", "gueltig_bis": "2017-06-01", "name": "PIRATEN NRW Wahlprogramm 2012", "titel": None, "pdf": "piraten-nrw-2012.pdf", "seiten": 76},
"spd-nrw-2012": {"id": "spd-nrw-2012", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "NRW", "wp": 16, "gueltig_ab": "2012-05-13", "gueltig_bis": "2017-06-01", "name": "SPD NRW Wahlprogramm 2012", "titel": None, "pdf": "spd-nrw-2012.pdf", "seiten": 22},
"afd-nrw-2017": {"id": "afd-nrw-2017", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "NRW", "wp": 17, "gueltig_ab": "2017-05-14", "gueltig_bis": "2022-05-15", "name": "AfD NRW Wahlprogramm 2017", "titel": None, "pdf": "afd-nrw-2017.pdf", "seiten": 84},
"cdu-nrw-2017": {"id": "cdu-nrw-2017", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "NRW", "wp": 17, "gueltig_ab": "2017-05-14", "gueltig_bis": "2022-05-15", "name": "CDU NRW Regierungsprogramm 2017", "titel": None, "pdf": "cdu-nrw-2017.pdf", "seiten": 120},
"fdp-nrw-2017": {"id": "fdp-nrw-2017", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "NRW", "wp": 17, "gueltig_ab": "2017-05-14", "gueltig_bis": "2022-05-15", "name": "FDP NRW Wahlprogramm 2017", "titel": None, "pdf": "fdp-nrw-2017.pdf", "seiten": 56},
"gruene-nrw-2017": {"id": "gruene-nrw-2017", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "NRW", "wp": 17, "gueltig_ab": "2017-05-14", "gueltig_bis": "2022-05-15", "name": "Grüne NRW Wahlprogramm 2017", "titel": None, "pdf": "gruene-nrw-2017.pdf", "seiten": 131},
"spd-nrw-2017": {"id": "spd-nrw-2017", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "NRW", "wp": 17, "gueltig_ab": "2017-05-14", "gueltig_bis": "2022-05-15", "name": "SPD NRW Regierungsprogramm 2017", "titel": None, "pdf": "spd-nrw-2017.pdf", "seiten": 116},
"afd-nrw-2022": {"id": "afd-nrw-2022", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "NRW", "wp": 18, "gueltig_ab": "2022-05-15", "gueltig_bis": None, "name": "AfD NRW Wahlprogramm 2022", "titel": "Wer sonst.", "pdf": "afd-nrw-2022.pdf", "seiten": 68},
"cdu-nrw-2022": {"id": "cdu-nrw-2022", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "NRW", "wp": 18, "gueltig_ab": "2022-05-15", "gueltig_bis": None, "name": "CDU NRW Wahlprogramm 2022", "titel": "Machen, worauf es ankommt", "pdf": "cdu-nrw-2022.pdf", "seiten": 109},
"fdp-nrw-2022": {"id": "fdp-nrw-2022", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "NRW", "wp": 18, "gueltig_ab": "2022-05-15", "gueltig_bis": None, "name": "FDP NRW Wahlprogramm 2022", "titel": "Nie gab es mehr zu tun", "pdf": "fdp-nrw-2022.pdf", "seiten": 96},
"gruene-nrw-2022": {"id": "gruene-nrw-2022", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "NRW", "wp": 18, "gueltig_ab": "2022-05-15", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN NRW Wahlprogramm 2022", "titel": "Von hier an Zukunft", "pdf": "gruene-nrw-2022.pdf", "seiten": 100},
"spd-nrw-2022": {"id": "spd-nrw-2022", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "NRW", "wp": 18, "gueltig_ab": "2022-05-15", "gueltig_bis": None, "name": "SPD NRW Wahlprogramm 2022", "titel": "Unser Land von morgen", "pdf": "spd-nrw-2022.pdf", "seiten": 116},
# ─── wahlprogramm · RP ───
"cdu-rp-2011": {"id": "cdu-rp-2011", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "RP", "wp": 16, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-05-18", "name": "CDU RP Wahlprogramm 2011", "titel": None, "pdf": "cdu-rp-2011.pdf", "seiten": 84},
"gruene-rp-2011": {"id": "gruene-rp-2011", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "RP", "wp": 16, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-05-18", "name": "Grüne RP Wahlprogramm 2011", "titel": None, "pdf": "gruene-rp-2011.pdf", "seiten": 110},
"spd-rp-2011": {"id": "spd-rp-2011", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "RP", "wp": 16, "gueltig_ab": "2011-03-27", "gueltig_bis": "2016-05-18", "name": "SPD RP Wahlprogramm 2011", "titel": None, "pdf": "spd-rp-2011.pdf", "seiten": 65},
"afd-rp-2016": {"id": "afd-rp-2016", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "RP", "wp": 17, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "AfD RP Wahlprogramm 2016", "titel": None, "pdf": "afd-rp-2016.pdf", "seiten": 17},
"cdu-rp-2016": {"id": "cdu-rp-2016", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "RP", "wp": 17, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "CDU RP Wahlprogramm 2016", "titel": None, "pdf": "cdu-rp-2016.pdf", "seiten": 102},
"fdp-rp-2016": {"id": "fdp-rp-2016", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "RP", "wp": 17, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "FDP RP Wahlprogramm 2016", "titel": None, "pdf": "fdp-rp-2016.pdf", "seiten": 84},
"gruene-rp-2016": {"id": "gruene-rp-2016", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "RP", "wp": 17, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "Grüne RP Wahlprogramm 2016", "titel": None, "pdf": "gruene-rp-2016.pdf", "seiten": 57},
"spd-rp-2016": {"id": "spd-rp-2016", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "RP", "wp": 17, "gueltig_ab": "2016-03-13", "gueltig_bis": "2021-03-14", "name": "SPD RP Wahlprogramm 2016", "titel": None, "pdf": "spd-rp-2016.pdf", "seiten": 64},
"afd-rp-2021": {"id": "afd-rp-2021", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "RP", "wp": 18, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "AfD Rheinland-Pfalz Wahlprogramm 2021", "titel": None, "pdf": "afd-rp-2021.pdf", "seiten": 100},
"cdu-rp-2021": {"id": "cdu-rp-2021", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "RP", "wp": 18, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "CDU Rheinland-Pfalz Wahlprogramm 2021", "titel": "Regierungsprogramm der CDU RLP 202126", "pdf": "cdu-rp-2021.pdf", "seiten": 100},
"fdp-rp-2021": {"id": "fdp-rp-2021", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "RP", "wp": 18, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "FDP Rheinland-Pfalz Wahlprogramm 2021", "titel": "FDP Rheinland-Pfalz Landtagswahlprogramm 2021", "pdf": "fdp-rp-2021.pdf", "seiten": 100},
"fw-rp-2021": {"id": "fw-rp-2021", "typ": "wahlprogramm", "partei": "FREIE WÄHLER", "bundesland": "RP", "wp": 18, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "FREIE WÄHLER Rheinland-Pfalz Wahlprogramm 2021", "titel": None, "pdf": "fw-rp-2021.pdf", "seiten": 80},
"gruene-rp-2021": {"id": "gruene-rp-2021", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "RP", "wp": 18, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Rheinland-Pfalz Wahlprogramm 2021", "titel": "BÜNDNIS 90/DIE GRÜNEN Rheinland-Pfalz Landtagswahlprogramm 2021", "pdf": "gruene-rp-2021.pdf", "seiten": 100},
"spd-rp-2021": {"id": "spd-rp-2021", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "RP", "wp": 18, "gueltig_ab": "2021-03-14", "gueltig_bis": None, "name": "SPD Rheinland-Pfalz Wahlprogramm 2021", "titel": "Wir mit Ihr — Regierungsprogramm der SPD Rheinland-Pfalz 20212026", "pdf": "spd-rp-2021.pdf", "seiten": 100},
# ─── wahlprogramm · SH ───
"cdu-sh-2012": {"id": "cdu-sh-2012", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SH", "wp": 18, "gueltig_ab": "2012-05-06", "gueltig_bis": "2017-06-06", "name": "CDU SH Wahlprogramm 2012", "titel": None, "pdf": "cdu-sh-2012.pdf", "seiten": 150},
"fdp-sh-2012": {"id": "fdp-sh-2012", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "SH", "wp": 18, "gueltig_ab": "2012-05-06", "gueltig_bis": "2017-06-06", "name": "FDP SH Wahlprogramm 2012", "titel": None, "pdf": "fdp-sh-2012.pdf", "seiten": 99},
"gruene-sh-2012": {"id": "gruene-sh-2012", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SH", "wp": 18, "gueltig_ab": "2012-05-06", "gueltig_bis": "2017-06-06", "name": "Grüne SH Wahlprogramm 2012", "titel": None, "pdf": "gruene-sh-2012.pdf", "seiten": 80},
"piraten-sh-2012": {"id": "piraten-sh-2012", "typ": "wahlprogramm", "partei": "PIRATEN", "bundesland": "SH", "wp": 18, "gueltig_ab": "2012-05-06", "gueltig_bis": "2017-06-06", "name": "PIRATEN SH Wahlprogramm 2012", "titel": None, "pdf": "piraten-sh-2012.pdf", "seiten": 80},
"spd-sh-2012": {"id": "spd-sh-2012", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SH", "wp": 18, "gueltig_ab": "2012-05-06", "gueltig_bis": "2017-06-06", "name": "SPD SH Wahlprogramm 2012", "titel": None, "pdf": "spd-sh-2012.pdf", "seiten": 36},
"ssw-sh-2012": {"id": "ssw-sh-2012", "typ": "wahlprogramm", "partei": "SSW", "bundesland": "SH", "wp": 18, "gueltig_ab": "2012-05-06", "gueltig_bis": "2017-06-06", "name": "SSW SH Wahlprogramm 2012", "titel": None, "pdf": "ssw-sh-2012.pdf", "seiten": 84},
"afd-sh-2017": {"id": "afd-sh-2017", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "SH", "wp": 19, "gueltig_ab": "2017-05-07", "gueltig_bis": "2022-05-08", "name": "AfD SH Wahlprogramm 2017", "titel": None, "pdf": "afd-sh-2017.pdf", "seiten": 56},
"cdu-sh-2017": {"id": "cdu-sh-2017", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SH", "wp": 19, "gueltig_ab": "2017-05-07", "gueltig_bis": "2022-05-08", "name": "CDU SH Wahlprogramm 2017", "titel": None, "pdf": "cdu-sh-2017.pdf", "seiten": 96},
"fdp-sh-2017": {"id": "fdp-sh-2017", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "SH", "wp": 19, "gueltig_ab": "2017-05-07", "gueltig_bis": "2022-05-08", "name": "FDP SH Wahlprogramm 2017", "titel": None, "pdf": "fdp-sh-2017.pdf", "seiten": 117},
"gruene-sh-2017": {"id": "gruene-sh-2017", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SH", "wp": 19, "gueltig_ab": "2017-05-07", "gueltig_bis": "2022-05-08", "name": "Grüne SH Wahlprogramm 2017", "titel": None, "pdf": "gruene-sh-2017.pdf", "seiten": 95},
"spd-sh-2017": {"id": "spd-sh-2017", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SH", "wp": 19, "gueltig_ab": "2017-05-07", "gueltig_bis": "2022-05-08", "name": "SPD SH Wahlprogramm 2017", "titel": None, "pdf": "spd-sh-2017.pdf", "seiten": 66},
"ssw-sh-2017": {"id": "ssw-sh-2017", "typ": "wahlprogramm", "partei": "SSW", "bundesland": "SH", "wp": 19, "gueltig_ab": "2017-05-07", "gueltig_bis": "2022-05-08", "name": "SSW SH Wahlprogramm 2017", "titel": None, "pdf": "ssw-sh-2017.pdf", "seiten": 60},
"cdu-sh-2022": {"id": "cdu-sh-2022", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SH", "wp": 20, "gueltig_ab": "2022-05-08", "gueltig_bis": None, "name": "CDU Schleswig-Holstein Wahlprogramm 2022", "titel": None, "pdf": "cdu-sh-2022.pdf", "seiten": 100},
"fdp-sh-2022": {"id": "fdp-sh-2022", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "SH", "wp": 20, "gueltig_ab": "2022-05-08", "gueltig_bis": None, "name": "FDP Schleswig-Holstein Wahlprogramm 2022", "titel": None, "pdf": "fdp-sh-2022.pdf", "seiten": 100},
"gruene-sh-2022": {"id": "gruene-sh-2022", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SH", "wp": 20, "gueltig_ab": "2022-05-08", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Schleswig-Holstein Wahlprogramm 2022", "titel": None, "pdf": "gruene-sh-2022.pdf", "seiten": 100},
"spd-sh-2022": {"id": "spd-sh-2022", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SH", "wp": 20, "gueltig_ab": "2022-05-08", "gueltig_bis": None, "name": "SPD Schleswig-Holstein Wahlprogramm 2022", "titel": None, "pdf": "spd-sh-2022.pdf", "seiten": 100},
"ssw-sh-2022": {"id": "ssw-sh-2022", "typ": "wahlprogramm", "partei": "SSW", "bundesland": "SH", "wp": 20, "gueltig_ab": "2022-05-08", "gueltig_bis": None, "name": "SSW Wahlprogramm 2022", "titel": "SSW Schleswig-Holstein Wahlprogramm 2022", "pdf": "ssw-sh-2022.pdf", "seiten": 80},
# ─── wahlprogramm · SL ───
"cdu-sl-2012": {"id": "cdu-sl-2012", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SL", "wp": 15, "gueltig_ab": "2012-03-25", "gueltig_bis": "2017-05-09", "name": "CDU SL Wahlprogramm 2012", "titel": None, "pdf": "cdu-sl-2012.pdf", "seiten": 36},
"linke-sl-2012": {"id": "linke-sl-2012", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "SL", "wp": 15, "gueltig_ab": "2012-03-25", "gueltig_bis": "2017-05-09", "name": "DIE LINKE SL Wahlprogramm 2012", "titel": None, "pdf": "linke-sl-2012.pdf", "seiten": 2},
"piraten-sl-2012": {"id": "piraten-sl-2012", "typ": "wahlprogramm", "partei": "PIRATEN", "bundesland": "SL", "wp": 15, "gueltig_ab": "2012-03-25", "gueltig_bis": "2017-05-09", "name": "PIRATEN SL Wahlprogramm 2012", "titel": None, "pdf": "piraten-sl-2012.pdf", "seiten": 21},
"spd-sl-2012": {"id": "spd-sl-2012", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SL", "wp": 15, "gueltig_ab": "2012-03-25", "gueltig_bis": "2017-05-09", "name": "SPD SL Wahlprogramm 2012", "titel": None, "pdf": "spd-sl-2012.pdf", "seiten": 40},
"afd-sl-2017": {"id": "afd-sl-2017", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "SL", "wp": 16, "gueltig_ab": "2017-03-26", "gueltig_bis": "2022-03-27", "name": "AfD SL Wahlprogramm 2017", "titel": None, "pdf": "afd-sl-2017.pdf", "seiten": 43},
"cdu-sl-2017": {"id": "cdu-sl-2017", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SL", "wp": 16, "gueltig_ab": "2017-03-26", "gueltig_bis": "2022-03-27", "name": "CDU SL Wahlprogramm 2017", "titel": None, "pdf": "cdu-sl-2017.pdf", "seiten": 72},
"gruene-sl-2017": {"id": "gruene-sl-2017", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SL", "wp": 16, "gueltig_ab": "2017-03-26", "gueltig_bis": "2022-03-27", "name": "Grüne SL Wahlprogramm 2017", "titel": None, "pdf": "gruene-sl-2017.pdf", "seiten": 75},
"linke-sl-2017": {"id": "linke-sl-2017", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "SL", "wp": 16, "gueltig_ab": "2017-03-26", "gueltig_bis": "2022-03-27", "name": "DIE LINKE SL Wahlprogramm 2017", "titel": None, "pdf": "linke-sl-2017.pdf", "seiten": 34},
"spd-sl-2017": {"id": "spd-sl-2017", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SL", "wp": 16, "gueltig_ab": "2017-03-26", "gueltig_bis": "2022-03-27", "name": "SPD SL Wahlprogramm 2017", "titel": None, "pdf": "spd-sl-2017.pdf", "seiten": 41},
"afd-sl-2022": {"id": "afd-sl-2022", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "SL", "wp": 17, "gueltig_ab": "2022-03-27", "gueltig_bis": None, "name": "AfD Saarland Wahlprogramm 2022", "titel": None, "pdf": "afd-sl-2022.pdf", "seiten": 100},
"cdu-sl-2022": {"id": "cdu-sl-2022", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SL", "wp": 17, "gueltig_ab": "2022-03-27", "gueltig_bis": None, "name": "CDU Saarland Wahlprogramm 2022", "titel": None, "pdf": "cdu-sl-2022.pdf", "seiten": 100},
"spd-sl-2022": {"id": "spd-sl-2022", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SL", "wp": 17, "gueltig_ab": "2022-03-27", "gueltig_bis": None, "name": "SPD Saarland Wahlprogramm 2022", "titel": "SPD Saarland Regierungsprogramm 2022", "pdf": "spd-sl-2022.pdf", "seiten": 100},
# ─── wahlprogramm · SN ───
"afd-sn-2014": {"id": "afd-sn-2014", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "SN", "wp": 6, "gueltig_ab": "2014-08-31", "gueltig_bis": "2019-09-01", "name": "AfD SN Wahlprogramm 2014", "titel": None, "pdf": "afd-sn-2014.pdf", "seiten": 26},
"cdu-sn-2014": {"id": "cdu-sn-2014", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SN", "wp": 6, "gueltig_ab": "2014-08-31", "gueltig_bis": "2019-09-01", "name": "CDU SN Wahlprogramm 2014", "titel": None, "pdf": "cdu-sn-2014.pdf", "seiten": 86},
"gruene-sn-2014": {"id": "gruene-sn-2014", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SN", "wp": 6, "gueltig_ab": "2014-08-31", "gueltig_bis": "2019-09-01", "name": "Grüne SN Wahlprogramm 2014", "titel": None, "pdf": "gruene-sn-2014.pdf", "seiten": 149},
"linke-sn-2014": {"id": "linke-sn-2014", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "SN", "wp": 6, "gueltig_ab": "2014-08-31", "gueltig_bis": "2019-09-01", "name": "DIE LINKE SN Wahlprogramm 2014", "titel": None, "pdf": "linke-sn-2014.pdf", "seiten": 56},
"spd-sn-2014": {"id": "spd-sn-2014", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SN", "wp": 6, "gueltig_ab": "2014-08-31", "gueltig_bis": "2019-09-01", "name": "SPD SN Wahlprogramm 2014", "titel": None, "pdf": "spd-sn-2014.pdf", "seiten": 56},
"afd-sn-2019": {"id": "afd-sn-2019", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "SN", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-01", "name": "AfD SN Wahlprogramm 2019", "titel": None, "pdf": "afd-sn-2019.pdf", "seiten": 76},
"cdu-sn-2019": {"id": "cdu-sn-2019", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SN", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-01", "name": "CDU SN Wahlprogramm 2019", "titel": None, "pdf": "cdu-sn-2019.pdf", "seiten": 71},
"gruene-sn-2019": {"id": "gruene-sn-2019", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SN", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-01", "name": "Grüne SN Wahlprogramm 2019", "titel": None, "pdf": "gruene-sn-2019.pdf", "seiten": 86},
"linke-sn-2019": {"id": "linke-sn-2019", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "SN", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-01", "name": "DIE LINKE SN Wahlprogramm 2019", "titel": None, "pdf": "linke-sn-2019.pdf", "seiten": 72},
"spd-sn-2019": {"id": "spd-sn-2019", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SN", "wp": 7, "gueltig_ab": "2019-09-01", "gueltig_bis": "2024-09-01", "name": "SPD SN Wahlprogramm 2019", "titel": None, "pdf": "spd-sn-2019.pdf", "seiten": 105},
"afd-sn-2024": {"id": "afd-sn-2024", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "SN", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "AfD Sachsen Wahlprogramm 2024", "titel": None, "pdf": "afd-sn-2024.pdf", "seiten": 100},
"bsw-sn-2024": {"id": "bsw-sn-2024", "typ": "wahlprogramm", "partei": "BSW", "bundesland": "SN", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "BSW Sachsen Wahlprogramm 2024", "titel": None, "pdf": "bsw-sn-2024.pdf", "seiten": 50},
"cdu-sn-2024": {"id": "cdu-sn-2024", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "SN", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "CDU Sachsen Wahlprogramm 2024", "titel": None, "pdf": "cdu-sn-2024.pdf", "seiten": 100},
"gruene-sn-2024": {"id": "gruene-sn-2024", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SN", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "BÜNDNIS 90/DIE GRÜNEN Sachsen Wahlprogramm 2024", "titel": None, "pdf": "gruene-sn-2024.pdf", "seiten": 100},
"linke-sn-2024": {"id": "linke-sn-2024", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "SN", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "DIE LINKE Sachsen Wahlprogramm 2024", "titel": None, "pdf": "linke-sn-2024.pdf", "seiten": 100},
"spd-sn-2024": {"id": "spd-sn-2024", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SN", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "SPD Sachsen Wahlprogramm 2024", "titel": None, "pdf": "spd-sn-2024.pdf", "seiten": 100},
# ─── wahlprogramm · TH ───
"afd-th-2014": {"id": "afd-th-2014", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "TH", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-10-27", "name": "AfD TH Wahlprogramm 2014", "titel": None, "pdf": "afd-th-2014.pdf", "seiten": 32},
"cdu-th-2014": {"id": "cdu-th-2014", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "TH", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-10-27", "name": "CDU TH Wahlprogramm 2014", "titel": None, "pdf": "cdu-th-2014.pdf", "seiten": 82},
"fdp-th-2014": {"id": "fdp-th-2014", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "TH", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-10-27", "name": "FDP TH Wahlprogramm 2014", "titel": None, "pdf": "fdp-th-2014.pdf", "seiten": 77},
"gruene-th-2014": {"id": "gruene-th-2014", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "TH", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-10-27", "name": "Grüne TH Wahlprogramm 2014", "titel": None, "pdf": "gruene-th-2014.pdf", "seiten": 84},
"linke-th-2014": {"id": "linke-th-2014", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "TH", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-10-27", "name": "DIE LINKE TH Wahlprogramm 2014", "titel": None, "pdf": "linke-th-2014.pdf", "seiten": 60},
"spd-th-2014": {"id": "spd-th-2014", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "TH", "wp": 6, "gueltig_ab": "2014-09-14", "gueltig_bis": "2019-10-27", "name": "SPD TH Wahlprogramm 2014", "titel": None, "pdf": "spd-th-2014.pdf", "seiten": 52},
"afd-th-2019": {"id": "afd-th-2019", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "TH", "wp": 7, "gueltig_ab": "2019-10-27", "gueltig_bis": "2024-09-01", "name": "AfD TH Wahlprogramm 2019", "titel": None, "pdf": "afd-th-2019.pdf", "seiten": 96},
"cdu-th-2019": {"id": "cdu-th-2019", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "TH", "wp": 7, "gueltig_ab": "2019-10-27", "gueltig_bis": "2024-09-01", "name": "CDU TH Wahlprogramm 2019", "titel": None, "pdf": "cdu-th-2019.pdf", "seiten": 48},
"fdp-th-2019": {"id": "fdp-th-2019", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "TH", "wp": 7, "gueltig_ab": "2019-10-27", "gueltig_bis": "2024-09-01", "name": "FDP TH Wahlprogramm 2019", "titel": None, "pdf": "fdp-th-2019.pdf", "seiten": 86},
"gruene-th-2019": {"id": "gruene-th-2019", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "TH", "wp": 7, "gueltig_ab": "2019-10-27", "gueltig_bis": "2024-09-01", "name": "Grüne TH Wahlprogramm 2019", "titel": None, "pdf": "gruene-th-2019.pdf", "seiten": 83},
"linke-th-2019": {"id": "linke-th-2019", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "TH", "wp": 7, "gueltig_ab": "2019-10-27", "gueltig_bis": "2024-09-01", "name": "DIE LINKE TH Wahlprogramm 2019", "titel": None, "pdf": "linke-th-2019.pdf", "seiten": 105},
"spd-th-2019": {"id": "spd-th-2019", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "TH", "wp": 7, "gueltig_ab": "2019-10-27", "gueltig_bis": "2024-09-01", "name": "SPD TH Wahlprogramm 2019", "titel": None, "pdf": "spd-th-2019.pdf", "seiten": 32},
"afd-th-2024": {"id": "afd-th-2024", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "TH", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "AfD Thüringen Wahlprogramm 2024", "titel": "AfD Thüringen Landtagswahlprogramm 2024", "pdf": "afd-th-2024.pdf", "seiten": 100},
"bsw-th-2024": {"id": "bsw-th-2024", "typ": "wahlprogramm", "partei": "BSW", "bundesland": "TH", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "BSW Thüringen Wahlprogramm 2024", "titel": None, "pdf": "bsw-th-2024.pdf", "seiten": 50},
"cdu-th-2024": {"id": "cdu-th-2024", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "TH", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "CDU Thüringen Wahlprogramm 2024", "titel": "Wahlprogramm der CDU Thüringen 2024", "pdf": "cdu-th-2024.pdf", "seiten": 83},
"linke-th-2024": {"id": "linke-th-2024", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "TH", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "DIE LINKE Thüringen Wahlprogramm 2024", "titel": None, "pdf": "linke-th-2024.pdf", "seiten": 100},
"spd-th-2024": {"id": "spd-th-2024", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "TH", "wp": 8, "gueltig_ab": "2024-09-01", "gueltig_bis": None, "name": "SPD Thüringen Wahlprogramm 2024", "titel": None, "pdf": "spd-th-2024.pdf", "seiten": 100},
}
# ─────────────────────────────────────────────────────────────────────────────
@ -86,6 +424,14 @@ def get_programm(programm_id: str) -> Optional[Programm]:
return PROGRAMME.get(programm_id)
def get_programm_by_pdf(pdf: str) -> Optional[Programm]:
"""Reverse-Lookup über pdf-Dateinamen."""
for prog in PROGRAMME.values():
if prog.get("pdf") == pdf:
return prog
return None
def aktuelles_wahlprogramm(bundesland: str, partei: str) -> Optional[Programm]:
"""Aktuell gültiges Wahlprogramm einer Partei in einem Bundesland.
@ -109,9 +455,6 @@ def wahlprogramm_zum_zeitpunkt(
``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 (
@ -134,11 +477,8 @@ def grundsatzprogramm_zum_zeitpunkt(
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"
@ -147,7 +487,6 @@ def grundsatzprogramm_zum_zeitpunkt(
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"
@ -189,366 +528,7 @@ def alle_versionen(bundesland: str, partei: str) -> list[Programm]:
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 — Reserve für Programme, die NICHT aus
# embeddings.PROGRAMME oder WAHLPROGRAMME migriert werden können (z.B.
# zukünftig: historische Vorgänger-Wahlprogramme aus früheren Legislaturen,
# falls deren PDFs eingebunden werden ohne dass embeddings.PROGRAMME sie
# kennt).
# ─────────────────────────────────────────────────────────────────────────────
_ADDITIONAL_PROGRAMME: list[Programm] = []
# Historische Skelett-Einträge (PDFs jetzt in embeddings.PROGRAMME) —
# wurden in einer früheren Iteration manuell als pdf-pending markiert,
# bevor die Beschaffung lief. Aktuelle Daten leben in embeddings.PROGRAMME
# und werden über _migrate_from_legacy() automatisch übernommen.
_ARCHIVED_SKELETONS: 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
from .legislaturen import aktuelle_legislatur
# Schritt 1: Wahlprogramme aus WAHLPROGRAMME.
# Geltungsbeginn = Wahltag (siehe ADR 0013 Folge-Entscheidung B1+B2):
# Programme werden zur Wahl beschlossen; Opposition muss nicht auf eine
# Regierungsbildung warten, an der sie nicht beteiligt ist. Der
# ``regierungsbildung``-Wert aus WAHLPROGRAMME bleibt erhalten und wird
# vom Bewertungs-Kontext-Block separat angezeigt ("Regierung zur
# Antragszeit"), aber die Programm-Geltung selbst startet am Wahltag.
for bundesland, parteien in WAHLPROGRAMME.items():
# Wahltag aus legislaturen lookup (aktuelle WP des BL).
leg = aktuelle_legislatur(bundesland)
wahltag = leg.get("wahltermin") if leg else None
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
# Geltungsbeginn: bevorzugt expliziter ``wahltag``-Eintrag
# (für historische Programme aus Block 2 nötig), Fallback
# auf aktuellen Wahltag der Legislatur, letzter Fallback auf
# ``regierungsbildung`` (rückwärts-kompatibel).
gueltig_ab = (
info.get("wahltag")
or wahltag
or info.get("regierungsbildung")
or "1900-01-01"
)
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": gueltig_ab,
"wp": leg.get("wp") if leg else None,
"gueltig_ab": gueltig_ab,
"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.
# typ=parteiprogramm: ohne ``bundesland`` → Bundes-Grundsatz, mit
# ``bundesland`` → Landes-Grundsatzprogramm. Beide funktional gleich,
# nur unterscheidet sich der Geltungsbereich.
for pid, info in _EMB_PROGRAMME.items():
if info.get("typ") != "parteiprogramm":
continue # Wahlprogramme schon in Schritt 1 + Schritt 2b
if pid in PROGRAMME:
continue
bl = info.get("bundesland")
prog2: Programm = {
"id": pid,
"titel": info.get("name", ""),
"name": info.get("name", ""),
"typ": "grundsatzprogramm-land" if bl else "grundsatzprogramm-bund",
"partei": info.get("partei", ""),
"bundesland": bl,
"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 2b: Historische Wahlprogramme aus embeddings.PROGRAMME.
# Wahlprogramme aus WAHLPROGRAMME (Schritt 1) sind die jeweils
# aktuelle Version pro (BL, Partei). Vorperioden-Programme sind nur
# in embeddings.PROGRAMME geführt (mit explizitem ``gueltig_ab`` und
# ``gueltig_bis`` als Eintrag-Felder) und werden hier nachregistriert.
# Voraussetzung: der embeddings-Eintrag hat ``gueltig_ab`` und/oder
# ``gueltig_bis`` gesetzt. Ohne das zählt er als "aktuelles Programm",
# wurde aber bereits via WAHLPROGRAMME migriert.
for pid, info in _EMB_PROGRAMME.items():
if info.get("typ") != "wahlprogramm":
continue
if pid in PROGRAMME:
continue
# Nur Einträge mit explizit gesetzter Geltung als historisch werten.
gueltig_ab = info.get("gueltig_ab")
gueltig_bis = info.get("gueltig_bis")
if gueltig_ab is None and gueltig_bis is None:
continue
bl = info.get("bundesland")
prog2b: Programm = {
"id": pid,
"titel": info.get("name", ""),
"name": info.get("name", ""),
"typ": "wahlprogramm",
"partei": info.get("partei", ""),
"bundesland": bl,
"beschluss": None,
"wahl": gueltig_ab,
"wp": info.get("wp"),
"gueltig_ab": gueltig_ab or "1900-01-01",
"gueltig_bis": gueltig_bis,
"pdf": info.get("pdf", ""),
"seiten": 0,
"hinweis": None,
}
PROGRAMME[pid] = prog2b
# 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()
"""Alle eingetragenen Programme."""
return list(PROGRAMME.values())

View File

@ -54,7 +54,7 @@ def build_pdf_href(zitat: dict, bundesland: str = "") -> str:
Bevorzugt das bereits gepflegte url-Feld. Falls leer, rekonstruiert
die URL aus dem quelle-Feld (Format: 'Titel · S. N' oder 'Titel, S. N')
über die WAHLPROGRAMME-Registry.
über die ``programme``-Registry.
"""
url = zitat.get("url", "")
if url:
@ -66,18 +66,14 @@ def build_pdf_href(zitat: dict, bundesland: str = "") -> str:
return ""
seite = seite_m.group(1)
# pid aus WAHLPROGRAMME-Registry ermitteln: Dateiname ohne .pdf
from .wahlprogramme import WAHLPROGRAMME
# pid aus programme-Registry ermitteln: über titel oder name match.
from .programme import all_programme
pid = ""
for bl_data in WAHLPROGRAMME.values():
for partei_data in bl_data.values():
titel = partei_data.get("titel", "")
partei_name = partei_data.get("partei", "")
file_name = partei_data.get("file", "")
if titel and (titel in quelle or partei_name in quelle):
pid = file_name.replace(".pdf", "")
break
if pid:
for prog in all_programme():
titel = prog.get("titel") or ""
name = prog.get("name") or ""
if (titel and titel in quelle) or (name and name in quelle):
pid = prog.get("id", "")
break
if not pid:

View File

@ -1,13 +1,13 @@
"""Erkennung fehlender Wahlprogramme (#128).
Prüft für ein gegebenes Bundesland, welche der im Landtag vertretenen
Fraktionen in der WAHLPROGRAMME-Registry nicht hinterlegt sind.
Wird nach dem LLM-Call in analyze_antrag() aufgerufen, damit das
Fraktionen in der ``programme``-Registry nicht hinterlegt sind. Wird
nach dem LLM-Call in ``analyze_antrag()`` aufgerufen, damit das
Assessment-Ergebnis die Lücken explizit ausweist.
"""
from .bundeslaender import BUNDESLAENDER
from .wahlprogramme import WAHLPROGRAMME
from .programme import parteien_mit_wahlprogramm
def check_missing_programmes(bundesland: str, fraktionen: list[str]) -> list[str]:
@ -33,5 +33,5 @@ def check_missing_programmes(bundesland: str, fraktionen: list[str]) -> list[str
if not fraktionen:
return []
indexed = WAHLPROGRAMME.get(bundesland, {})
indexed = set(parteien_mit_wahlprogramm(bundesland))
return [f for f in fraktionen if f not in indexed]

View File

@ -254,7 +254,7 @@ def get_missing_programmes(bundesland: Optional[str] = None) -> list[dict]:
Returns:
Liste von Dicts mit ``bl``, ``partei``, ``dateiname``, ``kandidaten``.
"""
from .wahlprogramme import WAHLPROGRAMME
from .programme import aktuelles_wahlprogramm
missing: list[dict] = []
data = _load_links()
@ -266,14 +266,15 @@ def get_missing_programmes(bundesland: Optional[str] = None) -> list[dict]:
if isinstance(kandidaten, dict):
kandidaten = [kandidaten]
wp_info = WAHLPROGRAMME.get(bl, {}).get(partei)
wp_info = aktuelles_wahlprogramm(bl, partei)
if wp_info:
dateiname = wp_info["file"]
dest = _REFERENZEN_DIR / dateiname
if dest.exists():
continue # Datei liegt bereits vor
dateiname = wp_info.get("pdf")
if dateiname:
dest = _REFERENZEN_DIR / dateiname
if dest.exists():
continue # Datei liegt bereits vor
else:
dateiname = None # noch nicht in WAHLPROGRAMME registriert
dateiname = None # noch nicht in programme.PROGRAMME registriert
missing.append({
"bl": bl,
@ -311,24 +312,29 @@ def _cli() -> None:
args = parser.parse_args()
if args.pin_existing:
from .wahlprogramme import WAHLPROGRAMME
from .programme import all_programme
lock = _load_lock()
added = 0
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
dateiname = info.get("file") if isinstance(info, dict) else None
if not dateiname:
continue
pdf_path = _REFERENZEN_DIR / dateiname
if not pdf_path.exists():
continue
key = _lock_key(dateiname)
if key in lock:
continue
lock[key] = sha256_of_file(pdf_path)
added += 1
print(f" pinned {bl}/{partei}: {dateiname}{lock[key][:12]}")
for prog in all_programme():
if prog.get("typ") != "wahlprogramm":
continue
if prog.get("gueltig_bis") is not None:
continue # nur aktuelle Wahlprogramme pinnen
bl = prog.get("bundesland")
partei = prog.get("partei")
dateiname = prog.get("pdf")
if not dateiname:
continue
pdf_path = _REFERENZEN_DIR / dateiname
if not pdf_path.exists():
continue
key = _lock_key(dateiname)
if key in lock:
continue
lock[key] = sha256_of_file(pdf_path)
added += 1
print(f" pinned {bl}/{partei}: {dateiname}{lock[key][:12]}")
if added:
_save_lock(lock)
print(f"\n{added} neue Eintraege in {_LOCK_FILE.name}.")
@ -362,16 +368,16 @@ def _cli() -> None:
print(f"Keine URL-Kandidaten für {bl}/{partei} in wahlprogramm-links.yaml.")
sys.exit(1)
from .wahlprogramme import WAHLPROGRAMME
wp_info = WAHLPROGRAMME.get(bl, {}).get(partei)
from .programme import aktuelles_wahlprogramm
wp_info = aktuelles_wahlprogramm(bl, partei)
if not wp_info:
print(
f"WARNUNG: {bl}/{partei} ist noch nicht in wahlprogramme.py eingetragen.\n"
f"WARNUNG: {bl}/{partei} ist noch nicht in programme.py eingetragen.\n"
"Die Datei wird heruntergeladen, muss aber manuell registriert werden."
)
dateiname = f"{partei.lower()}-{bl.lower()}-neu.pdf"
else:
dateiname = wp_info["file"]
dateiname = wp_info.get("pdf", f"{partei.lower()}-{bl.lower()}-neu.pdf")
dest = _REFERENZEN_DIR / dateiname

View File

@ -1,12 +1,20 @@
"""Wahlprogramm-Referenzsystem mit Zitaten und Seitenreferenzen.
"""Wahlprogramm-Suche (Keyword-Fallback, wenn Embeddings nicht verfügbar).
Bundesland-bewusst seit Issue #5: ``WAHLPROGRAMME[bundesland][partei]`` statt
flach. Konsumiert ``BUNDESLAENDER`` aus ``bundeslaender.py`` für die
Regierungsfraktionen-Lookup und für Plausibilitätsprüfungen.
Bis #222: hier lebte zusätzlich die ``WAHLPROGRAMME``-Datentabelle als
zweite Source-of-Truth. Sie wurde nach ``programme.PROGRAMME`` migriert
(siehe ADR 0013). Dieses Modul wurde dadurch reduziert auf:
Verantwortlich für die schlüsselwortbasierte Fallback-Suche in den
paged-Textversionen der Wahlprogramme. Die semantische Suche lebt in
``embeddings.py``.
- ``get_wahlprogramm(bl, partei)`` Compat-Adapter auf
``programme.aktuelles_wahlprogramm`` (für Aufrufer, die das Programm
via (Bundesland, Partei) suchen Hauptverwender ist die Keyword-Suche
hier in diesem Modul plus admin-Tools).
- ``load_wahlprogramm_text`` / ``search_wahlprogramm`` /
``find_relevant_quotes`` / ``format_quote_for_prompt`` Keyword-
Fallback für die ``analyzer.py``-Pipeline, wenn die Embeddings-DB
nicht initialisiert ist.
Die semantische Suche lebt in ``embeddings.py``, die Programm-Stamm-
daten in ``programme.py``.
"""
import re
@ -14,168 +22,12 @@ from pathlib import Path
from typing import Optional
from .bundeslaender import BUNDESLAENDER
from .programme import (
aktuelles_wahlprogramm,
parteien_mit_wahlprogramm,
)
# WAHLPROGRAMME[bundesland][partei] -> Metadaten
#
# Pflichtfelder: file, titel, partei, jahr, seiten
# Geltungsdaten: regierungsbildung (ISO YYYY-MM-DD) — Tag der Vereidigung der
# Regierung, die nach der Wahl aus diesem Programm hervorging.
# NICHT das Wahldatum: das Programm wird mit der Regierungs-
# bildung wirksam (Koalitionsvertrag, Kabinett vereidigt).
# regierungsende (ISO YYYY-MM-DD oder None) — Ende der
# Regierungs-Geltung. ``None`` = aktuell laufende Regierung.
#
# Beim Hinzufügen eines neuen Bundeslands: Eintrag hier UND parallel
# in WAHLPROGRAMM_KONTEXT_FILES.
WAHLPROGRAMME: dict[str, dict[str, dict]] = {
# NRW — LTW 15.05.2022. Kabinett Wüst II (CDU+GRÜNE) vereidigt 29.06.2022.
"NRW": {
"CDU": {"file": "cdu-nrw-2022.pdf", "titel": "Machen, worauf es ankommt", "partei": "CDU NRW", "jahr": 2022, "seiten": 109, "regierungsbildung": "2022-06-29", "regierungsende": None},
"SPD": {"file": "spd-nrw-2022.pdf", "titel": "Unser Land von morgen", "partei": "SPD NRW", "jahr": 2022, "seiten": 116, "regierungsbildung": "2022-06-29", "regierungsende": None},
"GRÜNE": {"file": "gruene-nrw-2022.pdf","titel": "Von hier an Zukunft", "partei": "BÜNDNIS 90/DIE GRÜNEN NRW", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-06-29", "regierungsende": None},
"FDP": {"file": "fdp-nrw-2022.pdf", "titel": "Nie gab es mehr zu tun", "partei": "FDP NRW", "jahr": 2022, "seiten": 96, "regierungsbildung": "2022-06-29", "regierungsende": None},
"AfD": {"file": "afd-nrw-2022.pdf", "titel": "Wer sonst.", "partei": "AfD NRW", "jahr": 2022, "seiten": 68, "regierungsbildung": "2022-06-29", "regierungsende": None},
},
# Sachsen-Anhalt — LTW 06.06.2021. Kabinett Haseloff III (CDU+SPD+FDP) vereidigt 16.09.2021.
"LSA": {
"CDU": {"file": "cdu-lsa-2021.pdf", "titel": "Unsere Heimat. Unsere Verantwortung.", "partei": "CDU Sachsen-Anhalt", "jahr": 2021, "seiten": 82, "regierungsbildung": "2021-09-16", "regierungsende": None},
"SPD": {"file": "spd-lsa-2021.pdf", "titel": "Zusammenhalt und neue Chancen. Politik fürs ganze Land", "partei": "SPD Sachsen-Anhalt", "jahr": 2021, "seiten": 77, "regierungsbildung": "2021-09-16", "regierungsende": None},
"GRÜNE": {"file": "gruene-lsa-2021.pdf","titel": "Verlässlich für Sachsen-Anhalt", "partei": "BÜNDNIS 90/DIE GRÜNEN Sachsen-Anhalt","jahr": 2021, "seiten": 164,"regierungsbildung": "2021-09-16", "regierungsende": None},
"FDP": {"file": "fdp-lsa-2021.pdf", "titel": "Wahlprogramm der FDP Sachsen-Anhalt zur Landtagswahl 2021", "partei": "FDP Sachsen-Anhalt", "jahr": 2021, "seiten": 76, "regierungsbildung": "2021-09-16", "regierungsende": None},
"AfD": {"file": "afd-lsa-2021.pdf", "titel": "Alles für unsere Heimat! Programm der AfD Sachsen-Anhalt zur Landtagswahl 2021","partei": "AfD Sachsen-Anhalt", "jahr": 2021, "seiten": 64, "regierungsbildung": "2021-09-16", "regierungsende": None},
"LINKE": {"file": "linke-lsa-2021.pdf", "titel": "Wahlprogramm zur Landtagswahl 2021", "partei": "DIE LINKE Sachsen-Anhalt", "jahr": 2021, "seiten": 88, "regierungsbildung": "2021-09-16", "regierungsende": None},
},
# Mecklenburg-Vorpommern — LTW 26.09.2021. Kabinett Schwesig II (SPD+LINKE) vereidigt 15.11.2021.
"MV": {
"CDU": {"file": "cdu-mv-2021.pdf", "titel": "Zusammen. Den Blick nach vorn. Gemeinsam die Zukunft meistern", "partei": "CDU Mecklenburg-Vorpommern", "jahr": 2021, "seiten": 56, "regierungsbildung": "2021-11-15", "regierungsende": None},
"SPD": {"file": "spd-mv-2021.pdf", "titel": "Verantwortung für heute und morgen — Regierungsprogramm 20212026", "partei": "SPD Mecklenburg-Vorpommern", "jahr": 2021, "seiten": 95, "regierungsbildung": "2021-11-15", "regierungsende": None},
"GRÜNE": {"file": "gruene-mv-2021.pdf","titel": "Für Klima, Land und ein besseres Miteinander", "partei": "BÜNDNIS 90/DIE GRÜNEN Mecklenburg-Vorpommern","jahr": 2021, "seiten": 88, "regierungsbildung": "2021-11-15", "regierungsende": None},
"FDP": {"file": "fdp-mv-2021.pdf", "titel": "Wahlprogramm der Freien Demokraten Mecklenburg-Vorpommern zur Landtagswahl 2021","partei": "FDP Mecklenburg-Vorpommern", "jahr": 2021, "seiten": 120,"regierungsbildung": "2021-11-15", "regierungsende": None},
"AfD": {"file": "afd-mv-2021.pdf", "titel": "Landeswahlprogramm der AfD Mecklenburg-Vorpommern 2021", "partei": "AfD Mecklenburg-Vorpommern", "jahr": 2021, "seiten": 84, "regierungsbildung": "2021-11-15", "regierungsende": None},
"LINKE": {"file": "linke-mv-2021.pdf", "titel": "Das ist links! — Zukunftsprogramm für Mecklenburg-Vorpommern", "partei": "DIE LINKE Mecklenburg-Vorpommern", "jahr": 2021, "seiten": 82, "regierungsbildung": "2021-11-15", "regierungsende": None},
},
# Berlin — AGH-Wahl 26.09.2021, Wiederholungswahl 12.02.2023. Senat Wegner I (CDU+SPD) vereidigt 27.04.2023.
"BE": {
"CDU": {"file": "cdu-be-2023.pdf", "titel": "Unser Berlin. Mehr geht nur gemeinsam. — Berlin-Plan der CDU Berlin 20212026", "partei": "CDU Berlin", "jahr": 2021, "seiten": 135, "regierungsbildung": "2023-04-27", "regierungsende": None},
"SPD": {"file": "spd-be-2023.pdf", "titel": "Ganz sicher Berlin — Wahlprogramm der SPD Berlin zur Abgeordnetenhauswahl 2021", "partei": "SPD Berlin", "jahr": 2021, "seiten": 86, "regierungsbildung": "2023-04-27", "regierungsende": None},
"GRÜNE": {"file": "gruene-be-2023.pdf","titel": "Unser Plan für Berlin — Landeswahlprogramm BÜNDNIS 90/DIE GRÜNEN Berlin 2021", "partei": "BÜNDNIS 90/DIE GRÜNEN Berlin", "jahr": 2021, "seiten": 280, "regierungsbildung": "2023-04-27", "regierungsende": None},
"LINKE": {"file": "linke-be-2023.pdf", "titel": "rot. radikal. realistisch. — Unser Programm für die soziale Stadt", "partei": "DIE LINKE Berlin", "jahr": 2021, "seiten": 130, "regierungsbildung": "2023-04-27", "regierungsende": None},
"AfD": {"file": "afd-be-2023.pdf", "titel": "Wahlprogramm der AfD Berlin für die Wahl des Abgeordnetenhauses am 26. September 2021","partei": "AfD Berlin", "jahr": 2021, "seiten": 166, "regierungsbildung": "2023-04-27", "regierungsende": None},
},
# Thüringen — LTW 01.09.2024. Kabinett Voigt I (CDU+BSW+SPD, Brombeer) vereidigt 12.12.2024.
"TH": {
"CDU": {"file": "cdu-th-2024.pdf", "titel": "Wahlprogramm der CDU Thüringen 2024", "partei": "CDU Thüringen", "jahr": 2024, "seiten": 83, "regierungsbildung": "2024-12-12", "regierungsende": None},
"AfD": {"file": "afd-th-2024.pdf", "titel": "AfD Thüringen Landtagswahlprogramm 2024","partei": "AfD Thüringen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-12", "regierungsende": None},
"LINKE": {"file": "linke-th-2024.pdf", "titel": "DIE LINKE Thüringen Wahlprogramm 2024", "partei": "DIE LINKE Thüringen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-12", "regierungsende": None},
"BSW": {"file": "bsw-th-2024.pdf", "titel": "BSW Thüringen Wahlprogramm 2024", "partei": "BSW Thüringen", "jahr": 2024, "seiten": 50, "regierungsbildung": "2024-12-12", "regierungsende": None},
"SPD": {"file": "spd-th-2024.pdf", "titel": "SPD Thüringen Wahlprogramm 2024", "partei": "SPD Thüringen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-12", "regierungsende": None},
},
# Brandenburg — LTW 22.09.2024. Kabinett Woidke III (SPD+BSW) vereidigt 11.12.2024.
"BB": {
"SPD": {"file": "spd-bb-2024.pdf", "titel": "SPD Brandenburg Wahlprogramm 2024", "partei": "SPD Brandenburg", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-11", "regierungsende": None},
"AfD": {"file": "afd-bb-2024.pdf", "titel": "AfD Brandenburg Wahlprogramm 2024", "partei": "AfD Brandenburg", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-11", "regierungsende": None},
"CDU": {"file": "cdu-bb-2024.pdf", "titel": "CDU Brandenburg Wahlprogramm 2024", "partei": "CDU Brandenburg", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-11", "regierungsende": None},
"BSW": {"file": "bsw-bb-2024.pdf", "titel": "BSW Brandenburg Wahlprogramm 2024", "partei": "BSW Brandenburg", "jahr": 2024, "seiten": 50, "regierungsbildung": "2024-12-11", "regierungsende": None},
},
# Hamburg — Bürgerschaftswahl 02.03.2025. Senat Tschentscher III (SPD+GRÜNE) vereidigt 07.05.2025.
"HH": {
"SPD": {"file": "spd-hh-2025.pdf", "titel": "SPD Hamburg Wahlprogramm Bürgerschaftswahl 2025", "partei": "SPD Hamburg", "jahr": 2025, "seiten": 100, "regierungsbildung": "2025-05-07", "regierungsende": None},
"CDU": {"file": "cdu-hh-2025.pdf", "titel": "CDU Hamburg Wahlprogramm Bürgerschaftswahl 2025", "partei": "CDU Hamburg", "jahr": 2025, "seiten": 100, "regierungsbildung": "2025-05-07", "regierungsende": None},
"GRÜNE": {"file": "gruene-hh-2025.pdf","titel": "Gute Gründe für Grün — Regierungsprogramm BÜNDNIS 90/DIE GRÜNEN Hamburg 2025", "partei": "BÜNDNIS 90/DIE GRÜNEN Hamburg", "jahr": 2025, "seiten": 100, "regierungsbildung": "2025-05-07", "regierungsende": None},
"LINKE": {"file": "linke-hh-2025.pdf", "titel": "DIE LINKE Hamburg Wahlprogramm Bürgerschaftswahl 2025", "partei": "DIE LINKE Hamburg", "jahr": 2025, "seiten": 100, "regierungsbildung": "2025-05-07", "regierungsende": None},
"AfD": {"file": "afd-hh-2025.pdf", "titel": "AfD Hamburg Wahlprogramm Bürgerschaftswahl 2025", "partei": "AfD Hamburg", "jahr": 2025, "seiten": 100, "regierungsbildung": "2025-05-07", "regierungsende": None},
},
# Schleswig-Holstein — LTW 08.05.2022. Kabinett Günther II (CDU+GRÜNE) vereidigt 29.06.2022.
"SH": {
"CDU": {"file": "cdu-sh-2022.pdf", "titel": "CDU Schleswig-Holstein Wahlprogramm 2022", "partei": "CDU Schleswig-Holstein", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-06-29", "regierungsende": None},
"SPD": {"file": "spd-sh-2022.pdf", "titel": "SPD Schleswig-Holstein Wahlprogramm 2022", "partei": "SPD Schleswig-Holstein", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-06-29", "regierungsende": None},
"GRÜNE": {"file": "gruene-sh-2022.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Schleswig-Holstein Wahlprogramm 2022", "partei": "BÜNDNIS 90/DIE GRÜNEN Schleswig-Holstein", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-06-29", "regierungsende": None},
"FDP": {"file": "fdp-sh-2022.pdf", "titel": "FDP Schleswig-Holstein Wahlprogramm 2022", "partei": "FDP Schleswig-Holstein", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-06-29", "regierungsende": None},
"SSW": {"file": "ssw-sh-2022.pdf", "titel": "SSW Schleswig-Holstein Wahlprogramm 2022", "partei": "SSW", "jahr": 2022, "seiten": 80, "regierungsbildung": "2022-06-29", "regierungsende": None},
},
# Baden-Württemberg — LTW 14.03.2021. Kabinett Kretschmann III (GRÜNE+CDU) vereidigt 12.05.2021.
"BW": {
"GRÜNE": {"file": "gruene-bw-2021.pdf","titel": "Wachsen wir über uns hinaus — Landtagswahlprogramm BÜNDNIS 90/DIE GRÜNEN Baden-Württemberg 2021", "partei": "BÜNDNIS 90/DIE GRÜNEN Baden-Württemberg", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-12", "regierungsende": None},
"CDU": {"file": "cdu-bw-2021.pdf", "titel": "Neue Ideen für eine neue Zeit — Regierungsprogramm der CDU Baden-Württemberg zur Landtagswahl 2021", "partei": "CDU Baden-Württemberg", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-12", "regierungsende": None},
"AfD": {"file": "afd-bw-2021.pdf", "titel": "AfD Baden-Württemberg Landtagswahlprogramm 2021", "partei": "AfD Baden-Württemberg", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-12", "regierungsende": None},
"SPD": {"file": "spd-bw-2021.pdf", "titel": "SPD Baden-Württemberg Wahlprogramm zur Landtagswahl 2021", "partei": "SPD Baden-Württemberg", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-12", "regierungsende": None},
"FDP": {"file": "fdp-bw-2021.pdf", "titel": "FDP Baden-Württemberg Landtagswahlprogramm 2021", "partei": "FDP Baden-Württemberg", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-12", "regierungsende": None},
},
# Rheinland-Pfalz — LTW 14.03.2021. Kabinett Dreyer III (SPD+GRÜNE+FDP) vereidigt 18.05.2021;
# seit 10.07.2024 fortgeführt als Schweitzer I in derselben WP18.
"RP": {
"SPD": {"file": "spd-rp-2021.pdf","titel": "Wir mit Ihr — Regierungsprogramm der SPD Rheinland-Pfalz 20212026", "partei": "SPD Rheinland-Pfalz", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-18", "regierungsende": None},
"CDU": {"file": "cdu-rp-2021.pdf","titel": "Regierungsprogramm der CDU RLP 202126", "partei": "CDU Rheinland-Pfalz", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-18", "regierungsende": None},
"AfD": {"file": "afd-rp-2021.pdf","titel": "AfD Rheinland-Pfalz Wahlprogramm 2021", "partei": "AfD Rheinland-Pfalz", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-18", "regierungsende": None},
"GRÜNE": {"file": "gruene-rp-2021.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Rheinland-Pfalz Landtagswahlprogramm 2021", "partei": "BÜNDNIS 90/DIE GRÜNEN Rheinland-Pfalz", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-18", "regierungsende": None},
"FREIE WÄHLER": {"file": "fw-rp-2021.pdf", "titel": "FREIE WÄHLER Rheinland-Pfalz Wahlprogramm 2021", "partei": "FREIE WÄHLER Rheinland-Pfalz", "jahr": 2021, "seiten": 80, "regierungsbildung": "2021-05-18", "regierungsende": None},
"FDP": {"file": "fdp-rp-2021.pdf","titel": "FDP Rheinland-Pfalz Landtagswahlprogramm 2021", "partei": "FDP Rheinland-Pfalz", "jahr": 2021, "seiten": 100, "regierungsbildung": "2021-05-18", "regierungsende": None},
},
# Bayern — LTW 08.10.2023. Kabinett Söder III (CSU+FREIE WÄHLER) vereidigt 07.11.2023.
"BY": {
"CSU": {"file": "csu-by-2023.pdf", "titel": "CSU Bayern Bayernplan 2023", "partei": "CSU Bayern", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-11-07", "regierungsende": None},
"FREIE WÄHLER": {"file": "fw-by-2023.pdf", "titel": "FREIE WÄHLER Bayern Wahlprogramm 2023", "partei": "FREIE WÄHLER Bayern", "jahr": 2023, "seiten": 80, "regierungsbildung": "2023-11-07", "regierungsende": None},
"GRÜNE": {"file": "gruene-by-2023.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Bayern Regierungsprogramm 2023", "partei": "BÜNDNIS 90/DIE GRÜNEN Bayern", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-11-07", "regierungsende": None},
"SPD": {"file": "spd-by-2023.pdf", "titel": "SPD Bayern Zukunftsprogramm 2023", "partei": "SPD Bayern", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-11-07", "regierungsende": None},
"AfD": {"file": "afd-by-2023.pdf", "titel": "AfD Bayern Wahlprogramm 2023", "partei": "AfD Bayern", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-11-07", "regierungsende": None},
},
# Bremen — Bürgerschaftswahl 14.05.2023. Senat Bovenschulte II (SPD+GRÜNE+LINKE) vereidigt 05.07.2023.
# AfD war wegen Listenstreit nicht zur Wahl zugelassen — stattdessen ist
# BIW (Bürger in Wut) als 6. Fraktion in der 21. Bürgerschaft.
"HB": {
"SPD": {"file": "spd-hb-2023.pdf", "titel": "SPD Bremen Wahlprogramm Bürgerschaftswahl 2023", "partei": "SPD Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-05", "regierungsende": None},
"CDU": {"file": "cdu-hb-2023.pdf", "titel": "CDU Bremen Wahlprogramm Bürgerschaftswahl 2023", "partei": "CDU Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-05", "regierungsende": None},
"GRÜNE": {"file": "gruene-hb-2023.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Bremen Wahlprogramm 2023", "partei": "BÜNDNIS 90/DIE GRÜNEN Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-05", "regierungsende": None},
"LINKE": {"file": "linke-hb-2023.pdf", "titel": "DIE LINKE Bremen Wahlprogramm Bürgerschaftswahl 2023", "partei": "DIE LINKE Bremen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2023-07-05", "regierungsende": None},
"BiW": {"file": "biw-hb-2023.pdf", "titel": "BÜRGER IN WUT — Programm für die Bürgerschaftswahl 2023", "partei": "BiW Bremen", "jahr": 2023, "seiten": 26, "regierungsbildung": "2023-07-05", "regierungsende": None},
},
# Hessen — LTW 08.10.2023. Kabinett Rhein II (CDU+SPD) vereidigt 18.01.2024.
"HE": {
"CDU": {"file": "cdu-he-2023.pdf", "titel": "CDU Hessen Regierungsprogramm 2023", "partei": "CDU Hessen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2024-01-18", "regierungsende": None},
"SPD": {"file": "spd-he-2023.pdf", "titel": "SPD Hessen Wahlprogramm 2023", "partei": "SPD Hessen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2024-01-18", "regierungsende": None},
"GRÜNE": {"file": "gruene-he-2023.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Hessen Wahlprogramm 2023", "partei": "BÜNDNIS 90/DIE GRÜNEN Hessen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2024-01-18", "regierungsende": None},
"FDP": {"file": "fdp-he-2023.pdf", "titel": "FDP Hessen Wahlprogramm 2023", "partei": "FDP Hessen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2024-01-18", "regierungsende": None},
"AfD": {"file": "afd-he-2023.pdf", "titel": "AfD Hessen Wahlprogramm 2023", "partei": "AfD Hessen", "jahr": 2023, "seiten": 100, "regierungsbildung": "2024-01-18", "regierungsende": None},
},
# Niedersachsen — LTW 09.10.2022. Kabinett Weil III (SPD+GRÜNE) vereidigt 08.11.2022.
"NI": {
"SPD": {"file": "spd-ni-2022.pdf", "titel": "SPD Niedersachsen Regierungsprogramm 2022", "partei": "SPD Niedersachsen", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-11-08", "regierungsende": None},
"CDU": {"file": "cdu-ni-2022.pdf", "titel": "CDU Niedersachsen Regierungsprogramm 2022", "partei": "CDU Niedersachsen", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-11-08", "regierungsende": None},
"GRÜNE": {"file": "gruene-ni-2022.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Niedersachsen Wahlprogramm 2022", "partei": "BÜNDNIS 90/DIE GRÜNEN Niedersachsen", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-11-08", "regierungsende": None},
"AfD": {"file": "afd-ni-2022.pdf", "titel": "AfD Niedersachsen Wahlprogramm 2022", "partei": "AfD Niedersachsen", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-11-08", "regierungsende": None},
},
# Saarland — LTW 27.03.2022. Kabinett Rehlinger I (SPD-Alleinregierung) vereidigt 25.04.2022.
"SL": {
"SPD": {"file": "spd-sl-2022.pdf", "titel": "SPD Saarland Regierungsprogramm 2022", "partei": "SPD Saarland", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-04-25", "regierungsende": None},
"CDU": {"file": "cdu-sl-2022.pdf", "titel": "CDU Saarland Wahlprogramm 2022", "partei": "CDU Saarland", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-04-25", "regierungsende": None},
"AfD": {"file": "afd-sl-2022.pdf", "titel": "AfD Saarland Wahlprogramm 2022", "partei": "AfD Saarland", "jahr": 2022, "seiten": 100, "regierungsbildung": "2022-04-25", "regierungsende": None},
},
# Sachsen — LTW 01.09.2024. Kabinett Kretschmer III (CDU+SPD, Minderheit) vereidigt 18.12.2024.
"SN": {
"CDU": {"file": "cdu-sn-2024.pdf", "titel": "CDU Sachsen Wahlprogramm 2024", "partei": "CDU Sachsen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-18", "regierungsende": None},
"SPD": {"file": "spd-sn-2024.pdf", "titel": "SPD Sachsen Wahlprogramm 2024", "partei": "SPD Sachsen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-18", "regierungsende": None},
"AfD": {"file": "afd-sn-2024.pdf", "titel": "AfD Sachsen Wahlprogramm 2024", "partei": "AfD Sachsen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-18", "regierungsende": None},
"BSW": {"file": "bsw-sn-2024.pdf", "titel": "BSW Sachsen Wahlprogramm 2024", "partei": "BSW Sachsen", "jahr": 2024, "seiten": 50, "regierungsbildung": "2024-12-18", "regierungsende": None},
"LINKE": {"file": "linke-sn-2024.pdf", "titel": "DIE LINKE Sachsen Wahlprogramm 2024", "partei": "DIE LINKE Sachsen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-18", "regierungsende": None},
"GRÜNE": {"file": "gruene-sn-2024.pdf","titel": "BÜNDNIS 90/DIE GRÜNEN Sachsen Wahlprogramm 2024","partei": "BÜNDNIS 90/DIE GRÜNEN Sachsen", "jahr": 2024, "seiten": 100, "regierungsbildung": "2024-12-18", "regierungsende": None},
},
# Bundestag — BTW 23.02.2025. Kabinett Merz I (CDU+CSU+SPD) vereidigt 06.05.2025.
# Aktiv die BTW-2025-Wahlprogramme aller acht im 21. Bundestag relevanten
# Parteien. Grundsatzprogramme bleiben in embeddings.PROGRAMME als
# zweite Referenz erhalten.
"BUND": {
"CDU": {"file": "cdu-bund-2025.pdf", "titel": "Politikwechsel für Deutschland — Wahlprogramm CDU/CSU BTW 2025", "partei": "CDU", "jahr": 2025, "seiten": 82, "regierungsbildung": "2025-05-06", "regierungsende": None},
"CSU": {"file": "csu-bund-2025.pdf", "titel": "Politikwechsel für Deutschland — Wahlprogramm CDU/CSU BTW 2025 (CSU)", "partei": "CSU", "jahr": 2025, "seiten": 81, "regierungsbildung": "2025-05-06", "regierungsende": None},
"SPD": {"file": "spd-bund-2025.pdf", "titel": "Mehr für Dich. Besser für Deutschland. — SPD Regierungsprogramm BTW 2025", "partei": "SPD", "jahr": 2025, "seiten": 68, "regierungsbildung": "2025-05-06", "regierungsende": None},
"GRÜNE": {"file": "gruene-bund-2025.pdf","titel": "Zusammen wachsen — Regierungsprogramm BÜNDNIS 90/DIE GRÜNEN BTW 2025", "partei": "BÜNDNIS 90/DIE GRÜNEN", "jahr": 2025, "seiten": 160, "regierungsbildung": "2025-05-06", "regierungsende": None},
"FDP": {"file": "fdp-bund-2025.pdf", "titel": "Alles lässt sich ändern — FDP Wahlprogramm BTW 2025", "partei": "FDP", "jahr": 2025, "seiten": 52, "regierungsbildung": "2025-05-06", "regierungsende": None},
"AfD": {"file": "afd-bund-2025.pdf", "titel": "Zeit für Deutschland — AfD Bundestagswahlprogramm 2025", "partei": "AfD", "jahr": 2025, "seiten": 177, "regierungsbildung": "2025-05-06", "regierungsende": None},
"LINKE": {"file": "linke-bund-2025.pdf", "titel": "Alle wollen regieren. Wir wollen verändern. — DIE LINKE Wahlprogramm BTW 2025", "partei": "DIE LINKE", "jahr": 2025, "seiten": 60, "regierungsbildung": "2025-05-06", "regierungsende": None},
"BSW": {"file": "bsw-bund-2025.pdf", "titel": "Unser Land verdient mehr — BSW Wahlprogramm BTW 2025", "partei": "BSW", "jahr": 2025, "seiten": 45, "regierungsbildung": "2025-05-06", "regierungsende": None},
},
}
# Pro Bundesland: Markdown-Übersichtsdatei mit Wahlprogramm-Zusammenfassungen,
# wird als Kontext in den LLM-Prompt geladen (nicht für die Suche).
WAHLPROGRAMM_KONTEXT_FILES: dict[str, str] = {
@ -187,52 +39,15 @@ KONTEXT_PATH = Path(__file__).parent / "kontext"
def get_wahlprogramm(bundesland: str, partei: str) -> Optional[dict]:
"""Liefert die Wahlprogramm-Metadaten oder None, wenn keins vorliegt."""
return WAHLPROGRAMME.get(bundesland, {}).get(partei)
"""Liefert das aktuell gültige Wahlprogramm dieser Partei in dem
Bundesland als ``programme.Programm``-Dict (oder ``None``).
def parteien_mit_wahlprogramm(bundesland: str) -> list[str]:
"""Liste der Parteien, für die im gegebenen Bundesland ein Wahlprogramm vorliegt."""
return list(WAHLPROGRAMME.get(bundesland, {}).keys())
def regierungsbildung_for(bundesland: str) -> Optional[str]:
"""Datum der Regierungsbildung (Vereidigung) der Regierung, die nach der
aktuellen Wahl aus diesem Programm hervorging.
Achtung: NICHT die Vereidigung der gerade amtierenden Regierung bei
Sukzessionen innerhalb derselben WP (z.B. RP Dreyer III Schweitzer I
in WP18) bleibt das Datum bei der ersten Regierung der WP. Die
Wahlprogramme bleiben zur Wahl wirksam, auch wenn die MP-Person später
wechselt. Für die "aktuell amtierende" Regierung siehe
``legislaturen.aktuelle_regierung``.
Compat-Adapter auf ``programme.aktuelles_wahlprogramm``. Aufrufer,
die früher ``info["file"]`` gelesen haben, müssen ``info["pdf"]``
lesen; ``info["partei"]`` (Langform) gibt es nicht mehr Kurzform
+ ``info["bundesland"]`` reichen für die Anzeige.
"""
parteien = WAHLPROGRAMME.get(bundesland, {})
for info in parteien.values():
rb = info.get("regierungsbildung")
if rb is not None:
return rb
return None
def regierungsende_for(bundesland: str) -> Optional[str]:
"""Datum, ab dem die aus dieser Wahl hervorgegangene Regierungsperiode
endet. ``None`` solange laufend (auch nach Sukzession in derselben WP).
"""
parteien = WAHLPROGRAMME.get(bundesland, {})
for info in parteien.values():
return info.get("regierungsende")
return None
def regierung_aktuell(bundesland: str) -> bool:
"""True, wenn die aus dieser Wahl hervorgegangene Regierungsperiode noch
läuft (bildung gesetzt, ende=None)."""
parteien = WAHLPROGRAMME.get(bundesland, {})
if not parteien:
return False
info = next(iter(parteien.values()))
return info.get("regierungsbildung") is not None and info.get("regierungsende") is None
return aktuelles_wahlprogramm(bundesland, partei)
def load_wahlprogramm_text(bundesland: str, partei: str) -> dict[int, str]:
@ -246,11 +61,14 @@ def load_wahlprogramm_text(bundesland: str, partei: str) -> dict[int, str]:
if not info:
return {}
# Versuche paged-Textdatei zu laden
paged_file = KONTEXT_PATH / info['file'].replace('.pdf', '-paged.txt')
pdf_name = info.get("pdf", "")
if not pdf_name:
return {}
paged_file = KONTEXT_PATH / pdf_name.replace('.pdf', '-paged.txt')
if not paged_file.exists():
# Fallback: Normale Textdatei
txt_file = KONTEXT_PATH / info['file'].replace('.pdf', '.txt')
txt_file = KONTEXT_PATH / pdf_name.replace('.pdf', '.txt')
if txt_file.exists():
return {1: txt_file.read_text()}
return {}
@ -283,16 +101,11 @@ def search_wahlprogramm(
keywords: list[str],
max_results: int = 3,
) -> list[dict]:
"""Sucht relevante Passagen in einem Wahlprogramm.
Args:
bundesland: Bundesland-Code (NRW, LSA, )
partei: Partei-Kürzel (CDU, SPD, GRÜNE, FDP, AfD, )
keywords: Suchbegriffe
max_results: Maximale Anzahl Ergebnisse
"""Sucht relevante Passagen in einem Wahlprogramm (Keyword-basiert,
Fallback wenn die Embeddings-DB nicht da ist).
Returns:
Liste von {bundesland, partei, seite, text, score, url, quelle}
Liste von {bundesland, partei, seite, text, score, url, quelle}.
"""
info = get_wahlprogramm(bundesland, partei)
if not info:
@ -302,6 +115,10 @@ def search_wahlprogramm(
if not pages:
return []
pdf_name = info.get("pdf", "")
name = info.get("name") or partei
jahr = info.get("gueltig_ab", "")[:4] or "?"
results = []
keywords_lower = [k.lower() for k in keywords]
@ -335,8 +152,8 @@ def search_wahlprogramm(
"seite": page_num,
"text": best_para,
"score": score,
"url": f"/static/referenzen/{info['file']}#page={page_num}",
"quelle": f"{info['partei']} Wahlprogramm {info['jahr']}, S. {page_num}",
"url": f"/static/referenzen/{pdf_name}#page={page_num}",
"quelle": f"{name}, S. {page_num}",
})
results.sort(key=lambda x: x['score'], reverse=True)
@ -402,12 +219,9 @@ def format_quote_for_prompt(quotes: dict[str, list[dict]]) -> str:
return ""
lines = ["\n## Relevante Passagen aus Wahlprogrammen\n"]
lines.append("Nutze diese Originalzitate als Belege in deiner Bewertung:\n")
for partei, zitate in quotes.items():
for z in zitate:
lines.append(f"### {z['quelle']}")
lines.append(f'> "{z["text"]}"')
lines.append("")
for partei, partei_quotes in quotes.items():
if partei_quotes:
lines.append(f"\n### {partei}")
for q in partei_quotes:
lines.append(f"- S. {q['seite']}: \"{q['text']}\"")
return "\n".join(lines)

View File

@ -157,16 +157,16 @@ class TestBuildPdfHref:
assert build_pdf_href(zitat) == "/api/wahlprogramm-cite?pid=cdu-nrw-2022&seite=15"
def test_empty_url_falls_back_to_quelle_lookup(self):
"""Ohne url muss die quelle reconstruiert werden via WAHLPROGRAMME."""
"""Ohne url muss die quelle reconstruiert werden via programme.PROGRAMME."""
from app.redline_utils import build_pdf_href
# Ein in WAHLPROGRAMME hinterlegter Titel
from app.wahlprogramme import WAHLPROGRAMME
# Pick the first programme from the registry
bl, parteien = next(iter(WAHLPROGRAMME.items()))
partei, info = next(iter(parteien.items()))
titel = info.get("titel", "")
if not titel:
pytest.skip("Kein WAHLPROGRAMME-Eintrag mit titel verfuegbar")
from app.programme import all_programme
prog = next(
(p for p in all_programme() if p.get("titel")),
None,
)
if prog is None:
pytest.skip("Kein Programm-Eintrag mit titel verfügbar")
titel = prog["titel"]
zitat = {
"quelle": f"{titel} · S. 42",
"text": "Wir wollen die Energiewende",
@ -193,12 +193,11 @@ class TestBuildPdfHref:
def test_query_uses_first_5_words_of_text(self):
from app.redline_utils import build_pdf_href
from app.wahlprogramme import WAHLPROGRAMME
bl, parteien = next(iter(WAHLPROGRAMME.items()))
partei, info = next(iter(parteien.items()))
titel = info.get("titel", "")
if not titel:
pytest.skip("Kein WAHLPROGRAMME-Eintrag mit titel verfuegbar")
from app.programme import all_programme
prog = next((p for p in all_programme() if p.get("titel")), None)
if prog is None:
pytest.skip("Kein Programm-Eintrag mit titel verfügbar")
titel = prog["titel"]
zitat = {
"quelle": f"{titel} · S. 5",
"text": "Eins zwei drei vier fünf sechs sieben",
@ -214,12 +213,11 @@ class TestBuildPdfHref:
def test_handles_seite_with_comma_separator(self):
"""Quelle 'Titel, S. 42' (Komma) muss genauso parsen wie '· S. 42'."""
from app.redline_utils import build_pdf_href
from app.wahlprogramme import WAHLPROGRAMME
bl, parteien = next(iter(WAHLPROGRAMME.items()))
partei, info = next(iter(parteien.items()))
titel = info.get("titel", "")
if not titel:
pytest.skip("Kein WAHLPROGRAMME-Eintrag mit titel verfuegbar")
from app.programme import all_programme
prog = next((p for p in all_programme() if p.get("titel")), None)
if prog is None:
pytest.skip("Kein Programm-Eintrag mit titel verfügbar")
titel = prog["titel"]
zitat = {"quelle": f"{titel}, S. 17", "text": "x", "url": ""}
href = build_pdf_href(zitat)
assert "seite=17" in href

View File

@ -2,7 +2,7 @@
import pytest
from app.wahlprogramm_check import check_missing_programmes
from app.wahlprogramme import WAHLPROGRAMME
from app.programme import parteien_mit_wahlprogramm as _parteien_mit_wp
from app.bundeslaender import BUNDESLAENDER
@ -12,8 +12,7 @@ class TestCheckMissingProgrammes:
def test_all_covered_returns_empty(self):
"""Alle Fraktionen haben ein hinterlegtes Programm → leere Liste."""
bl = "NRW"
# Nur Fraktionen übergeben, die in WAHLPROGRAMME["NRW"] liegen
indexed = list(WAHLPROGRAMME[bl].keys())
indexed = _parteien_mit_wp(bl)
result = check_missing_programmes(bl, indexed)
assert result == [], (
f"Erwartet [], bekommen {result!r} — alle Fraktionen sollten abgedeckt sein"
@ -23,7 +22,7 @@ class TestCheckMissingProgrammes:
"""Eine Fraktion ohne Programm → wird in der Rückgabe gemeldet."""
bl = "NRW"
# AfD ist in NRW hinterlegt, BSW nicht
fraktionen = list(WAHLPROGRAMME[bl].keys()) + ["BSW"]
fraktionen = _parteien_mit_wp(bl) + ["BSW"]
result = check_missing_programmes(bl, fraktionen)
assert "BSW" in result
@ -49,14 +48,14 @@ class TestCheckMissingProgrammes:
check_missing_programmes("XX", ["CDU"])
def test_bundesland_without_wahlprogramme_entry(self):
"""Aktives Bundesland ohne WAHLPROGRAMME-Eintrag → alle Fraktionen fehlend."""
# Finde ein aktives BL, das keinen Eintrag in WAHLPROGRAMME hat
"""Aktives Bundesland ohne Programm-Einträge → alle Fraktionen fehlend."""
# Finde ein aktives BL, das keine Wahlprogramme registriert hat
bl_without = next(
(code for code in BUNDESLAENDER if code not in WAHLPROGRAMME),
(code for code in BUNDESLAENDER if not _parteien_mit_wp(code)),
None,
)
if bl_without is None:
pytest.skip("Alle bekannten Bundesländer haben WAHLPROGRAMME-Einträge")
pytest.skip("Alle bekannten Bundesländer haben Programm-Einträge")
fraktionen = BUNDESLAENDER[bl_without].landtagsfraktionen[:2]
result = check_missing_programmes(bl_without, fraktionen)
assert result == fraktionen

View File

@ -1,342 +1,102 @@
"""Tests for wahlprogramme.py — registry consistency + file existence."""
import re
"""Tests for wahlprogramme.py.
Nach dem #222-Refactor ist dieses Modul nur noch ein dünner Wrapper:
keyword-basierte Suche + PDF-Text-Loader + ein Compat-Adapter
``get_wahlprogramm`` der zu ``programme.aktuelles_wahlprogramm``
delegiert. Die Stamm-Daten (``WAHLPROGRAMME``-Literal) sind nach
``programme.PROGRAMME`` gewandert. Strukturelle Daten-Tests leben
deshalb in ``test_programme.py``.
"""
import pytest
from app.wahlprogramme import (
WAHLPROGRAMME,
REFERENZEN_PATH,
get_wahlprogramm,
parteien_mit_wahlprogramm,
regierung_aktuell,
regierungsbildung_for,
regierungsende_for,
load_wahlprogramm_text,
search_wahlprogramm,
find_relevant_quotes,
format_quote_for_prompt,
)
from app.programme import parteien_mit_wahlprogramm
# ─────────────────────────────────────────────────────────────────────────────
# Registry consistency
# Stichproben aktiver Bundesländer + zugeordnete Parteien
# ─────────────────────────────────────────────────────────────────────────────
class TestRegistryStructure:
def test_active_bundeslaender_present(self):
for code in ["NRW", "LSA", "MV", "BE"]:
assert code in WAHLPROGRAMME, f"missing wahlprogramme entry for {code}"
def test_each_entry_has_required_keys(self):
required = {"file", "titel", "partei", "jahr", "seiten"}
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
missing = required - set(info.keys())
assert not missing, f"{bl}/{partei} missing keys: {missing}"
def test_jahr_is_integer(self):
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
assert isinstance(info["jahr"], int), f"{bl}/{partei} jahr not int"
def test_seiten_is_positive_integer(self):
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
assert isinstance(info["seiten"], int)
assert info["seiten"] > 0
def test_file_extension_is_pdf(self):
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
assert info["file"].endswith(".pdf")
# ─────────────────────────────────────────────────────────────────────────────
# Regierungsbildungs-Felder — Konsistenz pro Bundesland
# ─────────────────────────────────────────────────────────────────────────────
class TestRegierungsbildung:
"""Pro Bundesland gehören alle Wahlprogramm-Einträge zur gleichen
Legislatur d.h. dasselbe regierungsbildung-Datum. Ausnahme: BUND, wo
Grundsatzprogramme stehen, die regierungsbildung=None tragen."""
_ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
def test_every_entry_has_regierungs_fields(self):
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
assert "regierungsbildung" in info, f"{bl}/{partei}: regierungsbildung fehlt"
assert "regierungsende" in info, f"{bl}/{partei}: regierungsende fehlt"
def test_regierungsbildung_iso_or_none(self):
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
rb = info.get("regierungsbildung")
if rb is not None:
assert isinstance(rb, str) and self._ISO_DATE.match(rb), \
f"{bl}/{partei}: regierungsbildung kein ISO-Datum: {rb!r}"
def test_regierungsende_iso_or_none(self):
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
re_ = info.get("regierungsende")
if re_ is not None:
assert isinstance(re_, str) and self._ISO_DATE.match(re_), \
f"{bl}/{partei}: regierungsende kein ISO-Datum: {re_!r}"
def test_alle_parteien_eines_bl_haben_gleiches_datum(self):
"""Alle Wahlprogramm-Einträge eines Bundeslands gehören zur selben
Regierung und müssen daher dasselbe Bildungs-/Endedatum tragen."""
for bl, parteien in WAHLPROGRAMME.items():
bildung = {info.get("regierungsbildung") for info in parteien.values()}
ende = {info.get("regierungsende") for info in parteien.values()}
assert len(bildung) == 1, \
f"{bl}: regierungsbildung divergent: {bildung}"
assert len(ende) == 1, \
f"{bl}: regierungsende divergent: {ende}"
def test_grundsatzprogramme_haben_keine_regierung(self):
"""Grundsatzprogramme (ist_grundsatz=True) tragen keine Regierungs-
bildung sie sind zeitlos."""
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
if info.get("ist_grundsatz"):
assert info.get("regierungsbildung") is None, \
f"{bl}/{partei} ist Grundsatzprogramm, sollte regierungsbildung=None haben"
class TestRegierungsHelper:
def test_regierungsbildung_for_known_bl(self):
assert regierungsbildung_for("NRW") == "2022-06-29"
assert regierungsbildung_for("HH") == "2025-05-07"
def test_regierungsbildung_for_bund_btw2025(self):
# BUND tragt nun die BTW-2025-Wahlprogramme; Kabinett Merz I
# vereidigt 06.05.2025. Grundsatzprogramme bleiben nur in
# embeddings.PROGRAMME als zweite Referenz.
assert regierungsbildung_for("BUND") == "2025-05-06"
def test_regierungsbildung_for_unknown_bl(self):
assert regierungsbildung_for("XX") is None
def test_regierung_aktuell_true_for_active_bl(self):
assert regierung_aktuell("NRW") is True
assert regierung_aktuell("BB") is True
assert regierung_aktuell("BUND") is True
def test_regierungsende_for_active_is_none(self):
assert regierungsende_for("NRW") is None
# ─────────────────────────────────────────────────────────────────────────────
# File existence — every registered file must exist on disk
# ─────────────────────────────────────────────────────────────────────────────
class TestFileExistence:
"""Catches typos in the file field that would silently break embedding
indexing or PDF download links."""
def test_every_registered_pdf_exists(self):
missing = []
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
path = REFERENZEN_PATH / info["file"]
if not path.exists():
missing.append(f"{bl}/{partei}: {info['file']}")
assert not missing, "missing PDFs:\n " + "\n ".join(missing)
# ─────────────────────────────────────────────────────────────────────────────
# Lookup helpers
# ─────────────────────────────────────────────────────────────────────────────
class TestGetWahlprogramm:
def test_returns_dict_for_known_combination(self):
info = get_wahlprogramm("MV", "CDU")
assert info is not None
assert info["partei"] == "CDU Mecklenburg-Vorpommern"
def test_returns_none_for_unknown_bundesland(self):
assert get_wahlprogramm("XX", "CDU") is None
def test_returns_none_for_unknown_partei(self):
assert get_wahlprogramm("NRW", "BSW") is None
class TestParteienMitWahlprogramm:
"""Smoke-Test: in den jeweiligen BL liegen die erwarteten Fraktionen.
Strikte Schema-Tests gegen ``programme.PROGRAMME`` in test_programme.py."""
def test_nrw_has_five_parteien(self):
parteien = parteien_mit_wahlprogramm("NRW")
assert len(parteien) == 5
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"}
def test_mv_has_six_parteien(self):
parteien = parteien_mit_wahlprogramm("MV")
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE"}
def test_be_has_five_parteien(self):
parteien = parteien_mit_wahlprogramm("BE")
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "LINKE", "AfD"}
def test_bund_has_eight_parteien(self):
# BTW 2025: CDU, CSU, SPD, GRÜNE, FDP, AfD, LINKE, BSW.
parteien = parteien_mit_wahlprogramm("BUND")
assert set(parteien) == {"CDU", "CSU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE", "BSW"}
def test_by_has_five_parteien(self):
parteien = parteien_mit_wahlprogramm("BY")
assert set(parteien) == {"CSU", "FREIE WÄHLER", "GRÜNE", "SPD", "AfD"}
def test_hb_has_five_parteien(self):
# AfD war wegen Listenstreit nicht zur Bürgerschaftswahl 2023 zugelassen.
# Stattdessen ist BiW (Bürger in Wut) als 6. Fraktion in der 21. WP.
parteien = parteien_mit_wahlprogramm("HB")
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "LINKE", "BiW"}
def test_he_has_five_parteien(self):
parteien = parteien_mit_wahlprogramm("HE")
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"}
def test_ni_has_four_parteien(self):
parteien = parteien_mit_wahlprogramm("NI")
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "AfD"}
def test_sl_has_three_parteien(self):
parteien = parteien_mit_wahlprogramm("SL")
assert set(parteien) == {"SPD", "CDU", "AfD"}
def test_sn_has_six_parteien(self):
parteien = parteien_mit_wahlprogramm("SN")
assert set(parteien) == {"CDU", "SPD", "AfD", "BSW", "LINKE", "GRÜNE"}
def test_unknown_bundesland_empty_list(self):
assert parteien_mit_wahlprogramm("XX") == []
# ─────────────────────────────────────────────────────────────────────────────
# embeddings.PROGRAMME consistency cross-check
# get_wahlprogramm — Compat-Adapter
# ─────────────────────────────────────────────────────────────────────────────
class TestEmbeddingsRegistryConsistency:
"""Every entry in WAHLPROGRAMME must also exist in embeddings.PROGRAMME
so the indexer can find it. Mismatch is the kind of bug a manual smoke
misses but would show up during indexing."""
def test_every_wahlprogramm_has_embeddings_entry(self):
from app.embeddings import PROGRAMME
class TestGetWahlprogramm:
def test_returns_programm_for_known_combination(self):
prog = get_wahlprogramm("NRW", "CDU")
assert prog is not None
assert prog["pdf"] == "cdu-nrw-2022.pdf"
assert prog["partei"] == "CDU"
assert prog["bundesland"] == "NRW"
# Match WAHLPROGRAMME-Eintrag → PROGRAMME-Eintrag entweder ueber den
# file-stem (Standard: "cdu-mv-2021" matcht "cdu-mv-2021") ODER ueber
# den `pdf`-Wert in PROGRAMME (BUND-Grundsatzprogramme nutzen kuerzere
# PROGRAMME-Keys wie "cdu-grundsatz" obwohl die Datei
# "cdu-grundsatzprogramm.pdf" heisst).
pdf_to_pid = {p.get("pdf"): pid for pid, p in PROGRAMME.items() if p.get("pdf")}
missing = []
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
file_stem = info["file"].rsplit(".", 1)[0]
if file_stem in PROGRAMME:
continue
if info["file"] in pdf_to_pid:
continue
missing.append(f"{bl}/{partei}{info['file']}")
assert not missing, (
"WAHLPROGRAMME entries missing in embeddings.PROGRAMME:\n "
+ "\n ".join(missing)
)
def test_returns_none_for_unknown_bundesland(self):
assert get_wahlprogramm("XX", "CDU") is None
def test_returns_none_for_unknown_partei(self):
assert get_wahlprogramm("NRW", "BSW") is None # BSW nicht im NRW-Landtag
# ─────────────────────────────────────────────────────────────────────────────
# Strikte Cross-Konsistenz mit programme.PROGRAMME (Drift-Schutz, ADR 0013)
# File existence — every registered pdf must exist on disk
# ─────────────────────────────────────────────────────────────────────────────
class TestWahlprogrammeProgrammeConsistency:
"""WAHLPROGRAMME (legacy, mit titel/seiten/regierung) und
programme.PROGRAMME (zentrale Registry mit Geltungsdaten) speichern
überlappende Felder. Diese Tests fangen stille Drift, bis #222 die
Quelle vereinheitlicht (Compat-Shim).
Invarianten:
- Für jedes (bl, partei) in WAHLPROGRAMME liefert
``aktuelles_wahlprogramm(bl, partei)`` einen Eintrag (nicht None).
- Der ``pdf``-Wert in PROGRAMME stimmt mit ``file`` in WAHLPROGRAMME
überein.
- Die ``partei``-Kurzform stimmt überein (PROGRAMME["partei"] ist die
Kurzform; WAHLPROGRAMME-Key ist auch Kurzform).
"""
class TestFileExistence:
"""Catches typos im pdf-Feld der Programm-Registry, die das
Indexing oder PDF-Download silently brechen würden."""
def test_every_wahlprogramm_has_aktuelles_programm_match(self):
from app.programme import aktuelles_wahlprogramm
mismatches = []
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
prog = aktuelles_wahlprogramm(bl, partei)
if prog is None:
mismatches.append(
f"{bl}/{partei}: aktuelles_wahlprogramm liefert None, "
f"obwohl WAHLPROGRAMME-Eintrag {info['file']} existiert"
)
assert not mismatches, "\n ".join(mismatches)
def test_pdf_filenames_match_between_registries(self):
from app.programme import aktuelles_wahlprogramm
drift = []
for bl, parteien in WAHLPROGRAMME.items():
for partei, info in parteien.items():
prog = aktuelles_wahlprogramm(bl, partei)
if prog is None:
continue # vom Vortest abgedeckt
wp_pdf = info["file"]
pr_pdf = prog.get("pdf")
if wp_pdf != pr_pdf:
drift.append(f"{bl}/{partei}: WAHLPROGRAMME.file={wp_pdf!r} ≠ PROGRAMME.pdf={pr_pdf!r}")
assert not drift, "Drift zwischen WAHLPROGRAMME und PROGRAMME:\n " + "\n ".join(drift)
def test_partei_kurzform_consistency(self):
"""PROGRAMME["partei"] ist die Kurzform (z.B. 'CDU'), nicht
die Langform ('CDU NRW'). Test-Sicherheitsnetz, falls jemand
versehentlich die Langform reinträgt."""
from app.programme import aktuelles_wahlprogramm
wrong = []
for bl, parteien in WAHLPROGRAMME.items():
for partei in parteien.keys():
prog = aktuelles_wahlprogramm(bl, partei)
if prog is None:
continue
if prog.get("partei") != partei:
wrong.append(
f"{bl}/{partei}: PROGRAMME.partei={prog.get('partei')!r} "
f"≠ WAHLPROGRAMME-Key {partei!r}"
)
assert not wrong, "\n ".join(wrong)
def test_no_orphan_aktuelle_programme_in_registry(self):
"""Die andere Richtung: jedes aktuelle Wahlprogramm in PROGRAMME
(gueltig_bis IS NULL, typ='wahlprogramm') muss in WAHLPROGRAMME
vorhanden sein. Sonst ist die Bewertungs-Pipeline blind dafür."""
def test_every_registered_pdf_exists(self):
from app.programme import all_programme
orphans = []
missing = []
for prog in all_programme():
if prog.get("typ") != "wahlprogramm":
pdf = prog.get("pdf")
if not pdf:
continue
if prog.get("gueltig_bis") is not None:
continue # historisches Programm
bl = prog.get("bundesland")
partei = prog.get("partei")
if bl not in WAHLPROGRAMME:
orphans.append(f"{prog['id']}: BL {bl} nicht in WAHLPROGRAMME")
continue
if partei not in WAHLPROGRAMME[bl]:
orphans.append(f"{prog['id']}: {bl}/{partei} fehlt in WAHLPROGRAMME")
assert not orphans, "Aktuelle Wahlprogramme in PROGRAMME ohne WAHLPROGRAMME-Eintrag:\n " + "\n ".join(orphans)
path = REFERENZEN_PATH / pdf
if not path.exists():
missing.append(f"{prog['id']}: {pdf}")
assert not missing, "missing PDFs:\n " + "\n ".join(missing)
# ─────────────────────────────────────────────────────────────────────────────
# load_wahlprogramm_text — Fallback-Pfade (#134 Coverage-Backfill)
# ─────────────────────────────────────────────────────────────────────────────
class TestLoadWahlprogrammText:
def test_returns_empty_for_unknown_combination(self):
from app.wahlprogramme import load_wahlprogramm_text
assert load_wahlprogramm_text("XX", "XYZ") == {}
def test_paged_textfile_used_when_present(self, tmp_path, monkeypatch):
@ -345,7 +105,7 @@ class TestLoadWahlprogrammText:
from app import wahlprogramme as wp_mod
# Mock get_wahlprogramm -> bekannte Datei
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
lambda bl, p: {"file": "test.pdf"})
lambda bl, p: {"pdf": "test.pdf"})
paged = tmp_path / "test-paged.txt"
paged.write_text("--- PAGE 1 ---\nseite eins\n--- PAGE 2 ---\nseite zwei")
monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path)
@ -355,11 +115,11 @@ class TestLoadWahlprogrammText:
assert "seite zwei" in result[2]
def test_falls_back_to_normal_textfile(self, tmp_path, monkeypatch):
"""Ohne paged-Datei wird auf normale .txt-Datei zurueckgefallen,
"""Ohne paged-Datei wird auf normale .txt-Datei zurückgefallen,
komplett unter Seite 1."""
from app import wahlprogramme as wp_mod
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
lambda bl, p: {"file": "test.pdf"})
lambda bl, p: {"pdf": "test.pdf"})
normal = tmp_path / "test.txt"
normal.write_text("flacher text ohne seitenmarker")
monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path)
@ -368,39 +128,50 @@ class TestLoadWahlprogrammText:
assert result == {1: "flacher text ohne seitenmarker"}
def test_returns_empty_when_no_textfile(self, tmp_path, monkeypatch):
"""Weder paged- noch normale Textdatei → leeres Dict."""
"""Weder paged- noch flat-Textdatei vorhanden → leeres Dict."""
from app import wahlprogramme as wp_mod
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
lambda bl, p: {"file": "test.pdf"})
# tmp_path ist leer
lambda bl, p: {"pdf": "test.pdf"})
monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path)
assert wp_mod.load_wahlprogramm_text("X", "Y") == {}
result = wp_mod.load_wahlprogramm_text("X", "Y")
assert result == {}
# ─────────────────────────────────────────────────────────────────────────────
# search_wahlprogramm — Edge cases
# ─────────────────────────────────────────────────────────────────────────────
class TestSearchWahlprogramm:
def test_returns_empty_for_unknown_combination(self):
from app.wahlprogramme import search_wahlprogramm
assert search_wahlprogramm("XX", "XYZ", ["test"]) == []
result = search_wahlprogramm("XX", "XYZ", ["foo"])
assert result == []
def test_returns_empty_when_text_missing(self, monkeypatch):
"""Bekannte Partei + Bundesland aber keine Textdatei → leer."""
"""get_wahlprogramm liefert ein Programm, aber kein paged-Text:
search_wahlprogramm muss [] liefern, nicht crashen."""
from app import wahlprogramme as wp_mod
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
lambda bl, p: {"file": "missing.pdf"})
lambda bl, p: {"pdf": "missing.pdf",
"name": "X Wahlprogramm 2024",
"gueltig_ab": "2024-01-01"})
monkeypatch.setattr(wp_mod, "load_wahlprogramm_text",
lambda bl, p: {})
assert wp_mod.search_wahlprogramm("X", "Y", ["test"]) == []
assert search_wahlprogramm("X", "Y", ["foo"]) == []
# ─────────────────────────────────────────────────────────────────────────────
# find_relevant_quotes — Bundesland-Validierung
# ─────────────────────────────────────────────────────────────────────────────
class TestFindRelevantQuotes:
def test_unknown_bundesland_raises(self):
from app.wahlprogramme import find_relevant_quotes
with pytest.raises(ValueError, match="Unbekanntes Bundesland"):
find_relevant_quotes("Antrag-Text", ["CDU"], bundesland="ZZ")
find_relevant_quotes("text", ["CDU"], "XX")
class TestFormatQuoteForPrompt:
def test_empty_quotes_returns_empty_string(self):
from app.wahlprogramme import format_quote_for_prompt
assert format_quote_for_prompt({}) == ""

View File

@ -0,0 +1,361 @@
"""Generate the new programme.PROGRAMME literal from the lazy-migrated state.
Reads the in-memory PROGRAMME dict (after _migrate_from_legacy ran), enriches
with titel from WAHLPROGRAMME (where available) and seiten from PyMuPDF, then
emits a Python literal sorted by (typ, bundesland, gueltig_ab).
"""
import sys
sys.path.insert(0, "/app")
from pathlib import Path
# Trigger migration
from app import programme as programme_mod
programme_mod._ensure_initialized()
PROGRAMME_NEU = programme_mod.PROGRAMME
# Quelle für titel
from app.wahlprogramme import WAHLPROGRAMME
# Override: titel-Mapping aus _ARCHIVED_SKELETONS (programme.py).
# Diese sind über partei+bundesland+typ identifiziert, weil die Skeleton-IDs
# Jahres-Suffixe haben, die embeddings.PROGRAMME-IDs nicht.
TITEL_OVERRIDE_BY_PARTEI_BL_TYP = {
("CSU", "BY", "grundsatzprogramm-land"): "Für ein neues Miteinander — Grundsatzprogramm der CSU",
("CDU", "NRW", "grundsatzprogramm-land"): "Aufstieg, Sicherheit, Perspektive — Das Nordrhein-Westfalen-Programm",
("CDU", "SN", "grundsatzprogramm-land"): "Zukunftsplan für Sachsen",
("CDU", "LSA", "grundsatzprogramm-land"): "Sachsen-Anhalt. Unsere Verantwortung. Unsere Zukunft.",
("SSW", "SH", "grundsatzprogramm-land"): "SSW Rahmenprogramm",
}
import fitz
REFERENZEN = Path("/app/app/static/referenzen")
def get_titel(prog: dict) -> str | None:
"""titel aus WAHLPROGRAMME[bl][partei] holen, falls vorhanden."""
bl = prog.get("bundesland")
partei = prog.get("partei")
if not bl or not partei:
return None
wp_entry = WAHLPROGRAMME.get(bl, {}).get(partei)
if not wp_entry:
return None
# Nur wenn das pdf zueinander passt
if wp_entry.get("file") != prog.get("pdf"):
return None
return wp_entry.get("titel") or None
def get_seiten(pdf: str) -> int | None:
"""PDF-Seitenzahl via PyMuPDF."""
p = REFERENZEN / pdf
if not p.exists():
return None
try:
doc = fitz.open(p)
n = doc.page_count
doc.close()
return n
except Exception as e:
print(f"# fitz-Fehler bei {pdf}: {e}", file=sys.stderr)
return None
# Schlüssel für Python-Literal — diktiert die Reihenfolge im Output-Dict
KEYS_ORDER = [
"id", "typ", "partei", "bundesland", "wp",
"gueltig_ab", "gueltig_bis",
"name", "titel", "pdf", "seiten",
# Verbleibende, die Tests heute lesen — im Schema-Refactor entfernt:
# "beschluss", "wahl", "hinweis", "ist_grundsatz" — übergehen.
]
def repr_value(v):
if v is None:
return "None"
if isinstance(v, bool):
return "True" if v else "False"
if isinstance(v, (int, float)):
return repr(v)
if isinstance(v, str):
# double-quote-strings, mit Escape von eingebetteten "-Zeichen
escaped = v.replace("\\", "\\\\").replace('"', '\\"')
return f'"{escaped}"'
return repr(v)
def fmt_entry(prog: dict) -> str:
out = []
for k in KEYS_ORDER:
v = prog.get(k)
if v is None and k in ("titel", "wp", "seiten", "bundesland", "gueltig_bis"):
# Optional-Felder: explizit None auch raus, wenn Feld nicht gesetzt
out.append(f'"{k}": None')
elif v is None:
continue # Pflichtfeld nicht gesetzt → überspringen, fällt sonst auf None
else:
out.append(f'"{k}": {repr_value(v)}')
return "{" + ", ".join(out) + "}"
def gruppe(prog: dict):
"""Sortier-Schlüssel."""
typ = prog.get("typ", "zzz")
bl = prog.get("bundesland") or ""
ga = prog.get("gueltig_ab") or "0000"
partei = prog.get("partei", "")
return (typ, bl, ga, partei)
# Anreichern + ausgeben
results = []
for pid, prog in PROGRAMME_NEU.items():
enriched = dict(prog)
enriched.setdefault("id", pid)
# Migration setzt titel = name als Default — das ist kein echter Slogan,
# nur Doppelung. titel ECHT nur dann tragen, wenn entweder explizit
# in WAHLPROGRAMME ("Machen, worauf es ankommt") oder vom Skeleton
# in programme.py (Land-Grundsatzprogramme).
if enriched.get("titel") == enriched.get("name"):
enriched.pop("titel", None)
if not enriched.get("titel"):
t = get_titel(prog)
if t and t != enriched.get("name"):
enriched["titel"] = t
# Override für Land-Grundsatzprogramme (Slogans aus alten Skeletons)
if not enriched.get("titel"):
key = (
prog.get("partei"),
prog.get("bundesland"),
prog.get("typ"),
)
override = TITEL_OVERRIDE_BY_PARTEI_BL_TYP.get(key)
if override:
enriched["titel"] = override
if not enriched.get("seiten"):
s = get_seiten(prog.get("pdf", ""))
if s:
enriched["seiten"] = s
results.append(enriched)
results.sort(key=gruppe)
# Statistik
n_titel = sum(1 for p in results if p.get("titel"))
n_seiten = sum(1 for p in results if p.get("seiten"))
print(f"# {len(results)} Einträge insgesamt", file=sys.stderr)
print(f"# {n_titel} mit titel ({len(results) - n_titel} ohne)", file=sys.stderr)
print(f"# {n_seiten} mit seiten ({len(results) - n_seiten} ohne)", file=sys.stderr)
# Output: vollständige neue programme.py
header = '''"""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)
- ``analyzer.py`` (sucht das zum Antrag passende Wahlprogramm)
- UI (zeigt Geltungszeitraum + zugeordnete Regierung pro Programm)
Siehe ``app/legislaturen.py`` für Wahlperioden + Regierungen Programm-
Daten beschreiben das Dokument selbst, nicht die Regierung, die aus ihm
hervorging. Die Verbindung läuft über
``legislaturen.regierung_zum_zeitpunkt(bundesland, datum)``.
"""
from __future__ import annotations
from pathlib import Path
from typing import Literal, Optional, TypedDict
# ─────────────────────────────────────────────────────────────────────────────
# Schema
# ─────────────────────────────────────────────────────────────────────────────
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, typ, partei, gueltig_ab, name, pdf.
Optional: bundesland, wp, gueltig_bis, titel, seiten.
Was hier bewusst NICHT drin ist:
- ``regierungsbildung`` / ``regierungsende`` gehört zu
``legislaturen.REGIERUNGEN``. Verbindung ProgrammRegierung läuft
über ``legislaturen.regierung_zum_zeitpunkt(bl, antrag_datum)``.
- ``partei`` Langform ("CDU NRW") ableitbar via partei + bundesland.
- ``jahr`` ``int(gueltig_ab[:4])`` reicht.
"""
id: str # eindeutiger Schlüssel, z.B. "cdu-nrw-2022"
typ: ProgrammTyp
partei: str # kanonisch (CDU, BiW, BÜNDNIS 90/DIE GRÜNEN, …)
bundesland: Optional[str] # BL-Code; None nur bei Bundesgrundsatzprogrammen
wp: Optional[int] # Legislatur-Nummer; nur typ=wahlprogramm
gueltig_ab: str # ISO YYYY-MM-DD; bei wahl: Wahltag
gueltig_bis: Optional[str] # ISO; None = aktuell gültig
name: str # voll-qualifiziert für Citation, z.B. "CDU NRW Wahlprogramm 2022"
titel: Optional[str] # Slogan ("Machen, worauf es ankommt"); None wenn nicht erfasst
pdf: str # Dateiname in static/referenzen/
seiten: Optional[int] # PDF-Seitenzahl
REFERENZEN_PATH = Path(__file__).parent / "static" / "referenzen"
KONTEXT_PATH = Path(__file__).parent / "kontext"
# ─────────────────────────────────────────────────────────────────────────────
# Daten — alle 287 Programme (historisch + aktuell), sortiert nach
# (typ, bundesland, gueltig_ab, partei). Auto-generiert aus der bisherigen
# embeddings.PROGRAMME + WAHLPROGRAMME-Lazy-Migration via fitz für seiten.
# Generator: tools/build_programme_literal.py (siehe Commit-Historie).
# ─────────────────────────────────────────────────────────────────────────────
'''
print(header)
# Literal
print("PROGRAMME: dict[str, Programm] = {")
last_gruppe = None
for prog in results:
typ = prog.get("typ", "?")
bl = prog.get("bundesland") or "BUND"
cur = (typ, bl)
if cur != last_gruppe:
print(f"\n # ─── {typ} · {bl} ───")
last_gruppe = cur
print(f' "{prog["id"]}": {fmt_entry(prog)},')
print("}")
# Helper-API
helpers = '''
# ─────────────────────────────────────────────────────────────────────────────
# 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 get_programm_by_pdf(pdf: str) -> Optional[Programm]:
"""Reverse-Lookup über pdf-Dateinamen."""
for prog in PROGRAMME.values():
if prog.get("pdf") == pdf:
return prog
return None
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.
"""
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.
"""
if bundesland is not None:
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
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
def all_programme() -> list[Programm]:
"""Alle eingetragenen Programme."""
return list(PROGRAMME.values())
'''
print(helpers)