Commit Graph

45 Commits

Author SHA1 Message Date
Dotty Dotter
997d59a9a5 fix(v2): Queue-Widget ist immer sichtbar (auch ohne aktive Jobs)
Vorher: filterte stale-Jobs raus, bei leerer aktiver Queue display:none → User sah nichts.
Jetzt: immer sichtbar mit 'Queue leer · N Worker bereit' wenn nichts aktiv.
Tooltip zeigt Stale-Jobs als 'letzter Lauf'-Liste, wenn keine aktiven Jobs da sind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:13:30 +02:00
Dotty Dotter
273d45ea36 fix: PDF-Link mit #page=N-Hash — Browser-PDF-Viewer landet jetzt direkt auf der richtigen Seite
Browser-PDF-Reader (Chrome, Firefox) ignorieren das von /OpenAction-Eintrag im
PDF-Catalog (#88f9c7d) komplett. Der zuverlaessige Weg: URL-Hash-Anker '#page=N'.

Drei Stellen angepasst:
- redline_utils.build_pdf_href: haengt #page={seite} an die URL
- embeddings._build_zitat_url (rebind): analog
- v2/components/quote_card.html: bei alten DB-Eintraegen ohne Hash wird er
  on-the-fly aus dem 'seite='-Query-Param erzeugt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:09:46 +02:00
Dotty Dotter
50c026e3a0 fix(v2): Topbar-Höhe runter, Share-Felder erweitert (Kopieren/LinkedIn/Email/Bild), Smoke-Test 401-Pattern
- Topbar padding 10px -> 4px, min-height 32px (User: 'Header weniger hoch')
- Share-Buttons im Antragsdetail erweitert auf 7 Plattformen analog v1:
  Kopieren (Clipboard), Threads, X, Mastodon, LinkedIn, E-Mail (mailto), Bild (Freepik)
- v2DetailShareCopy/Email/Image-Helper, ANTRAG_TOPICS ans Template uebergeben
- Smoke-Test akzeptiert 401 fuer auth-protected Routen (curl ohne Accept-Header
  bekommt 401-JSON, echte Browser bekommen 302-Redirect via _auth_redirect_handler)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:57:04 +02:00
Dotty Dotter
553e99d14e feat(v2): globaler BL-Selector im Header + Auth-gated Sidebar + Queue-Widget
Bundesland-Auswahl:
- Topbar: einziger BL-Selektor mit localStorage.gwoe.bl-Persistenz
- BL-Felder entfernt aus durchsuchen.html, landtag_suche.html, neu.html, auswertungen.html
- Screens hoeren auf v2-bl-changed CustomEvent + initial via window.v2GetGlobalBl()

Sichtbarkeit (Sidebar):
- Durchsuchen + Tags: immer
- Merkliste / Neuer Antrag / Landtag-Suche / Auswertungen / Export / Feed: nur eingeloggt
- Cluster + Batch-Analyse + Administration: nur Admin

Server-Side Schutz:
- _v2_template_context()-Helper liefert is_authenticated, is_admin, v2_bundeslaender
- HTML-Routen mit Depends(require_auth) bzw. require_admin
- 401/403-Browser-Requests redirecten auf /?login=1 statt JSON-Error

Queue-Widget (#149):
- Neues Component-Partial v2/components/queue_widget.html
- Statusbar unten links + Hover-Tooltip mit den letzten 20 Jobs
- 5s-Polling auf /api/queue/status, blendet sich aus wenn keine Jobs

Smoke-Test angepasst an neue Auth-Erwartungen (302 fuer auth-protected Routen).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:50:36 +02:00
Dotty Dotter
38bffb23fa fix: Job-Polling vor Redirect statt sofortigem Antrag-nicht-gefunden
Vorher: Klick 'Analysieren' -> POST /api/analyze-drucksache -> sofort
window.location.href = '/antrag/{ds}' -> aber Job laeuft noch im Hintergrund
-> Detail-Seite zeigt 'Antrag nicht gefunden'.

Jetzt:
- already_checked -> sofortiger Redirect
- skipped (nicht abstimmbar) -> Hinweistext im Form
- queued -> Polling auf /status/{job_id} alle 2s, max 3 Min
- completed -> Redirect zur Detail-Seite
- failed/rejected -> Fehlermeldung mit Grund

Anwendung in v2/screens/landtag_suche.html + v2/screens/neu.html.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:35:55 +02:00
Dotty Dotter
565849bd84 feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
  (ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
  Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze

Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
  /api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie

Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
Dotty Dotter
2c0e94d29d feat(#106,#135,#128): Monitoring + abgeordnetenwatch + Wahlprogramm-Check
- monitoring.py: taeglicher Scan-Adapter aller aktiven BL, kein Auto-Fetch (#135)
- monitoring_digest.html: Mail-Template mit '0-Kontext'-Hinweis
- abgeordnetenwatch.py + sync_*.py: Phase 1 Roll-Call-Voting (#106)
  - 17 Parlamente (16 BL + BT)
  - 9 BL-spezifische Drucksachen-Patterns + Date-Title-Fallback
  - 28977 Votes fuer BUND in DB
- wahlprogramm_check.py: fehlende Programme erkennen (#128)
- NI-Skip-Liste, NRW Empty-Query-Fallback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:16 +02:00
Dotty Dotter
4fbdc1522a #114 Dark Mode: CSS-Variables + Toggle + prefers-color-scheme + localStorage 2026-04-10 23:56:29 +02:00
Dotty Dotter
16f8caedc1 #103 Registrierung + Admin-Freischaltung + Matrix-Modal-Fix + Issues
Registrierung:
- POST /api/auth/register: erstellt User in Keycloak mit enabled=false
- GET /api/auth/pending-users: Liste nicht-freigeschalteter User (Admin)
- POST /api/auth/approve-user: User freischalten (Admin)
- Registrierungs-Dialog im Hamburger-Menü
- Admin: "Freischaltungen"-Button (nur sichtbar mit admin-Rolle)

Matrix:
- Zeilen-Header klickbar → Erklärung der Berührungsgruppe mit
  konkretem Lebensalltag-Beispiel
- Spalten-Header klickbar → Erklärung des Werts mit Staatsprinzip
- Feld-Erklärungen: 25 konkrete Bürger:innen-Texte (Schule, Bus,
  Miete, Steuer, Spielplatz...)
- Spalten nummeriert: "1. Menschenwürde" etc.

Neue Issues angelegt:
#104 Zeitreihe, #105 Clustering, #106 Abstimmungsverhalten,
#107 Vergleichsansicht, #108 Empfehlungen, #109 Share-Buttons
2026-04-10 23:53:05 +02:00
Dotty Dotter
221d9426b7 Matrix: Header klickbar + konkrete Bürger:innen-Texte aus dem Lebensalltag 2026-04-10 23:43:57 +02:00
Dotty Dotter
632064d98f Fix: Matrix-Modal onclick via data-Attribute statt inline JS-Quoting 2026-04-10 23:40:21 +02:00
Dotty Dotter
14e2e1eee2 Matrix klickbar: Feld-Info-Modal mit Bürger:innen-Erklärungen + Spalten nummeriert
Klick auf jedes Matrix-Feld öffnet ein Modal mit:
- Feld-Code + voller Name (z.B. "D4: Soziale Gestaltung")
- Zeile + Spalte in Klartext
- "Was bedeutet das für Bürger:innen?" Erklärung (25 Texte)
- Falls bewertet: Aspekt aus der LLM-Analyse + Rating-Farbe
- Falls nicht bewertet: "Dieses Feld wird vom Antrag nicht berührt"

Spaltenüberschriften: "1. Menschenwürde" statt nur "Menschenwürde"
2026-04-10 23:38:37 +02:00
Dotty Dotter
13714410ab Batch+Queue ins Hamburger: Overlay-Panels mit Live-Status, Queue immer sichtbar 2026-04-10 23:27:27 +02:00
Dotty Dotter
cf313bd257 #100 Sortierung: Dropdown mit 6 Optionen (Score/Datum/Nr/Titel) + localStorage-Persistenz 2026-04-10 23:26:05 +02:00
Dotty Dotter
d24949740b #99 Queue: 3 parallele Worker + Job-Visualisierung + Admin-Schutz
Queue (queue.py):
- QUEUE_CONCURRENCY ENV (default 3) statt hartcodiert 1
- N Worker-Coroutines via asyncio tasks (nicht Semaphore — jeder
  Worker pickt eigenständig von der Queue)
- Per-Job-Tracking: job_id → {status, drucksache, duration, error}
- get_queue_status() liefert jobs-Array für UI-Tabelle

Visualisierung (index.html):
- Fortschrittsbalken (X/Y fertig, grün)
- Job-Tabelle: Drucksache + Status-Icon + Dauer
- Fertige Jobs klickbar → Detail-Ansicht
- Auto-Refresh alle 3s

Admin-Schutz (auth.py + main.py):
- Neue require_admin Dependency: prüft Keycloak-Rolle "admin" oder
  "gwoe-admin". Im Dev-Modus durchlassen.
- Batch-Analyse, Programme-Index, Assessment-Delete: require_admin
- Einzelanalyse, Bookmarks, Kommentare: bleiben require_auth
- Keycloak: Rolle "admin" erstellt + User tobias zugewiesen

Tests: 206 passed.

Refs: #99
2026-04-10 23:15:42 +02:00
Dotty Dotter
5f5d9edf83 Batch-Analyse UI: Button im Prüfen-Tab mit BL-Auswahl + Limit + Queue-Polling 2026-04-10 23:08:49 +02:00
Dotty Dotter
cfe36cbd65 #98 GWÖ-Matrix interaktiv: volle Begriffe + Tooltips + Staatsprinzipien
Matrix-Tabelle:
- Zeilen-Header: volle Berührungsgruppen-Namen (statt "A: Lieferant:innen"
  jetzt "A: Ausgelagerte Betriebe, Lieferant:innen")
- Spalten-Header: Mouseover zeigt Staatsprinzip + Kernfragen
  (z.B. "Sozialstaatsprinzip — Gerechte Verteilung? Daseinsvorsorge?")
- Bewertete Felder: Tooltip mit Feldcode + voller Name + Aspekt aus der
  Bewertung + Rating-Erklärung ("++ stark fördernd")
- Nicht-bewertete Felder: ○ mit Tooltip "Nicht bewertet (Antrag berührt
  dieses Feld nicht)" statt leere Zelle

Detail-Liste:
- Feld-Labels jetzt mit vollem Namen aus MATRIX_LABELS
- Aspekt kursiv hinter dem Label
- Rating-Zahl neben dem Symbol (z.B. "++ (+5)")

Daten aus models.py::MATRIX_LABELS via Template-Variable matrix_labels.

Tests: 206 passed.

Refs: #98
2026-04-10 23:06:37 +02:00
Dotty Dotter
5d2a0338ee Kommentar-Sichtbarkeit: Öffentlich/Angemeldete/Nur ich + Badges + Server-Filter 2026-04-10 22:40:27 +02:00
Dotty Dotter
ad97a76824 Hamburger-Menü: Auswertungen/Quellen/Methodik/Auth als Dropdown, primäre Tabs bleiben 2026-04-10 22:29:55 +02:00
Dotty Dotter
e5d4ce2553 Merkliste-Tab + Kopfzeile einheitliche Schriftgröße (0.9rem) 2026-04-10 22:25:52 +02:00
Dotty Dotter
e1deec8b53 Merkliste: eigener Tab mit Bookmark-Übersicht, klickbar zum Detail 2026-04-10 22:24:43 +02:00
Dotty Dotter
4b40de4e93 #94 Bookmarks + Kommentare: DB-Schema, API, UI
DB (database.py):
- bookmarks-Tabelle (user_id + drucksache, toggle)
- comments-Tabelle (user_id, user_name, drucksache, text, visibility)
- Functions: toggle_bookmark, get_bookmarks, add_comment, get_comments, delete_comment

API (main.py):
- POST /api/bookmark (toggle, Auth-pflichtig)
- GET /api/bookmarks (User-Bookmarks)
- POST /api/comment (Auth-pflichtig, max 2000 Zeichen)
- GET /api/comments?drucksache= (öffentlich)
- DELETE /api/comment/{id} (nur eigene, Auth-pflichtig)

UI (index.html):
- Bookmark-Button ("🔖 Merken" / " Gemerkt") im Detail-Footer
- Kommentar-Bereich: Liste + Eingabefeld + Senden-Button
- Kommentare laden automatisch beim Detail-Öffnen
- Eigene Kommentare löschbar (✕ Button)
- Ohne Login: "Anmelden um zu kommentieren"

Gruppen-Sichtbarkeit (visibility) ist vorbereitet aber noch nicht
im UI exponiert — kommt als separater Schritt wenn Keycloak-Gruppen
konfiguriert sind.

Tests: 206 passed.

Refs: #94
2026-04-10 22:19:46 +02:00
Dotty Dotter
5ec0b08648 Fix: normalizePartei als globale Funktion (war in updateStats scoped → ReferenceError in showDetail) 2026-04-10 22:15:13 +02:00
Dotty Dotter
b851173e6d UI-Polish: 6 Fixes aus visuellem Review
1. AfD/AFD Duplikat in Partei-Stats: normalizePartei() client-seitig
2. Antragsteller:in Labels: aus item.fraktionen ableiten wenn
   istAntragsteller null (LLM liefert es inconsistent)
3. Überlange Titel in Liste: auf 80 Zeichen + Ellipsis gekürzt
4. Methodik-Text: "verworfen" → "verifiziert / nicht wörtlich markiert"
5. Bewertungsdatum im Header (neben Drucksache-Nr statt nur im Footer)
6. Index-Button: Schloss-Icon + Tooltip "Erfordert Anmeldung"
2026-04-10 22:13:30 +02:00
Dotty Dotter
f1a7da8544 Hybrid-Zitate: verified/unverified statt drop + UI-Labels
reconstruct_zitate droppt Zitate nicht mehr bei No-Match, sondern
markiert sie als verified=false. Das ist ehrlicher: paraphrasierte
Zitate sind wertvoller Kontext, sie brauchen nur ein visuelles
Unterscheidungsmerkmal.

UI:
- Verifizierte Zitate: grüner solid Border, "✓ verifiziert"
- Paraphrasierte Zitate: gelber dashed Border, "~ paraphrasiert
  (nicht wörtlich im Programm)"
- Warning-Text: "Zu diesem Themenkomplex konnten keine konkreten
  Formulierungen im Wahlprogramm gefunden werden"
- Antragsteller:in / Landesregierung als farbige Badges

Zitat-Model: neues Optional[bool] Feld "verified".

Tests: 206 passed (test_drops angepasst auf neues Verhalten).
2026-04-10 21:45:36 +02:00
Dotty Dotter
9c162d14ac UI: Warning-Text verbessert + Antragsteller:in/Landesregierung Labels als Badges 2026-04-10 21:41:15 +02:00
Dotty Dotter
0d0c06106a Auth-UI: Logout-Button + Re-Analyze-Feedback + Uhrzeit beim Bewertungsdatum 2026-04-10 21:24:07 +02:00
Dotty Dotter
f728388286 #97 Neu bewerten: manueller Re-Analyse-Button + Bewertungsdatum
Fußzeile unter jedem Assessment-Detail jetzt mit:
- Bewertungsdatum ("Bewertet am DD.MM.YYYY") aus updated_at
- Quelle + Modell (batch-reanalyze / webapp, qwen-plus)
- "Neu bewerten"-Button (Auth-pflichtig, ausgegraut ohne Login)

Flow: Klick → DELETE /api/assessment/delete → POST /api/analyze-drucksache
→ Queue → pollAnalysis → Detail neu laden

Neuer DELETE-Endpoint /api/assessment/delete mit require_auth.

API-Response erweitert um updatedAt, source, model für beide
Endpoints (list + single assessment).

Tests: 206 passed.

Refs: #97
2026-04-10 21:10:33 +02:00
Dotty Dotter
14140571d8 Fix: CDU-PDF AssertionError Fallback + Kopfzeile vereinheitlicht + Fehler-Debug 2026-04-10 20:05:28 +02:00
Dotty Dotter
916b0ca643 Debug: JS-Fehler anzeigen + docker-compose version entfernt 2026-04-10 19:55:08 +02:00
Dotty Dotter
d75e9441a3 Quellen-Seite: Programme nach Bundesland gruppiert statt einer langen Liste 2026-04-10 19:10:18 +02:00
Dotty Dotter
ee08cb0c29 Quellen-Seite: PDF-Thumbnails der ersten Seite + Thumbnail-API-Endpoint 2026-04-10 18:40:13 +02:00
Dotty Dotter
0870e8a910 #96: Methodik-Seite um konkretes Bewertungsbeispiel ergänzt 2026-04-10 16:34:44 +02:00
Dotty Dotter
07507de24a #96 Methodik-/Transparenz-Seite unter /methodik
Neue Seite für Endnutzer-Transparenz über die Bewertungsmethodik:

- GWÖ-Matrix 2.0 Erklärung mit interaktivem 5×5-Grid
- Analyse-Pipeline als 5-Schritt-Visualisierung (Download → Embedding
  → LLM → Verifikation → Darstellung)
- Wahlprogramm-Vergleich: Erklärung des Retrieval + Top-K + Verifikation
- Qualitätssicherung: Sub-D Property-Tests, server-seitige Quellen-
  Rekonstruktion, automatische Neu-Analyse
- Einschränkungen: KI-Bias, keine juristische Bewertung, nur indexierte
  Programme, kein Abstimmungsverhalten
- Datenquellen: dynamische Tabelle aller angebundenen Parlamente aus
  ADAPTERS + bundeslaender.py
- Technische Details aufklappbar (details/summary) für Interessierte,
  Haupttext verständlich für Nicht-Techniker
- Links zu Quellen-Seite, Adapter-Matrix, ADRs

In Hauptnavigation verlinkt (neben Quellen + Auswertungen).
Template-Variablen: adapter_count, model_name, programme_count,
chunk_count, bundeslaender — alles dynamisch aus dem Backend.

Tests: 194/194 grün.

Refs: #96
2026-04-10 16:14:38 +02:00
Dotty Dotter
7159240f49 #43 Keycloak SSO: JWT-Middleware + UI-Guiding
Auth-Schicht vorbereitet — Dev-Modus (KEYCLOAK_URL leer) lässt alles
durch, Prod-Modus (ENV gesetzt) validiert JWT gegen Keycloak-JWKS.

Backend (app/auth.py):
- JWKS-Cache mit 1h TTL (async httpx fetch)
- get_current_user: Optional, gibt User-Dict oder None
- require_auth: Pflicht, gibt User-Dict oder HTTP 401
- keycloak_login_url: Baut die OIDC-Login-URL
- _is_auth_enabled: prüft ob alle 3 ENV-Vars gesetzt sind

Abgesicherte POST-Endpoints:
- POST /analyze → Depends(require_auth)
- POST /api/analyze-drucksache → Depends(require_auth)
- POST /api/programme/index → Depends(require_auth)

Neue Endpoints:
- GET /api/auth/me → {authenticated, sub, email, name, roles} oder {authenticated: false}
- GET /api/auth/login-url → {enabled, url} für Keycloak-Redirect

Frontend (index.html):
- initAuth() beim DOMContentLoaded → prüft /api/auth/me
- "Anmelden"-Button im Header (neben "Quellen")
- "Jetzt prüfen"-Button: disabled + Tooltip "Nur nach Anmeldung
  verfügbar" wenn nicht eingeloggt; aktiv wenn eingeloggt
- currentUser-State steuert Button-Zustände

Dev-Modus: Solange KEYCLOAK_URL nicht gesetzt ist (lokale Dev, aktueller
Prod-Stand), sind alle Endpoints offen wie bisher. Kein Breaking Change.

Dependency: python-jose[cryptography]>=3.3.0 in requirements.txt.

Tests: 194/194 grün (auth.py hat keine Seiteneffekte im Import).

Refs: #43
2026-04-10 14:28:57 +02:00
Dotty Dotter
a821c19202 #47: Auto-Re-Analyse bei nicht-verifizierbaren Zitaten
Statt eine Nachricht "Textstelle nicht auffindbar" zu zeigen (was User
zurecht als Quatsch bezeichnet hat), erkennt der Cite-Endpoint jetzt
halluzinierte Zitate und triggert automatisch eine Re-Analyse:

Flow:
1. User klickt auf Zitat-Link
2. render_highlighted_page gibt (pdf, page, highlighted=False) zurück
3. Endpoint prüft: ds+bl Parameter vorhanden? Assessment in DB?
4. → Löscht altes Assessment, startet Re-Analyse als Background-Task
5. → Zeigt HTML-Warte-Seite mit Spinner und "Wird neu analysiert..."
6. → Auto-Redirect nach 15s zurück zum Assessment

Das neue Assessment hat durch reconstruct_zitate verifizierte Zitate,
die dann beim nächsten Klick korrekt gehighlighted werden.

Änderungen:
- embeddings.render_highlighted_page: Return-Typ (bytes, int, bool) —
  drittes Element ist True wenn Highlight gesetzt wurde
- database.delete_assessment: neue Funktion für die Re-Analyse
- main.py cite-Endpoint: akzeptiert ds= und bl= als optionale Params,
  triggert Re-Analyse bei highlighted=False + ds vorhanden
- Frontend: makeCiteUrl reicht ds+bl aus dem Assessment-Kontext mit
  durch in die Cite-URL
- Cache-Control auf 1h reduziert (war 24h, zu aggressiv für
  Assessments die sich durch Re-Analyse ändern)

Tests: 194/194 grün.

Refs: #47, #60
2026-04-10 10:35:01 +02:00
Dotty Dotter
6f35efe4d7 #47: Volles PDF mit Highlight statt 1-Seiten-Extract
User-Feedback: "Kontext geht verloren wenn nur 1 Seite kommt".

Änderung: render_highlighted_page liefert jetzt das GESAMTE Wahlprogramm-
PDF mit gelber Highlight-Annotation auf der Fundstelle, statt eines
1-Seiten-Auszugs. Der Browser öffnet das vollständige Programm.

Frontend hängt #page=N an die URL → Browser scrollt direkt zur
Fundstelle. found_page wird als X-Found-Page Header mitgeliefert,
falls der Text auf einer anderen Seite als angefordert gefunden wurde
(Pre-#60 halluzinierte Seitennummern).

Return-Typ geändert: (bytes, int) statt bytes — zweiter Wert ist die
1-indexed Seitennummer wo der Treffer tatsächlich liegt.

Tests angepasst: Tuple-Unpacking, Size-Check entfernt (volles PDF ist
größer als 1-Seiten-Extract, der alte Vergleich war obsolet).

Refs: #47
2026-04-10 10:16:00 +02:00
Dotty Dotter
47897e13cd #47 Fix: Highlighting retroaktiv für alle bestehenden Assessments
Problem: Alle Assessments in der Prod-DB haben Pre-#47-URLs
(/static/referenzen/X.pdf#page=N). Die _chunk_pdf_url-Änderung wirkt
nur auf NEUE Analysen, die noch nicht stattgefunden haben.

Fix (zwei Seiten):

1. Endpoint /api/wahlprogramm-cite akzeptiert jetzt auch pdf=<filename>
   als Alternative zu pid=<programm_id>. Reverse-Lookup über PROGRAMME-
   Registry: pdf-Filename → programm_id. Damit können die statischen
   URLs aus Pre-#47-Assessments trotzdem an den Cite-Endpoint geleitet
   werden.

2. Frontend: neue JS-Funktion makeCiteUrl(z) die JEDE Zitat-URL on-the-
   fly umschreibt:
   - /static/referenzen/X.pdf#page=N + z.text
     → /api/wahlprogramm-cite?pdf=X.pdf&seite=N&q=<urlencoded text>
   - /api/wahlprogramm-cite?... → durchreichen (schon Cite-URL)
   - Fallback: URL unverändert

   Funktioniert retroaktiv für ALLE ~31 Assessments in der DB, ohne
   Re-Analyse. Sobald ein User auf ein Zitat klickt, wird die Seite
   des Wahlprogramms mit gelber Markierung gerendert.

Tests: 194/194 grün.

Refs: #47
2026-04-10 09:57:58 +02:00
Dotty Dotter
92dcd25f73 #63 B+C: Force-Honesty + UI-Warning bei Score ohne Zitate
Problem: BUND 21/3660 zeigt Score 10/10 für Linke und Grüne, aber null
Zitate — der Report sieht aus als sei die Bewertung fundiert, obwohl das
LLM mangels indexierter Quellen (linke-grundsatz fehlt) aus
Trainingswissen geraten hat. User-Feedback: "Da muss stehen warum."

Fix C — Force-Honesty im Prompt:

- format_quotes_for_prompt akzeptiert neuen Parameter searched_parties.
  Parteien, für die kein Chunk retrievt wurde, werden explizit als
  "KEINE QUELLEN VORHANDEN" markiert, mit der Anweisung "score: 0,
  zitate: [], Begründung: keine Quellen im Index".
- Neue ZITATEREGEL Punkt 5: "Wenn KEINE QUELLEN VORHANDEN → score 0."
  Das ist die strukturelle Lösung — das LLM darf nicht mehr raten.
- analyzer.py: fraktionen-Liste wird an format_quotes_for_prompt als
  searched_parties durchgereicht.

Fix B — UI-Transparenz:

- index.html: gelbe Warn-Box (amber, border-left #ffc107) wenn
  wp.wahlprogramm.score > 0 aber wp.wahlprogramm.zitate.length === 0:
  "Keine belegbaren Quellen im Index gefunden — Score basiert auf
  LLM-Einschätzung, nicht auf verifizierten Programm-Stellen."
- Wird für bestehende Assessments sofort sichtbar (JS-seitig berechnet),
  keine DB-Migration nötig. Neue Assessments nach Force-Honesty sollten
  idealerweise Score=0 haben, aber die Warning ist ein Fallback für
  den Fall dass das LLM die Prompt-Regel nicht immer 100% befolgt.

Fix A (Linke/AfD-Grundsatzprogramme) folgt als separater Commit —
sind öffentlich downloadbar, brauchen manuellen Sichtbarkeitscheck.

Tests: 194/194 grün (keine Schema-Änderung, nur Prompt + Template).

Refs: #63, ADR 0001
2026-04-10 09:32:31 +02:00
Dotty Dotter
7cf073122f Phase E (substituted): Auswertungen-Drilldown-Modal (#59)
Sachsen-Adapter (#26/#38) ist Eigensystem mit ASP.NET-Webforms-Postbacks
(__VIEWSTATE/__CALLBACKID, siehe bundeslaender.py:343-348) und braucht
HAR-Aufnahme → Blocker für autonome Bearbeitung. Phase E entsprechend
substituiert mit der Frontend-Erweiterung der Auswertungen.

- Matrix-Zellen sind jetzt klickbar (`cell-with-data`-Klasse +
  hover-outline mit Blue-Border)
- Klick öffnet ein Modal, das `/api/auswertungen/zeitreihe?
  bundesland=...&partei=...` aufruft und die Score-Entwicklung dieser
  (BL, Partei)-Kombination über alle bekannten WPs als Tabelle rendert
- ESC-Taste oder Backdrop-Klick schließt das Modal
- Schließt damit den Frontend-Loop für die in Phase C gebauten
  Backend-Endpoints

(CLAUDE.md-Sync separat — die Datei liegt im Projekt-Root außerhalb
des Webapp-Git-Repos.)

Refs: #59 (Phase E substituted)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:30:10 +02:00
Dotty Dotter
26f13bd29d Phase D: zwei getrennte Suchfelder + Auswertungen-Link (#16, #59)
Schließt #16 (UI: zwei klar getrennte Suchen) und ergänzt den Header
um den Link auf das neue Auswertungen-Dashboard aus Phase C.

- Search-Row in `index.html` aufgespalten in zwei untereinanderliegende
  Inputs: oben "Suche in geprüften Anträgen (DB)" mit Live-Debouncing
  (wie bisher), unten "Im Landtag suchen (live)" mit Enter-Trigger und
  expliziter Such-Button. Beide Felder schreiben in dieselbe Liste,
  sind aber visuell und semantisch klar getrennt.
- `searchLandtag()` zieht jetzt aus `landtag-search-input` statt aus
  dem DB-Suchfeld
- `changeBundesland()` resettet zusätzlich das Landtag-Feld
- Header: neuer `📈 Auswertungen`-Link neben `📚 Quellen`

Refs: #16, #59 (Phase D)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:27:29 +02:00
Dotty Dotter
3631e5418c Phase C: Auswertungen-Dashboard #58 + CSV-Export #45 (Roadmap #59)
Drei-dimensionale Aggregations-Sicht über Bundesland × Partei ×
Wahlperiode mit minimalem Frontend.

Backend (`app/auswertungen.py`):

- `aggregate_matrix(filter_wp=None)` — 2D-Matrix Bundesland × Partei mit
  (n, Ø-Score) pro Zelle, optional gefiltert nach Wahlperiode
- `aggregate_zeitreihe(bundesland, partei)` — Score-Verlauf einer
  (BL, Partei)-Kombination über alle bekannten WPs
- `export_long_format()` — Long-Format-CSV-Export für externe Tools
  (deckt #45 vollständig ab)
- Partei-Auflösung läuft strikt durch `normalize_partei()` aus #55 —
  damit wird BB-`FREIE WÄHLER` korrekt als `BVB-FW` aggregiert und
  NICHT mit dem RP-FW zusammengezählt

Wahlperioden-Helper (`app/wahlperioden.py`):

- `wahlperiode_for(datum, bundesland)` mappt ein ISO-Datum + BL auf eine
  Kennung wie `"NRW-WP18"` oder `"MV-WP7"` (Vorgänger-WP). Single Source
  of Truth ist `BUNDESLAENDER[bl].wahlperiode_start`
- `all_wahlperioden()` für UI-Filter-Dropdowns

Endpoints in `app/main.py`:

- `GET /auswertungen` — HTML-Seite (neues Template)
- `GET /api/auswertungen/matrix?wahlperiode=NRW-WP18` — JSON-Matrix
- `GET /api/auswertungen/zeitreihe?bundesland=MV&partei=CDU` — JSON-Verlauf
- `GET /api/auswertungen/export.csv` — CSV-Download

Frontend (`app/templates/auswertungen.html`):

- Statisches Template mit Vanilla-JS, kein Build-Step
- Wahlperioden-Dropdown + Reload-Button + CSV-Export-Button
- Matrix-Tabelle mit Score-Color-Coding (rot ≤ 3, gelb 3-6, grün > 6)
- Sticky-Bundesland-Spalte für horizontales Scrolling

Tests (`tests/test_auswertungen.py`):

- 19 Cases mit in-memory SQLite-Fixture
- Verifiziert WP-Mapping, Matrix-Aggregation, Koalitions-Counting,
  WP-Filter-Korrektheit, BVB-FW-Disambiguierung in der Matrix,
  CSV-Long-Format
- 176 Unit-Tests grün (157 alt + 19 neu)

Refs: #58, #45, #59 (Phase C)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:25:57 +02:00
Dotty Dotter
f1867d463c Bundesland filter & transparency: stringent split + visible source (#8)
Brings the Bundesland-Dropdown from a cosmetic header widget to a real
filter that propagates through every layer (Listing, internal search,
statistics, party/tag filters, upload mode), and at the same time makes
the source parliament visible in every place where assessments from
multiple bundesländer can be mixed.

Backend
- database.get_all_assessments(bundesland=None) — new optional filter,
  "ALL" treated as None.
- database.search_assessments — bug fix: previous `if bundesland:`
  branch incorrectly added a `WHERE bundesland='ALL'` clause; now
  guarded with `bundesland and bundesland != "ALL"`.
- main.list_assessments — accepts ?bundesland= query param, includes the
  bundesland field in the response so the frontend can render badges.
- main.get_single_assessment — also includes bundesland in the response
  so the detail header can show the source parlament.
- main.search_landtag — early HTTP 400 when bundesland is missing or
  "ALL"; the live Landtag adapter cannot serve a synthetic Bundesweit
  request.
- main.index() and main.list_bundeslaender — synthetic "🌍 Bundesweit"
  entry prepended to the bundesländer list (kept out of bundeslaender.py
  on purpose — ALL is not a real state). Both endpoints additionally
  expose a parlament_names map so the frontend can render the source
  parliament without an extra round-trip.

Report (PDF + HTML)
- generate_html_report / generate_pdf_report — new optional bundesland
  parameter. When set, the report header carries the parliament name
  ("Landtag von Sachsen-Anhalt", "Landtag Nordrhein-Westfalen", …)
  beside the title. Three call sites updated: run_analysis,
  run_drucksache_analysis, download_assessment_pdf.

Frontend (templates/index.html)
- Header dropdown gets the synthetic ALL entry as first option;
  initial currentBundesland is now 'ALL' (was 'NRW').
- localStorage persistence: changeBundesland writes, DOMContentLoaded
  reads and validates against the visible options.
- changeBundesland resets the score / party / tag filter state, syncs
  the upload-mode bundesland select, disables the Landtag-Suche button
  + tooltip when ALL, and toggles a data-mode attribute on
  .list-content (used by CSS to show/hide the per-item bundesland
  badge).
- loadAssessments now sends ?bundesland=… so the API does the actual
  filtering. updateStats renders an additional per-bundesland average
  block (Ø NRW: x · Ø LSA: y) when in ALL mode and the loaded list
  spans more than one bundesland.
- renderList prepends a small "bl-badge" beside the Drucksachen-Nummer.
  Hidden in single-bundesland mode via CSS selector to avoid clutter.
- showDetail header now shows the parliament name as its own line
  (.detail-parlament).
- searchLandtag has an early-out alert if currentBundesland === 'ALL',
  saving a network round-trip.
- Upload-Mode bundesland select now starts with a "— Bundesland wählen
  —" placeholder (no auto-default), and startAnalysis validates that a
  concrete bundesland was chosen.

CSS
- .bl-badge plus the .list-content[data-mode="single"] hide rule.
- .detail-parlament for the detail header line.
- .header-parlament for the PDF report header line.

Resolves #8.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 23:00:39 +02:00
Dotty Dotter
bcd532be89 Fix responsive layout for mobile viewports (#6)
Body becomes a flex column so the header takes its natural height and the
main container fills the rest via flex:1 — replaces the brittle
calc(100vh - 70px) that assumed a 70px header and broke as soon as the
header wrapped on mobile. Adds 100dvh fallback for iOS Safari address
bar quirks.

Mobile breakpoint (≤900px) reworked: list scrolls internally via
list-content max-height:50vh, detail-panel uses overflow:visible so the
whole document scrolls naturally instead of nesting scrollers. Tapping
an item auto-scrolls to the detail panel and a new "← Zur Liste" button
(mobile-only) jumps back. Adds a tighter ≤600px breakpoint that hides
the subtitle, collapses the matrix grid to one column and shrinks the
matrix table for phone screens.

Resolves issue #6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 13:48:55 +02:00
Dotty Dotter
63de3ca20d Initial commit: GWÖ-Antragsprüfer v1.0
Features:
- GWÖ-Matrix 2.0 Analyse für NRW-Landtagsanträge
- Verbesserungsvorschläge im Redline-Format (Original/Vorschlag/Begründung)
- Wahlprogramm- und Parteiprogrammtreue-Bewertung
- Landtag-Suche via OPAL-API
- Tag-Wolke mit Multi-Select Filter
- Partei-Filter mit Durchschnittswerten
- PDF-Report-Generierung
- Security Headers (CSP, X-Frame-Options, etc.)
- Persistente SQLite-DB via Docker Volumes

Tech Stack:
- FastAPI + Jinja2
- Qwen LLM via DashScope API
- SQLite + aiosqlite
- WeasyPrint für PDF
- Docker Compose mit Traefik
2026-03-28 22:30:24 +01:00