"""Expose authenticated server-side statistics endpoints. Probabilities and ranks are computed in Python, never by the model. """ from datetime import date, datetime, timezone from fastapi import APIRouter, Depends, Query, Request from app.deps import require_login from app.models import ApiEnvelope, ok from app.stats_math import build_stats, by_remedy router = APIRouter( prefix="/api/stats", tags=["stats"], dependencies=[Depends(require_login)], ) def _entry_dicts(request: Request) -> list[dict]: return [e.model_dump(mode="json") for e in request.app.state.entry_store._load()] def _daily_dicts( request: Request, start: date | None, end: date | None, ) -> list[dict]: rows = request.app.state.daily_store._load() out = [] for row in rows: if start and row.date < start: continue if end and row.date > end: continue out.append(row.model_dump(mode="json")) return out def _filter_entries( entries: list[dict], start: date | None, end: date | None, ) -> list[dict]: if start is None and end is None: return entries filtered = [] for entry in entries: ts = entry.get("ts") if isinstance(ts, str): day = date.fromisoformat(ts[:10]) else: continue if start and day < start: continue if end and day > end: continue filtered.append(entry) return filtered @router.get("", response_model=ApiEnvelope) def get_stats( request: Request, start: date | None = None, end: date | None = None, min_n: int | None = Query(default=None, ge=1), ) -> dict[str, object]: """Return aggregate statistics for entries and daily rows.""" settings = request.app.state.settings effective_min_n = settings.min_stats_n if min_n is None else min_n entries = _filter_entries(_entry_dicts(request), start, end) daily = _daily_dicts(request, start, end) payload = build_stats( entries, daily, min_n=effective_min_n, shrink_k=settings.stats_shrink_k, generated_at=datetime.now(timezone.utc).isoformat(), ) return ok(payload) @router.get("/remedies", response_model=ApiEnvelope) def get_remedy_leaderboard( request: Request, start: date | None = None, end: date | None = None, min_n: int | None = Query(default=None, ge=1), ) -> dict[str, object]: """Return remedies sorted by shrinkage rank.""" settings = request.app.state.settings effective_min_n = settings.min_stats_n if min_n is None else min_n entries = _filter_entries(_entry_dicts(request), start, end) rows = by_remedy( entries, min_n=effective_min_n, shrink_k=settings.stats_shrink_k, ) return ok({"items": rows, "min_n": effective_min_n})