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.
This commit is contained in:
2026-06-05 02:13:49 +08:00
commit fc468464b2
117 changed files with 18282 additions and 0 deletions

101
scripts/generate_grid.py Normal file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Generate 100x100m resolution grid index for Wuhan city, China."""
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely.geometry import box
from pathlib import Path
def generate_wuhan_grid(
boundary_path: str = "Datas/武汉市.geojson",
output_dir: str = "processed",
grid_size: float = 100,
) -> tuple[gpd.GeoDataFrame, pd.DataFrame]:
"""Generate 100m resolution grid covering Wuhan boundary."""
print(f"Loading Wuhan boundary from {boundary_path}...")
wuhan = gpd.read_file(boundary_path)
bounds = wuhan.total_bounds
print(f"Wuhan bounds: minx={bounds[0]:.4f}, miny={bounds[1]:.4f}, maxx={bounds[2]:.4f}, maxy={bounds[3]:.4f}")
minx, miny, maxx, maxy = bounds
cell_size_deg = grid_size / 111000.0
print(f"Creating grid with {grid_size}m cells (vectorized)...")
x_coords = np.arange(minx, maxx, cell_size_deg)
y_coords = np.arange(miny, maxy, cell_size_deg)
print(f" Grid dimensions: {len(x_coords)} x {len(y_coords)}")
x_grid, y_grid = np.meshgrid(x_coords, y_coords)
x_flat = x_grid.flatten()
y_flat = y_grid.flatten()
print(f" Total cells in bounding box: {len(x_flat)}")
minxs = x_flat
minys = y_flat
maxxs = minxs + cell_size_deg
maxys = minys + cell_size_deg
geometries = [box(mx, my, Mx, My) for mx, my, Mx, My in zip(minxs, minys, maxxs, maxys)]
cells = np.arange(len(geometries))
rows = cells // len(x_coords)
cols = cells % len(x_coords)
print(" Building GeoDataFrame...")
grid_gdf = gpd.GeoDataFrame({
'row': rows,
'col': cols,
'geometry': geometries
}, crs="EPSG:4326")
print("Filtering to cells intersecting Wuhan boundary...")
wuhan_union = wuhan.unary_union
mask = grid_gdf.intersects(wuhan_union)
grid_gdf = grid_gdf[mask].copy().reset_index(drop=True)
print(f"Cells within Wuhan boundary: {len(grid_gdf)}")
grid_gdf['grid_id'] = [f"r{r}_c{c}" for r, c in zip(grid_gdf['row'], grid_gdf['col'])]
centroids = grid_gdf.geometry.centroid
grid_gdf['center_lon'] = centroids.x
grid_gdf['center_lat'] = centroids.y
grid_gdf['polygon'] = grid_gdf.geometry.apply(lambda g: g.wkt)
parquet_df = grid_gdf[['grid_id', 'center_lon', 'center_lat', 'row', 'col', 'polygon']].copy()
return grid_gdf, parquet_df
def main():
output_dir = Path("processed")
output_dir.mkdir(parents=True, exist_ok=True)
grid_gdf, parquet_df = generate_wuhan_grid()
geojson_path = output_dir / "grid_100m_index.geojson"
print(f"Exporting to GeoJSON: {geojson_path}")
grid_gdf.to_file(geojson_path, driver="GeoJSON")
print(f" Exported {len(grid_gdf)} features")
parquet_path = output_dir / "grid_100m_index.parquet"
print(f"Exporting to Parquet: {parquet_path}")
parquet_df.to_parquet(parquet_path, index=False)
print(f" Exported {len(parquet_df)} rows")
print("\n=== Grid Summary ===")
print(f"Total grid cells: {len(grid_gdf)}")
print(f"Bounds: {grid_gdf.total_bounds}")
print(f"Grid ID format example: {grid_gdf['grid_id'].iloc[0]}")
print(f"Center coordinate range:")
print(f" Lon: {parquet_df['center_lon'].min():.4f} to {parquet_df['center_lon'].max():.4f}")
print(f" Lat: {parquet_df['center_lat'].min():.4f} to {parquet_df['center_lat'].max():.4f}")
if __name__ == "__main__":
main()