antragstracker/backend/src/tracker/api/routes/fraktionen.py
Dotty Dotter 9d8a73e2a9 feat: Parteien-Filter, Klassifikation, Umsetzungsbewertung, KI-Neubewertung
- Vorlagen + Ketten: Partei-Dropdown-Filter mit Badges (#9)
- Vorlagen-Detail: Ketten-Klassifikation mit Begründung anzeigen
- Vorlagen-Detail: Umsetzungsbewertungen mit Score + Begründung
- SPA-Routing: Catch-All für direkten URL-Zugriff
- Status-Engine: Begründungen für alle Ketten-Status generieren
- Kurze Beschlusstexte (<=5 Zeichen) nicht mehr als Beschluss werten
- POST /api/bewertung/vorlagen/{id} + /ketten/{id} für KI-Neubewertung
- Frontend: 'Neu bewerten' Button + Kommentarfeld auf beiden Detailseiten
- Job-Status-Polling mit Spinner
- ALLRIS-Rescrape vor Bewertung noch offen (#10)

Closes #9
2026-04-01 10:36:22 +02:00

129 lines
3.9 KiB
Python

from __future__ import annotations
"""API routes for Fraktions-Dashboard."""
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, Query
from tracker.core.kategorien import BEWERTUNGSKATEGORIEN
from tracker.db.session import get_connection
router = APIRouter(prefix="/fraktionen", tags=["Fraktionen"])
def _db():
conn = get_connection()
try:
yield conn
finally:
conn.close()
@router.get("/kategorien")
def get_kategorien():
"""Return category definitions for frontend tooltips."""
return {
key: {
"label": val["label"],
"farbe": val["farbe"],
"icon": val["icon"],
"beschreibung": val["beschreibung"],
"beispiel": val["beispiel"],
"score_range": val["score_range"],
}
for key, val in BEWERTUNGSKATEGORIEN.items()
}
@router.get("")
def list_fraktionen(conn=Depends(_db)):
"""List all parties with Antrag counts."""
rows = conn.execute("""
SELECT p.id, p.kuerzel, p.name, p.farbe, COUNT(a.vorlage_id) as anzahl
FROM parteien p
LEFT JOIN antragsteller a ON p.id = a.partei_id
GROUP BY p.id
HAVING anzahl > 0
ORDER BY anzahl DESC
""").fetchall()
return [dict(r) for r in rows]
@router.get("/{kuerzel}/dashboard")
def fraktion_dashboard(
kuerzel: str,
jahr: Optional[int] = None,
conn=Depends(_db),
):
"""Dashboard for a single Fraktion with Umsetzungsanalyse."""
# Find party
partei = conn.execute(
"SELECT id, kuerzel, name, farbe FROM parteien WHERE kuerzel = ?", (kuerzel,)
).fetchone()
if not partei:
raise HTTPException(404, f"Fraktion '{kuerzel}' nicht gefunden")
# Base query: all Vorlagen by this party
jahr_filter = ""
params = [partei["id"]]
if jahr:
jahr_filter = "AND strftime('%Y', v.datum_eingang) = ?"
params.append(str(jahr))
# Total Anträge
total = conn.execute(f"""
SELECT COUNT(DISTINCT v.id) as c
FROM vorlagen v
JOIN antragsteller a ON a.vorlage_id = v.id
WHERE a.partei_id = ? {jahr_filter}
""", params).fetchone()["c"]
# With Ketten-Match results
umsetzung_rows = conn.execute(f"""
SELECT
json_extract(kb.anmerkungen, '$.bewertung') as bewertung,
COUNT(*) as anzahl,
ROUND(AVG(kb.score), 2) as avg_score
FROM vorlagen v
JOIN antragsteller a ON a.vorlage_id = v.id
JOIN ki_bewertungen kb ON kb.vorlage_id = v.id AND kb.typ = 'umsetzung_match'
WHERE a.partei_id = ? {jahr_filter}
GROUP BY bewertung
ORDER BY anzahl DESC
""", params).fetchall()
umsetzung = [dict(r) for r in umsetzung_rows]
bewertet = sum(r["anzahl"] for r in umsetzung_rows)
# Top Anträge with scores
antraege = conn.execute(f"""
SELECT v.id, v.aktenzeichen, v.betreff, v.typ, v.datum_eingang,
kb.score as umsetzung_score,
json_extract(kb.anmerkungen, '$.bewertung') as umsetzung_bewertung,
kb.begruendung as umsetzung_begruendung
FROM vorlagen v
JOIN antragsteller a ON a.vorlage_id = v.id
LEFT JOIN ki_bewertungen kb ON kb.vorlage_id = v.id AND kb.typ = 'umsetzung_match'
WHERE a.partei_id = ? {jahr_filter}
ORDER BY v.datum_eingang DESC
LIMIT 200
""", params).fetchall()
# Jahre für Filter
jahre = conn.execute("""
SELECT DISTINCT strftime('%Y', v.datum_eingang) as j
FROM vorlagen v
JOIN antragsteller a ON a.vorlage_id = v.id
WHERE a.partei_id = ? AND v.datum_eingang IS NOT NULL
ORDER BY j DESC
""", [partei["id"]]).fetchall()
return {
"partei": dict(partei),
"total_antraege": total,
"bewertet": bewertet,
"umsetzung": umsetzung,
"antraege": [dict(r) for r in antraege],
"jahre": [r["j"] for r in jahre],
}