feat: GeoScene frontend POC + Docker deploy for remote host

Migrate maps to @geoscene/core, polish monitoring/alerts UX, fix timeline
basemap flicker and district alert regions, and ship compose/nginx Docker
deploy assets with CBPOA_ROOT data mounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-24 03:28:29 +08:00
parent fe8bed58f5
commit e22b004f9e
77 changed files with 3421 additions and 3589 deletions

View File

@@ -13,6 +13,7 @@ from config import DATA_DIR, ALERT_P1_RISK, ALERT_P2_RISK, WUHAN_BOUNDS, LAT_STE
from models import Alert, AlertResponse
from utils.date_helpers import get_latest_date, validate_date_format
from utils.risk import risk_value_to_level
from utils.district_lookup import district_for_grid
router = APIRouter(prefix="/api/alerts", tags=["alerts"])
@@ -95,13 +96,14 @@ def _generate_alerts_cached(date: str) -> List[Alert]:
lat, lon = grid_id_to_center(grid_id)
risk_level = risk_value_to_level(max_risk)
district = district_for_grid(grid_id)
alerts.append(
Alert(
alert_id=f"alert_{date}_{grid_id}",
grid_id=grid_id,
region="武汉市",
street=f"Grid {grid_id}",
region=district,
street=grid_id,
latitude=lat,
longitude=lon,
risk_value=max_risk,

View File

@@ -17,6 +17,8 @@ from utils.date_helpers import get_latest_date
from utils.geojson import parse_geojson_file, load_districts
from utils.geo import point_in_polygon
from utils.risk import calculate_trend
from utils.daily_risk_avg import daily_avg_risk
from utils.district_lookup import grid_district_lookup
router = APIRouter(prefix="/api/analysis", tags=["analysis"])
@@ -83,22 +85,10 @@ async def get_trend(days: int = Query(default=7, ge=1, le=30)):
for i in range(days):
date = base_date - timedelta(days=days - 1 - i)
date_str = date.strftime("%Y%m%d")
filepath = DATA_DIR / f"risk_{date_str}.geojson"
if filepath.exists():
grids = parse_geojson_file(filepath)
if grids:
avg_risk = sum(g["risk_value"] for g in grids) / len(grids)
values.append(round(avg_risk, 4))
else:
values.append(0)
else:
values.append(0)
# Disk+memory cached mean — avoids re-parsing ~45MB GeoJSON every request
values.append(daily_avg_risk(date_str))
dates.append(date.strftime("%Y-%m-%d"))
# Preserve the full requested date range: a "7天" request must return 7
# contiguous points. Days with no geojson (or empty grids) stay 0 rather
# than being dropped, which previously produced fewer, non-contiguous points.
trend_direction = calculate_trend(values)
return TrendResponse(
@@ -110,15 +100,8 @@ async def get_trend(days: int = Query(default=7, ge=1, le=30)):
@lru_cache(maxsize=1)
def _grid_district_lookup() -> dict:
"""Map precomputed r{row}_c{col} grid id -> district name (loaded once)."""
path = PROJECT_ROOT / "processed" / "grid_district_mapping.parquet"
if not path.exists():
return {}
df = pd.read_parquet(path)
# Some grids have a null district_name; drop them so the lookup only ever
# returns valid strings (missing keys fall back to "其他").
df = df.dropna(subset=["district_name"])
return dict(zip(df["grid_id"].astype(str), df["district_name"].astype(str)))
"""Backward-compatible alias — prefer utils.district_lookup."""
return grid_district_lookup()
@lru_cache(maxsize=1)