Prodrome¶
Early warning for sepsis with a calibrated alert budget, validated on a hospital it never trained on.
A prodrome is the early set of signs that a disease is coming, before the specific symptoms that name it. Greek prodromos, "running before": a forerunner.
Not a medical device. Not cleared by any regulator. Not for clinical use. It predicts a recorded clinical suspicion, not a biological event.
pip install "prodrome[dev]"
prodrome init && prodrome data fetch && prodrome data prepare
prodrome train && prodrome calibrate && prodrome eval
The numbers¶
Trained on hospital system A of the PhysioNet/CinC 2019 corpus, calibrated on held-out patients from A, then tested on all 20,000 patients of system B, which it never saw. External results first, because that is the number that means something.
| config | train | test | utility | auprc | auroc | detect | in window | lead h | alerts/pt-day | precision | ece |
|---|---|---|---|---|---|---|---|---|---|---|---|
| default | A | B | 0.2468 | 0.072 | 0.802 | 0.456 | 0.153 | 33.0 | 0.11 | 0.305 | 0.003 |
| clock | A | B | 0.2413 | 0.047 | 0.628 | 0.347 | 0.065 | 45.5 | 0.04 | 0.346 | 0.097 |
| logistic | A | B | 0.1504 | 0.043 | 0.667 | 0.394 | 0.137 | 30.0 | 0.19 | 0.166 | 0.015 |
| qsofa | A | B | 0.0000 | 0.018 | 0.578 | 0.000 | 0.000 | n/a | 0.00 | n/a | 0.173 |
| sirs | A | B | 0.0000 | 0.026 | 0.652 | 0.000 | 0.000 | n/a | 0.00 | n/a | 0.274 |
| default (internal) | A | A | 0.3338 | 0.109 | 0.834 | 0.573 | 0.207 | 23.0 | 0.17 | 0.332 | 0.004 |
Reproduce every row with prodrome bench. The corpus is 42 MB under the Open Database
License and needs no credentials.
Three findings, and none of them flatter the model¶
A three-feature clock gets 98 percent of the utility score¶
The clock row alerts on nothing but how long the patient has been in the unit. No
physiology, no laboratory values, nothing learned. It scores 0.2413 against the trained
model's 0.2468. Alerting on every patient from ICU hour 48, with no model at all,
scores +0.227.
Septic stays here average 59 hours against 38 for the rest, so "this patient has been here a while" predicts "this patient will be recorded septic at some point", and the challenge utility metric rewards it. What the trained model actually buys shows up in the other columns: ranking 0.802 against 0.628, more than twice the in-window detection, and a calibration error thirty times smaller. A table reporting utility alone would have hidden all of that, which is why the clock is a shipped configuration rather than a footnote. See What the model reads.
It is substantially reading the workup, not the patient¶
prodrome ablation refits the same model on restricted feature sets:
| ablation | features | utility | share of full | auroc | in window |
|---|---|---|---|---|---|
| full | 205 | 0.2468 | 100% | 0.8016 | 0.153 |
| physiology_only | 125 | 0.1159 | 47% | 0.7697 | 0.117 |
| process_only | 75 | 0.2159 | 87% | 0.7263 | 0.109 |
| clock_only | 3 | 0.2428 | 98% | 0.6656 | 0.074 |
| workup_only | 72 | 0.2119 | 86% | 0.7539 | 0.115 |
Measured values alone reach 47 percent of the full utility. The staleness and ordering pattern alone, with no measured value of any kind, reach 86 percent. Nobody orders a lactate for a patient they are relaxed about, so which tests exist encodes clinician suspicion, and the label is a record of clinician suspicion.
A guarantee calibrated at one hospital may not survive the move¶
Every model states one sentence after calibration, and the report checks it on held-out patients:
| model | precision on A (calibrated here) | precision on B (never seen) | promise held on B |
|---|---|---|---|
| clock | 0.506 | 0.346 | yes |
| default | 0.332 | 0.305 | yes, barely |
| logistic | 0.311 | 0.166 | no |
The logistic model kept its promise at home and broke it elsewhere, by a wide margin, and nothing in its internal report predicted that. This is the whole argument for treating external validation as the primary result. See Calibrated abstention.
Neither qSOFA nor SIRS could certify 30 percent precision at any threshold inside the alert budget, so their policy alerts on nothing and their utility is exactly zero. That is the honest output, not a failure of the harness.
How it works¶
hourly rows -> causal features -> model -> risk per hour
| |
no feature sees the alert policy
future, proven per column (threshold + promise)
| |
| above -> alert, with the guarantee
| below -> silence
v
evaluate on the training site AND on a site never trained on
|
promotion gate -> promote or exit non-zero
- Causality: the truncation test that proves no feature reads a stay's future, verified by injecting a deliberate leak.
- Calibrated abstention: how the threshold is chosen, what it promises, and what happens when nothing can be certified.
- Evaluate and gate: wiring the gate into CI so a regression cannot be merged.
What this must never claim¶
- Not a medical device, not cleared by any regulator, not for clinical use.
- It predicts a recorded clinical suspicion, not a biological event.
- Two hospital systems in one country is not the world.
- The reported lead times are generous; the strict in-window rate sits beside them.
- No survival or treatment-effect claim. This is a warning system, not a causal one.