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).
2026-05-09 00:37:35 +02:00
|
|
|
"""Wahlprogramm-Suche (Keyword-Fallback, wenn Embeddings nicht verfügbar).
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
- ``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``.
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
"""
|
2026-03-28 22:30:24 +01:00
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
from .bundeslaender import BUNDESLAENDER
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
from .programme import (
|
|
|
|
|
aktuelles_wahlprogramm,
|
|
|
|
|
parteien_mit_wahlprogramm,
|
|
|
|
|
)
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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] = {
|
|
|
|
|
"NRW": "wahlprogramme-nrw-2022.md",
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
REFERENZEN_PATH = Path(__file__).parent / "static" / "referenzen"
|
|
|
|
|
KONTEXT_PATH = Path(__file__).parent / "kontext"
|
|
|
|
|
|
|
|
|
|
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
def get_wahlprogramm(bundesland: str, partei: str) -> Optional[dict]:
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
"""Liefert das aktuell gültige Wahlprogramm dieser Partei in dem
|
|
|
|
|
Bundesland — als ``programme.Programm``-Dict (oder ``None``).
|
feat: regierungsbildung+regierungsende in WAHLPROGRAMME, gueltig_ab in Grundsatzprogrammen
WAHLPROGRAMME erweitert:
- Pflichtfelder regierungsbildung (Vereidigung Kabinett) + regierungsende
(None=laufend) pro Wahlprogramm. Geltung beginnt mit Regierungs-
bildung, NICHT Wahltag.
- 6 fehlende Bundeslaender ergaenzt: BY, HB, HE, NI, SL, SN.
- BUND BTW-2025: 8 Wahlprogramme (CDU, CSU, SPD, GRUENE, FDP, AfD, LINKE,
BSW) ersetzen die bisherigen Grundsatzprogramm-Eintraege. Vereidigung
Merz I 2025-05-06.
- Helper regierungsbildung_for(), regierungsende_for(), regierung_aktuell().
embeddings.PROGRAMME erweitert:
- 6 Grundsatzprogramme (CDU 2024, SPD 2007, GRUENE 2020, FDP 2012,
AfD 2016, LINKE 2011) tragen jetzt Beschluss-Datum als gueltig_ab,
gueltig_bis=None.
- 8 BTW-2025-Wahlprogramme als Indexer-Eintraege.
- FDP-Programm-Name auf den korrekten "Karlsruher Freiheitsthesen 2012"
umgestellt (vorher generisch "FDP Grundsatzprogramm 2012").
Tests: 71 grün (test_wahlprogramme + test_embeddings + test_legislaturen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:27:09 +02:00
|
|
|
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
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.
|
2026-05-08 14:25:35 +02:00
|
|
|
"""
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
return aktuelles_wahlprogramm(bundesland, partei)
|
feat: regierungsbildung+regierungsende in WAHLPROGRAMME, gueltig_ab in Grundsatzprogrammen
WAHLPROGRAMME erweitert:
- Pflichtfelder regierungsbildung (Vereidigung Kabinett) + regierungsende
(None=laufend) pro Wahlprogramm. Geltung beginnt mit Regierungs-
bildung, NICHT Wahltag.
- 6 fehlende Bundeslaender ergaenzt: BY, HB, HE, NI, SL, SN.
- BUND BTW-2025: 8 Wahlprogramme (CDU, CSU, SPD, GRUENE, FDP, AfD, LINKE,
BSW) ersetzen die bisherigen Grundsatzprogramm-Eintraege. Vereidigung
Merz I 2025-05-06.
- Helper regierungsbildung_for(), regierungsende_for(), regierung_aktuell().
embeddings.PROGRAMME erweitert:
- 6 Grundsatzprogramme (CDU 2024, SPD 2007, GRUENE 2020, FDP 2012,
AfD 2016, LINKE 2011) tragen jetzt Beschluss-Datum als gueltig_ab,
gueltig_bis=None.
- 8 BTW-2025-Wahlprogramme als Indexer-Eintraege.
- FDP-Programm-Name auf den korrekten "Karlsruher Freiheitsthesen 2012"
umgestellt (vorher generisch "FDP Grundsatzprogramm 2012").
Tests: 71 grün (test_wahlprogramme + test_embeddings + test_legislaturen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:27:09 +02:00
|
|
|
|
|
|
|
|
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
def load_wahlprogramm_text(bundesland: str, partei: str) -> dict[int, str]:
|
2026-03-28 22:30:24 +01:00
|
|
|
"""Lädt Wahlprogramm-Text mit Seitenzuordnung.
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
Returns:
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
Dict mit Seitennummer -> Text. Leer, wenn kein Wahlprogramm hinterlegt
|
|
|
|
|
oder die paged-Textdatei fehlt.
|
2026-03-28 22:30:24 +01:00
|
|
|
"""
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
info = get_wahlprogramm(bundesland, partei)
|
|
|
|
|
if not info:
|
2026-03-28 22:30:24 +01:00
|
|
|
return {}
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
pdf_name = info.get("pdf", "")
|
|
|
|
|
if not pdf_name:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
paged_file = KONTEXT_PATH / pdf_name.replace('.pdf', '-paged.txt')
|
2026-03-28 22:30:24 +01:00
|
|
|
if not paged_file.exists():
|
|
|
|
|
# Fallback: Normale Textdatei
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
txt_file = KONTEXT_PATH / pdf_name.replace('.pdf', '.txt')
|
2026-03-28 22:30:24 +01:00
|
|
|
if txt_file.exists():
|
|
|
|
|
return {1: txt_file.read_text()}
|
|
|
|
|
return {}
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
text = paged_file.read_text()
|
|
|
|
|
pages = {}
|
|
|
|
|
current_page = 1
|
|
|
|
|
current_text = []
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
for line in text.split('\n'):
|
|
|
|
|
if line.startswith('--- PAGE '):
|
|
|
|
|
if current_text:
|
|
|
|
|
pages[current_page] = '\n'.join(current_text)
|
|
|
|
|
match = re.search(r'PAGE (\d+)', line)
|
|
|
|
|
if match:
|
|
|
|
|
current_page = int(match.group(1))
|
|
|
|
|
current_text = []
|
|
|
|
|
else:
|
|
|
|
|
current_text.append(line)
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
if current_text:
|
|
|
|
|
pages[current_page] = '\n'.join(current_text)
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
return pages
|
|
|
|
|
|
|
|
|
|
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
def search_wahlprogramm(
|
|
|
|
|
bundesland: str,
|
|
|
|
|
partei: str,
|
|
|
|
|
keywords: list[str],
|
|
|
|
|
max_results: int = 3,
|
|
|
|
|
) -> list[dict]:
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
"""Sucht relevante Passagen in einem Wahlprogramm (Keyword-basiert,
|
|
|
|
|
Fallback wenn die Embeddings-DB nicht da ist).
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
Returns:
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
Liste von {bundesland, partei, seite, text, score, url, quelle}.
|
2026-03-28 22:30:24 +01:00
|
|
|
"""
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
info = get_wahlprogramm(bundesland, partei)
|
|
|
|
|
if not info:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
pages = load_wahlprogramm_text(bundesland, partei)
|
2026-03-28 22:30:24 +01:00
|
|
|
if not pages:
|
|
|
|
|
return []
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
pdf_name = info.get("pdf", "")
|
|
|
|
|
name = info.get("name") or partei
|
|
|
|
|
jahr = info.get("gueltig_ab", "")[:4] or "?"
|
|
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
results = []
|
|
|
|
|
keywords_lower = [k.lower() for k in keywords]
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
for page_num, text in pages.items():
|
|
|
|
|
text_lower = text.lower()
|
|
|
|
|
score = sum(1 for kw in keywords_lower if kw in text_lower)
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
if score > 0:
|
|
|
|
|
paragraphs = text.split('\n\n')
|
|
|
|
|
relevant_paragraphs = []
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
for para in paragraphs:
|
|
|
|
|
para_clean = para.strip()
|
|
|
|
|
if len(para_clean) < 50:
|
|
|
|
|
continue
|
|
|
|
|
para_lower = para_clean.lower()
|
|
|
|
|
if any(kw in para_lower for kw in keywords_lower):
|
|
|
|
|
relevant_paragraphs.append(para_clean)
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
if relevant_paragraphs:
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
best_para = max(
|
|
|
|
|
relevant_paragraphs,
|
|
|
|
|
key=lambda p: sum(1 for kw in keywords_lower if kw in p.lower()),
|
|
|
|
|
)
|
2026-03-28 22:30:24 +01:00
|
|
|
if len(best_para) > 300:
|
|
|
|
|
best_para = best_para[:297] + "..."
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
results.append({
|
|
|
|
|
"partei": partei,
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
"bundesland": bundesland,
|
2026-03-28 22:30:24 +01:00
|
|
|
"seite": page_num,
|
|
|
|
|
"text": best_para,
|
|
|
|
|
"score": score,
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
"url": f"/static/referenzen/{pdf_name}#page={page_num}",
|
|
|
|
|
"quelle": f"{name}, S. {page_num}",
|
2026-03-28 22:30:24 +01:00
|
|
|
})
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
results.sort(key=lambda x: x['score'], reverse=True)
|
|
|
|
|
return results[:max_results]
|
|
|
|
|
|
|
|
|
|
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
def find_relevant_quotes(
|
|
|
|
|
antrag_text: str,
|
|
|
|
|
fraktionen: list[str],
|
|
|
|
|
bundesland: str,
|
|
|
|
|
) -> dict[str, list[dict]]:
|
2026-03-28 22:30:24 +01:00
|
|
|
"""Findet relevante Zitate aus Wahlprogrammen für einen Antrag.
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
Args:
|
|
|
|
|
antrag_text: Volltext des Antrags
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
fraktionen: Liste der einreichenden Fraktionen
|
|
|
|
|
bundesland: Bundesland-Code (Pflichtparameter; bestimmt, welche
|
|
|
|
|
Wahlprogramme durchsucht werden und welche Regierungsfraktionen
|
|
|
|
|
zusätzlich einbezogen werden).
|
|
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
Returns:
|
|
|
|
|
Dict mit Partei -> Liste von Zitaten
|
|
|
|
|
"""
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
if bundesland not in BUNDESLAENDER:
|
|
|
|
|
raise ValueError(f"Unbekanntes Bundesland: {bundesland}")
|
|
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
# Extrahiere Keywords aus Antrag (einfache Heuristik)
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
stopwords = {
|
|
|
|
|
'der', 'die', 'das', 'und', 'oder', 'für', 'mit', 'von', 'zu', 'auf',
|
|
|
|
|
'ist', 'sind', 'wird', 'werden', 'hat', 'haben', 'ein', 'eine', 'einer',
|
|
|
|
|
'den', 'dem', 'des', 'im', 'in', 'an', 'bei', 'nach', 'über', 'unter',
|
|
|
|
|
'durch', 'als', 'auch', 'nur', 'noch', 'aber', 'wenn', 'dass', 'sich',
|
|
|
|
|
'nicht', 'wie', 'so', 'aus', 'zum', 'zur', 'vom', 'beim', 'seit', 'bis',
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
words = re.findall(r'\b[A-Za-zäöüÄÖÜß]{4,}\b', antrag_text)
|
|
|
|
|
keywords = [w for w in words if w.lower() not in stopwords]
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
|
|
|
|
word_freq: dict[str, int] = {}
|
2026-03-28 22:30:24 +01:00
|
|
|
for w in keywords:
|
|
|
|
|
w_lower = w.lower()
|
|
|
|
|
word_freq[w_lower] = word_freq.get(w_lower, 0) + 1
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
top_keywords = sorted(word_freq.keys(), key=lambda x: word_freq[x], reverse=True)[:15]
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
|
|
|
|
# Antragsteller + Regierungsfraktionen des Bundeslands
|
|
|
|
|
regierungsfraktionen = BUNDESLAENDER[bundesland].regierungsfraktionen
|
|
|
|
|
parteien_to_search = set(fraktionen) | set(regierungsfraktionen)
|
|
|
|
|
|
|
|
|
|
quotes: dict[str, list[dict]] = {}
|
2026-03-28 22:30:24 +01:00
|
|
|
for partei in parteien_to_search:
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
if get_wahlprogramm(bundesland, partei):
|
|
|
|
|
found = search_wahlprogramm(bundesland, partei, top_keywords, max_results=2)
|
2026-03-28 22:30:24 +01:00
|
|
|
if found:
|
|
|
|
|
quotes[partei] = found
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
return quotes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_quote_for_prompt(quotes: dict[str, list[dict]]) -> str:
|
|
|
|
|
"""Formatiert Zitate für den LLM-Prompt."""
|
|
|
|
|
if not quotes:
|
|
|
|
|
return ""
|
Refactor wahlprogramme/embeddings/analyzer for multi-state (#5)
Atomic refactor of the three modules that previously hardcoded NRW
behaviour. After this commit, every analysis path consults the central
BUNDESLAENDER registry for governing fractions, parliament name, and
state metadata.
wahlprogramme.py
- WAHLPROGRAMME is now nested {bundesland: {partei: meta}}; NRW data
hoisted unchanged under the "NRW" key.
- New WAHLPROGRAMM_KONTEXT_FILES dict maps a state to its overview
markdown file (currently only NRW).
- find_relevant_quotes(text, fraktionen, bundesland) — bundesland is
now a required positional. Governing fractions for the requested
state are merged with the submitting fractions before lookup.
- Helpers get_wahlprogramm() and parteien_mit_wahlprogramm() expose
the new shape to other modules.
- ValueError on unknown bundesland (no silent fallback).
embeddings.py
- Schema migration in init_embeddings_db: adds a `bundesland` column
to the chunks table when missing, plus an index, and backfills
existing rows from the PROGRAMME registry. Grundsatzprogramme
(federal level) keep bundesland NULL by design.
- find_relevant_chunks accepts a bundesland filter that matches state
rows OR NULL — so federal Grundsatzprogramme remain visible to every
analysis.
- get_relevant_quotes_for_antrag(text, fraktionen, bundesland, …) —
bundesland required, governing fractions read from BUNDESLAENDER
instead of hardcoded ["CDU","GRÜNE"]. Order-preserving dedup
replaces the previous set-based merge.
- index_programm now writes the bundesland column on insert.
- Dropped the hardcoded "Wahlprogramm NRW 2022" label in
format_quotes_for_prompt — bundesland context is implicit in the
surrounding prompt block.
analyzer.py
- get_bundesland_context reads parlament_name, regierungsfraktionen,
landtagsfraktionen and the optional WAHLPROGRAMM_KONTEXT_FILES entry
from the central registry. Throws ValueError on unknown OR inactive
bundesland — kills the silent NRW fallback that previously masked
configuration gaps.
- The Antragsteller-detection heuristic now iterates
BUNDESLAENDER[bundesland].landtagsfraktionen instead of
WAHLPROGRAMME.keys(), so we recognise parties for which we don't
yet have a Wahlprogramm PDF.
- Both quote lookups (semantic + keyword fallback) now receive the
bundesland.
Resolves issue #5. Foundation for #2 (LSA), #3 (Berlin), #4 (MV).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 18:48:11 +02:00
|
|
|
|
2026-03-28 22:30:24 +01:00
|
|
|
lines = ["\n## Relevante Passagen aus Wahlprogrammen\n"]
|
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).
2026-05-09 00:37:35 +02:00
|
|
|
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']}\"")
|
2026-03-28 22:30:24 +01:00
|
|
|
return "\n".join(lines)
|