gwoe-antragspruefer/app/bundeslaender.py
Dotty Dotter 9e0f11f7c9 Activate Berlin (PARDOK) — search-only MVP (#3)
PortalaAdapter is now parameterizable and serves both LSA and Berlin
from a single class. Berlin is activated as the third live bundesland
(after NRW + LSA), with the deliberate caveat that the LTW 2023
Wahlprogramme are not yet indexed.

PortalaAdapter refactor
- Class attributes (bundesland, name, base_url, db_id, wahlperiode)
  moved into the constructor. New optional parameters:
    - portala_path: "/portal" for LSA, "/portala" for Berlin
    - document_type: "Antrag" for LSA, None for Berlin (BE's ETYPF
      index uses different value strings; the document_type subtree
      is dropped from the action.search.json tree)
    - pdf_url_prefix: "/files/" by default; absolute URLs in the hit
      list are passed through unchanged (Berlin embeds full
      starweb/adis/citat/... links)
    - date_window_days: 730 for LSA, 180 for BE (BE has ~10x more
      documents per WP, narrower window keeps payloads bounded)
- _build_search_body builds the JSON tree dynamically: when
  document_type is None, the entire ETYPF/DTYPF/DART subtree is
  omitted, mirrored in the parsed/sref display strings as well.
- _parse_hit_list_html now auto-detects between two formats:
    1. LSA-style: <pre>$VAR1 = …</pre> Perl Data::Dumper records
       (existing parser, untouched).
    2. Berlin-style: production HTML cards with efxRecordRepeater
       divs, h3 titles, h6 metadata lines containing the document
       type, drucksachen-id and date, plus a direct <a href="…pdf">
       to the PDF on the same host.
- Berlin extracts originator parties from the h6 line ("Antrag CDU,
  SPD" → ["CDU","SPD"], typ "Antrag") via the new word-boundary
  _normalize_fraktion regex.
- _normalize_fraktion rewritten with regex word boundaries, fixing a
  long-standing bug where comma-separated fraction lists like
  "CDU, SPD" failed to match CDU. Also picks up BSW for the
  Brombeer/SPD-BSW landtage and "Senat von Berlin" as Landesregierung.

bundeslaender.py
- BE flipped to aktiv=True. anmerkung documents the Wahlprogramm-
  Lücke and the auto-detected hit-list format.

Live verified against pardok.parlament-berlin.de:
- WP 19 with 180-day date window returns 2962 hits, page 1 contains
  5 records all with title, drucksache, date, PDF URL.
- 19/3107 ("Kleingewässerprogramm") correctly extracted as Antrag of
  CDU+SPD; 19/3104-3106 as Vorlagen zur Beschlussfassung; 19/3108 as
  Vorlage zur Kenntnisnahme.
- LSA still returns the same 5 current Anträge of März 2026 — no
  regression from the refactor.

Known limitation (will be tracked as a follow-up issue)
- Berlin Wahlprogramme zur LTW 2023 are not yet indexed in the
  embeddings DB. The 2023 PDFs are no longer linked from the live
  party websites (which currently feature 2026 draft programmes), and
  Wayback has no snapshots. The analyzer therefore falls back to
  bundesländer-übergreifende Grundsatzprogramme for BE Anträge until
  the 2023 PDFs are sourced manually.

Refs #3.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 23:33:16 +02:00

374 lines
15 KiB
Python

"""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",
aktiv=True,
anmerkung=(
"PARDOK = portala/eUI-Framework (gleiche Engine wie LSA-PADOKA, "
"unter /portala/ statt /portal/). Hit list arrives as production "
"HTML cards instead of LSA-style Perl Data::Dumper blocks — "
"PortalaAdapter auto-detects both formats. document_type=None "
"for BE because Berlin's ETYPF index uses different value strings "
"than LSA. Wahlprogramme zur LTW 2023 sind noch nicht indexiert "
"(Folge-Issue) — Analyse läuft daher mit Grundsatzprogramm-"
"Zitaten als Fallback. Open-Data-XML unter "
"parlament-berlin.de/dokumente/open-data ist eine alternative "
"Datenquelle, derzeit nicht verwendet."
),
),
"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="PARDOK",
doku_base_url="https://padoka.landtag.sachsen-anhalt.de",
drucksache_format="8/1234",
dokukratie_scraper="st",
aktiv=True,
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 wurde von StarWeb auf das portala/eUI-Framework migriert "
"(gleiche Engine wie Berlin/PARDOK). dokukratie's st.yml ist veraltet. "
"Suche läuft via POST /portal/browse.tt.json + report.tt.html."
),
),
"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