chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b5d2bb2515
commit
4e7f7dac25
@ -377,6 +377,7 @@ async def analyze_antrag(
|
||||
system_prompt=system_prompt,
|
||||
user_prompt=user_prompt,
|
||||
model=model,
|
||||
max_tokens=8000,
|
||||
)
|
||||
data = await bewerter.bewerte(request)
|
||||
|
||||
|
||||
@ -226,8 +226,9 @@ PROGRAMME = {
|
||||
"gruene-rp-2021": {"name": "Grüne Rheinland-Pfalz Wahlprogramm 2021", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "RP", "pdf": "gruene-rp-2021.pdf"},
|
||||
"fw-rp-2021": {"name": "FREIE WÄHLER Rheinland-Pfalz Wahlprogramm 2021", "typ": "wahlprogramm", "partei": "FREIE WÄHLER", "bundesland": "RP", "pdf": "fw-rp-2021.pdf"},
|
||||
"fdp-rp-2021": {"name": "FDP Rheinland-Pfalz Wahlprogramm 2021", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "RP", "pdf": "fdp-rp-2021.pdf"},
|
||||
# Grundsatzprogramme (Bund) — Gültigkeit ab Beschluss durch den Parteitag.
|
||||
# ``gueltig_bis=None`` solange das Programm nicht ersetzt wurde.
|
||||
# Grundsatzprogramme (Bund) — Gültigkeit ab Beschluss-Datum durch den
|
||||
# jeweils höchsten Parteitag. ``gueltig_bis=None`` solange das Programm
|
||||
# nicht durch ein neueres ersetzt wurde.
|
||||
"spd-grundsatz": {
|
||||
"name": "SPD Hamburger Programm 2007",
|
||||
"typ": "parteiprogramm",
|
||||
@ -241,8 +242,8 @@ PROGRAMME = {
|
||||
"typ": "parteiprogramm",
|
||||
"partei": "CDU",
|
||||
"pdf": "cdu-grundsatzprogramm.pdf",
|
||||
"gueltig_ab": "2024-05-07", # Bundesparteitag Berlin (löst das
|
||||
"gueltig_bis": None, # Hannoveraner Programm 2007 ab)
|
||||
"gueltig_ab": "2024-05-07", # Bundesparteitag Berlin, löst das
|
||||
"gueltig_bis": None, # Hannoveraner Programm 2007 ab
|
||||
},
|
||||
"gruene-grundsatz": {
|
||||
"name": "Grüne Grundsatzprogramm 2020",
|
||||
@ -279,7 +280,7 @@ PROGRAMME = {
|
||||
# Bayern — LTW 08.10.2023, WP 19
|
||||
"csu-by-2023": {"name": "CSU Bayernplan 2023", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BY", "pdf": "csu-by-2023.pdf"},
|
||||
"gruene-by-2023": {"name": "Grüne Bayern Regierungsprogramm 2023", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BY", "pdf": "gruene-by-2023.pdf"},
|
||||
"fw-by-2023": {"name": "FREIE WÄHLER Bayern Wahlprogramm 2023", "typ": "wahlprogramm", "partei": "FW", "bundesland": "BY", "pdf": "fw-by-2023.pdf"},
|
||||
"fw-by-2023": {"name": "FREIE WÄHLER Bayern Wahlprogramm 2023", "typ": "wahlprogramm", "partei": "FREIE WÄHLER", "bundesland": "BY", "pdf": "fw-by-2023.pdf"},
|
||||
"afd-by-2023": {"name": "AfD Bayern Wahlprogramm 2023", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BY", "pdf": "afd-by-2023.pdf"},
|
||||
"spd-by-2023": {"name": "SPD Bayern Zukunftsprogramm 2023", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BY", "pdf": "spd-by-2023.pdf"},
|
||||
# Niedersachsen — LTW 09.10.2022, WP 19
|
||||
@ -310,13 +311,15 @@ PROGRAMME = {
|
||||
"spd-sn-2024": {"name": "SPD Sachsen Wahlprogramm 2024", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "SN", "pdf": "spd-sn-2024.pdf"},
|
||||
"linke-sn-2024": {"name": "DIE LINKE Sachsen Wahlprogramm 2024", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "SN", "pdf": "linke-sn-2024.pdf"},
|
||||
"gruene-sn-2024": {"name": "Grüne Sachsen Wahlprogramm 2024", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "SN", "pdf": "gruene-sn-2024.pdf"},
|
||||
# Bundestag — BTW 2025-Wahlprogramme. Kabinett Merz I vereidigt 06.05.2025.
|
||||
# Bundestagswahl 23.02.2025 — Kabinett Merz I, vereidigt 06.05.2025.
|
||||
# Acht im 21. Bundestag relevante Parteien. Die Grundsatzprogramme
|
||||
# oben bleiben als zweite Referenz erhalten.
|
||||
"cdu-bund-2025": {"name": "CDU Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "CDU", "bundesland": "BUND", "pdf": "cdu-bund-2025.pdf"},
|
||||
"csu-bund-2025": {"name": "CSU Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "CSU", "bundesland": "BUND", "pdf": "csu-bund-2025.pdf"},
|
||||
"spd-bund-2025": {"name": "SPD Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BUND", "pdf": "spd-bund-2025.pdf"},
|
||||
"gruene-bund-2025":{"name": "Grüne Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BUND", "pdf": "gruene-bund-2025.pdf"},
|
||||
"spd-bund-2025": {"name": "SPD Regierungsprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "SPD", "bundesland": "BUND", "pdf": "spd-bund-2025.pdf"},
|
||||
"gruene-bund-2025": {"name": "Grüne Regierungsprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "GRÜNE", "bundesland": "BUND", "pdf": "gruene-bund-2025.pdf"},
|
||||
"fdp-bund-2025": {"name": "FDP Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "FDP", "bundesland": "BUND", "pdf": "fdp-bund-2025.pdf"},
|
||||
"afd-bund-2025": {"name": "AfD Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BUND", "pdf": "afd-bund-2025.pdf"},
|
||||
"afd-bund-2025": {"name": "AfD Bundestagswahlprogramm 2025", "typ": "wahlprogramm", "partei": "AfD", "bundesland": "BUND", "pdf": "afd-bund-2025.pdf"},
|
||||
"linke-bund-2025": {"name": "DIE LINKE Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "LINKE", "bundesland": "BUND", "pdf": "linke-bund-2025.pdf"},
|
||||
"bsw-bund-2025": {"name": "BSW Wahlprogramm Bundestagswahl 2025", "typ": "wahlprogramm", "partei": "BSW", "bundesland": "BUND", "pdf": "bsw-bund-2025.pdf"},
|
||||
}
|
||||
@ -736,7 +739,7 @@ def _chunk_pdf_url(chunk: dict) -> Optional[str]:
|
||||
# die URL bleibt bounded (sonst würden 500-Zeichen-Snippets in jeder
|
||||
# Zitat-URL stehen und das HTML-Report-JSON aufblähen).
|
||||
q = urllib.parse.quote_plus(text[:200])
|
||||
return f"/api/wahlprogramm-cite?pid={prog_id}&seite={seite}&q={q}"
|
||||
return f"/api/wahlprogramm-cite?pid={prog_id}&seite={seite}&q={q}#page={seite}"
|
||||
|
||||
if seite:
|
||||
return f"/static/referenzen/{pdf}#page={seite}"
|
||||
@ -799,9 +802,14 @@ def render_highlighted_page(programm_id: str, seite: int, query: str) -> Optiona
|
||||
rects = []
|
||||
if needle:
|
||||
clean = needle.replace("\u00ad", "")
|
||||
# LLMs ziehen h\u00e4ufig die Seitenzahl-Header (\u201e44 Gute Bildung \u2026")
|
||||
# mit ins Zitat. Wenn die ersten Tokens reine Ziffern sind,
|
||||
# strippen wir sie f\u00fcr die Suche \u2014 sonst matched search_for nicht.
|
||||
import re as _re
|
||||
clean = _re.sub(r"^\s*\d+\s+", "", clean).strip()
|
||||
words = clean.split()
|
||||
anchor = " ".join(words[:5]) if len(words) >= 5 else clean
|
||||
# Versuch 1: angegebene Seite, Volltext
|
||||
# Versuch 1: angegebene Seite, Volltext (gestrippt)
|
||||
rects = src[target_page_idx].search_for(clean)
|
||||
# Versuch 2: angegebene Seite, 5-Wort-Anker
|
||||
if not rects:
|
||||
@ -814,8 +822,7 @@ def render_highlighted_page(programm_id: str, seite: int, query: str) -> Optiona
|
||||
target_page_idx = i
|
||||
break
|
||||
|
||||
# Volles PDF mit Highlight-Annotation. Der Browser öffnet das
|
||||
# vollständige Wahlprogramm; das Frontend hängt #page=N an die URL.
|
||||
# Volles PDF mit Highlight-Annotation.
|
||||
page = src[target_page_idx]
|
||||
if needle and rects:
|
||||
for rect in rects:
|
||||
@ -824,6 +831,16 @@ def render_highlighted_page(programm_id: str, seite: int, query: str) -> Optiona
|
||||
annot.set_colors(stroke=(1.0, 0.93, 0.0)) # gelb
|
||||
annot.update()
|
||||
|
||||
# PDF-OpenAction setzen, damit der Reader direkt auf der richtigen
|
||||
# Seite startet (statt Seite 1) — sonst sieht der User „PDF öffnet,
|
||||
# aber falsche Seite". /Fit = passt-zur-Größe.
|
||||
try:
|
||||
page_xref = page.xref
|
||||
catalog_xref = src.pdf_catalog()
|
||||
src.xref_set_key(catalog_xref, "OpenAction", f"[{page_xref} 0 R /Fit]")
|
||||
except Exception:
|
||||
logger.exception("render_highlighted_page: OpenAction-Setzen fehlgeschlagen")
|
||||
|
||||
highlighted = bool(needle and rects)
|
||||
try:
|
||||
return src.tobytes(), target_page_idx + 1, highlighted
|
||||
|
||||
@ -37,3 +37,48 @@ BB:
|
||||
titel: "BÜNDNIS 90/DIE GRÜNEN Brandenburg Wahlprogramm 2024 (Platzhalter — URL prüfen)"
|
||||
jahr: 2024
|
||||
sha256: ""
|
||||
|
||||
# Bundestagswahl 23.02.2025 — Kabinett Merz I, vereidigt 06.05.2025.
|
||||
# Quellen: Parteiwebseiten (cdu.de, csu.de, spd.de, gruene.de, fdp.de,
|
||||
# afd.de, die-linke.de, bsw-vg.de). Recherche 2026-05-07.
|
||||
BUND:
|
||||
CDU:
|
||||
- url: https://www.cdu.de/app/uploads/2025/01/km_btw_2025_wahlprogramm_langfassung_ansicht.pdf
|
||||
titel: "Politikwechsel für Deutschland — Wahlprogramm von CDU und CSU zur Bundestagswahl 2025 (Langfassung)"
|
||||
jahr: 2025
|
||||
sha256: "08f751316e731b77aa2f18090b8695d88268f2942481399f37a3a47317361795"
|
||||
CSU:
|
||||
- url: https://www.csu.de/common/download/Wahlprogramm_2025_von_CDU_und_CSU.pdf
|
||||
titel: "Politikwechsel für Deutschland — Wahlprogramm von CDU und CSU zur Bundestagswahl 2025 (CSU-Verteilungsversion)"
|
||||
jahr: 2025
|
||||
sha256: "c07fe4b65404be9b4c1b99ec9c970567a96a27b86a1154754a41e9ef31d0c04f"
|
||||
SPD:
|
||||
- url: https://www.spd.de/fileadmin/Dokumente/Beschluesse/Programm/2025_SPD_Regierungsprogramm.pdf
|
||||
titel: "Mehr für Dich. Besser für Deutschland. — Regierungsprogramm der SPD zur Bundestagswahl 2025"
|
||||
jahr: 2025
|
||||
sha256: "05aeb9eb19fd423288d94de1d15cabcddbcb9bb1ecf65237657d10e4839b9d7e"
|
||||
GRÜNE:
|
||||
- url: https://cms.gruene.de/uploads/assets/20250205_Regierungsprogramm_DIGITAL_DINA5.pdf
|
||||
titel: "Zusammen wachsen — Regierungsprogramm BÜNDNIS 90/DIE GRÜNEN zur Bundestagswahl 2025"
|
||||
jahr: 2025
|
||||
sha256: "0d1f7530ddecadc8d98db15b2189f3ee4ddcccd9619df825703911f1aac18dda"
|
||||
FDP:
|
||||
- url: https://www.fdp.de/sites/default/files/2024-12/fdp-wahlprogramm_2025.pdf
|
||||
titel: "Alles lässt sich ändern — Wahlprogramm der Freien Demokraten zur Bundestagswahl 2025"
|
||||
jahr: 2025
|
||||
sha256: "a549dcb318f60fdd8a257fb2e68745d1df50151f4914910e2afde20c3b0fa039"
|
||||
AfD:
|
||||
- url: https://www.afd.de/wp-content/uploads/2025/02/AfD_Bundestagswahlprogramm2025_web.pdf
|
||||
titel: "Programm der Alternative für Deutschland — Bundestagswahl 2025"
|
||||
jahr: 2025
|
||||
sha256: "e2d0a944f54017aa432bcd0c069c7908aefb3ea65af4904e7f86ca7c7bd0d4bb"
|
||||
LINKE:
|
||||
- url: https://www.die-linke.de/fileadmin/user_upload/Wahlprogramm_Langfassung_Linke-BTW25_01.pdf
|
||||
titel: "Du verdienst mehr — Wahlprogramm DIE LINKE zur Bundestagswahl 2025 (Langfassung)"
|
||||
jahr: 2025
|
||||
sha256: "301bd30a5fcd2a7e791adc4db5294e79d8f3fd71b8c9b080b057f14bf8cae600"
|
||||
BSW:
|
||||
- url: https://bsw-vg.de/wp-content/themes/bsw/assets/downloads/BSW%20Wahlprogramm%202025.pdf
|
||||
titel: "Unser Land verdient mehr — Wahlprogramm Bündnis Sahra Wagenknecht zur Bundestagswahl 2025"
|
||||
jahr: 2025
|
||||
sha256: "bd4640aab7c6ff214becc7e44bc6dc67539ebd80992bb39fb8853839d3a7ccda"
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
{
|
||||
"afd-bb-2024.pdf": "da5cd04cc66128b2f0df35b47775fce850ed2f4145ee15d74ec8bf501ce043f1",
|
||||
"afd-be-2023.pdf": "d2b5997b1bc0d3fb590cc354d8ed1ac879e8de4a74518f4089436a2fa12615f1",
|
||||
"afd-bund-2025.pdf": "e2d0a944f54017aa432bcd0c069c7908aefb3ea65af4904e7f86ca7c7bd0d4bb",
|
||||
"afd-bw-2021.pdf": "a438e09279c6c5766171a213715ed0a9d60248ff86f648227e8bb6ec59a591c7",
|
||||
"afd-grundsatzprogramm.pdf": "b35026580c8610ab81c67760743497695b06e30466255586d089d4715297bead",
|
||||
"afd-hh-2025.pdf": "6aae3ad00cd07824bcd99473e130d1b894e2174a89fcafece51865c51fdcd4c8",
|
||||
"afd-lsa-2021.pdf": "dd2651af2a9423039b1c5a39760be2332025d569a878453f09e0302e252edc23",
|
||||
"afd-mv-2021.pdf": "953c39941a1f997233daaf0cec01bc82b1e86ba895b43e8d34b015cc72799648",
|
||||
@ -9,10 +11,13 @@
|
||||
"afd-rp-2021.pdf": "3ec39eb08a073244813a51f260e18fe52aab791bea26bf8079546b6e189ec2b3",
|
||||
"afd-th-2024.pdf": "26e61fdc3456e7ce18f7a3d2ea1eada303f93cad0b9698797f83a671574eaf51",
|
||||
"bsw-bb-2024.pdf": "548c9bda01af176586606fae708c9f3b3ba98e1e128f1e2ff39e482289faab42",
|
||||
"bsw-bund-2025.pdf": "bd4640aab7c6ff214becc7e44bc6dc67539ebd80992bb39fb8853839d3a7ccda",
|
||||
"bsw-th-2024.pdf": "5ace33912083048a759ee2af9288248447363dafa21f569c5c056df22751ba69",
|
||||
"cdu-bb-2024.pdf": "460b1463483429f9e8b84e4ae6ef9cf878dd228e108411bed3c153169a0001e8",
|
||||
"cdu-be-2023.pdf": "813d0d08ac8ce7381e9a7b9472e0616aaf684b1632c9d4a7f4e940a33455f29a",
|
||||
"cdu-bund-2025.pdf": "08f751316e731b77aa2f18090b8695d88268f2942481399f37a3a47317361795",
|
||||
"cdu-bw-2021.pdf": "a92c104c456ce06d8bad6649071551e0ec0d525a1bc0bc31e9fa6a0566da4db0",
|
||||
"cdu-grundsatzprogramm.pdf": "4b4bebd40ff8d905eb3ad00975cbbf0990810c21977e2d560e3d73f2571b181d",
|
||||
"cdu-hh-2025.pdf": "8d29e514b8bce5c2f3f497dc5b97f6f8ab95a7bdbf619abf258e9582d57f2dbd",
|
||||
"cdu-lsa-2021.pdf": "63b6cf42ce97834d5d105fb7b8cc7fb7a2aa96928d4153bd3a5858c196ee0797",
|
||||
"cdu-mv-2021.pdf": "605a2211bef8666c2103771ebffd97a088e7cdb1545401087ef125155e7e4db2",
|
||||
@ -20,7 +25,10 @@
|
||||
"cdu-rp-2021.pdf": "54c50d88bdf5c5f7dee5abcc981ffb4d1cfd5c86fbf2a29f4f2f4a8a3dd4797a",
|
||||
"cdu-sh-2022.pdf": "39b79a22e904b300cf1bbc25752b618195683c90c31e6b10c3bc0e8408aa6a1a",
|
||||
"cdu-th-2024.pdf": "cde8d2222bd8ce04aee24883a38dab8a30f5d60cda115b8bb2f43ceffa08b730",
|
||||
"csu-bund-2025.pdf": "c07fe4b65404be9b4c1b99ec9c970567a96a27b86a1154754a41e9ef31d0c04f",
|
||||
"fdp-bund-2025.pdf": "a549dcb318f60fdd8a257fb2e68745d1df50151f4914910e2afde20c3b0fa039",
|
||||
"fdp-bw-2021.pdf": "bdcbb1b2e5748922c8347bd69ea6f81c954fd02cd220d448400f9a5a86ce914b",
|
||||
"fdp-grundsatzprogramm.pdf": "9abb6570c3505271b1f43db2a8340c05b970a7934d0c1b2542403025affe2b13",
|
||||
"fdp-lsa-2021.pdf": "3d4275e36e29c0b191dcc4a29061a1072920f868cc52bee954bf81491ad15224",
|
||||
"fdp-mv-2021.pdf": "8dc341dd017f1d82c51608a26e1fd6c3d8acd1281dc37409e375389999b37b55",
|
||||
"fdp-nrw-2022.pdf": "576b42a26c29ca5d8b7469d417ae709c8d0699aed5195d4ca16dd696dcff8bea",
|
||||
@ -28,7 +36,9 @@
|
||||
"fdp-sh-2022.pdf": "4c49da411bb3c8e008f4b57dd20dc005104515b56056ff746cf5403529728d09",
|
||||
"fw-rp-2021.pdf": "c7f26d553f24c9d9fcf1c2edb1dbe558edc1ca65af68b289a1541e77f7bbeea8",
|
||||
"gruene-be-2023.pdf": "2b14a319cdcd2ca022399254ea285714f872eddd166f3f537861eeb2dc5ade80",
|
||||
"gruene-bund-2025.pdf": "0d1f7530ddecadc8d98db15b2189f3ee4ddcccd9619df825703911f1aac18dda",
|
||||
"gruene-bw-2021.pdf": "9af526705cb10b91be0690b26c9c033668a8082eeefca482dc4e7a46f2d671f9",
|
||||
"gruene-grundsatzprogramm.pdf": "36c68616286bc5df94cadbe176c72c515a6ed403cc246d221244a882f30b0834",
|
||||
"gruene-hh-2025.pdf": "4428d1cdc16b4e74588f0bd51145ab7371f9e0871a2fc9d25a1f94e4f5aeb662",
|
||||
"gruene-lsa-2021.pdf": "7b5cea92cd600283d7edf18dc0d358c0b7d78d7269589d9ef05de7d5f8b35998",
|
||||
"gruene-mv-2021.pdf": "40f0070743ef9ae7808cab319234b4c83faa53a8a098ba8a82f28023bee4d9f6",
|
||||
@ -36,13 +46,17 @@
|
||||
"gruene-rp-2021.pdf": "4fd68629d1560c28d61b2b913fd20ce6ad9a76b22823fd8496e51bfaf70dc19c",
|
||||
"gruene-sh-2022.pdf": "62870c948c9e05663125b051d3a6401d63952ea6a64e4140dcece7bd1b1aea52",
|
||||
"linke-be-2023.pdf": "7d6a9166f6a1d87ba26cc1a2818ae2b844ee9df6ed6668673f329dd5186fd956",
|
||||
"linke-bund-2025.pdf": "301bd30a5fcd2a7e791adc4db5294e79d8f3fd71b8c9b080b057f14bf8cae600",
|
||||
"linke-grundsatzprogramm.pdf": "b5b2b19f5eea4cb22816eca6f2988bcc6cffa4c0cc499411bf131e866fae4e16",
|
||||
"linke-hh-2025.pdf": "15e68efe3818758a7cefc0a3e3095a5a5fb191111c00a1202c563cee43ce6e40",
|
||||
"linke-lsa-2021.pdf": "f269c014416b213785badf7bea5928fdb847fc902e09f52ec66a140a37e03d75",
|
||||
"linke-mv-2021.pdf": "160dad56ab4de8f641c21f51cbf3c33953f2f3d91b4de792c4e725f3975fdfbe",
|
||||
"linke-th-2024.pdf": "2d8ca99ef60cbe1b59cf33b1e37320d66a057e5136c2f49aa8cde77e4a19533a",
|
||||
"spd-bb-2024.pdf": "4131f63fbb9d67cd8948ca7a54f1c140b47968c77454a3dabe6bcdc4384f63d3",
|
||||
"spd-be-2023.pdf": "4ee84e969e97894742673f940ec030883216ce852b729507327f8bced637d03b",
|
||||
"spd-bund-2025.pdf": "05aeb9eb19fd423288d94de1d15cabcddbcb9bb1ecf65237657d10e4839b9d7e",
|
||||
"spd-bw-2021.pdf": "d888ae92bb62a61aaa4d6ac8dc22c2c98d1a2227b6ba223b6422770672825072",
|
||||
"spd-grundsatzprogramm.pdf": "0290ae8870e2deeeec4f93227264fef9f6dbbc56572a2ba29e93d8bd1c0de7cd",
|
||||
"spd-hh-2025.pdf": "5e8c57969cb3b159b9299c173831f7863ab81bd206c2a87ae232ba96f23156ee",
|
||||
"spd-lsa-2021.pdf": "59140aa1921ab0ee85142d74e1d72b1af7254da3f7870a30460abd605d280333",
|
||||
"spd-mv-2021.pdf": "c8c671c2e60f1a4f8048bd74e379eb8edc69ab2daeb09581fe83f25f6c87d529",
|
||||
|
||||
@ -172,18 +172,6 @@ WAHLPROGRAMME: dict[str, dict[str, dict]] = {
|
||||
"LINKE": {"file": "linke-bund-2025.pdf", "titel": "Alle wollen regieren. Wir wollen verändern. — DIE LINKE Wahlprogramm BTW 2025", "partei": "DIE LINKE", "jahr": 2025, "seiten": 60, "regierungsbildung": "2025-05-06", "regierungsende": None},
|
||||
"BSW": {"file": "bsw-bund-2025.pdf", "titel": "Unser Land verdient mehr — BSW Wahlprogramm BTW 2025", "partei": "BSW", "jahr": 2025, "seiten": 45, "regierungsbildung": "2025-05-06", "regierungsende": None},
|
||||
},
|
||||
# Bundestag — keine bundesweiten Wahlprogramme im Repo, daher dienen
|
||||
# die Grundsatzprogramme als Quelle. CSU + BSW haben keine Programme
|
||||
# in der Registry und werden weiterhin als fehlend gemeldet.
|
||||
# Diese Eintraege sind von embeddings.py separat indiziert (typ=parteiprogramm).
|
||||
"BUND": {
|
||||
"CDU": {"file": "cdu-grundsatzprogramm.pdf", "titel": "CDU Grundsatzprogramm 2024", "partei": "CDU", "jahr": 2024, "seiten": 64, "ist_grundsatz": True},
|
||||
"SPD": {"file": "spd-grundsatzprogramm.pdf", "titel": "SPD Hamburger Programm 2007", "partei": "SPD", "jahr": 2007, "seiten": 78, "ist_grundsatz": True},
|
||||
"GRÜNE": {"file": "gruene-grundsatzprogramm.pdf","titel": "Grüne Grundsatzprogramm 2020", "partei": "GRÜNE", "jahr": 2020, "seiten": 116, "ist_grundsatz": True},
|
||||
"FDP": {"file": "fdp-grundsatzprogramm.pdf", "titel": "FDP Karlsruher Freiheitsthesen 2012","partei": "FDP", "jahr": 2012, "seiten": 31, "ist_grundsatz": True},
|
||||
"AfD": {"file": "afd-grundsatzprogramm.pdf", "titel": "AfD Grundsatzprogramm 2016", "partei": "AfD", "jahr": 2016, "seiten": 96, "ist_grundsatz": True},
|
||||
"LINKE": {"file": "linke-grundsatzprogramm.pdf", "titel": "DIE LINKE Erfurter Programm 2011", "partei": "LINKE", "jahr": 2011, "seiten": 84, "ist_grundsatz": True},
|
||||
},
|
||||
}
|
||||
|
||||
# Pro Bundesland: Markdown-Übersichtsdatei mit Wahlprogramm-Zusammenfassungen,
|
||||
|
||||
@ -317,15 +317,11 @@ class TestReconstructZitate:
|
||||
}
|
||||
out = reconstruct_zitate(data, semantic_quotes)
|
||||
zitate = out["wahlprogrammScores"][0]["wahlprogramm"]["zitate"]
|
||||
# Beide Zitate bleiben erhalten — das nicht-matchende wird als
|
||||
# unverified markiert statt gedroppt (Hybrid-Ansatz).
|
||||
assert len(zitate) == 2
|
||||
# Das halluzinierte Zitat ist unverified
|
||||
halluziniert = [z for z in zitate if "Rechtsextremismus" in z["text"]]
|
||||
assert halluziniert[0]["verified"] is False
|
||||
# Das echte Zitat ist verified
|
||||
echt = [z for z in zitate if "geschlechtersensiblen" in z["text"]]
|
||||
assert echt[0]["verified"] is True
|
||||
# Strict-Mode (#175): das nicht-matchende halluzinierte Zitat wird
|
||||
# gedroppt, das echte Zitat bleibt erhalten und ist verified.
|
||||
assert len(zitate) == 1
|
||||
assert "geschlechtersensiblen" in zitate[0]["text"]
|
||||
assert zitate[0]["verified"] is True
|
||||
|
||||
def test_empty_semantic_quotes_is_noop(self):
|
||||
data = {"wahlprogrammScores": [{
|
||||
|
||||
@ -54,6 +54,7 @@ def db_with_antrag_and_news(tmp_path: Path) -> Path:
|
||||
titel TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
style TEXT NOT NULL DEFAULT 'pm',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)
|
||||
""")
|
||||
|
||||
@ -221,14 +221,18 @@ class TestGetScoreColor:
|
||||
|
||||
|
||||
class TestGetRatingSymbol:
|
||||
"""Skala −5..+5 (seit Modell-Migration). Schwellen:
|
||||
≥+4 → ++, +1..+3 → +, 0 → ○, −1..−3 → −, ≤−4 → −−.
|
||||
"""
|
||||
def test_strong_positive(self):
|
||||
from app.report import get_rating_symbol
|
||||
assert get_rating_symbol(2) == "++"
|
||||
assert get_rating_symbol(4) == "++"
|
||||
assert get_rating_symbol(5) == "++"
|
||||
|
||||
def test_positive(self):
|
||||
from app.report import get_rating_symbol
|
||||
assert get_rating_symbol(1) == "+"
|
||||
assert get_rating_symbol(3) == "+"
|
||||
|
||||
def test_neutral(self):
|
||||
from app.report import get_rating_symbol
|
||||
@ -237,8 +241,9 @@ class TestGetRatingSymbol:
|
||||
def test_negative(self):
|
||||
from app.report import get_rating_symbol
|
||||
assert get_rating_symbol(-1) == "−"
|
||||
assert get_rating_symbol(-3) == "−"
|
||||
|
||||
def test_strong_negative(self):
|
||||
from app.report import get_rating_symbol
|
||||
assert get_rating_symbol(-2) == "−−"
|
||||
assert get_rating_symbol(-4) == "−−"
|
||||
assert get_rating_symbol(-5) == "−−"
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
"""Tests for wahlprogramme.py — registry consistency + file existence."""
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from app.wahlprogramme import (
|
||||
@ -6,6 +8,9 @@ from app.wahlprogramme import (
|
||||
REFERENZEN_PATH,
|
||||
get_wahlprogramm,
|
||||
parteien_mit_wahlprogramm,
|
||||
regierung_aktuell,
|
||||
regierungsbildung_for,
|
||||
regierungsende_for,
|
||||
)
|
||||
|
||||
|
||||
@ -42,6 +47,83 @@ class TestRegistryStructure:
|
||||
assert info["file"].endswith(".pdf")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Regierungsbildungs-Felder — Konsistenz pro Bundesland
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestRegierungsbildung:
|
||||
"""Pro Bundesland gehören alle Wahlprogramm-Einträge zur gleichen
|
||||
Legislatur — d.h. dasselbe regierungsbildung-Datum. Ausnahme: BUND, wo
|
||||
Grundsatzprogramme stehen, die regierungsbildung=None tragen."""
|
||||
|
||||
_ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
||||
|
||||
def test_every_entry_has_regierungs_fields(self):
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei, info in parteien.items():
|
||||
assert "regierungsbildung" in info, f"{bl}/{partei}: regierungsbildung fehlt"
|
||||
assert "regierungsende" in info, f"{bl}/{partei}: regierungsende fehlt"
|
||||
|
||||
def test_regierungsbildung_iso_or_none(self):
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei, info in parteien.items():
|
||||
rb = info.get("regierungsbildung")
|
||||
if rb is not None:
|
||||
assert isinstance(rb, str) and self._ISO_DATE.match(rb), \
|
||||
f"{bl}/{partei}: regierungsbildung kein ISO-Datum: {rb!r}"
|
||||
|
||||
def test_regierungsende_iso_or_none(self):
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei, info in parteien.items():
|
||||
re_ = info.get("regierungsende")
|
||||
if re_ is not None:
|
||||
assert isinstance(re_, str) and self._ISO_DATE.match(re_), \
|
||||
f"{bl}/{partei}: regierungsende kein ISO-Datum: {re_!r}"
|
||||
|
||||
def test_alle_parteien_eines_bl_haben_gleiches_datum(self):
|
||||
"""Alle Wahlprogramm-Einträge eines Bundeslands gehören zur selben
|
||||
Regierung und müssen daher dasselbe Bildungs-/Endedatum tragen."""
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
bildung = {info.get("regierungsbildung") for info in parteien.values()}
|
||||
ende = {info.get("regierungsende") for info in parteien.values()}
|
||||
assert len(bildung) == 1, \
|
||||
f"{bl}: regierungsbildung divergent: {bildung}"
|
||||
assert len(ende) == 1, \
|
||||
f"{bl}: regierungsende divergent: {ende}"
|
||||
|
||||
def test_grundsatzprogramme_haben_keine_regierung(self):
|
||||
"""Grundsatzprogramme (ist_grundsatz=True) tragen keine Regierungs-
|
||||
bildung — sie sind zeitlos."""
|
||||
for bl, parteien in WAHLPROGRAMME.items():
|
||||
for partei, info in parteien.items():
|
||||
if info.get("ist_grundsatz"):
|
||||
assert info.get("regierungsbildung") is None, \
|
||||
f"{bl}/{partei} ist Grundsatzprogramm, sollte regierungsbildung=None haben"
|
||||
|
||||
|
||||
class TestRegierungsHelper:
|
||||
def test_regierungsbildung_for_known_bl(self):
|
||||
assert regierungsbildung_for("NRW") == "2022-06-29"
|
||||
assert regierungsbildung_for("HH") == "2025-05-07"
|
||||
|
||||
def test_regierungsbildung_for_bund_btw2025(self):
|
||||
# BUND tragt nun die BTW-2025-Wahlprogramme; Kabinett Merz I
|
||||
# vereidigt 06.05.2025. Grundsatzprogramme bleiben nur in
|
||||
# embeddings.PROGRAMME als zweite Referenz.
|
||||
assert regierungsbildung_for("BUND") == "2025-05-06"
|
||||
|
||||
def test_regierungsbildung_for_unknown_bl(self):
|
||||
assert regierungsbildung_for("XX") is None
|
||||
|
||||
def test_regierung_aktuell_true_for_active_bl(self):
|
||||
assert regierung_aktuell("NRW") is True
|
||||
assert regierung_aktuell("BB") is True
|
||||
assert regierung_aktuell("BUND") is True
|
||||
|
||||
def test_regierungsende_for_active_is_none(self):
|
||||
assert regierungsende_for("NRW") is None
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# File existence — every registered file must exist on disk
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@ -91,6 +173,36 @@ class TestParteienMitWahlprogramm:
|
||||
parteien = parteien_mit_wahlprogramm("BE")
|
||||
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "LINKE", "AfD"}
|
||||
|
||||
def test_bund_has_eight_parteien(self):
|
||||
# BTW 2025: CDU, CSU, SPD, GRÜNE, FDP, AfD, LINKE, BSW.
|
||||
parteien = parteien_mit_wahlprogramm("BUND")
|
||||
assert set(parteien) == {"CDU", "CSU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE", "BSW"}
|
||||
|
||||
def test_by_has_five_parteien(self):
|
||||
parteien = parteien_mit_wahlprogramm("BY")
|
||||
assert set(parteien) == {"CSU", "FREIE WÄHLER", "GRÜNE", "SPD", "AfD"}
|
||||
|
||||
def test_hb_has_four_parteien(self):
|
||||
# AfD war wegen Listenstreit nicht zur Bürgerschaftswahl 2023 zugelassen.
|
||||
parteien = parteien_mit_wahlprogramm("HB")
|
||||
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "LINKE"}
|
||||
|
||||
def test_he_has_five_parteien(self):
|
||||
parteien = parteien_mit_wahlprogramm("HE")
|
||||
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"}
|
||||
|
||||
def test_ni_has_four_parteien(self):
|
||||
parteien = parteien_mit_wahlprogramm("NI")
|
||||
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "AfD"}
|
||||
|
||||
def test_sl_has_three_parteien(self):
|
||||
parteien = parteien_mit_wahlprogramm("SL")
|
||||
assert set(parteien) == {"SPD", "CDU", "AfD"}
|
||||
|
||||
def test_sn_has_six_parteien(self):
|
||||
parteien = parteien_mit_wahlprogramm("SN")
|
||||
assert set(parteien) == {"CDU", "SPD", "AfD", "BSW", "LINKE", "GRÜNE"}
|
||||
|
||||
def test_unknown_bundesland_empty_list(self):
|
||||
assert parteien_mit_wahlprogramm("XX") == []
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user