""" Router for geocoded case data and grid aggregated data """ from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List, Optional import logging import pandas as pd from pathlib import Path logger = logging.getLogger("cbpoa.geocoded") router = APIRouter(prefix="/api/geocoded", tags=["geocoded"]) PROJECT_ROOT = Path(__file__).parent.parent.parent DATA_DIR = PROJECT_ROOT / "outputs" class GridCaseData(BaseModel): """Grid case data for visualization""" grid_id: int latitude: float longitude: float total_cases: int outpatient_cases: int inpatient_cases: int case_density: float risk_index: float risk_level: str class GridCaseResponse(BaseModel): grids: List[GridCaseData] total_count: int total_cases: int class GeocodedCaseData(BaseModel): """Individual geocoded case""" case_id: str case_type: str latitude: float longitude: float district: str street: Optional[str] geocode_method: str confidence: float class GeocodedResponse(BaseModel): cases: List[GeocodedCaseData] total_count: int @router.get("/grid", response_model=GridCaseResponse, summary="Get aggregated grid case data") async def get_grid_cases(): """ Get 100x100m grid aggregated case data for high-resolution visualization. Returns grid cells with case counts, density, and risk indices. """ grid_file = DATA_DIR / "grid_risk_summary.csv" if not grid_file.exists(): raise HTTPException(status_code=404, detail="Grid data not found") try: df = pd.read_csv(grid_file) grids = [] for _, row in df.iterrows(): grids.append(GridCaseData( grid_id=int(row['grid_id']), latitude=float(row['center_y']), longitude=float(row['center_x']), total_cases=int(row['total_cases']), outpatient_cases=int(row['outpatient_cases']), inpatient_cases=int(row['inpatient_cases']), case_density=float(row['cases_per_km2']), risk_index=float(row['risk_index']), risk_level=str(row['risk_level']) )) total_cases = int(df['total_cases'].sum()) return GridCaseResponse( grids=grids, total_count=len(grids), total_cases=total_cases ) except Exception as e: logger.exception("Error loading grid case data") raise HTTPException(status_code=500, detail="Internal server error") @router.get("/geocoded", response_model=GeocodedResponse, summary="Get geocoded case data") async def get_geocoded_cases( limit: int = 1000, district: Optional[str] = None, ): """ Get individual geocoded case data. Args: limit: Maximum number of cases to return (for performance) district: Filter by district name """ cases_file = DATA_DIR / "geocoded_all_cases.csv" if not cases_file.exists(): raise HTTPException(status_code=404, detail="Geocoded data not found") try: df = pd.read_csv(cases_file) # Drop rows with missing coordinates df = df.dropna(subset=['latitude', 'longitude']) # Fix swapped lat/lon (Wuhan: lat ~29.9-31.4, lon ~113.7-115.1) swapped = df['latitude'] > 50 # longitude values are >113 df.loc[swapped, ['latitude', 'longitude']] = df.loc[swapped, ['longitude', 'latitude']].values # Filter by district if specified if district: df = df[df['district'] == district] # Limit for performance df = df.head(limit) cases = [] for _, row in df.iterrows(): street_val = row.get('street') if pd.isna(street_val): street_val = None district_val = row.get('district', '') if pd.isna(district_val): district_val = '未知' cases.append(GeocodedCaseData( case_id=str(row['case_id']), case_type=str(row['case_type']), latitude=float(row['latitude']), longitude=float(row['longitude']), district=str(district_val), street=street_val, geocode_method=str(row.get('geocode_method', 'unknown')), confidence=float(row.get('confidence', 0) or 0) if not pd.isna(row.get('confidence')) else 0.0 )) return GeocodedResponse( cases=cases, total_count=len(cases) ) except Exception as e: logger.exception("Error loading geocoded case data") raise HTTPException(status_code=500, detail="Internal server error") @router.get("/geocoded/count", summary="Get geocoded case count") async def get_geocoded_count(): """Get total count of geocoded cases.""" cases_file = DATA_DIR / "geocoded_all_cases.csv" if not cases_file.exists(): raise HTTPException(status_code=404, detail="Geocoded data not found") try: df = pd.read_csv(cases_file) street_matched = len(df[df['geocode_method'] == 'street']) district_fallback = len(df[df['geocode_method'] == 'district']) return { "total": len(df), "street_matched": street_matched, "district_fallback": district_fallback, "match_rate": round(street_matched / len(df) * 100, 1) } except Exception as e: logger.exception("Error counting geocoded cases") raise HTTPException(status_code=500, detail="Internal server error")