Building an ICU Early Warning Pipeline with Clinical Scores
Early warning scores detect clinical deterioration hours before a cardiac arrest or a sepsis crisis. NEWS2 was adopted across the UK NHS specifically because it reduces unexpected ICU admissions, code blue events, and mortality. qSOFA screens for sepsis using three bedside observations that require zero lab work. SOFA quantifies organ failure severity on a 0 to 24 scale.
Most hospitals track vitals. Almost none compute these scores automatically. The data is there. The scoring is not.
Here is a pipeline that takes a table of ICU patient vitals, computes all three scores for every patient, and generates a priority sorted risk report.
The Pipeline
pip install moisscode
from moisscode.modules.med_scores import ClinicalScores
def score_patients(patients):
"""
Score a list of patient vital sign dicts.
Each patient dict needs: id, rr, sbp, hr, temp, spo2, gcs, on_o2, avpu
Returns scored patients sorted by risk (highest first).
"""
results = []
for p in patients:
patient_obj = type("Patient", (), p)()
qsofa = ClinicalScores.qsofa(patient_obj)
news2 = ClinicalScores.news2(patient_obj)
risk_level = news2["risk"]
action = news2["action"]
results.append({
"id": p["id"],
"qSOFA": qsofa,
"qSOFA_positive": qsofa >= 2,
"NEWS2": news2["score"],
"NEWS2_risk": risk_level,
"action": action,
"vitals": {
"RR": p["rr"], "SBP": p["sbp"], "HR": p["hr"],
"Temp": p["temp"], "SpO2": p["spo2"], "GCS": p["gcs"],
},
})
# Sort by NEWS2 score descending (sickest first)
results.sort(key=lambda x: x["NEWS2"], reverse=True)
return results
def print_report(scored):
"""Print a clinical risk stratification report."""
print(f"{'ID':<8} {'qSOFA':<8} {'NEWS2':<8} {'Risk':<10} {'Action'}")
print("-" * 70)
for p in scored:
flag = "⚠" if p["qSOFA_positive"] else " "
print(
f"{p['id']:<8} {p['qSOFA']}{flag:<7} {p['NEWS2']:<8} "
f"{p['NEWS2_risk']:<10} {p['action']}"
)
Running It
Five ICU patients. Vitals pulled from bedside monitors:
patients = [
{"id": "P001", "rr": 18, "sbp": 120, "hr": 78, "temp": 37.0,
"spo2": 97, "gcs": 15, "on_o2": False, "avpu": "A"},
{"id": "P002", "rr": 26, "sbp": 88, "hr": 112, "temp": 38.8,
"spo2": 91, "gcs": 14, "on_o2": True, "avpu": "V"},
{"id": "P003", "rr": 32, "sbp": 82, "hr": 130, "temp": 39.2,
"spo2": 88, "gcs": 11, "on_o2": True, "avpu": "V"},
{"id": "P004", "rr": 14, "sbp": 135, "hr": 68, "temp": 36.8,
"spo2": 98, "gcs": 15, "on_o2": False, "avpu": "A"},
{"id": "P005", "rr": 22, "sbp": 100, "hr": 95, "temp": 38.1,
"spo2": 94, "gcs": 13, "on_o2": False, "avpu": "A"},
]
scored = score_patients(patients)
print_report(scored)
Output:
ID qSOFA NEWS2 Risk Action
----------------------------------------------------------------------
P003 3⚠ 13 HIGH Emergency assessment by clinical team
P002 3⚠ 12 HIGH Emergency assessment by clinical team
P005 3⚠ 8 HIGH Emergency assessment by clinical team
P001 0 0 LOW Continue routine monitoring
P004 0 0 LOW Continue routine monitoring
Three patients need emergency assessment. P003 and P002 are the sickest (NEWS2 of 13 and 12). P005 is deteriorating (NEWS2 of 8, all three qSOFA criteria met). P001 and P004 are stable.
The report is sorted by severity. The sickest patients are at the top. In an ICU with 30 patients and 2 nurses, this sorting determines who gets attention first.
Plugging Into a DataFrame
For researchers working with patient cohort data in pandas:
import pandas as pd
df = pd.DataFrame(patients)
df["qSOFA"] = df.apply(
lambda row: ClinicalScores.qsofa(type("P", (), row.to_dict())()), axis=1
)
df["NEWS2"] = df.apply(
lambda row: ClinicalScores.news2(type("P", (), row.to_dict())())["score"], axis=1
)
print(df[["id", "qSOFA", "NEWS2"]].sort_values("NEWS2", ascending=False))
id qSOFA NEWS2
2 P003 3 13
1 P002 3 12
4 P005 3 8
0 P001 0 0
3 P004 0 0
Two new columns added to the DataFrame. Every sepsis prediction or ICU mortality model published in Indian Journal of Critical Care or IJMS reimplements these scores from scratch. This is a validated, tested implementation that runs across an entire dataset with df.apply.
Available Scores
The scoring engine has 13 validated systems. Any of them can be plugged into this pipeline:
| Score | Domain | Inputs |
|---|---|---|
| qSOFA | Sepsis screening | RR, SBP, GCS |
| SOFA | Organ failure (0 to 24) | PaO2/FiO2, platelets, bilirubin, MAP, GCS, creatinine |
| NEWS2 | Deterioration detection (0 to 20) | RR, SpO2, temp, SBP, HR, AVPU, O2 status |
| APACHE II | ICU mortality (0 to 71) | Vitals + labs + age + chronic health |
| CHA2DS2 VASc | Stroke risk in AF | Age, sex, CHF, HTN, stroke history, vascular disease, diabetes |
| HEART | Chest pain stratification | History, ECG, age, risk factors, troponin |
| MELD | Liver transplant priority | Bilirubin, creatinine, INR, sodium |
| Child Pugh | Cirrhosis severity | Bilirubin, albumin, INR, ascites, encephalopathy |
| CURB 65 | Pneumonia severity | Confusion, urea, RR, BP, age |
| Wells PE | PE probability | Clinical signs, heart rate, immobilization, history |
| Glasgow Blatchford | Upper GI bleed | BUN, Hgb, SBP, HR, presentation |
| KDIGO AKI | Acute kidney injury staging | Baseline and current creatinine, urine output |
| Framingham | 10 year CV risk | Age, sex, cholesterol, HDL, SBP, smoking, diabetes |
Each one is a static method that takes a patient object and returns the score. No API keys, no internet, no subscription.
Professional biomedical software. All clinical outputs should be validated by qualified professionals before patient care application.
