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>
48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import { memo } from 'react';
|
||
import { Users, CalendarDays, Wallet, HeartPulse, Siren } from 'lucide-react';
|
||
import { StatCard } from '@/components/StatCard';
|
||
import { TESTIDS } from '@/utils/testids';
|
||
import type { InpatientClinicalResponse } from '@/services/api';
|
||
|
||
interface ClinicalKpiRowProps {
|
||
kpis: InpatientClinicalResponse['kpis'];
|
||
}
|
||
|
||
/** 住院临床 5 项核心指标。375px 下 2 列,sm 起 5 列。 */
|
||
export const ClinicalKpiRow = memo(function ClinicalKpiRow({ kpis }: ClinicalKpiRowProps) {
|
||
return (
|
||
<div
|
||
data-testid={TESTIDS.clinicalKpis}
|
||
className="grid grid-cols-2 sm:grid-cols-5 gap-3"
|
||
>
|
||
<StatCard
|
||
icon={<Users className="w-4 h-4 text-primary" />}
|
||
label="住院总人次"
|
||
value={kpis.total_admissions.toLocaleString()}
|
||
/>
|
||
<StatCard
|
||
icon={<CalendarDays className="w-4 h-4 text-primary" />}
|
||
label="中位住院日"
|
||
value={`${kpis.median_los_days} 天`}
|
||
/>
|
||
<StatCard
|
||
icon={<Wallet className="w-4 h-4 text-primary" />}
|
||
label="人均费用"
|
||
value={`¥${Math.round(kpis.mean_cost).toLocaleString()}`}
|
||
/>
|
||
<StatCard
|
||
icon={<HeartPulse className="w-4 h-4 text-success" />}
|
||
label="治愈好转率"
|
||
value={`${(kpis.cure_rate * 100).toFixed(1)}%`}
|
||
color="#16A34A"
|
||
/>
|
||
<StatCard
|
||
icon={<Siren className="w-4 h-4 text-warning" />}
|
||
label="急诊入院占比"
|
||
value={`${(kpis.emergency_admit_ratio * 100).toFixed(1)}%`}
|
||
color="#D97706"
|
||
/>
|
||
</div>
|
||
);
|
||
});
|