User-Feedback: 'Bei der Kuerzung der Beitraege geht der PM Generieren-
Button unter.' Mein 9-Zeilen-line-clamp lag auf dem ganzen Item-DIV
und schnitt deshalb den Button am Ende weg.
Fix: nur Summary-<p> wird auf 5 Zeilen geclampt; Meta, Title, Tags
und PM-Button bleiben unangetastet sichtbar. Item-Hoehe haengt damit
mehr von Title-Laenge ab, ist aber immer komplett.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei Korrekturen:
1. Mobile-Topbar: Auth-Widget + Bundesland-Selector + Theme-Toggle
pushten die rechte Kante über 390 px Phone-Viewport. Fix: Topbar
darf in @media (max-width: 900px) flex-wrappen, height auto,
row-gap fuer mehrzeilig.
2. Topbar-Link "Klassische Ansicht" → /classic entfernt (verlinkt auf
das alte v1-Frontend; v2 bzw. das neue v3 sind die aktiven Modi).
3. /tags-Seite hatte zwei Bugs:
- Titel wurde aus a.titel (existiert nicht) statt a.title gelesen
→ User sah nur Drucksachen-Nummern und dachte "kaum Daten".
- Kein Visual-Feedback welche Tag-Kombinationen leer wären.
Beide gefixt: title-Field korrekt, plus Tag-Greying via class
.tag-pill.disabled fuer Tags die zu 0 Treffern fuehren wuerden.
Ausserdem Score-Field gwoeScore-Fallback und HTML-Escape fuer alle
Strings (vorher XSS-anfaellig bei Title/Fraktion).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CSS-Grid 1fr-Track schrumpft per Spec NICHT unter min-content der Kinder.
Bei 390-px-Viewport bleibt der Track deshalb auf ~968px stehen (max-
content der weitesten Inhalte). Standard-Fix: minmax(0,1fr) hebt das
auf, body.overflow-x:hidden als Notbremse, overflow-wrap:anywhere
fuer lange unbreakable Strings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug auf 390-px-Phone-Viewport: html/body waren korrekt 390 px, aber
.v2-main rendert mit 968 px Breite, alle Inhalte überlaufen horizontal
um 200-580 px. Ursache: .v2-shell hatte im mobile-Media-Query nur
grid-template-columns: 1fr — ohne explizite width:100% nimmt Grid die
max-content-Breite des 1fr-Tracks an, was bei .v3-page max-width:880
auf 880+padding wächst. Folge: Sidebar ausgeblendet, aber Layout
trotzdem auf Desktop-Breite.
Fix in @media (max-width: 900px):
- body.v2 .v2-shell { width: 100%; max-width: 100vw; min-width: 0; }
- body.v2 .v2-main { max-width: 100%; min-width: 0; padding: 16px 12px; }
- body.v2 .v2-topbar/.v2-footer { max-width: 100%; }
body-Selector mit body.v2-Prefix für Spezifität gegen die Default-
Regel ohne body.v2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: AfD-Parteiprogramm-Block enthielt ein Zitat mit quelle "CDU
Grundsatzprogramm 2024, S. 33" (DRS 21/4939). Ursache: reconstruct_zitate
hatte alle Chunks aller Parteien in einen Pool gemischt. Wenn der LLM
unter AfD-Parteiprogramm einen Text emittierte, der zufaellig auch im
CDU-Programm vorkam, matched der Code den CDU-Chunk und ueberschrieb
quelle/url mit CDU-Werten.
Fix: Match strikt auf chunks_by_party[fraktion][kind]. Fallback auf
gleiche Partei/andere Kategorie (z.B. AfD hat nur Grundsatz-, kein
Wahlprogramm im Index). Wenn kein Match in der eigenen Partei → Zitat
verwerfen statt fremde quelle behalten. Lieber 0 Zitate als ein
Misattributions-Zitat.
Plus v3-UI:
- News-Box von ganz hinten nach oberhalb "Neu analysieren" verschoben
- News-Liste auf 1 Item gekuerzt + 9-Zeilen-Clamp via -webkit-line-clamp
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routen:
- /antrag/{drs} → v3 (Standard, Bürger:innen-Modus single column)
- /v2/antrag/{drs} → v2 (alter Profi-Modus, weiterhin erreichbar)
- /v3/antrag/{drs} → Alias auf v3 (für alte Bookmarks)
UI-Hinweise auf alternative Ansichten ausgeblendet:
- v3-Topbar-Pill "Bürger:innen-Modus · Beta" + "→ Profi-Modus"-Toggle raus
- v2-Topbar-Link "→ Bürger:innen-Modus · v3 Beta" raus
Im Admin-Bereich (/v2/admin/stand) neuer Block "Alternative Ansichten"
mit Beispiel-Drucksache, Live-Link auf v3 (Default) und v2 (Profi).
Nur Admins sehen die Hinweise auf v2.
Trennlinien-Cleanup im Rest-Block:
- Doppellinie unter Abstimmungsergebnis aufgelöst (.v3-rest hatte
border-top, das v3-section.border-bottom doppelt war).
- Neue Trennlinien via Klasse v3-rest-divider-top vor:
· Teilen-Block (zwischen Ähnliche und Teilen)
· Aktions-Links (zwischen Teilen und administrativem Bereich)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DB-Felder die bisher in der UI fehlten, jetzt in v3 sichtbar:
- _row_to_detail() liefert themen, kernpunkte, schwerpunkt, link,
konfidenz, fehlende_programme, wahlperiode an's Frontend.
- _wahlperiode_silent() leitet die WP aus datum+bundesland ab via
wahlperioden.wahlperiode_for() — silent-fail bei Lookup-Fehler.
v3-Template:
- Wahlperiode in der Antrag-ID-Zeile ("18. Wahlperiode")
- Themen-Chips als Reihe unter byline
- Kernforderungen als Bullet-Liste in der Zusammenfassungs-Sektion
- Konfidenz-Pille (hoch/mittel/niedrig) neben der Empfehlung
- Schwerpunkt-Felder (Top-Matrix-Cells) als Chips über der Matrix
- Disclaimer "fehlende Programme" am Programm-Treue-Block
- Original-PDF-Link im Aktions-Block
- Ähnliche Anträge als eigener Block, geladen via JS aus
/api/assessment/similar (Re-Use des bestehenden Endpoints aus #108)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der vorherige Refactor hatte die expliziten Sub-Headers im Body
abgeworfen. User wollte sie zurück (BELEGE-Layout-Spec):
- Header-Row: Programm-Label links, "Bewertung"-Tag + Score-Chip rechts
- Body: Sub-Label "Einschätzung" über der Begründung,
Sub-Label "Belege" über den Zitaten
Default bleibt <details open> — alles sofort lesbar, Falt-Toggle
optional. Chevron ▾ am Label rotiert beim Schließen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-Feedback: voriges Layout war "Usability-Alptraum" — alle 8 Blöcke
(4 Fraktionen × 2 Programme) waren default-closed, was 8 Klicks pro
Antrag bedeutete um Begründungen zu lesen.
Fix: <details open> als Default. Inhalt sofort vollständig sichtbar
beim Scrollen. Faltmechanismus bleibt für User, die die Liste nur
skimmen wollen — Chevron ▾ am Label rotiert beim Falten. Hover-Affordanz
über Label-Color-Change zum Brand-Blau (kein extra Caret-Element).
Layout-Spec bleibt: pro Partei zwei Treue-Blöcke (Wahlprogramm,
Parteiprogramm). Summary = Label + Score-Chip rechts. Body =
Einschätzung-Text + Belege via quote_card-Macro.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skelett für Issues #184 + #185 — minimal, nicht-disruptiv:
- v3/base.html extendet v2/base.html (Topbar/Sidebar/Footer geteilt)
- v3/screens/antrag_detail.html extendet vorerst v2-Screen 1:1 und
injiziert nur Beta-Pill + Toggle "→ Profi-Modus"
- v2/screens/antrag_detail.html bekommt Topbar-Link "→ Bürger:innen-
Modus (v3 Beta)" → /v3/antrag/<drs>
- _render_antrag_detail() teilt DB-Reads/Context zwischen v2 + v3 —
Datenbasis garantiert in Sync, Unterschied ist nur template_name
- _MATRIX_EXPLANATIONS auf Modul-Ebene ausgelagert (war bisher
inline im v2-Route, jetzt von beiden Modi referenziert)
- v3.css als Add-On nach v2.css (lädt im v3/base head)
Was v3 noch NICHT tut: Score-Hero-Vereinfachung, Matrix→5-Werte,
Glossar-Tooltips, Default-Collapsing der Profi-Blöcke (Verbesserungen,
Kommentare). Diese Iterationen folgen pro PR — v2 bleibt unberührt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matrix-Info-Modal:
- _row_to_detail liefert label+aspect je Matrix-Cell ans Frontend
- Modal zeigt antragsspezifische Bewertung (Label + ausformulierte
Begründung + Rating-Chip) UND die allgemeine Felderklärung
(was misst dieses Feld?). v1-Verhalten wiederhergestellt.
Abstimmungsergebnis (Vote-Block):
- Outlink-Pfeil ↗ ist jetzt ein klickbares <a> auf v.quelle_url
(target=_blank). Vorher: Span ohne Link, Pfeil tat nichts.
- Marker ⚠ (Heuchelei) und ! (Opportunismus) bekommen sichtbare
Hover-Affordanz (Hintergrund + dotted-border on focus). Native
title= bleibt fuer Screenreader-/Tooltip aktiv. tabindex=0+role=button
macht sie keyboard-erreichbar.
- Legende unter dem Vote-Pill-Block erklaert die Marker beim
ersten Auftreten in einer Liste — wird nur eingeblendet wenn auf
dem Block mindestens ein Marker tatsaechlich vorkommt.
- Vote-Pills via Klassen v2-vote-pill/-ja/-nein/-enth statt
Inline-Styles (CD-Annaeherung).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- _row_to_detail liefert zitate inline pro Wahlprogramm/Parteiprogramm-Block
- Template rendert <details>: Summary mit Score-Chip, Body mit Einschätzung+Belege
- v2.css: neue Klassen v2-treue-block/-label/-body, v2-pill, v2-einschaetzung
- Separate "Belege — Partei"-Sektion entfernt (ist jetzt inline pro Programm)
Tests: tests/test_v2_pdf_consistency.py (#176 generalisiert) bleibt grün —
fraktions_scores trägt zusätzliche zitate-Felder, ändert aber keine
Score/Begründungs-Werte aus dem Vergleich.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 21 — Score-Hero auf Float-Layout:
- Score-Zahl floated links, Verdict-Text fließt rechts daneben UND
unter sie weiter (vorher: flex-baseline, Text bricht nicht um).
- overflow:hidden auf Container fuer clean float-clearing.
Phase 22 — Merken-Button oben rechts auf gleicher Höhe wie 'Bewertung'-Label:
- Visuelle Einheit: 'Bewertung'-Header links, Merken-Button rechts.
- Vorher: Merken-Button stand alleinig zwischen Score und Vote-Block,
optisch losgeloest.
Refs: #177
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auf Desktop ist die Sidebar permanent — der Burger-Button hatte dort keine
Funktion. display: none default + @media max-width:900px → inline-flex.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Topbar:
- height: 32px (statt auto), line-height: 1, alle children max 24px
- Topbar-Icons explizit auf 12x12 (statt 14)
- selects/buttons/a mit fester Hoehe 22px, padding 2px 6px
Landtag-Suche:
- search_landtag filtert jetzt Drucksachen aus, deren Titel typische
Frage-Praefixe haben (Welche/Wie viele/Wann/Was/Hat/Ist/...) oder mit '?'
enden — bei NRW-OPAL liefert der Adapter alle als 'sonstige', daher
Title-Heuristik. Server-side, damit alle Adapter profitieren.
- Neuer Helper drucksache_typen.likely_kleine_anfrage_titel()
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: /api/wahlprogramm-cite lieferte das gesamte PDF mit Highlight-Annot
auf der gefundenen Seite, aber der Browser-PDF-Viewer landete auf Seite 1.
Sieht User: 'PDF oeffnet, aber falsche Seite'.
Jetzt: doc.xref_set_key(catalog, 'OpenAction', '[<page-ref> 0 R /Fit]')
schreibt eine PDF-Open-Action ins Dokument-Catalog. Reader springt beim
Oeffnen direkt auf target_page_idx, ohne dass Browser-Hash-Anker noetig sind.
Plus: Topbar select/button padding-top/bottom 1px, links 0px (User: 'nur so
hoch wie noetig').
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- render_highlighted_page: führende Seitenzahl-Tokens ('44 Gute Bildung …')
vor search_for entfernen — LLMs ziehen den Header oft ins Zitat mit, was
PyMuPDFs Volltext-Match scheitern lässt
- v2-Topbar: padding 4px -> 2px, line-height 1.2, min-height entfernt
(auto-size, nur so hoch wie noetig)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sechs der zehn aktiven Bundesländer hatten bisher keine Wahlprogramme
indexiert (alle sechs heute neu aktiviert: BW/HH/TH in Phase 1, SH/BB/RP
in Phase 2). Antrag-Analysen für diese BL fielen damit auf föderale
Grundsatzprogramme als Fallback zurück.
Beschafft via abgeordnetenwatch.de für die jeweils laufende WP:
- TH WP8 (LTW 01.09.2024): CDU, AfD, LINKE, BSW, SPD — 5 PDFs
- BB WP8 (LTW 22.09.2024): SPD, AfD, CDU, BSW — 4 PDFs
- HH WP23 (Bürgerschaftswahl 02.03.2025): SPD, CDU, GRÜNE, LINKE, AfD — 5 PDFs
- SH WP20 (LTW 08.05.2022): CDU, SPD, GRÜNE, FDP, SSW — 5 PDFs
- BW WP17 (LTW 14.03.2021): GRÜNE, CDU, AfD, SPD, FDP — 5 PDFs
- RP WP18 (LTW 14.03.2021): SPD, CDU, AfD, GRÜNE, FREIE WÄHLER, FDP — 6 PDFs
Insgesamt 30 PDFs in app/static/referenzen/, plus 30 Einträge in
WAHLPROGRAMME[bl][partei] und embeddings.PROGRAMME.
Naming-Schema wie etabliert: <partei>-<bl>-<jahr>.pdf, also
spd-th-2024.pdf, fw-rp-2021.pdf etc.
Wichtig zu Memory feedback_legislaturprogramme: alle BL nutzen das
Programm der LAUFENDEN Wahlperiode, NICHT Programme aus späteren
Wahlen. BW und RP wählen am 08.03.2026 / 22.03.2026 neu — der
18./19. Landtag konstituiert sich erst, daher sind die 17./18. WP
mit den 2021er Programmen weiterhin laufend bis zur Konstituierung.
Indexierung im prod-Container ist NICHT Teil dieses Commits — muss
separat ausgeführt werden:
ssh vserver 'docker exec gwoe-antragspruefer python -c "
from app.embeddings import index_programm
from pathlib import Path
d = Path(\"/app/app/static/referenzen\")
for pid in [
\"cdu-th-2024\",\"afd-th-2024\",\"linke-th-2024\",\"bsw-th-2024\",\"spd-th-2024\",
\"spd-bb-2024\",\"afd-bb-2024\",\"cdu-bb-2024\",\"bsw-bb-2024\",
\"spd-hh-2025\",\"cdu-hh-2025\",\"gruene-hh-2025\",\"linke-hh-2025\",\"afd-hh-2025\",
\"cdu-sh-2022\",\"spd-sh-2022\",\"gruene-sh-2022\",\"fdp-sh-2022\",\"ssw-sh-2022\",
\"gruene-bw-2021\",\"cdu-bw-2021\",\"afd-bw-2021\",\"spd-bw-2021\",\"fdp-bw-2021\",
\"spd-rp-2021\",\"cdu-rp-2021\",\"afd-rp-2021\",\"gruene-rp-2021\",\"fw-rp-2021\",\"fdp-rp-2021\",
]:
index_programm(pid, d)
"'
77 pytest tests passing — der File-Existenz-Check in test_wahlprogramme.py
hätte einen Tippfehler im PDF-Namen sofort gefangen.
Erledigt UI-Aktivierungs-Issues #37 (TH), #39 (BB), #40 (HH), #32 (SH),
#41 (BW), #42 (RP).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 PDFs in app/static/referenzen/ + Einträge in WAHLPROGRAMME
und embeddings.PROGRAMME für die beiden bisher nur per
föderalem Grundsatzprogramm-Fallback abgedeckten Landtage:
- **MV** (WP 8, seit 26.10.2021): CDU, SPD, GRÜNE, FDP, AfD, LINKE
Wahlprogramme zur LTW 26.09.2021. Issue #4.
- **BE** (WP 19, konstituiert nach Wiederholungswahl 12.02.2023):
CDU, SPD, GRÜNE, LINKE, AfD Programme zur AGH-Wahl 26.09.2021.
Die Wiederholungswahl 2023 nutzte dieselben Programme wie die
Originalwahl, daher die "be-2023.pdf"-Benennung mit Programm-
jahr 2021. Issue #10.
Quellen: abgeordnetenwatch.de Mirror für 9 PDFs, library.fes.de
für SPD MV, cdu-mv.de direkt für CDU MV, fdp-mv.de direkt für
FDP MV. Alle PDFs verifiziert via pdftotext gegen das im Programm
genannte Wahldatum, um zu vermeiden, dass aktuellere
Wahlkampf-Entwürfe (z.B. das CDU "Berlin-Plan 2026") als
Legislatur-Programm fehlinterpretiert werden.
Indexierung in die embeddings-DB ist NICHT Teil dieses Commits —
sie muss separat im prod-Container ausgeführt werden:
docker exec gwoe-antragspruefer python -c "
from app.embeddings import index_programm
from pathlib import Path
d = Path('/app/static/referenzen')
for pid in ['cdu-mv-2021','spd-mv-2021','gruene-mv-2021',
'fdp-mv-2021','afd-mv-2021','linke-mv-2021',
'cdu-be-2023','spd-be-2023','gruene-be-2023',
'linke-be-2023','afd-be-2023']:
index_programm(pid, d)
"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brings Sachsen-Anhalt online as the second supported Bundesland after
NRW. Closes the gap that issue #2 left open: with the PortalaAdapter
already in place from c7242f8, this commit adds the reference data and
flips the activation switch.
Wahlprogramme (LTW Sachsen-Anhalt 06.06.2021)
- Six PDFs added under app/static/referenzen/{cdu,spd,gruene,fdp,afd,
linke}-lsa-2021.pdf, plus paged plain-text extractions under
app/kontext/*.txt for the keyword fallback search.
- Sources verified by hand:
- CDU "Unsere Heimat. Unsere Verantwortung." (cdulsa.de, 82 pages)
- SPD "Zusammenhalt und neue Chancen" (FES library, 77 pages)
- GRÜNE "Verlässlich für Sachsen-Anhalt" (gruene-lsa.de, 164 pages)
- FDP "Wahlprogramm zur Landtagswahl 2021" (Naumann-Stiftung, 76 pages)
- AfD "Alles für unsere Heimat!" (klimawahlen.de mirror, 64 pages)
- LINKE "Wahlprogramm zur Landtagswahl 2021" (dielinke-sachsen-anhalt.de,
88 pages)
- The CDU PDF was the trickiest: KAS blocks bot downloads via
Cloudflare; the cdulsa.de copy was located by an autonomous web
search and verified to be byte-identical with the official document.
Embeddings indexed (in production container, OpenAI-compatible
DashScope embeddings via the existing index_programm pipeline):
- CDU 134, SPD 145, GRÜNE 183, FDP 100, AfD 64, LINKE 143 chunks
- Total LSA: 769 new chunks alongside the existing 775 NRW chunks
and 335 federal Grundsatzprogramm chunks.
wahlprogramme.py
- WAHLPROGRAMME["LSA"] populated with all six parties (canonical fraction
codes, original titles, page counts).
embeddings.py
- PROGRAMME extended with the six new "<partei>-lsa-2021" entries that
the indexer pipeline expects.
bundeslaender.py
- LSA flipped to aktiv=True. The frontend dropdown will now offer
Sachsen-Anhalt as a selectable bundesland and analyzer.get_bundesland_
context() will produce a real LSA prompt block (CDU/SPD/FDP as
governing fractions, all six landtagsfraktionen).
End-to-end smoke test (live in production container before commit)
- Adapter: PortalaAdapter.search() returned current Anträge of März 2026
(LINKE + GRÜNE) with correct titles and PDF URLs.
- Semantic search for an LSA "ÖPNV in der Altmark" sample antrag
matched LINKE S.53, SPD S.68, FDP S.52 — all three with similarity
> 0.6 and topical hits (Regionalisierungsmittel, ÖPNV-Förderprogramm,
Wasserstoffnetz).
Resolves issue #2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>