feat: add analysis pages and raster risk map

Ship a new app version with broader analytics, restructured
dashboards, and a server-rendered risk map.

Frontend:
- Add Overview, Demographic, Disease, and Environmental Health
  analysis pages
- Add AnomalyMarkers, CalendarHeatmap, and MetricHeatmapTable
  components
- Rebuild Alerts map onto server-rendered raster risk tiles;
  expand Monitoring, Trend, and District Comparison views
- Extend API client, stores, and TypeScript types

Backend:
- Add environment router (pollutants, lag correlations)
- Add risk_raster util serving XYZ 100m risk tiles
- Expand cases endpoints (demographics, seasonality, diagnoses)
  and insights; harden auth and file-based loaders

Data & tooling:
- Add processed outpatient/inpatient/combined case parquet (LFS)
- Add nested CLAUDE.md guides, pyrightconfig, and test updates
This commit is contained in:
2026-06-21 17:35:03 +08:00
parent f092c3c550
commit e95e2f1338
63 changed files with 8534 additions and 988 deletions

View File

@@ -18,9 +18,9 @@ for (const [, , color] of RISK_COLORS) {
function getRiskColor(value: number): string {
for (const [min, max, color] of RISK_COLORS) {
if (value >= min && value <= max) return color;
if (value >= min && value < max) return color;
}
return '#22c55e';
return '#ef4444';
}
// 100m grid step in degrees
@@ -116,6 +116,7 @@ export function LodGridLayer({
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.pointerEvents = 'none';
canvas.style.display = visibleRef.current ? '' : 'none';
pane.appendChild(canvas);
canvasRef.current = canvas;
@@ -173,8 +174,8 @@ export function LodGridLayer({
canvas.style.transform = '';
drawnOriginRef.current = null;
if (!visibleRef.current) return;
// Visibility is controlled via canvas CSS display (see visible effect),
// so we still draw pixels even when hidden to keep them ready on re-show.
const currentGrids = gridsRef.current;
if (!currentGrids || currentGrids.length === 0) return;
@@ -354,13 +355,21 @@ export function LodGridLayer({
};
}, [map]);
// Trigger redraw when data changes
// Trigger redraw when data/geometry-affecting inputs change.
useEffect(() => {
const canvas = canvasRef.current;
if (canvas && (canvas as any).__lodRedraw) {
(canvas as any).__lodRedraw();
}
}, [grids, forecastDay, riskRange, visible]);
}, [grids, forecastDay, riskRange]);
// Visibility toggle: hide/show via CSS instead of a full geometry redraw.
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
canvas.style.display = visible ? '' : 'none';
}
}, [visible]);
return null;
}