diff --git a/backend/src/cyclone/api.py b/backend/src/cyclone/api.py index 90d854b..ef1ea49 100644 --- a/backend/src/cyclone/api.py +++ b/backend/src/cyclone/api.py @@ -93,7 +93,6 @@ from cyclone.store import ( AlreadyMatchedError, BatchRecord, InvalidStateError, - dashboard_kpis, store, utcnow, ) @@ -2729,34 +2728,6 @@ def get_remittance(remittance_id: str) -> dict: return body -@app.get("/api/dashboard/kpis", dependencies=[Depends(matrix_gate)]) -def get_dashboard_kpis( - months: int = Query(6, ge=1, le=24), - top_n_providers: int = Query(4, ge=0, le=50), - top_n_denials: int = Query(5, ge=0, le=50), -) -> dict: - """Server-aggregated Dashboard KPIs over the whole claim population. - - Backs the Dashboard's "Claims / Billed / Received / Pending AR / - Denial rate" tiles + the monthly sparkline series + the - top-providers and top-denials lists. - - Why this exists instead of ``GET /api/claims?limit=N``: - The Dashboard's KPIs are aggregates over *every* claim — billed, - received, denial rate, pending count, monthly billed/received. With - 60k+ claims in production, paginating ``/api/claims`` and reducing - client-side silently produces wrong numbers (denial rate sampled, - billed summed from the first 100 rows). This endpoint does the - aggregation server-side in a single read so the Dashboard's numbers - are always correct regardless of dataset size. - """ - return dashboard_kpis( - months=months, - top_n_providers=top_n_providers, - top_n_denials=top_n_denials, - ) - - @app.get("/api/providers", dependencies=[Depends(matrix_gate)]) def list_providers( request: Request, diff --git a/backend/src/cyclone/api_routers/__init__.py b/backend/src/cyclone/api_routers/__init__.py index 5ca8320..297d2fa 100644 --- a/backend/src/cyclone/api_routers/__init__.py +++ b/backend/src/cyclone/api_routers/__init__.py @@ -10,12 +10,20 @@ invariant here so adding a new router doesn't silently miss the gate. """ from fastapi import APIRouter -from cyclone.api_routers import acks, admin, claim_acks, health, ta1_acks +from cyclone.api_routers import ( + acks, + admin, + claim_acks, + dashboard, + health, + ta1_acks, +) routers: list[APIRouter] = [ acks.router, # gated admin.router, # gated claim_acks.router, # gated + dashboard.router, # gated health.router, # public — health probes must work pre-auth ta1_acks.router, # gated ] diff --git a/backend/src/cyclone/api_routers/dashboard.py b/backend/src/cyclone/api_routers/dashboard.py new file mode 100644 index 0000000..616b564 --- /dev/null +++ b/backend/src/cyclone/api_routers/dashboard.py @@ -0,0 +1,39 @@ +"""``/api/dashboard/kpis`` — server-aggregated Dashboard tiles. + +Backs the Dashboard's "Claims / Billed / Received / Pending AR / +Denial rate" tiles + the monthly sparkline series + the +top-providers and top-denials lists. + +Why this exists instead of ``GET /api/claims?limit=N``: +The Dashboard's KPIs are aggregates over *every* claim — billed, +received, denial rate, pending count, monthly billed/received. With +60k+ claims in production, paginating ``/api/claims`` and reducing +client-side silently produces wrong numbers (denial rate sampled, +billed summed from the first 100 rows). This endpoint does the +aggregation server-side in a single read so the Dashboard's numbers +are always correct regardless of dataset size. + +SP36 Task 4: this single endpoint moved here from ``api.py:2732``. +""" +from __future__ import annotations + +from fastapi import APIRouter, Depends, Query + +from cyclone.auth.deps import matrix_gate +from cyclone.store import dashboard_kpis + +router = APIRouter(dependencies=[Depends(matrix_gate)]) + + +@router.get("/api/dashboard/kpis") +def get_dashboard_kpis( + months: int = Query(6, ge=1, le=24), + top_n_providers: int = Query(4, ge=0, le=50), + top_n_denials: int = Query(5, ge=0, le=50), +) -> dict: + """Server-aggregated Dashboard KPIs over the whole claim population.""" + return dashboard_kpis( + months=months, + top_n_providers=top_n_providers, + top_n_denials=top_n_denials, + ) \ No newline at end of file