128 lines
5.2 KiB
Markdown
128 lines
5.2 KiB
Markdown
|
|
# 0010 — Stimmverhalten × GWÖ-Bewertung als JOIN-Aggregat
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Status** | accepted |
|
|||
|
|
| **Datum** | 2026-05-06 |
|
|||
|
|
| **Refs** | #165, #166, #167, #168, #169, #171, #162, MEMORY/project_konsistenz_marker.md |
|
|||
|
|
|
|||
|
|
## Kontext
|
|||
|
|
|
|||
|
|
Vor dieser Entscheidung lagen zwei Erkenntnisquellen unverbunden in der DB:
|
|||
|
|
|
|||
|
|
- `assessments` (~96 produktiv, dev) — pro Drucksache eine GWÖ-Bewertung
|
|||
|
|
mit `gwoe_score` (0–10), `gwoe_matrix` (5×5 Felder, Rating −5..+5),
|
|||
|
|
`wahlprogramm_scores` (pro Fraktion: WP-Treue + PP-Treue + Zitate).
|
|||
|
|
- `plenum_vote_results` (~7281 produktiv, dev) — pro Drucksache wer wie
|
|||
|
|
abgestimmt hat (`fraktionen_ja`/`_nein`/`_enthaltung` als JSON-Arrays,
|
|||
|
|
pro Fraktion normalisiert via `normalize_partei`).
|
|||
|
|
|
|||
|
|
Die User-Frage „Welche Parteien stimmen häufiger gemeinwohlorientierten
|
|||
|
|
Anträgen zu?" — und die diametrale „Wer stimmt gegen das eigene
|
|||
|
|
Wahlprogramm?" — verlangt einen JOIN auf `(bundesland, drucksache)`.
|
|||
|
|
Heute schneiden sich nur ~19 von 7281 Votes mit Assessments. Die
|
|||
|
|
Aussagekraft ist also methodisch absichtlich konservativ ausgelegt
|
|||
|
|
(`min_n=5`, „ausreichend"-Flag, Sample-Size-Banner sichtbar).
|
|||
|
|
|
|||
|
|
## Optionen
|
|||
|
|
|
|||
|
|
### Option A — Materialisiertes Aggregat als eigene Tabelle (`stimm_index`)
|
|||
|
|
|
|||
|
|
Bei jedem Assessment-Insert oder Vote-Insert ein Trigger, der ein
|
|||
|
|
fertiges Aggregat in eine eigene Tabelle schreibt.
|
|||
|
|
|
|||
|
|
- **Pro:** Lese-Latenz minimal.
|
|||
|
|
- **Kontra:** Konsistenz-Logik in zwei Codepfaden (Insert-Trigger +
|
|||
|
|
Bulk-Reindex), Schema-Migration nötig, schwer testbar.
|
|||
|
|
|
|||
|
|
### Option B — On-the-fly-JOIN mit TTL-Cache
|
|||
|
|
|
|||
|
|
Aggregat-Funktionen lesen synchron aus den Quelltabellen, JOIN per
|
|||
|
|
SQL, Aggregation in Python, Ergebnis 60s im Process-Memory cachen.
|
|||
|
|
|
|||
|
|
- **Pro:** Keine Schema-Migration, ein Codepfad, testbar wie reine
|
|||
|
|
Funktionen, 60s-TTL deckt Burst-Traffic ab.
|
|||
|
|
- **Kontra:** Erste Anfrage nach Cache-Miss ~3 s, danach ~0,17 s.
|
|||
|
|
|
|||
|
|
### Option C — Computed-View
|
|||
|
|
|
|||
|
|
SQLite-View mit dem JOIN. Aggregation im SQL.
|
|||
|
|
|
|||
|
|
- **Pro:** Eine Query.
|
|||
|
|
- **Kontra:** SQLite-Views sind nicht persistent indexiert,
|
|||
|
|
Performance schlechter als B; aggregations-Logik mit
|
|||
|
|
GROUP_CONCAT-JSON-Tricks unleserlich.
|
|||
|
|
|
|||
|
|
## Entscheidung
|
|||
|
|
|
|||
|
|
**Option B** umgesetzt, ergänzt um zwei Marker im Antrag-Detail:
|
|||
|
|
|
|||
|
|
1. **Vier Aggregat-Funktionen** in `app/auswertungen.py`:
|
|||
|
|
- `aggregate_stimm_index(filter_bl, filter_wp, exclude_antragsteller, min_n)`
|
|||
|
|
→ pro Fraktion `Ø(gwoe_score|JA) − Ø(gwoe_score|NEIN)`, Domain `[−10, +10]`.
|
|||
|
|
- `aggregate_heuchelei(...)` → Anteil der Anträge mit
|
|||
|
|
`wahlprogramm.score(F)≥7 ∧ F∈NEIN`.
|
|||
|
|
- `aggregate_stimm_index_pro_wert(...)` → Heatmap Fraktion×{Würde,
|
|||
|
|
Solidarität, Nachhaltigkeit, Gerechtigkeit, Demokratie}.
|
|||
|
|
- `aggregate_stimm_index_cross_bl(...)` → gleiche Fraktion in
|
|||
|
|
mehreren Ländern nebeneinander.
|
|||
|
|
|
|||
|
|
2. **Helper `_load_assessments_with_votes`** mit dem zentralen JOIN:
|
|||
|
|
```sql
|
|||
|
|
SELECT a.…, p.fraktionen_ja, p.fraktionen_nein, p.fraktionen_enthaltung
|
|||
|
|
FROM assessments a
|
|||
|
|
INNER JOIN plenum_vote_results p
|
|||
|
|
ON a.bundesland = p.bundesland AND a.drucksache = p.drucksache
|
|||
|
|
WHERE a.gwoe_score IS NOT NULL
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **Antrag-Detail-Marker** (`templates/v2/screens/antrag_detail.html`):
|
|||
|
|
- **Heuchelei-⚠** neben jeder NEIN-Fraktion mit eigenem
|
|||
|
|
`wahlprogramm.score≥7`. Tooltip nennt den Score.
|
|||
|
|
- **Konsistenz-Block** über dem Vote-Block: rot bei Konflikt
|
|||
|
|
(„unterstützen" + abgelehnt; oder „ablehnen" + angenommen),
|
|||
|
|
grün bei Übereinstimmung. Bei mehreren Votes (Überweisung →
|
|||
|
|
Endabstimmung) wird das erste *definitive* Outcome
|
|||
|
|
(`angenommen|abgelehnt|bestätigt`) genommen.
|
|||
|
|
|
|||
|
|
4. **Sample-Size-Ehrlichkeit:** `min_n=5` als Default, „ausreichend"-
|
|||
|
|
Flag pro Fraktion, Caveat-Banner über jedem Chart, Nutzer kann
|
|||
|
|
Antragsteller-Ausschluss togglen.
|
|||
|
|
|
|||
|
|
5. **CSV-Export** `/api/auswertungen/stimmverhalten.csv` als
|
|||
|
|
Long-Format für externe Auswertung.
|
|||
|
|
|
|||
|
|
## Konsequenzen
|
|||
|
|
|
|||
|
|
### Positiv
|
|||
|
|
|
|||
|
|
- Eine Domain-Funktion pro Aussage, leicht testbar.
|
|||
|
|
- Cache-Invalidation trivial: nach Assessment-Insert oder Vote-Ingest
|
|||
|
|
`cache_clear()` aufrufen (siehe `news_aggregator.run_aggregator`).
|
|||
|
|
- Fraktionsnamen sind in beiden Quellen vornormalisiert (`SPD`,
|
|||
|
|
`GRÜNE`, …) — kein zusätzlicher Mapping-Layer im JOIN.
|
|||
|
|
- Heuchelei- und Konsistenz-Marker im Detail sind die punktgenauen
|
|||
|
|
Belege für die aggregierten Aussagen im `/auswertungen`-Tab — der
|
|||
|
|
Nutzer kann von Aggregat zu Antrag durchklicken.
|
|||
|
|
|
|||
|
|
### Negativ
|
|||
|
|
|
|||
|
|
- Daten-Lücke zwischen Votes und Bewertungen ist sichtbar: 7281 vs.
|
|||
|
|
~96 Bewertungen, nur 19 in beiden Mengen. Die Aggregate sagen
|
|||
|
|
„ausreichend=False" für viele Fraktionen. Das ist methodisch
|
|||
|
|
ehrlich, aber bedeutet: das Feature wird erst nach **Phase 3
|
|||
|
|
(Vote-Orphans-Auto-Bewertung)** seine volle Aussagekraft
|
|||
|
|
entfalten.
|
|||
|
|
- Cache-Lebensdauer 60 s: bei `--reload`-Dev-Server ist der Cache
|
|||
|
|
nach jedem Auto-Reload weg. Akzeptabel für dev, in prod ein
|
|||
|
|
Worker-Prozess.
|
|||
|
|
|
|||
|
|
### Folgen für andere ADRs
|
|||
|
|
|
|||
|
|
- **0009 Protokoll-Parser-Registry**: pro neuem BL-Parser wachsen
|
|||
|
|
die Daten in `plenum_vote_results`, was den JOIN-Recall direkt
|
|||
|
|
hebt. Phase 5 hardcases verstärken Aussagen aus Phase 1.
|
|||
|
|
- **0011 Aktuelle-Themen × PM-Generator** (folgend): der
|
|||
|
|
Konsistenz-Block ist Vorlage für Empfehlungs-vs-Vote-Konflikte
|
|||
|
|
als PM-Anlass.
|