Commit Graph

242 Commits

Author SHA1 Message Date
Dotty Dotter
7545d714f8 fix: News-Clamp auf Summary statt aufs ganze Item — PM-Button bleibt sichtbar
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>
2026-05-07 12:52:41 +02:00
Dotty Dotter
349af3749a fix: result_row default-href auf /antrag/ (= v3) statt /v2/antrag/
Suchergebnisse auf der Übersicht und in /durchsuchen oeffneten weiter-
hin den alten Profi-Modus, weil das result_row-Macro als Default
'/v2/antrag/'+drucksache eingebaut hatte. Jetzt zeigt der Default-
Pfad auf '/antrag/' = v3 Buerger:innen-Modus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:46:40 +02:00
Dotty Dotter
c64f4df4da feat(methodik): Aktualisiert auf Stand Mai 2026 — neue Sektion Stimmverhalten, Versionsstand, partei-skopierte Verifikation
Methodik-Seite jetzt synchron zu Codebase + Features:

Was-macht-der-Pruefer-Liste:
- Empfehlung + Bewertungs-Sicherheit (Konfidenz im Tooltip) ergaenzt
- Schwerpunkt-Felder, Themen-Tags, Kernforderungen aufgenommen
- Abstimmungsergebnis + Wahlprogramm-Konflikt-Marker erwaehnt
- Aehnliche Antraege + News-Match aufgenommen

Pipeline-Step 4: ehrlicher dokumentiert — Verifikation ist
partei-skopiert (heutiger Bugfix), wortgleiche Treffer in fremdem
Programm fuehren zur Verwerfung statt zur Misattribution.

Neue Sektion "Stimmverhalten & Marker" mit
- Tabelle Heuchelei (⚠) / Opportunismus (!) inkl. Schwellen
- Hinweis auf v3-Sprache "Wahlprogramm-Konflikt"
- Konsistenz-Hinweis (Mehrheit deckt sich / gegen GWOE-Empfehlung)
- Code-Pointer zu app/marker.py

Einschraenkungen erweitert:
- Aktuelles vs. historisches Programm (Issue #186)
- Drucksachen-Eindeutigkeit-Limit

Qualitaetssicherung:
- Score-Cap-Invariante dokumentiert
- Neu-Analyse korrekt als manuell beschrieben (vorher "automatisch" -
  nicht ganz richtig)
- Property-/Fixture-Tests-Abschnitt referenziert ADR 0003 + 0008

Neue Sektion "Versionsstand": Live-Tabelle mit Sprachmodell, Embedding-
Modell, Programm-/Chunk-Counts, Adapter-Count, Matrix-Version, Skala.
Werte werden via Template-Context aus dem laufenden System gefuellt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:08:17 +02:00
Dotty Dotter
f0588714bf fix: Topbar-Wrap auf Mobile, Klassische-Ansicht raus, Tags-Greying + Title-Bug
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>
2026-05-07 12:05:10 +02:00
Dotty Dotter
848039627c fix: Mobile Overflow — minmax(0,1fr) + overflow-x:hidden
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>
2026-05-07 11:59:22 +02:00
Dotty Dotter
d8d8f99965 fix: Mobile-Overflow — v2-shell auf 100vw zwingen, sonst wuchs 1fr-Grid auf Content-Breite
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>
2026-05-07 11:55:03 +02:00
Dotty Dotter
535c2f15e4 fix: Citation-Binding partei-skopiert (Cross-Partei-Misattribution gestoppt)
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>
2026-05-07 11:51:03 +02:00
Dotty Dotter
1ef5578e02 feat(v3): v3 wird Default unter /antrag/{drs}, v2 zieht nach /v2/antrag/{drs}
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>
2026-05-07 11:29:06 +02:00
Dotty Dotter
4f4d9f3478 feat(v3): Merken+Vote-Zeile direkt unter Metadaten, Aktions-Links unter Teilen
User-Wünsche:
- Merken + Bewertung-treffend nach oben — direkt unter den Metadaten,
  noch vor der Zusammenfassung.
- PDF-Bericht / Original / JSON / Permalink-Zeile aus dem Kopf des
  Rest-Blocks weiter nach unten — zwischen Teilen und Neu-Analysieren.

Reihenfolge im Rest-Block jetzt: Ähnliche Anträge → Teilen → Aktions-
Links (PDF/…) → Neu analysieren → Historie → News → Kommentare.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:24:10 +02:00
Dotty Dotter
9eaa376fbe feat(v3): Wortwahl entjargonisiert, Layout-Reorganisation, Score mit Farb-Tint
Wortwahl (User-feedback: "vermeide Konfidenz, neutralere Formulierung
als Heuchelei"):
- "Konfidenz" → "Bewertungs-Sicherheit" (Tooltip behält wissenschaftl. Begriff)
- "Heuchelei-Marker"/"Opportunismus-Marker" → einheitlich
  "Wahlprogramm-Konflikt" (Tooltip behält wissenschaftl. Klassifikation)
- "Mehrheit kontra GWÖ-Empfehlung" → "Mehrheit gegen GWÖ-Empfehlung"
- Marker-Legende-Block entfernt (Tooltip pro Marker reicht)

Layout-Reorganisation:
- Stärkster/Schwächster Wert nach oben — zwischen Bewertung und
  User-Aktionen (vorher in "Rest"). Grid 2-spaltig, responsive.
- Konsistenz-Hinweis ("Mehrheit ... GWÖ-Empfehlung") an die Spitze
  des Abstimmungsergebnis-Blocks (vorher in "Rest").
- Marker-Legende komplett raus.

Visuelles:
- Score x/10 mit Farb-Tint hinterlegt: --score-{high,mid,low}-bg/-fg
  aus tokens.css. Farbe folgt der Score-Schwelle (≥7 grün, 4-6 mittel,
  <4 rot).
- Globale `body.v2 p { max-width: 72ch }` mit höherer Spezifität für
  .v3-page-Children überschrieben — alle Body-Texte nehmen volle Breite.

Programm-Treue Fallback:
- Wenn keine Zitate für ein Programm gefunden: erklärender
  Hinweistext im Body-Bereich, statt leerer Block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:16:39 +02:00
Dotty Dotter
f471586f33 feat(v3): Themen, Kernpunkte, Schwerpunkt, Konfidenz, fehlende Programme, Wahlperiode + Ähnliche Anträge
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>
2026-05-07 11:01:18 +02:00
Dotty Dotter
4de2df9cea feat(v3): single-column Restructure — neue Reihenfolge, CD-konforme Radien
User-Spec: 1a (single column überall), CD-Rundungen subtil 2-4 px,
keine 8px-Pills, keine Kreise. v3 wird nach Reife der neue Default.

Reihenfolge:
1. Metadaten/Titel
2. Zusammenfassung
3. Bewertung — Score xl + Empfehlung daneben + verdict_body voll breit
4. Merken + Bewertung treffend
5. Matrix 5×5 (volle Profi-Variante mit Klick-Modal)
6. Programm-Treue — pro Partei: WP-Zeile + PP-Zeile (default closed),
   Klick auf die Zeile expandiert Begründung + Belege. Score-Chip
   rechtsbündig.
7. Verbesserungsvorschläge — volle Breite, kein max-width
8. Abstimmungsergebnis — Plenum + namentliche Abstimmung
9. Rest — PDF/Teilen/Re-analyze/Historie + Stärken/Schwächen +
   Konsistenz-Hinweis + Marker-Legende + News-Box + Kommentare

v2-Templates unangetastet (Sub-Blocks vom letzten Commit beibehalten,
nur main-Block wird in v3 komplett überschrieben). v2-JS-Handler
bleiben funktionsfähig — gleiche DOM-IDs (v2-merkliste-btn, v2-vote-up,
v2-comments-list, v2-matrix-field-modal, …) im neuen Layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:51:33 +02:00
Dotty Dotter
895187ac36 feat(v3): Bürger:innen-Modus visuell ausgebaut — Wort-Etikett, 5-Werte-Bars, Glossar, Collapsibles
v2 unangetastet — v3 überschreibt nur Sub-Blocks. Neue v2-Blocks
für Override-Punkte: antrag_id_section, score_hero_section,
matrix_section, verbesserungen_section, aktions_section,
comments_section.

v3-Anpassungen:
1. Drucksache-ID: "Antrag im Landtag NRW · Drucksache 18/18246" —
   "Drucksache" als Glossar-Hinweis klickbar.
2. Score-Hero: großes Wort-Etikett ("Stark gemeinwohlfördernd" /
   "Gemischt" / "Widerspricht dem Gemeinwohl") aus verdict_title oder
   abgeleitet. Score-Zahl als kleiner Untertitel mit Glossar-Link.
   Akzentfarbe links (grün/blau/rot je nach Score).
3. Matrix: 5 Werte als Diverging-Bars (-5..+5) sofort sichtbar;
   volle 5×5-Matrix in <details>. Aggregation = Spalten-Mittel über
   die 5 Berührungsgruppen.
4. Verbesserungsvorschläge: <details> default zu, Hint-Text mit Anzahl.
5. Aktions-Links: JSON-Export raus — nur PDF + Permalink.
6. Kommentare: <details> default zu (für Erst-Leser:innen unwichtig).

Glossar-System: 6 Begriffe (Drucksache, Fraktion, GWÖ-Score,
GWÖ-Matrix, Heuchelei-Marker, Opportunismus-Marker) mit Klick/Focus-
Tooltip via Modal. Trigger: Element mit class="v3-glossar"
data-glossar="<key>".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:15:50 +02:00
Dotty Dotter
acda3cdb3a fix: Programm-Treue Sub-Labels (Einschätzung, Belege) zurück + Bewertung-Tag
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>
2026-05-07 10:11:12 +02:00
Dotty Dotter
bd0fe54559 fix: Programm-Treue im BELEGE-Layout, default-OPEN statt default-closed
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>
2026-05-07 10:03:04 +02:00
Dotty Dotter
c4750d3274 feat(v3): Bürger:innen-Modus-Sandbox unter /v3/antrag/{drs}
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>
2026-05-07 09:55:06 +02:00
Dotty Dotter
c4f8ce398a feat: Matrix-Klick mit Antrags-Begründung + Vote-Block-Verbesserungen
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>
2026-05-07 09:33:53 +02:00
Dotty Dotter
70d9790b4b feat(#177): Programm-Treue im BELEGE-Layout — pro Partei zwei aufklappbare Blöcke (WP+PP)
- _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>
2026-05-07 09:21:42 +02:00
Dotty Dotter
38e58e4ee0 feat(#175 Phase 23 step 1): Matrix-Block-Renderer via v2-Macro vorbereitet
Erste Stufe der 'one-source-of-truth'-Architektur (User-Vorschlag):
build_matrix_html_v2() rendert die GWÖ-Matrix mit dem matrix_mini-
Macro aus v2/components/matrix_mini.html — gleiche Quelle wie die
Web-View.

Noch nicht aktiv: das v2.css-Stylesheet muss erst im PDF-WeasyPrint-
Aufruf eingebunden werden (Klassen wie m-pp/m-p/m-0/m-n/m-nn). Bis
dahin nutzt der PDF-Renderer weiterhin build_matrix_html mit eigenem
inline-CSS.

Folge-Schritte (Refactor-Pattern):
1. v2.css als zusaetzliches Stylesheet in HTML(string=...).write_pdf()
2. Pro Block (Programmtreue, Verbesserungsvorschlaege, Vote): analog
   v2-Macro extrahieren + im PDF includen.
3. Wenn alle Blocks via Macro rendern: PDF-HTML wird zur reinen
   Block-Liste, Layout-Wechsel ist nur ein @page-CSS-Tausch.

Refs: #175

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:15:25 +02:00
Dotty Dotter
d80e6e7aad feat(#177 Phase 21+22): Verdict-Umfluss + Merken bei Bewertung
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>
2026-05-07 09:13:37 +02:00
Dotty Dotter
045461492f fix: ist_antragsteller/ist_regierung Fallback aus Drucksachen-Metadaten
LLM-Output setzt diese Flags nicht zuverlaessig (alle Werte heute None
in NRW/18/18246-Beispiel). _row_to_detail leitet sie jetzt im
Fallback aus den Drucksachen-Meta ab:
- ist_antragsteller := Fraktion in row.fraktionen (Antragsteller-Liste)
- ist_regierung := Fraktion in BUNDESLAENDER[bl].regierungsfraktionen

Damit erscheinen die 'Antragsteller:in' / 'Regierungsfraktion'-Pills
auch bei alten Assessments ohne explizite LLM-Flags. LLM-Wert (falls
gesetzt) hat weiterhin Vorrang.
2026-05-07 09:10:49 +02:00
Dotty Dotter
481a791934 fix(#183): Browser-Speicher — Modal-Chart-destroy, Force-Sim-stop, Polling-Pause
- **Zeitreihe-Modal-Chart**: vor Re-Render alte Chart.destroy(),
  bei closeModal() + Escape ebenfalls. Vorher akkumulierten sich
  Chart-Instanzen bei jedem Modal-Open (Listener bleiben hängen).
- **Cluster-Force-Sim**: `_currentForceSim` global gemerkt, beim
  Verlassen Detail-View (showList) und vor neuem renderClusterGraph
  per sim.stop() beendet. Vorher liefen tick-Listener weiter, hielten
  nodes/links/SVG-Refs.
- **Polling-Pause auf visibilitychange** in queue_widget, admin_stand
  und admin_queue. Wenn Tab versteckt → clearInterval, beim Show wieder
  starten. Spart CPU/Akku + verhindert verwaiste Polls.

Refs: #183. Heap-Snapshot-Verifikation in Folge-Schritt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:01:48 +02:00
Dotty Dotter
d8999f8a64 fix(#180): @page-Größe in pt statt px für korrekte PNG-Pixel-Dimensionen
WeasyPrint konvertiert CSS-px zu PDF-pt mit 96/72=0.75-Faktor:
1080px CSS-Page → 810pt PDF-Page → 810px PNG (bei zoom=1).
Mit 'size: 1080pt 1080pt' wird die PDF-Page direkt 1080pt
und PyMuPDF rendert 1080×1080px wie erwartet.
2026-05-07 08:04:56 +02:00
Dotty Dotter
52ff36a136 feat(#180): PNG-Scorecards via WeasyPrint→PyMuPDF
PyMuPDF (fitz) ist bereits in requirements.txt — also kein extra
Dependency noetig. Render-Pipeline: HTML-Template → WeasyPrint-PDF →
fitz Pixmap → PNG-Bytes.

Endpoint: GET /api/assessment/scorecard.png?drucksache=&bundesland=
&format=og|square&scale=2.0
- scale=2.0 (Default) liefert Retina-Aufloesung.
- scale=1.0..4.0 erlaubt.

Refactor: gemeinsamer Helper _render_scorecard_pdf() fuer .pdf und .png
— vorher Code-Duplikat zwischen den Endpoints.

Refs: #179, #180

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:03:38 +02:00
Dotty Dotter
eeedf85d7e refactor(Phase 17 follow-up): copyDraftToClipboard nutzt data-pm-id statt JSON.stringify im onclick
Vorher: `onclick="copyDraftToClipboard(this, ${JSON.stringify(...).replace(/"/g, '&quot;')}, ...)"`
— funktional korrekt, aber Pattern-anfaellig (gleiche Klasse wie der
merkliste-bug aus Phase 17). Plus < und > waren nicht escaped.

Nachher: Button traegt nur eine numerische data-pm-id; der Handler
fetched den Draft per API und kopiert den Body. Robuster, weniger
Quote-Escaping, einheitlicher mit dem versionsHtml-Pattern oben in
derselben Datei.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:01:00 +02:00
Dotty Dotter
53f8d2cad5 fix(Phase 17 audit): Cluster-Sort nutzte members.length statt size/drucksachen.length
Audit-Befund: alte UI sortierte _clusters nach (members || []).length —
Backend liefert aber size + drucksachen, members ist leer. Folge: alle
Cards hatten size 0 als Sort-Wert, Reihenfolge war effektiv random.

Backwards-compat-Lookup mit drucksachen → members → size-Fallback.

(Andere c.members-Lookups in antrag_detail.html + aktuelle-themen.html
betreffen News-Cluster, deren API tatsächlich 'members' liefert — kein Bug.)
2026-05-06 23:52:17 +02:00
Dotty Dotter
bdbfc1ff7d feat(Phase 18): PM-Prompt verschaerft + Auto-Re-Generate bei zu kurzem Output
- SYSTEM_PROMPT mit explizitem 'Mindestens 320 Worte, < 280 ist
  Verstoss' + Hinweis 'wenn Substanz ausgeht: Lebenslage vertiefen
  statt abbrechen'.
- Output-Format-Beispiel mit MINDESTENS-Hinweis.
- generate_draft prüft nach LLM-Call die Wortzahl. Bei <280 Worten:
  ein einzelner Re-Prompt mit höherer Temperatur (0.5) und Hint zur
  ersten zu-kurzen Wortzahl. Wenn der zweite Versuch laenger ist,
  wird er übernommen — sonst bleibt der erste.
- max_retries=1 fuer den zweiten Call (nicht endlos).

Audit-Hauptbefund war 15/19 PMs unter Soll 320–380 Worten.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:47:42 +02:00
Dotty Dotter
727d7d2976 fix(#179): Scorecard via WeasyPrint-PDF (Container hat kein Chromium)
Container hat keine Playwright/Chromium-Installation — PNG-Render
deferred. WeasyPrint-PDF mit exaktem Seiten-Setup (1200x630pt fuer
og, 1080x1080pt fuer square) liefert jetzt /api/assessment/scorecard.pdf.

Folge-Issue fuer PNG: Chromium ins Image bauen oder pypdfium2/pdf2image
fuer pdf→png-Konvertierung ergaenzen.
2026-05-06 23:46:01 +02:00
Dotty Dotter
1faf4e9220 feat(#179 Phase 15): Scorecards — HTML-Template + PNG-Endpoint
Mockup im GWÖ-Stil mit:
- Drucksachen-Header (Kicker + Datum)
- Titel + antragstellende Fraktionen als Pills
- Empfehlungs-Verdict
- 420-Zeichen-Zusammenfassung
- Big Score-Zahl (farbcodiert nach 8/5/3-Schwellen)
- 5x5 Mini-Matrix mit korrekten 5 Klassen (rating-pp/-p/-0/-n/-nn)
- Footer mit Brand + Drucksachen-ID

Endpoints:
- GET /v2/scorecard?drucksache=&bundesland=&format=og|square (HTML)
- GET /api/assessment/scorecard.png?... (PNG via Playwright,
  1200x630 für og, 1080x1080 für square)

Pattern entlehnt von app/og_card.py (Playwright-Headless-Render).

Refs: #179

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:44:21 +02:00
Dotty Dotter
c268d889fa fix(#175 #176 Phase 14): PDF-Matrix-Coloring + Symbol an v2 angleichen
- get_rating_symbol nutzt jetzt -5..+5-Skala (vorher: rating>=2 → ++,
  was bei rating=2 oder 3 falsche '++' gab; jetzt: rating>=4 → ++).
- PDF-Tabelle nutzt 5 Klassen (rating-pp/-p/-0/-n/-nn) statt 3
  (positive/negative/neutral). Heller Grün/Rot-Tint für mid-strength
  ratings, kräftiges Grün/Rot für Extreme. Visuell deutlich
  unterscheidbar.
- Beibehaltung der alten Klassennamen für Backwards-Compat falls
  irgendwo zwischengespeicherte HTML-Reports liegen.

Damit ist die v2/PDF-Konsistenz fuer NRW/18/18246 (#176) bezüglich
Matrix-Symbole und -Farben hergestellt.

Refs: #175, #176

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:42:15 +02:00
Dotty Dotter
ee93fcd76a fix(#177): _row_to_detail Matrix-Rating-Shift entfernt — Wurzel von '+/++ gleichfarbig'
DB speichert seit langem die volle -5..+5 Skala (siehe models.py
MatrixEntry mit ge=-5, le=5), aber _row_to_detail shiftet noch
'rating - 3' (Migration-Reste der alten 1..5 → -2..+2-Skala).
Folge: rating=5 wurde zu 2, rating=4 zu 1, beide landeten im
matrix_mini auf der m-p-Klasse (rating 1..3) → kraftiges Gruen
(m-pp) wurde fast nie ausgespielt.

Fix: kein Shift; defensive int-Konversion + Clamp -5..+5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:39:52 +02:00
Dotty Dotter
c87b99f778 feat(#177 Phase 13): Matrix-Coloring + Programm-Treue mit Begründung
- **Matrix-Coloring konsistent**: Symbol wird jetzt aus rating
  abgeleitet (rating_symbol-Macro) statt vom LLM übernommen. Bisher
  wurde z.B. rating=4 + symbol="+" geliefert → Template zeigte "+"
  aber mit m-pp-Klasse (kräftiges Grün) → "++/+ wirkten gleichfarbig".
  Stichprobe: 7/30 Assessments hatten rating/symbol-Mismatch.
- **„Antragsteller:in" / „Regierungsfraktion"** als Pill ausgeschrieben
  statt 1-Buchstabe-Badges A/R.
- **Programm-Treue mit Begründung sichtbar**: Wahlprogramm- und
  Parteiprogramm-Begründung als Block direkt unter den Score-Chips.
  Vorher nur Tooltip — auf Mobil schwer zugänglich.
- **„Redline" → „Verbesserungsvorschläge"** in beiden Heading-Pfaden.

Layout-Umstellung (Matrix↔Vote oben, Programm-Treue↔Verbesserung unten)
in #177 als Follow-up — braucht gerichtete Session mit Browser-Vorschau.

Refs: #177

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:38:01 +02:00
Dotty Dotter
a06bcb4d89 fix(#178 Phase 12): Teilen-Funktion umfassend gefixt
- **X (Twitter) raus** — Button + Logik entfernt.
- **Copy & Paste**: vollständiger Body ohne Länge-Cut, mehrzeilig
  strukturiert (Score, Titel, Drucksache, Beschreibung, Permalink,
  Hashtags). Statt 240-Zeichen-Twitter-Variante.
- **Threads**: encodeURIComponent kümmert sich um UTF-8 — keine
  Sonderzeichen-Probleme.
- **Mastodon**: gleicher Body wie Threads, Limit auf 420 Zeichen
  (mit Permalink-Reserve), Instance-Prompt bleibt.
- **LinkedIn**: Composer öffnet nur den Permalink (LinkedIn-API-
  Limitation), aber der vollständige Body landet parallel in der
  Zwischenablage. Toast informiert User.
- **E-Mail**: strukturierter Body mit Umbrüchen — Score-Zeile, Titel,
  Drucksache, Beschreibung, Permalink, Footer. Statt der knappen
  Threads-Variante.
- **Magnific**: korrekte URL mit `last_filter=selection&last_value=1
  &selection=1` — license-Filter vorausgewählt.

Refs: #178

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:34:09 +02:00
Dotty Dotter
d853101275 feat(Phase 11b): Bypass-DB-Logging + Auto-Rotation-Skript
- auth_bypass_uses-Tabelle additiv (used_at, client_ip, path, user_agent).
- _check_debug_token schreibt jeden Use als Best-Effort-Insert
  (Try/Except, kein Fehler an User).
- scripts/rotate-debug-token.sh: wöchentlicher Cron, generiert
  neues Secret + re-creates dev-Container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:31:51 +02:00
Dotty Dotter
c13292133c fix: Validator akzeptiert Bundesrats-3-Komponenten-Drucksachen
Damit /api/analyze-drucksache die Bundesrats-spezifische 400-Meldung
liefern kann (vorher haengen blieb am Path-Traversal-Validator mit
generischem 'Ungueltige Drucksache-ID').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:30:29 +02:00
Dotty Dotter
5667259bff feat(#6 Phase 11): Bundesrats-Drucksachen erkennen + markieren + ablehnen
DIP-Drucksachen mit `herausgeber: 'BR'` (Bundesrat) haben Bundesländer
als Antragsteller (z.B. SN, HE) statt Fraktionen. Variante b — explizite
Behandlung statt nur ausschließen:

- Drucksache-dataclass: neue Felder `is_bundesrat: bool`,
  `urheber_bundeslaender: list[str]`. Existierende Pfade unberührt.
- BundestagAdapter._doc_to_drucksache: liest herausgeber + urheber-Liste,
  setzt Bundesländer-Codes (bezeichnung wie "SN") in
  urheber_bundeslaender. fraktionen bleibt leer fuer BR — verhindert
  dass Stimmverhalten-Aggregate verwirrt werden.
- /api/search-landtag liefert is_bundesrat + urheber_bundeslaender im
  Response.
- /api/analyze-drucksache (POST) lehnt BR-Drucksachen mit HTTP 400 +
  klarer Meldung ab statt crashen.
- v2-Search-UI rendert grayen Bundesrat-Sticker mit BL-Codes statt
  Fraktionen, "Analysieren"-Button durch "nicht unterstuetzt" ersetzt.

is_bundesrat_drucksache() in drucksache_typen.py als Format-Helper
(N/M/JJ-Pattern) bleibt fuer Cases wo nur die Drucksache-ID ohne
Adapter-Metadaten verfuegbar ist.

Refs: #6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:29:12 +02:00
Dotty Dotter
03948038c4 fix: Merkliste-Lösch-Row blieb im DOM — ID-Lookup vs. escAttr inkonsistent
Row-ID wird via escAttr gebildet ([^a-zA-Z0-9_-] → '_'), z.B. '18/18089'
landet als id='merkliste-row-18_18089'. Der getElementById-Lookup nutzte
aber CSS.escape, das 18/18089 zu 18\\/18089 escaped — zwei verschiedene
Strings, getElementById lieferte null, el.remove() lief nicht.

Plus: getElementById akzeptiert ohnehin keinen CSS-Selektor — der
CSS.escape-Lookup war doppelt falsch.

Fix: gleiche Sanitization-Regex wie escAttr im Lookup nutzen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:40:27 +02:00
Dotty Dotter
c599e5f6b5 fix: Merkliste-Löschen ging nicht — onclick-Attribut HTML-broken
JSON.stringify(a.drucksache) lieferte einen JSON-String mit Doublequotes
(z.B. "18/18089"). Eingesetzt in onclick="merkliste_remove("18/18089")"
brach das das HTML-Attribut beim ersten inneren Doublequote, der Browser
warf 'Unexpected end of input' beim Click und der DELETE-Request kam nie
beim Server an.

Fix: escHtml() um den JSON-String, sodass Quotes als &quot; gerendert
werden — onclick-Attribut bleibt valide.

Bug headless mit Playwright + DEBUG_AUTH_TOKEN gefunden (commit f8cfa42).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:39:16 +02:00
Dotty Dotter
cb6971ff86 fix: ungeschlossene <div class='controls-bar'> verschluckte 3 Tabs
In panel-themen wurde <div class='controls-bar'> geöffnet, aber nie
geschlossen. Folge: alle nachfolgenden Panels (Stimmverhalten,
Score-Verteilung, Cluster-Link) rutschten als Kinder in panel-themen
rein und erbten dessen display:none.

Bei aktiviertem Tab-Switch wurde das richtige Panel zwar mit class
'active' versehen, aber sein PARENT (panel-themen) blieb display:none
— daher 0×0 Bounding-Box auf allen Charts.

Ohne Debug-Bypass headless gefunden — Diagnose-Skript zeigte panel-
stimmverhalten als Kind von panel-themen im DOM-Stack.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:30:21 +02:00
Dotty Dotter
f8cfa42d9f feat: DEBUG_AUTH_TOKEN Bypass fuer Diagnose-Sessions auf dev
Wenn ENV `DEBUG_AUTH_TOKEN` gesetzt ist, akzeptieren require_auth +
require_admin einen Header `X-Debug-Token: <secret>` oder einen
Query-Param `?__debug_token=<secret>` und liefern einen Admin-Mock-
User. Jeder Use wird mit logger.warning protokolliert.

Default: leer = inaktiv (auch in prod, weil prod-compose das nicht
durchreicht).

Damit kann ein Diagnose-Tool (Playwright, curl) ohne Keycloak-Login
auf admin-only-Endpoints zugreifen — fuer Browser-Console-Auswertung
bei UI-Bugs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:26:39 +02:00
Dotty Dotter
60db39d5b3 fix: Force-Graph zeigt nichts — Edges sind Index-basiert, nicht ID-basiert
Backend liefert edges als {a: 0, b: 1, sim: ...} mit Indizes in der
nodes-Liste. d3.forceLink mappt per id-Lookup und fand 'drucksache' als
Lookup-Key nicht. Folge: keine Links, Force-Sim degeneriert ohne Layout.

Fix: Index-Strings als id verwenden.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:11:16 +02:00
Dotty Dotter
d72a6f30df fix: Cluster-Detail-View unsichtbar — display:'' griff CSS-default 'none'
Beim Klick auf eine Cluster-Card setzte showCluster() detail.style.display = ''.
Da #cluster-detail per CSS aber 'display:none' hat, fiel der Style auf
'none' zurueck — Detail-View blieb unsichtbar, Force-Graph wurde nie gesehen.

Fix: explizit 'block' setzen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:32:28 +02:00
Dotty Dotter
fdac89ab47 feat: Force-Graph-Visualisierung im /v2/cluster-Detail
Beim Klick auf einen Cluster wird jetzt zusätzlich zur Antragsliste
ein d3-Force-Graph eingeblendet. Knoten = Drucksachen, Kantendicke =
Cosine-Similarity, Knotenfarbe = dominante Fraktion. Klick auf einen
Knoten oeffnet das Antrag-Detail.

Daten kommen aus dem bereits vorhandenen /api/clusters-Response
(nodes/edges-Felder, vorher ungenutzt). Layout: forceSimulation mit
link/charge/center/collide. d3.v7.min.js wird im head_extra geladen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:21:15 +02:00
Dotty Dotter
56c68d3398 fix: Cluster-Liste leer — Backend-Felder drucksachen/avg_gwoe_score
Backend liefert seit ueber den Refactor fields drucksachen/
avg_gwoe_score; Frontend-Template las members/avg_score → leere
Cluster-Cards. Beide Schluessel akzeptieren (Backwards-Compat).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:52:36 +02:00
Dotty Dotter
c3d4ab186f fix: icon()-Macro mit ignore-missing + Coverage-Test
Folge zum scales.svg-Vorfall (commit 01ea766):

1. icon.html: `{% include … ignore missing %}` — fehlende SVG-Files
   rendern jetzt leeren Span statt einen 500 auszuloesen. data-icon-
   Attribut zeigt den angefragten Namen, hilft im DevTools-Inspector.
2. tests/test_icons.py: scannt alle templates/-Files nach
   icon("name")-Aufrufen und prueft, dass jedes referenzierte Icon
   als SVG-File existiert. 4 Tests, alle gruen — verhindert dass
   solche Aufrufe in Zukunft unentdeckt durchrutschen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:35:59 +02:00
Dotty Dotter
01ea7665cc fix: 500 nach Login durch fehlendes 'scales'-Icon — circle-half nutzen
Der Stimmverhalten-Nav-Eintrag (#169) referenzierte ein
phosphor/scales.svg-Icon, das nicht im Repo liegt. Folge: Jinja-
TemplateNotFound bei jedem Render von base.html nach Auth → 500
auf jeder authenticated Page.

Fix: circle-half (existierendes Icon, semantisch passend fuer
Pro/Contra-Balance).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:32:38 +02:00
Dotty Dotter
f660c89a63 feat(#190 Phase 10.3): Vote-Mehrheits-Bar im Antrag-Detail
Stacked Bar (Ja gruen / Enth grau / Nein rot) zeigt die Fraktions-
Mehrheit pro Plenum-Vote. Caveat-Tooltip ⓘ stellt klar: Anzahl
Fraktionen, nicht Sitz-/Stimm-Anteile (Plenarprotokoll liefert
keine Sitz-Counts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:12:39 +02:00
Dotty Dotter
1dfdb59954 feat(#189 Phase 10.2): Empfehlungs-Konsistenz-Drilldown analog zu Heuchelei
- get_empfehlungs_konsistenz_cases() liefert Antraege wo `partei` mit
  NEIN gestimmt hat, obwohl die GWÖ-Empfehlung "Unterstuetzen" lautete.
- Endpoint GET /api/auswertungen/empfehlungs-konsistenz-cases
- Frontend: Konsistenz-Bar bekommt onClick → Modal-Tabelle mit Drucksache,
  BL, Datum, GWÖ-Score, Empfehlung, Beschluss. Drucksachen-Link ins Detail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:11:34 +02:00
Dotty Dotter
ba1f104c8e feat(#178 Folge): Thread-Auto-Splitter + Quality-Audit-Skript
- _split_into_thread_posts() splittet zu lange Bodies an Satzgrenzen
  in mehrere Posts ≤ max_chars (Default 280). Greedy: möglichst viele
  Sätze pro Post. Hashtags am Ende bleiben erhalten.
- generate_draft(style='thread') ruft den Splitter auf, wenn das LLM
  weniger als 3 Posts oder Posts > 290 chars liefert.
- 7 Unit-Tests fuer den Splitter (test_thread_splitter.py).
- scripts/pm-quality-audit.sh: prueft alle PM-Drafts gegen Verbotsliste
  (GWÖ-Score, Matrix-Codes, Floskeln) + Wortzahl + Absatzzahl + Post-Laengen.
  Markdown-Report-Output. Audit von 23 Drafts: 4/23 ohne Auffaelligkeit;
  Hauptbefund: PMs haeufig zu kurz, Threads splittten ohne Auto-Splitter
  nicht zuverlaessig — Splitter behebt das.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:08:57 +02:00
Dotty Dotter
62636b5a78 quality: 9 Tests fuer auto_rate_runs + ruff F401 in main.py
Phase 8 (Code-Pflege):

- Neue Test-Datei tests/test_auto_rate_runs.py (9 Cases) deckt
  record_auto_rate_run, list_auto_rate_runs, auto_rate_today_total
  und das Schema ab.
- list_auto_rate_runs sortiert jetzt by id DESC (statt started_at DESC),
  weil started_at nur sekundengenau ist und Sub-Sekunden-Inserts
  unstabilen Output produzierten.
- ruff --select F401 --fix auf main.py: 7 ungenutzte Imports entfernt
  (MAX_SEARCH_QUERY_LEN, import_json_assessments, KLEINE_ANFRAGE,
  BUNDESLAENDER, lokale sqlite3/json/timezone-Reimports). Tests
  weiterhin grün (74 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:58:49 +02:00