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

@@ -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)