feat: remove cost/费用 statistics from clinical analytics
Per request — drop all monetary statistics (住院费用 is sensitive). - Backend statistics.py: remove mean_cost KPI + cost_histogram / cost_by_disease / cost_vs_los from /inpatient-clinical (models, computation, response) - Frontend: drop 人均费用 KPI card (now 4 KPIs), 住院费用分布, 各病种平均费用, 费用×住院天数散点; delete CostByDiseaseChart + CostVsLosScatter components; trim statsApi type + e2e fixture + chartColors Clinical page now: KPI(总人次/中位住院日/治愈好转率/急诊占比) + LOS dist + LOS-by-disease box + outcome donut + admission-route donut + age-band BMI box. Gates: backend 106 pytest · tsc 0 · build ok · clinical+user-flows e2e 19/19 · live endpoint confirmed cost-free Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,7 +116,6 @@ class KeyValueCount(BaseModel):
|
||||
class InpatientKpis(BaseModel):
|
||||
total_admissions: int
|
||||
median_los_days: float
|
||||
mean_cost: float
|
||||
cure_rate: float
|
||||
emergency_admit_ratio: float
|
||||
|
||||
@@ -129,17 +128,6 @@ class LosByDisease(BaseModel):
|
||||
n: int
|
||||
|
||||
|
||||
class CostByDisease(BaseModel):
|
||||
diagnosis: str
|
||||
mean_cost: float
|
||||
n: int
|
||||
|
||||
|
||||
class CostVsLos(BaseModel):
|
||||
los: int
|
||||
cost: float
|
||||
|
||||
|
||||
class LabelCount(BaseModel):
|
||||
outcome: Optional[str] = None
|
||||
route: Optional[str] = None
|
||||
@@ -168,9 +156,6 @@ class InpatientClinicalResponse(BaseModel):
|
||||
kpis: InpatientKpis
|
||||
los_histogram: list[KeyValueCount]
|
||||
los_by_disease: list[LosByDisease]
|
||||
cost_histogram: list[KeyValueCount]
|
||||
cost_by_disease: list[CostByDisease]
|
||||
cost_vs_los: list[CostVsLos]
|
||||
outcome_counts: list[OutcomeCount]
|
||||
admission_route_counts: list[RouteCount]
|
||||
bmi_by_age_band: list[BmiByAge]
|
||||
@@ -252,11 +237,10 @@ class TemporalResponse(BaseModel):
|
||||
def _empty_inpatient_clinical() -> InpatientClinicalResponse:
|
||||
return InpatientClinicalResponse(
|
||||
kpis=InpatientKpis(
|
||||
total_admissions=0, median_los_days=0.0, mean_cost=0.0,
|
||||
total_admissions=0, median_los_days=0.0,
|
||||
cure_rate=0.0, emergency_admit_ratio=0.0,
|
||||
),
|
||||
los_histogram=[], los_by_disease=[], cost_histogram=[],
|
||||
cost_by_disease=[], cost_vs_los=[], outcome_counts=[],
|
||||
los_histogram=[], los_by_disease=[], outcome_counts=[],
|
||||
admission_route_counts=[], bmi_by_age_band=[],
|
||||
)
|
||||
|
||||
@@ -287,8 +271,6 @@ def _compute_inpatient_clinical() -> InpatientClinicalResponse:
|
||||
|
||||
total = len(df)
|
||||
median_los = float(df_los["los"].median()) if len(df_los) else 0.0
|
||||
cost = pd.to_numeric(df["住院总费用"], errors="coerce")
|
||||
mean_cost = float(cost.mean()) if cost.notna().any() else 0.0
|
||||
|
||||
outcome = df["出院情况"].fillna("未知")
|
||||
cure_n = int(outcome.isin(["治愈", "好转"]).sum())
|
||||
@@ -301,7 +283,6 @@ def _compute_inpatient_clinical() -> InpatientClinicalResponse:
|
||||
kpis = InpatientKpis(
|
||||
total_admissions=total,
|
||||
median_los_days=round(median_los, 2),
|
||||
mean_cost=round(mean_cost, 2),
|
||||
cure_rate=round(cure_rate, 4),
|
||||
emergency_admit_ratio=round(emerg_ratio, 4),
|
||||
)
|
||||
@@ -328,39 +309,6 @@ def _compute_inpatient_clinical() -> InpatientClinicalResponse:
|
||||
n=int(len(grp)),
|
||||
))
|
||||
|
||||
# Cost histogram: 0-2k,2-4k,4-6k,6-8k,8-10k,10k+
|
||||
cost_valid = cost.dropna()
|
||||
cost_bins = [(0, 2000, "0-2k"), (2000, 4000, "2-4k"), (4000, 6000, "4-6k"),
|
||||
(6000, 8000, "6-8k"), (8000, 10000, "8-10k")]
|
||||
cost_histogram: list[KeyValueCount] = []
|
||||
for lo, hi, label in cost_bins:
|
||||
cost_histogram.append(KeyValueCount(
|
||||
bin_label=label, count=int(((cost_valid >= lo) & (cost_valid < hi)).sum())))
|
||||
cost_histogram.append(KeyValueCount(bin_label="10k+", count=int((cost_valid >= 10000).sum())))
|
||||
|
||||
# Cost by disease (top 8 by n)
|
||||
cost_by_disease: list[CostByDisease] = []
|
||||
df_cost = df[cost.notna()].copy()
|
||||
df_cost["_cost"] = cost[cost.notna()]
|
||||
if len(df_cost):
|
||||
top_cd = df_cost["诊断名称"].value_counts().head(8).index.tolist()
|
||||
for d in top_cd:
|
||||
grp = df_cost[df_cost["诊断名称"] == d]["_cost"]
|
||||
cost_by_disease.append(CostByDisease(
|
||||
diagnosis=str(d),
|
||||
mean_cost=round(float(grp.mean()), 2),
|
||||
n=int(len(grp)),
|
||||
))
|
||||
|
||||
# cost vs los scatter (up to 500 points)
|
||||
cost_vs_los: list[CostVsLos] = []
|
||||
scatter_df = df_los[cost.reindex(df_los.index).notna()].copy()
|
||||
scatter_df["_cost"] = cost.reindex(scatter_df.index)
|
||||
if len(scatter_df) > 500:
|
||||
scatter_df = scatter_df.sample(n=500, random_state=42)
|
||||
for _, r in scatter_df.iterrows():
|
||||
cost_vs_los.append(CostVsLos(los=int(r["los"]), cost=round(float(r["_cost"]), 2)))
|
||||
|
||||
# outcome counts
|
||||
outcome_counts = [
|
||||
OutcomeCount(outcome=str(k), count=int(v))
|
||||
@@ -399,9 +347,6 @@ def _compute_inpatient_clinical() -> InpatientClinicalResponse:
|
||||
kpis=kpis,
|
||||
los_histogram=los_histogram,
|
||||
los_by_disease=los_by_disease,
|
||||
cost_histogram=cost_histogram,
|
||||
cost_by_disease=cost_by_disease,
|
||||
cost_vs_los=cost_vs_los,
|
||||
outcome_counts=outcome_counts,
|
||||
admission_route_counts=admission_route_counts,
|
||||
bmi_by_age_band=bmi_by_age_band,
|
||||
|
||||
Reference in New Issue
Block a user