From Lab CSV to Clinical Report: Automating Lab Value Interpretation at Scale
Hospital laboratories produce thousands of results daily. Each result needs to be interpreted against reference ranges, checked for critical values, and flagged for follow up. This is manual triage done by lab technicians and duty doctors scrolling through lists.
A creatinine of 5.9 mg/dL and a potassium of 7.1 mEq/L sitting in a queue for two hours because nobody noticed the critical flag is a preventable death. Hyperkalemia above 6.5 causes cardiac arrhythmias. That's the kind of result that needs to trigger an immediate phone call, and it gets missed when buried under 500 normal results.
Here is a script that reads a CSV of lab results, interprets every value against clinical reference ranges, flags critical results, computes derived values like eGFR, and generates a per patient summary sorted by severity.
The Tool
pip install moisscode
import csv
from collections import defaultdict
from moisscode.modules.med_lab import LabEngine
def process_lab_csv(filepath, age_default=65, sex_default="M"):
"""
Read a lab CSV and generate interpreted results with critical flagging.
CSV format: patient_id, test_name, value
Returns per-patient summaries sorted by severity.
"""
lab = LabEngine()
patients = defaultdict(lambda: {"results": [], "critical_count": 0})
with open(filepath, "r") as f:
reader = csv.DictReader(f)
for row in reader:
pid = row["patient_id"]
test = row["test_name"]
value = float(row["value"])
result = lab.interpret(test, value)
is_critical = result.get("is_critical", False)
patients[pid]["results"].append({
"test": result.get("full_name", test),
"value": value,
"unit": result.get("unit", ""),
"status": result.get("status", "UNKNOWN"),
"reference": result.get("reference_range", ""),
"critical": is_critical,
})
if is_critical:
patients[pid]["critical_count"] += 1
# Compute eGFR for patients with creatinine
for pid, data in patients.items():
cr_results = [r for r in data["results"] if r["test"] == "Creatinine"]
if cr_results:
cr_value = cr_results[0]["value"]
gfr = lab.gfr(creatinine=cr_value, age=age_default, sex=sex_default)
data["eGFR"] = gfr["eGFR"]
data["ckd_stage"] = gfr["stage"]
# Sort patients by critical count (most critical first)
sorted_patients = sorted(
patients.items(),
key=lambda x: x[1]["critical_count"],
reverse=True,
)
return sorted_patients
def print_report(sorted_patients):
"""Print a priority-sorted clinical lab report."""
for pid, data in sorted_patients:
criticals = data["critical_count"]
header = f"Patient {pid}"
if criticals > 0:
header += f" *** {criticals} CRITICAL VALUE{'S' if criticals > 1 else ''} ***"
print(f"\n{'=' * 60}")
print(header)
print(f"{'=' * 60}")
for r in data["results"]:
flag = " *** CRITICAL ***" if r["critical"] else ""
print(
f" {r['test']}: {r['value']} {r['unit']} "
f"[{r['status']}] (ref: {r['reference']}){flag}"
)
if "eGFR" in data:
print(f" eGFR: {data['eGFR']} mL/min -> {data['ckd_stage']}")
Running It
Create a CSV file. This is the format that any LIMS or hospital database can export:
patient_id,test_name,value
P001,Cr,1.0
P001,K,4.2
P001,Na,140
P001,Glucose,95
P001,BUN,15
P002,Cr,3.8
P002,K,6.2
P002,Na,128
P002,Glucose,310
P002,BUN,68
P003,Cr,5.9
P003,K,7.1
P003,Na,119
P003,Glucose,45
P003,BUN,92
results = process_lab_csv("lab_results.csv", age_default=65, sex_default="M")
print_report(results)
Output:
============================================================
Patient P003 *** 2 CRITICAL VALUES ***
============================================================
Creatinine: 5.9 mg/dL [HIGH] (ref: 0.6-1.2 mg/dL)
Potassium: 7.1 mEq/L [PANIC_HIGH] (ref: 3.5-5.0 mEq/L) *** CRITICAL ***
Sodium: 119 mEq/L [CRITICAL_LOW] (ref: 136.0-145.0 mEq/L) *** CRITICAL ***
Glucose: 45 mg/dL [LOW] (ref: 70.0-100.0 mg/dL)
Blood Urea Nitrogen: 92 mg/dL [HIGH] (ref: 7.0-20.0 mg/dL)
eGFR: 9.9 mL/min -> G5 - Kidney failure
============================================================
Patient P002
============================================================
Creatinine: 3.8 mg/dL [HIGH] (ref: 0.6-1.2 mg/dL)
Potassium: 6.2 mEq/L [HIGH] (ref: 3.5-5.0 mEq/L)
Glucose: 310 mg/dL [HIGH] (ref: 70.0-100.0 mg/dL)
Sodium: 128 mEq/L [LOW] (ref: 136.0-145.0 mEq/L)
Blood Urea Nitrogen: 68 mg/dL [HIGH] (ref: 7.0-20.0 mg/dL)
eGFR: 16.8 mL/min -> G4 - Severely decreased
============================================================
Patient P001
============================================================
Creatinine: 1.0 mg/dL [NORMAL] (ref: 0.6-1.2 mg/dL)
Potassium: 4.2 mEq/L [NORMAL] (ref: 3.5-5.0 mEq/L)
Sodium: 140 mEq/L [NORMAL] (ref: 136.0-145.0 mEq/L)
Glucose: 95 mg/dL [NORMAL] (ref: 70.0-100.0 mg/dL)
Blood Urea Nitrogen: 15 mg/dL [NORMAL] (ref: 7.0-20.0 mg/dL)
eGFR: 83.5 mL/min -> G2 - Mildly decreased
Patient P003 is at the top. Two critical values: potassium of 7.1 (PANIC_HIGH, cardiac arrest risk) and sodium of 119 (CRITICAL_LOW, seizure risk). eGFR of 9.9 is G5 kidney failure. This patient needs a phone call right now, and this report puts them at the very top.
Patient P002 has no critical flags, but every single lab is abnormal. eGFR of 16.8 is Stage G4. This patient is next.
Patient P001 is completely normal. Everything within range. eGFR 83.5 is Stage G2, which is mildly decreased but expected for a 65 year old.
What The Engine Covers
The lab module interprets 82 tests across 17 panels:
| Panel | Tests Included |
|---|---|
| BMP | Na, K, Cl, CO2, BUN, Cr, Glucose, Ca |
| CMP | BMP + Albumin, Total Protein, ALP, ALT, AST, Bilirubin |
| CBC | WBC, RBC, Hgb, Hct, MCV, MCH, MCHC, Platelets |
| Lipid | Total Cholesterol, LDL, HDL, Triglycerides, VLDL |
| Hepatic | ALT, AST, ALP, Bilirubin, Albumin, Total Protein, GGT |
| Coagulation | PT, INR, aPTT, Fibrinogen, D Dimer |
| Thyroid | TSH, Free T3, Free T4 |
| Cardiac | Troponin I, BNP, CK MB |
| Iron | Iron, Ferritin, TIBC, Transferrin Saturation |
Each test has normal, high, low, critical high, critical low, and panic ranges. Critical values trigger the is_critical flag, which is what the report uses to sort patients.
Additional computed values:
- eGFR using CKD EPI 2021 (race free equation)
- ABG interpretation for acid base disorders (respiratory vs metabolic acidosis/alkalosis with compensation)
- Panel level interpretation that flags patterns across related tests
Adapting This
The CSV reader above is a starting point. In practice, lab data comes from HL7 feeds, LIMS database exports, or EMR API responses. The lab.interpret() function takes a test name and a numeric value. It doesn't care where the data comes from.
For real time critical value alerting, wrap interpret() in a listener that checks is_critical and sends notifications. For batch research, pipe a pandas DataFrame through interpret() and add status columns for downstream analysis.
Professional biomedical software. All clinical outputs should be validated by qualified professionals before patient care application.
