feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
"""
|
|
|
|
|
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
|
2026-06-05 02:27:10 +08:00
|
|
|
from functools import lru_cache
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
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"
|
|
|
|
|
|
2026-06-05 02:27:10 +08:00
|
|
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
|
|
|
def _load_csv(path: Path) -> pd.DataFrame:
|
|
|
|
|
return pd.read_csv(path)
|
|
|
|
|
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
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:
|
2026-06-05 02:27:10 +08:00
|
|
|
df = _load_csv(grid_file)
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
|
|
|
|
|
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:
|
2026-06-05 02:27:10 +08:00
|
|
|
df = _load_csv(cases_file)
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
|
|
|
|
|
# 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 = []
|
2026-06-05 02:27:10 +08:00
|
|
|
for row in df.to_dict('records'):
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
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:
|
2026-06-05 02:27:10 +08:00
|
|
|
df = _load_csv(cases_file)
|
feat: Initial CBPOA commit — 武汉儿童呼吸疾病风险评估系统
Context: Build a spatial risk assessment system correlating air quality
data with children's respiratory disease incidence across Wuhan.
Approach: FastAPI backend serving PostGIS spatial queries, React
frontend with Deck.gl maps, and a PyTorch SpatialTemporalGCN pipeline
for multi-day (1d/3d/7d) risk prediction.
Changes:
- backend/ — FastAPI API with auth (JWT), alerts, risk analysis,
geocoded case data, grid statistics, and report endpoints
- frontend/ — React dashboard with interactive risk maps, alert
monitoring, district comparison charts, and timeline player
- models/ — SpatialTemporalGCN model with trained weights and ONNX
export for inference
- scripts/ — ETL pipeline for weather + medical data, grid generation,
feature engineering, training, and daily inference
- deploy/ — Docker Compose configs for backend, frontend, and MLflow
- docs/ — API docs, deployment guide, user guide, and code review
Impact: Enables spatial risk visualization, alert monitoring, and
ML-driven health risk forecasting for environmental health teams.
2026-06-05 02:13:49 +08:00
|
|
|
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")
|