diff --git a/app/bundeslaender.py b/app/bundeslaender.py new file mode 100644 index 0000000..fab9df9 --- /dev/null +++ b/app/bundeslaender.py @@ -0,0 +1,361 @@ +"""Zentrale Konfiguration aller 16 deutschen Bundesländer. + +Dieses Modul ist die Single Source of Truth für alle bundeslandspezifischen +Daten: Parlamente, Regierungen, Wahlperioden, Doku-Systeme, etc. Andere +Module (main.py, parlamente.py, wahlprogramme.py, analyzer.py) lesen +ausschließlich von hier. + +Stand: April 2026. Nach jeder Landtagswahl bzw. Regierungsbildung müssen +die betroffenen Einträge aktualisiert werden. + +Datenquellen: Wikipedia, offizielle Landtagsseiten, parlamentsspiegel.de, +https://github.com/okfde/dokukratie (für Doku-System-Zuordnung). +""" + +from dataclasses import dataclass, field +from typing import Optional + + +@dataclass +class Bundesland: + """Konfiguration eines deutschen Bundeslands. + + Attributes: + code: Übliches Kürzel im politischen Sprachgebrauch (NRW, BY, LSA…). + Bei Mehrdeutigkeit ISO-3166-2-DE-Suffix; Sachsen-Anhalt nutzt + jedoch das politisch dominante "LSA" statt ISO "ST". + name: Vollständiger Landesname. + parlament_name: Offizieller Name des Parlaments. + wahlperiode: Aktuelle Wahlperiode als Zahl. + wahlperiode_start: Beginn der aktuellen WP (ISO-Datum). + naechste_wahl: Nächste reguläre Landtagswahl (ISO-Datum), oder None + wenn noch nicht festgesetzt. + regierungsfraktionen: Parteien der aktuellen Landesregierung in + Reihenfolge der Größe. + landtagsfraktionen: Alle aktuell im Landtag vertretenen Fraktionen. + doku_system: Verwendetes Parlamentsdokumentationssystem. + Werte: "OPAL", "StarWeb", "ParlDok", "PARDOK", "PARLIS", + "PARiS", "Eigensystem". + doku_base_url: Basis-URL der Parlamentsdokumentation. + drucksache_format: Beispielhaftes Format einer Drucksachen-ID, + z.B. "18/12345" für NRW WP18. + dokukratie_scraper: Code-Name des Dokukratie-Scrapers (falls + vorhanden), nützlich für künftige Adapter-Implementierung. + aktiv: Ob das Bundesland im Frontend auswählbar und im Analyzer + unterstützt ist. Inaktive Bundesländer werden im UI als + "(bald)" angezeigt und sind disabled. + anmerkung: Optionale Hinweise zu Sondersituationen (z.B. + Koalitionsverhandlungen, jüngste Wahl, geschätzte Termine). + """ + + code: str + name: str + parlament_name: str + wahlperiode: int + wahlperiode_start: str + naechste_wahl: Optional[str] + regierungsfraktionen: list[str] + landtagsfraktionen: list[str] + doku_system: str + doku_base_url: str + drucksache_format: str + dokukratie_scraper: Optional[str] + aktiv: bool = False + anmerkung: str = "" + + +# Hauptregister: code -> Bundesland-Instanz. +# Reihenfolge alphabetisch nach offiziellem Namen für stabile UI-Sortierung. +BUNDESLAENDER: dict[str, Bundesland] = { + "BW": Bundesland( + code="BW", + name="Baden-Württemberg", + parlament_name="Landtag von Baden-Württemberg", + wahlperiode=17, + wahlperiode_start="2021-05-01", + naechste_wahl="2031-03-08", + regierungsfraktionen=["GRÜNE", "CDU"], + landtagsfraktionen=["GRÜNE", "CDU", "AfD", "SPD", "FDP"], + doku_system="PARLIS", + doku_base_url="https://parlis.landtag-bw.de", + drucksache_format="17/12345", + dokukratie_scraper="bw", + anmerkung=( + "Wahl zum 18. Landtag fand am 08.03.2026 statt; Koalitionsverhandlungen " + "GRÜNE+CDU laufen, Kabinett Kretschmann III geschäftsführend. Nach " + "Konstituierung des 18. LT ca. Mai 2026 müssen WP und Wahltermin aktualisiert werden." + ), + ), + "BY": Bundesland( + code="BY", + name="Bayern", + parlament_name="Bayerischer Landtag", + wahlperiode=19, + wahlperiode_start="2023-10-30", + naechste_wahl="2028-10-08", + regierungsfraktionen=["CSU", "FW"], + landtagsfraktionen=["CSU", "GRÜNE", "FW", "AfD", "SPD"], + doku_system="Eigensystem", + doku_base_url="https://www.bayern.landtag.de", + drucksache_format="19/1234", + dokukratie_scraper="by", + anmerkung="Wahltermin 2028 noch nicht offiziell festgesetzt; Schätzung Herbst 2028.", + ), + "BE": Bundesland( + code="BE", + name="Berlin", + parlament_name="Abgeordnetenhaus von Berlin", + wahlperiode=19, + wahlperiode_start="2023-04-27", + naechste_wahl="2026-09-20", + regierungsfraktionen=["CDU", "SPD"], + landtagsfraktionen=["CDU", "SPD", "GRÜNE", "LINKE", "AfD"], + doku_system="PARDOK", + doku_base_url="https://pardok.parlament-berlin.de", + drucksache_format="19/1234", + dokukratie_scraper="be", + anmerkung=( + "PARDOK basiert auf StarWeb-Software (portala-Frontend). Berlin bietet " + "zusätzlich Open-Data-XML unter parlament-berlin.de/dokumente/open-data." + ), + ), + "BB": Bundesland( + code="BB", + name="Brandenburg", + parlament_name="Landtag Brandenburg", + wahlperiode=8, + wahlperiode_start="2024-10-23", + naechste_wahl="2029-09-23", + regierungsfraktionen=["SPD", "BSW"], + landtagsfraktionen=["SPD", "AfD", "CDU", "BSW"], + doku_system="StarWeb", + doku_base_url="https://www.parlamentsdokumentation.brandenburg.de", + drucksache_format="8/1234", + dokukratie_scraper="bb", + anmerkung="Kabinett Woidke IV (SPD-BSW) seit Dezember 2024. Knappe Mehrheit (zwei Sitze).", + ), + "HB": Bundesland( + code="HB", + name="Bremen", + parlament_name="Bremische Bürgerschaft", + wahlperiode=21, + wahlperiode_start="2023-07-05", + naechste_wahl="2027-05-09", + regierungsfraktionen=["SPD", "GRÜNE", "LINKE"], + landtagsfraktionen=["SPD", "CDU", "GRÜNE", "LINKE", "AfD", "BiW"], + doku_system="PARiS", + doku_base_url="https://paris.bremische-buergerschaft.de", + drucksache_format="21/1234", + dokukratie_scraper="hb", + anmerkung=( + "PARiS basiert auf StarWeb. AfD durch Listenstreichung 2023 nicht im Landtag, " + "stattdessen Bürger in Wut (BiW). Wahltag 2027 noch nicht festgesetzt." + ), + ), + "HH": Bundesland( + code="HH", + name="Hamburg", + parlament_name="Hamburgische Bürgerschaft", + wahlperiode=23, + wahlperiode_start="2025-03-26", + naechste_wahl="2030-03-03", + regierungsfraktionen=["SPD", "GRÜNE"], + landtagsfraktionen=["SPD", "CDU", "GRÜNE", "LINKE", "AfD"], + doku_system="ParlDok", + doku_base_url="https://www.buergerschaft-hh.de/parldok", + drucksache_format="23/1234", + dokukratie_scraper="hh", + anmerkung="Wahl am 02.03.2025; Senat Tschentscher III seit 07.05.2025 vereidigt.", + ), + "HE": Bundesland( + code="HE", + name="Hessen", + parlament_name="Hessischer Landtag", + wahlperiode=21, + wahlperiode_start="2024-01-18", + naechste_wahl="2028-10-22", + regierungsfraktionen=["CDU", "SPD"], + landtagsfraktionen=["CDU", "AfD", "SPD", "GRÜNE", "FDP"], + doku_system="StarWeb", + doku_base_url="https://starweb.hessen.de/starweb/LIS", + drucksache_format="21/1234", + dokukratie_scraper="he", + anmerkung="Wahltermin 2028 ist Schätzung.", + ), + "MV": Bundesland( + code="MV", + name="Mecklenburg-Vorpommern", + parlament_name="Landtag Mecklenburg-Vorpommern", + wahlperiode=8, + wahlperiode_start="2021-10-26", + naechste_wahl="2026-09-20", + regierungsfraktionen=["SPD", "LINKE"], + landtagsfraktionen=["SPD", "AfD", "CDU", "LINKE", "GRÜNE", "FDP"], + doku_system="ParlDok", + doku_base_url="https://www.dokumentation.landtag-mv.de", + drucksache_format="8/1234", + dokukratie_scraper="mv", + anmerkung="Wahltag offiziell auf 20.09.2026 festgelegt.", + ), + "NI": Bundesland( + code="NI", + name="Niedersachsen", + parlament_name="Niedersächsischer Landtag", + wahlperiode=19, + wahlperiode_start="2022-11-08", + naechste_wahl="2027-10-10", + regierungsfraktionen=["SPD", "GRÜNE"], + landtagsfraktionen=["SPD", "CDU", "GRÜNE", "AfD"], + doku_system="StarWeb", + doku_base_url="https://www.landtag-niedersachsen.de", + drucksache_format="19/12345", + dokukratie_scraper="ni", + anmerkung=( + "Wahltermin Herbst 2027 (zwischen 11.07. und 03.10.2027) noch nicht festgesetzt; " + "geschätzt. Olaf Lies (SPD) seit 20.05.2025 Ministerpräsident." + ), + ), + "NRW": Bundesland( + code="NRW", + name="Nordrhein-Westfalen", + parlament_name="Landtag Nordrhein-Westfalen", + wahlperiode=18, + wahlperiode_start="2022-06-01", + naechste_wahl="2027-05-15", + regierungsfraktionen=["CDU", "GRÜNE"], + landtagsfraktionen=["CDU", "SPD", "GRÜNE", "FDP", "AfD"], + doku_system="OPAL", + doku_base_url="https://opal.landtag.nrw.de", + drucksache_format="18/12345", + dokukratie_scraper="nw", + aktiv=True, + anmerkung=( + "OPAL in NRW ist eine eigene Implementierung, nicht identisch mit dem " + "StarWeb-basierten OPAL in RLP. Wahltermin 2027 ist Schätzung." + ), + ), + "RP": Bundesland( + code="RP", + name="Rheinland-Pfalz", + parlament_name="Landtag Rheinland-Pfalz", + wahlperiode=18, + wahlperiode_start="2021-05-18", + naechste_wahl="2031-03-22", + regierungsfraktionen=["SPD", "GRÜNE", "FDP"], + landtagsfraktionen=["SPD", "CDU", "AfD", "GRÜNE", "FREIE WÄHLER", "FDP"], + doku_system="StarWeb", + doku_base_url="https://opal.rlp.de", + drucksache_format="18/12345", + dokukratie_scraper="rp", + anmerkung=( + "OPAL in RLP basiert auf StarWeb. Wahl zum 19. Landtag fand am 22.03.2026 " + "statt; Koalitionsverhandlungen CDU+SPD laufen, Kabinett Schweitzer I " + "geschäftsführend. Nach Konstituierung müssen WP und Wahltermin aktualisiert werden." + ), + ), + "SL": Bundesland( + code="SL", + name="Saarland", + parlament_name="Landtag des Saarlandes", + wahlperiode=17, + wahlperiode_start="2022-04-25", + naechste_wahl="2027-04-18", + regierungsfraktionen=["SPD"], + landtagsfraktionen=["SPD", "CDU", "AfD"], + doku_system="Eigensystem", + doku_base_url="https://www.landtag-saar.de", + drucksache_format="17/1234", + dokukratie_scraper="sl", + anmerkung=( + "Einzige SPD-Alleinregierung in Deutschland. AfD-Status im 17. LT vor " + "produktiver Nutzung verifizieren." + ), + ), + "SN": Bundesland( + code="SN", + name="Sachsen", + parlament_name="Sächsischer Landtag", + wahlperiode=8, + wahlperiode_start="2024-10-01", + naechste_wahl="2029-09-02", + regierungsfraktionen=["CDU", "SPD"], + landtagsfraktionen=["CDU", "AfD", "BSW", "SPD", "LINKE", "GRÜNE"], + doku_system="ParlDok", + doku_base_url="https://edas.landtag.sachsen.de", + drucksache_format="8/1234", + dokukratie_scraper="sn", + anmerkung=( + "Minderheitsregierung CDU+SPD (Kabinett Kretschmer III seit 18.12.2024). " + "Doku-System EDAS basiert auf ParlDok." + ), + ), + "LSA": Bundesland( + code="LSA", + name="Sachsen-Anhalt", + parlament_name="Landtag von Sachsen-Anhalt", + wahlperiode=8, + wahlperiode_start="2021-07-06", + naechste_wahl="2026-09-06", + regierungsfraktionen=["CDU", "SPD", "FDP"], + landtagsfraktionen=["CDU", "AfD", "LINKE", "SPD", "GRÜNE", "FDP"], + doku_system="StarWeb", + doku_base_url="https://padoka.landtag.sachsen-anhalt.de", + drucksache_format="8/1234", + dokukratie_scraper="st", + anmerkung=( + "ISO-Code wäre ST; LSA ist im politischen Sprachgebrauch dominant. " + "Sven Schulze (CDU) seit 28.01.2026 MP nach Rücktritt Haseloff. " + "PADOKA = Parlamentsdokumentationssystem auf StarWeb-6.0.01-Basis." + ), + ), + "SH": Bundesland( + code="SH", + name="Schleswig-Holstein", + parlament_name="Schleswig-Holsteinischer Landtag", + wahlperiode=20, + wahlperiode_start="2022-06-07", + naechste_wahl="2027-04-18", + regierungsfraktionen=["CDU", "GRÜNE"], + landtagsfraktionen=["CDU", "GRÜNE", "SPD", "FDP", "SSW"], + doku_system="StarWeb", + doku_base_url="https://www.landtag.ltsh.de", + drucksache_format="20/1234", + dokukratie_scraper="sh", + anmerkung="SSW ist von der 5%-Hürde befreit.", + ), + "TH": Bundesland( + code="TH", + name="Thüringen", + parlament_name="Thüringer Landtag", + wahlperiode=8, + wahlperiode_start="2024-10-01", + naechste_wahl="2029-09-01", + regierungsfraktionen=["CDU", "BSW", "SPD"], + landtagsfraktionen=["AfD", "CDU", "LINKE", "BSW", "SPD"], + doku_system="StarWeb", + doku_base_url="https://parldok.thueringen.de", + drucksache_format="8/1234", + dokukratie_scraper="th", + anmerkung=( + "Erste Brombeer-Koalition Deutschlands (CDU+BSW+SPD) als Minderheitsregierung " + "mit 44 von 88 Sitzen. Mario Voigt (CDU) seit Dezember 2024 MP." + ), + ), +} + + +def get(code: str) -> Optional[Bundesland]: + """Bundesland-Konfig per Code abrufen, oder None.""" + return BUNDESLAENDER.get(code) + + +def aktive_bundeslaender() -> list[Bundesland]: + """Alle aktuell aktiven (im Analyzer unterstützten) Bundesländer.""" + return [bl for bl in BUNDESLAENDER.values() if bl.aktiv] + + +def alle_bundeslaender() -> list[Bundesland]: + """Alle 16 Bundesländer (aktive zuerst, dann alphabetisch nach Name).""" + aktiv = sorted([bl for bl in BUNDESLAENDER.values() if bl.aktiv], key=lambda b: b.name) + inaktiv = sorted([bl for bl in BUNDESLAENDER.values() if not bl.aktiv], key=lambda b: b.name) + return aktiv + inaktiv diff --git a/app/main.py b/app/main.py index cb7a716..4ec0d4a 100644 --- a/app/main.py +++ b/app/main.py @@ -17,6 +17,7 @@ from .database import ( search_assessments ) from .parlamente import get_adapter, ADAPTERS +from .bundeslaender import alle_bundeslaender from .analyzer import analyze_antrag from .report import generate_html_report, generate_pdf_report from .embeddings import ( @@ -87,10 +88,8 @@ async def index(request: Request): "request": request, "app_name": settings.app_name, "bundeslaender": [ - {"code": "NRW", "name": "Nordrhein-Westfalen", "active": True}, - {"code": "BY", "name": "Bayern", "active": False}, - {"code": "BW", "name": "Baden-Württemberg", "active": False}, - {"code": "HE", "name": "Hessen", "active": False}, + {"code": bl.code, "name": bl.name, "active": bl.aktiv} + for bl in alle_bundeslaender() ], }) @@ -495,22 +494,8 @@ async def run_drucksache_analysis( async def list_bundeslaender(): """List available bundesländer with their status.""" return [ - {"code": "NRW", "name": "Nordrhein-Westfalen", "active": True}, - {"code": "BY", "name": "Bayern", "active": False}, - {"code": "BW", "name": "Baden-Württemberg", "active": False}, - {"code": "HE", "name": "Hessen", "active": False}, - {"code": "NI", "name": "Niedersachsen", "active": False}, - {"code": "RP", "name": "Rheinland-Pfalz", "active": False}, - {"code": "SH", "name": "Schleswig-Holstein", "active": False}, - {"code": "SL", "name": "Saarland", "active": False}, - {"code": "SN", "name": "Sachsen", "active": False}, - {"code": "ST", "name": "Sachsen-Anhalt", "active": False}, - {"code": "TH", "name": "Thüringen", "active": False}, - {"code": "BB", "name": "Brandenburg", "active": False}, - {"code": "MV", "name": "Mecklenburg-Vorpommern", "active": False}, - {"code": "HH", "name": "Hamburg", "active": False}, - {"code": "HB", "name": "Bremen", "active": False}, - {"code": "BE", "name": "Berlin", "active": False}, + {"code": bl.code, "name": bl.name, "active": bl.aktiv} + for bl in alle_bundeslaender() ]