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.
102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
#!/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) |