Adds a substantial layer of data-backed statistics (all grounded in verified,
clean source data — no fabricated metrics).
Backend (new routers/statistics.py, prefix /api/stats; +106 pytest still green):
- /inpatient-clinical: LOS dist + by-disease quartiles, cost dist + by-disease +
cost-vs-LOS, outcome counts, admission-route counts, BMI-by-age, KPIs
(5822 admissions, median LOS 4d, mean ¥6294, cure 99.1%, emergency 47%)
- /symptoms: 主诉 keyword frequencies (发热/咳嗽/肺炎…) + revisit ratio (36%)
- /incidence-rate: per-10k-population standardized rate by district (cases ÷ pop)
- /env-correlation: pollutant×cases Pearson + 7×7 pairwise matrix + PM2.5 scatter
- /temporal: weekday distribution (+ month/yoy returned but UI omits them — data
is December-only, so seasonality/YoY would be misleading)
Frontend:
- NEW 住院临床分析 page (/analysis/clinical, nav 临床分析): 9 charts + KPI row —
LOS histogram + box-by-disease, cost histogram + scatter + by-disease, outcome
donut (severity-colored), admission-route donut, age-band BMI box
- DiseaseAnalysis: 主诉症状词频 horizontal bar + revisit ratio
- DistrictComparison: 标化发病率(每万人)with 病例数↔发病率 toggle (rate is
epidemiologically correct; raw counts mislead by population)
- EnvironmentalHealth: pollutant-cases correlation bar + 7×7 correlation heatmap +
PM2.5×cases scatter with least-squares regression line
- TrendAnalysis: 星期就诊分布 + honest "data is December-only" note
- statsApi client + types
Gates: tsc 0 · build ok · functional e2e 43/43 (incl 2 new clinical) · verified
live against real backend data via dev proxy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 of the UX modernization. Frontend-only role view-presets (D2 — no
backend, no JWT role claim, honestly labeled 视角 not 权限). Three conflict-free lanes.
Role infrastructure (worker-session):
- sessionStore with single swappable getRoleSource() seam (localStorage today,
one-line swap to /api/auth/me for future RBAC); Role = official|community|doctor|admin
- 视角 switcher in TopNav (replaces hardcoded "admin"); on change persists role +
navigates to that perspective's default landing
- roleViews.ts: ROLE_LABELS + roleDefaultPath (official→/overview?granularity=district,
community→/monitoring?granularity=street, doctor→/alerts?view=cluster, admin→/monitoring)
- RoleRedirect index route → current role's default; * fallback unchanged
URL-driven granularity (worker-monitoring):
- granularity (city|district|street) query param is the source of truth; drilldownStore
DERIVES from it via a one-way effect; deep-linkable + reload-safe
- reconciled the imperative desync — DistrictBreakdown no longer calls
useDrilldownStore.getState(); MonitoringDashboard owns useSearchParams, writes URL
- granularity-control Segmented (全市/区域/街道); district-rollup testid
Role-aware alerts + privacy (worker-alerts):
- doctor/cluster view: individual alert markers hard-locked off (effective flag is
single source of truth; toggle not rendered) → aggregated density only; DiseaseFilter
mounted; privacy invariant testable via hidden patient-point DOM mirror (count=0)
- 官员: 100m grid hidden (grid-layer-wrapper unrendered); admin/community unchanged
Gates: tsc 0 · vitest 75 · e2e 30/30 (incl 10 new P3 tests) · build ok
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>