antragstracker/backend/src/tracker/api/routes/ketten.py

187 lines
5.8 KiB
Python
Raw Normal View History

from __future__ import annotations
"""API routes for Ketten (chains)."""
from fastapi import APIRouter, Depends, HTTPException, Query
from tracker.api.models import (
KetteDetail,
KetteKurz,
KettenGliedOut,
PaginatedKetten,
ParteiOut,
VorlageKurz,
)
from tracker.core.graph import get_kette_graph
from tracker.db.session import get_connection
router = APIRouter(prefix="/ketten", tags=["Ketten"])
def _db():
conn = get_connection()
try:
yield conn
finally:
conn.close()
@router.get("", response_model=PaginatedKetten)
def list_ketten(
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=200),
status: str | None = None,
typ: str | None = None,
suche: str | None = None,
partei: str | None = None,
conn=Depends(_db),
):
"""List Ketten with optional filters."""
where_clauses = []
params: list = []
if status:
where_clauses.append("k.status = ?")
params.append(status)
if typ:
where_clauses.append("k.typ = ?")
params.append(typ)
if partei:
where_clauses.append(
"k.ursprung_id IN (SELECT a.vorlage_id FROM antragsteller a "
"JOIN parteien p ON a.partei_id = p.id WHERE p.kuerzel = ?)"
)
params.append(partei)
if suche:
where_clauses.append("k.thema LIKE ?")
params.append(f"%{suche}%")
where_sql = ("WHERE " + " AND ".join(where_clauses)) if where_clauses else ""
total = conn.execute(
f"SELECT COUNT(*) as cnt FROM ketten k {where_sql}", params
).fetchone()["cnt"]
offset = (page - 1) * page_size
rows = conn.execute(
f"""SELECT k.id, k.typ, k.thema, k.status, k.status_seit,
k.letzte_aktivitaet, k.vertagungen_count, k.ursprung_id,
v.aktenzeichen, v.typ as v_typ, v.betreff, v.datum_eingang,
v.ist_verwaltungsvorlage,
(SELECT COUNT(*) FROM ketten_glieder kg WHERE kg.kette_id = k.id) as glieder_count
FROM ketten k
LEFT JOIN vorlagen v ON k.ursprung_id = v.id
{where_sql}
ORDER BY k.letzte_aktivitaet DESC NULLS LAST, k.id DESC
LIMIT ? OFFSET ?""",
params + [page_size, offset],
).fetchall()
items = [
KetteKurz(
id=r["id"],
ursprung=VorlageKurz(
id=r["ursprung_id"],
aktenzeichen=r["aktenzeichen"],
typ=r["v_typ"],
betreff=r["betreff"],
datum_eingang=r["datum_eingang"],
ist_verwaltungsvorlage=bool(r["ist_verwaltungsvorlage"]),
) if r["ursprung_id"] else None,
typ=r["typ"],
thema=r["thema"],
status=r["status"],
status_seit=r["status_seit"],
letzte_aktivitaet=r["letzte_aktivitaet"],
vertagungen_count=r["vertagungen_count"],
glieder_count=r["glieder_count"],
)
for r in rows
]
return PaginatedKetten(items=items, total=total, page=page, page_size=page_size)
@router.get("/{kette_id}", response_model=KetteDetail)
def get_kette(kette_id: int, conn=Depends(_db)):
"""Get a single Kette with all Glieder."""
row = conn.execute(
"""SELECT k.id, k.typ, k.thema, k.status, k.status_seit,
k.letzte_aktivitaet, k.vertagungen_count, k.begruendung, k.ursprung_id,
v.aktenzeichen, v.typ as v_typ, v.betreff, v.datum_eingang,
v.ist_verwaltungsvorlage
FROM ketten k
LEFT JOIN vorlagen v ON k.ursprung_id = v.id
WHERE k.id = ?""",
(kette_id,),
).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Kette nicht gefunden")
# Get Glieder
glieder_rows = conn.execute(
"""SELECT kg.position, kg.rolle,
v.id, v.aktenzeichen, v.typ, v.betreff, v.datum_eingang,
v.ist_verwaltungsvorlage
FROM ketten_glieder kg
JOIN vorlagen v ON kg.vorlage_id = v.id
WHERE kg.kette_id = ?
ORDER BY kg.position""",
(kette_id,),
).fetchall()
glieder = [
KettenGliedOut(
vorlage=VorlageKurz(
id=g["id"],
aktenzeichen=g["aktenzeichen"],
typ=g["typ"],
betreff=g["betreff"],
datum_eingang=g["datum_eingang"],
ist_verwaltungsvorlage=bool(g["ist_verwaltungsvorlage"]),
),
position=g["position"],
rolle=g["rolle"],
)
for g in glieder_rows
]
# Antragsteller des Ursprungs
antragsteller = []
if row["ursprung_id"]:
antragsteller_rows = conn.execute("""
SELECT p.id, p.kuerzel, p.name, p.farbe
FROM antragsteller a
JOIN parteien p ON a.partei_id = p.id
WHERE a.vorlage_id = ?
""", (row["ursprung_id"],)).fetchall()
antragsteller = [ParteiOut(**dict(a)) for a in antragsteller_rows]
# Graph/Perlenschnur data
graph = get_kette_graph(conn, kette_id)
return KetteDetail(
id=row["id"],
ursprung=VorlageKurz(
id=row["ursprung_id"],
aktenzeichen=row["aktenzeichen"],
typ=row["v_typ"],
betreff=row["betreff"],
datum_eingang=row["datum_eingang"],
ist_verwaltungsvorlage=bool(row["ist_verwaltungsvorlage"]),
) if row["ursprung_id"] else None,
typ=row["typ"],
thema=row["thema"],
status=row["status"],
status_seit=row["status_seit"],
letzte_aktivitaet=row["letzte_aktivitaet"],
vertagungen_count=row["vertagungen_count"],
begruendung=row["begruendung"],
glieder=glieder,
antragsteller=antragsteller,
graph=graph,
)