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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user