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:
102
scripts/validate_grid.py
Normal file
102
scripts/validate_grid.py
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Wuhan 100m grid index."""
|
||||
|
||||
import sys
|
||||
import geopandas as gpd
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def validate_grid():
|
||||
print("=== Grid Validation ===\n")
|
||||
|
||||
errors = []
|
||||
|
||||
geojson_path = "processed/grid_100m_index.geojson"
|
||||
parquet_path = "processed/grid_100m_index.parquet"
|
||||
|
||||
print(f"1. Checking files exist...")
|
||||
try:
|
||||
grid = gpd.read_file(geojson_path)
|
||||
print(f" GeoJSON: {geojson_path} - OK ({len(grid)} features)")
|
||||
except Exception as e:
|
||||
errors.append(f"GeoJSON read failed: {e}")
|
||||
print(f" GeoJSON: FAILED - {e}")
|
||||
return errors
|
||||
|
||||
try:
|
||||
df = pd.read_parquet(parquet_path)
|
||||
print(f" Parquet: {parquet_path} - OK ({len(df)} rows)")
|
||||
except Exception as e:
|
||||
errors.append(f"Parquet read failed: {e}")
|
||||
print(f" Parquet: FAILED - {e}")
|
||||
return errors
|
||||
|
||||
print(f"\n2. Validating grid count...")
|
||||
expected_min = 800000
|
||||
expected_max = 1000000
|
||||
actual = len(grid)
|
||||
print(f" Expected: {expected_min}-{expected_max}")
|
||||
print(f" Actual: {actual}")
|
||||
if actual < expected_min or actual > expected_max:
|
||||
errors.append(f"Grid count {actual} outside expected range {expected_min}-{expected_max}")
|
||||
print(f" Status: FAILED")
|
||||
else:
|
||||
print(f" Status: OK")
|
||||
|
||||
print(f"\n3. Validating grid_id format...")
|
||||
sample_ids = df['grid_id'].head(5).tolist()
|
||||
print(f" Sample: {sample_ids}")
|
||||
invalid_ids = df[~df['grid_id'].str.match(r'^r\d+_c\d+$')]
|
||||
if len(invalid_ids) > 0:
|
||||
errors.append(f"Invalid grid_id format in {len(invalid_ids)} rows")
|
||||
print(f" Invalid format: {len(invalid_ids)} rows")
|
||||
else:
|
||||
print(f" All {len(df)} grid_ids valid")
|
||||
|
||||
print(f"\n4. Validating center coordinates...")
|
||||
lon_min, lat_min, lon_max, lat_max = df['center_lon'].min(), df['center_lat'].min(), df['center_lon'].max(), df['center_lat'].max()
|
||||
print(f" Lon range: {lon_min:.4f} to {lon_max:.4f}")
|
||||
print(f" Lat range: {lat_min:.4f} to {lat_max:.4f}")
|
||||
|
||||
wuhan_lon_range = (113.7, 115.2)
|
||||
wuhan_lat_range = (29.9, 31.4)
|
||||
if lon_min < wuhan_lon_range[0] or lon_max > wuhan_lon_range[1]:
|
||||
errors.append(f"Longitude range {lon_min:.4f}-{lon_max:.4f} outside Wuhan bounds")
|
||||
print(f" WARNING: Longitude outside expected Wuhan bounds")
|
||||
if lat_min < wuhan_lat_range[0] or lat_max > wuhan_lat_range[1]:
|
||||
errors.append(f"Latitude range {lat_min:.4f}-{lat_max:.4f} outside Wuhan bounds")
|
||||
print(f" WARNING: Latitude outside expected Wuhan bounds")
|
||||
if not errors:
|
||||
print(f" Coordinates within Wuhan bounds")
|
||||
|
||||
print(f"\n5. Validating required columns...")
|
||||
required_cols = ['grid_id', 'center_lon', 'center_lat', 'row', 'col', 'polygon']
|
||||
missing = [c for c in required_cols if c not in df.columns]
|
||||
if missing:
|
||||
errors.append(f"Missing columns: {missing}")
|
||||
print(f" Missing: {missing}")
|
||||
else:
|
||||
print(f" All required columns present: {required_cols}")
|
||||
|
||||
print(f"\n6. Validating geometry in GeoJSON...")
|
||||
if grid.geometry.is_valid.all():
|
||||
print(f" All geometries valid")
|
||||
else:
|
||||
invalid_count = (~grid.geometry.is_valid).sum()
|
||||
errors.append(f"{invalid_count} invalid geometries")
|
||||
print(f" WARNING: {invalid_count} invalid geometries")
|
||||
|
||||
print(f"\n=== Validation Summary ===")
|
||||
if errors:
|
||||
print(f"ERRORS: {len(errors)}")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
return errors
|
||||
else:
|
||||
print(f"PASSED: All validations passed")
|
||||
return []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
errors = validate_grid()
|
||||
sys.exit(1 if errors else 0)
|
||||
Reference in New Issue
Block a user