gwoe-antragspruefer/app/wahlperioden.py

53 lines
1.9 KiB
Python
Raw Permalink Normal View History

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
"""Wahlperioden-Helper für die Auswertungen.
Maps a Drucksache-Datum + Bundesland auf eine Wahlperioden-Kennung.
Single Source of Truth ist ``BUNDESLAENDER[bl].wahlperiode_start``
alles vor diesem Datum gehört zur Vorgänger-WP, alles ab dem Datum zur
aktuellen.
Granularität: pro Bundesland kennen wir genau zwei WP die laufende und
die direkt davor. Das reicht für die Aggregations-Sicht (#58), denn der
gesamte Antragsbestand stammt aus den letzten 510 Jahren. Sollte später
eine echte Multi-WP-Historie nötig werden, ist der Erweiterungspunkt
``_wp_calendar`` als Liste von ``(start, ende, wp_id)``-Tupeln pro BL.
"""
from __future__ import annotations
from typing import Optional
from .bundeslaender import BUNDESLAENDER
def wahlperiode_for(datum: str, bundesland: str) -> Optional[str]:
"""Liefert die Wahlperioden-Kennung für ein Datum + Bundesland.
Format der Kennung: ``"<BL>-WP<n>"``, z.B. ``"NRW-WP18"``,
``"MV-WP7"`` (Vorgänger-WP). Returns ``None`` wenn das Bundesland
unbekannt oder das Datum nicht parsbar ist.
>>> wahlperiode_for("2026-03-18", "MV")
'MV-WP8'
>>> wahlperiode_for("2020-01-01", "MV")
'MV-WP7'
"""
bl = BUNDESLAENDER.get(bundesland)
if bl is None:
return None
if not datum:
return f"{bundesland}-WP{bl.wahlperiode}" # default: aktuelle WP
# ISO-Datum ist lexikographisch vergleichbar — keine datetime-Parsing
# nötig, solange das Format YYYY-MM-DD eingehalten wird.
if datum >= bl.wahlperiode_start:
return f"{bundesland}-WP{bl.wahlperiode}"
return f"{bundesland}-WP{bl.wahlperiode - 1}"
def all_wahlperioden() -> list[str]:
"""Liste aller bekannten Wahlperioden-Kennungen (aktuelle + Vorgänger
pro Bundesland). Nützlich für UI-Filter-Dropdowns."""
out: list[str] = []
for code, bl in BUNDESLAENDER.items():
out.append(f"{code}-WP{bl.wahlperiode}")
out.append(f"{code}-WP{bl.wahlperiode - 1}")
return out