Stimm-Index pro Fraktion über Quartale. Linien-Chart pro Fraktion,
Lücken bei Quartalen mit n<3 (Ja UND Nein). Macht sichtbar, ob sich die
Gemeinwohl-Affinität einer Fraktion innerhalb der Wahlperiode verschiebt.
- `_quarter_for(datum)` Helper: ISO-Datum → "YYYY-Qn".
- `aggregate_stimm_index_zeitreihe()` analog zu pro_wert/pro_gruppe,
aber nach Quartal-Bucket statt Achse.
- `GET /api/auswertungen/stimm-index-zeitreihe?parteien=CDU,SPD,...`
- 4. Sub-Section im Stimmverhalten-Tab: Multi-Linien-Chart mit
Partei-Farben (CDU schwarz, SPD rot, GRÜNE grün, FDP gelb, AfD blau,
LINKE pink, BSW lila, SSW navy, BVB-FW orange).
Bei aktueller Sparse-Datenmenge (35 Assessments × 4 Quartale) ist der
Chart heute meist leer — Infrastruktur ist ready, fuellt sich automatisch
mit Issue #44 Batch-Bewertung.
Tests: 10 neue (4 _quarter_for, 6 aggregate). Suite jetzt 1005 grün.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase-2-Erweiterungen des Stimmverhalten-Tabs:
**1. Empfehlungs-Konsistenz (#167):**
Pro Fraktion: Anteil der Anträge mit GWÖ-Empfehlung
"Uneingeschränkt unterstützen" oder "Unterstützen mit Änderungen",
bei denen die Fraktion trotzdem NEIN gestimmt hat. Orthogonal zur
Heuchelei-Quote — prüft NICHT gegen Wahlprogramm-Treue, sondern gegen
die GWÖ-Empfehlung des Systems.
- `aggregate_empfehlungs_konsistenz()` in app/auswertungen.py
- `GET /api/auswertungen/empfehlungs-konsistenz`
- 5. Chart-Sub-Section im Stimmverhalten-Tab (rote Bar Chart, 0..100%)
**2. CSV-Export (Phase-1-Querschnitts-TODO):**
Long-Format-CSV mit Spalten: drucksache, bundesland, wahlperiode, datum,
gwoe_score, empfehlung, partei, vote, ist_antragsteller. Macht alle
Stimmverhalten-Aussagen wissenschaftlich auswertbar (R/pandas/Excel).
- `export_stimmverhalten_csv()` in app/auswertungen.py
- `GET /api/auswertungen/stimmverhalten.csv` mit
Filter-Parametern bundesland/wahlperiode/exclude_antragsteller
- "CSV-Export"-Button im Stimmverhalten-Tab neben dem Toggle
**Tests:** 27 Stimmverhalten-Tests (war 18, +4 Empfehlungs-Konsistenz,
+5 CSV-Export). Fixture um `empfehlung`-Spalte erweitert.
Suite: 989 Tests grün (war 980).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neue Auswertungs-Sicht: Welche Fraktionen stimmen häufiger gemeinwohl-
orientierten Anträgen zu? Verschneidet GWÖ-Bewertung pro Antrag mit
dem tatsächlichen Plenum-Stimmverhalten der Fraktionen.
Vier Aussagen, alle hinter dem neuen Tab "Stimmverhalten":
1. **Gemeinwohl-Stimm-Index** pro Fraktion: Ø-GWÖ-Score der JA-Anträge
minus Ø-GWÖ-Score der NEIN-Anträge. Domain −10..+10. Positiv = stimmt
eher Gemeinwohl-affinen Anträgen zu.
2. **Heuchelei-Quote** pro Fraktion: Anteil der Anträge mit
wahlprogramm_score ≥ 7 (passt zum eigenen Wahlprogramm), bei denen
die Fraktion trotzdem NEIN gestimmt hat.
3. **Stimm-Index pro GWÖ-Wert** als Heatmap: 5 Spalten (Würde,
Solidarität, Nachhaltigkeit, Gerechtigkeit, Demokratie) aus den
gwoe_matrix-Suffix-Spalten. Domain −5..+5 pro Zelle.
4. **Cross-BL-Vergleich** als Grouped Bar: gleiche Fraktion in
mehreren Ländern. Nur Fraktionen in ≥2 BL mit ausreichender
Datenbasis.
Querschnitt:
- `exclude_antragsteller=True` per Default (Toggle-Checkbox in UI),
weil Antragsteller-Fraktionen quasi immer JA stimmen → würde Index
verzerren. Toggle macht den Effekt sichtbar.
- `min_n=5` pro Fraktion fuer Stimm-Index, n=3 fuer Heatmaps.
Fraktionen unter dem Cutoff werden als "Nicht aussagekräftig" separat
gelistet.
- Caveat-Banner mit `n_assessments_matched` über jedem Chart.
Implementation:
- `app/auswertungen.py`: `_load_assessments_with_votes()` JOIN-Helper
+ 4 Aggregat-Funktionen analog zu `aggregate_matrix`-Pattern.
Reuse: `normalize_partei` für Aliasing (BÜNDNIS 90/DIE GRÜNEN →
GRÜNE), `wahlperiode_for` für WP-Filter.
- `app/main.py`: 4 neue read-only GET-Endpoints unter
`/api/auswertungen/stimm-index|heuchelei|stimm-index-pro-wert|
stimm-index-cross-bl`.
- `app/templates/v2/screens/auswertungen.html`: 4. Tab "Stimmverhalten"
mit 4 Sub-Sektionen, Chart.js Bars + HTML-Heatmap-Tabelle.
- `tests/test_auswertungen_stimmverhalten.py`: 18 neue Tests
(Fixture-DB mit 13 Assessments + 13 Vote-Results, Edge-Cases:
GRÜNE-positiver-Index, AfD-negativer-Index, exclude_antragsteller-
Effekt, min_n-Cutoff, leere DB).
Sparse-Data-Realität: aktuell 35 Assessments im prod, dünne Datenbasis
fuer einige Fraktionen. Feature wächst mit Issue #44 Batch-Bewertung.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pro BL zeigt die Tabelle nun:
- Doku-System (wie bisher)
- Drucksachen: alle aktiv (Adapter laufen)
- Plenum-Votes: 'aktiv' wenn Parser registriert (NRW), sonst 'Stub'
Plus Erklär-Hinweis: 'Plenum-Votes = fraktions-aggregierte
Abstimmungsergebnisse aus den Plenarprotokollen (#106). Stubs sind
Tracking-Stellen fuer kuenftige Implementierungen (Issues #148-#163).'
main.py reicht supported_bundeslaender() aus protokoll_parsers an die
Template-Context durch (plenum_vote_parsers-Set).
User-Frage zur Transparenz-Seite: 'Welcher Prompt wird ausgefuehrt?
Der System-Prompt ist deutlich umfangreicher.' Antwort: keiner allein —
beide werden in einem API-Call zusammen gesendet und gemeinsam
ausgewertet.
Auf /methodik#prompts neu vor den details-Bloecken:
- Erklaerung 'in einem einzigen API-Call', beide ins Kontextfenster
- 2-Spalten-Tabelle 'System (Wer/wie)' vs. 'User (Was)'
- Begruendung der Trennung (Caching, Compliance, Wartbarkeit)
- Code-Referenz zu qwen_bewerter.py:83-85 mit messages-Aufbau
Reine UI-Aenderung, keine Code-Logik betroffen.
Antrag-Detail-Endpoint liest plenum_votes via get_plenum_votes() und
reicht sie an antrag_detail.html durch.
Block rendert pro Plenum-Abstimmung eine Karte:
- Ergebnis (angenommen/abgelehnt/...) farb-kodiert
- 'einstimmig'-Annotation falls gesetzt
- Quelle (Protokoll-ID, mit URL als Tooltip)
- Fraktions-Chips fuer Ja/Nein/Enthaltung
Mehrfach-Abstimmungen einer Drucksache (Ueberweisung + finale
Beschlussfassung) erzeugen mehrere Karten — chronologisch via
parsed_at DESC im Repository sortiert.
Block erscheint nur, wenn Eintraege existieren (kein leerer Header).
System- und User-Prompt-Template stehen jetzt collapsed unter dem
neuen Abschnitt 'LLM-Prompts'. Der User-Prompt wird auf eine eigene
Konstante USER_PROMPT_TEMPLATE umgestellt und via .format(...) gerendert,
sodass das gleiche Template auf der Methodik-Seite gezeigt werden kann
ohne den f-string-Code zu duplizieren.
Closes#145
Adapter liefert fraktionen schon mit, das Frontend ignorierte sie bisher.
Treffer-Zeile bekommt jetzt unter dem Titel kleine Teal-Chips fuer jede
einreichende Fraktion (Beispiel: 'CDU SPD' bei kollektiven Antraegen).
Stylistisch konsistent zum Score-Chip-System (color-mix mit ecg-teal),
mono Font, uppercase 10px — bleibt auch bei vielen Fraktionen lesbar.
Closes#146
Beide Routes liefern jetzt all_canonical_keys() (ohne Landesregierung) als Dropdown-
Optionen. Verhindert Tippfehler und gibt nur tatsaechlich erkannte Parteien zur Auswahl.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend (Filter sind seit jeher da):
- /api/feed.xml?bundesland=&partei=&limit=
- /api/subscriptions GET/POST/DELETE
UI:
- /v2/feed: Form mit BL/Partei/Limit, generiert Feed-URL live, Buttons Oeffnen/
URL-Kopieren/In-Feedly. Default-BL aus Header-Selektor uebernommen
- /v2/abos: Liste eigener Abos + Form zum Anlegen/Loeschen, BL-Dropdown,
Partei-Freitext, Frequenz daily/weekly
- Sidebar 'Daten'-Gruppe um beide Eintraege erweitert (statt Direkt-Link auf
/api/feed.xml)
- Beide Routen mit Depends(require_auth) — Anonyme bekommen 401-Redirect
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Button-Text 'Übersprungen', der Grund nur als Tooltip — User versteht
nicht warum. Jetzt: 'Nicht abstimmbar' + sichtbare Italic-Begruendung unter der
Zeile mit dem konkreten Reason-Text vom Server (Backend liefert reason, typ
und typ_normiert).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>