# Routers — API Endpoints ## Pattern Each router file defines one `APIRouter(prefix=..., tags=[...])` with typed endpoints. ```python from fastapi import APIRouter from models import SomeResponse router = APIRouter(prefix="/api/domain", tags=["domain"]) @router.get("/endpoint", response_model=SomeResponse) async def get_something(...): ... ``` ## Conventions - Return Pydantic models (`response_model=`), never raw dicts - Use `Annotated[Type, Query(...)]` / `Path(...)` for request params - Spatial queries use `scipy.spatial.KDTree` for nearest-neighbor lookups - GeoJSON parsing is delegated to `utils/geojson.py` - Risk level mapping is in `utils/risk.py` — use `risk_value_to_level()` not inline thresholds - Large repeated queries use `@lru_cache` (from `functools`) - Date helpers from `utils/date_helpers.py` — always `get_latest_date()`, never guess ## File Map | File | Domain | |------|--------| | `risk.py` | Risk maps, grid detail, history (largest router, ~42 file reads) | | `alerts.py` | Alert feed, stats | | `cases.py` | Medical case queries (age, disease, district filters) | | `analysis.py` | Trend analysis, statistics | | `grid.py` | Grid metadata, elevation, population | | `reports.py` | Report generation, export | | `insights.py` | AI-generated insights | | `chat.py` | Chatbot endpoint | | `geocoded.py` | Geocoded case data | ## Anti-Patterns - Don't use sync I/O in `async def` — use `async with db.get_connection()` for DB - Don't catch bare `Exception` — use specific HTTPException or let it propagate to middleware - Don't return raw GeoJSON dicts without Pydantic validation - Don't inline risk thresholds — use `utils/risk.risk_value_to_level()`