Skip to main content

Building an ICU Early Warning Pipeline with Clinical Scores

· 5 min read
Nikhil Kunche
Founder, Aethryva Deeptech

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:

ScoreDomainInputs
qSOFASepsis screeningRR, SBP, GCS
SOFAOrgan failure (0 to 24)PaO2/FiO2, platelets, bilirubin, MAP, GCS, creatinine
NEWS2Deterioration detection (0 to 20)RR, SpO2, temp, SBP, HR, AVPU, O2 status
APACHE IIICU mortality (0 to 71)Vitals + labs + age + chronic health
CHA2DS2 VAScStroke risk in AFAge, sex, CHF, HTN, stroke history, vascular disease, diabetes
HEARTChest pain stratificationHistory, ECG, age, risk factors, troponin
MELDLiver transplant priorityBilirubin, creatinine, INR, sodium
Child PughCirrhosis severityBilirubin, albumin, INR, ascites, encephalopathy
CURB 65Pneumonia severityConfusion, urea, RR, BP, age
Wells PEPE probabilityClinical signs, heart rate, immobilization, history
Glasgow BlatchfordUpper GI bleedBUN, Hgb, SBP, HR, presentation
KDIGO AKIAcute kidney injury stagingBaseline and current creatinine, urine output
Framingham10 year CV riskAge, 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.

Scores Module Documentation | GitHub