cds-agent / src /backend /test_clinical_cases.py
bshepp
Initial commit: CDS Agent - Clinical Decision Support System
f2c113d
"""
Comprehensive Clinical Test Suite for the CDS Agent.
Tests diverse clinical scenarios across specialties, acuity levels, demographics,
and edge cases. Each test case is a realistic patient presentation designed to
exercise different parts of the pipeline.
Usage:
python test_clinical_cases.py # Run all cases sequentially
python test_clinical_cases.py --case cardio_acs # Run a single case by ID
python test_clinical_cases.py --specialty cardio # Run all cases in a specialty
python test_clinical_cases.py --list # List available cases
"""
import httpx
import asyncio
import json
import time
import sys
import argparse
API = "http://localhost:8002"
# ─────────────────────────────────────────────────
# Test Case Definitions
# ─────────────────────────────────────────────────
TEST_CASES = [
# ── Cardiology ──
{
"id": "cardio_acs",
"specialty": "Cardiology",
"title": "Acute Coronary Syndrome — Classic STEMI",
"expected_keywords": ["ACS", "STEMI", "troponin", "cath lab", "PCI", "aspirin", "heparin"],
"patient_text": (
"62-year-old male presenting to ED with crushing substernal chest pain radiating to "
"left arm and jaw for 45 minutes. Diaphoretic and nauseated. History: HTN, "
"hyperlipidemia, 30 pack-year smoking history (quit 5 years ago), father had MI at 55. "
"Medications: atorvastatin 40mg daily, lisinopril 10mg daily, ASA 81mg daily. "
"Vitals: BP 155/98, HR 105, RR 22, SpO2 94% on RA, Temp 37.2°C. "
"ECG: ST elevation in leads II, III, aVF with reciprocal changes in I, aVL. "
"Initial troponin I: 2.8 ng/mL (normal <0.04)."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "cardio_afib",
"specialty": "Cardiology",
"title": "New-Onset Atrial Fibrillation with Rapid Ventricular Response",
"expected_keywords": ["atrial fibrillation", "rate control", "CHA2DS2-VASc", "anticoagulation", "DOAC"],
"patient_text": (
"73-year-old female with 2-day history of palpitations and lightheadedness. "
"She also reports mild exertional dyspnea. No syncope. PMH: HTN, type 2 DM, "
"osteoarthritis, hypothyroidism. Medications: metformin 500mg BID, levothyroxine "
"100mcg daily, amlodipine 5mg daily, ibuprofen 400mg PRN (takes daily for knee pain). "
"Vitals: BP 138/82, HR 142 (irregular), RR 18, SpO2 96%. "
"ECG: atrial fibrillation with rapid ventricular response, no ST changes. "
"Labs: TSH 0.8, K+ 4.2, Cr 1.1, BNP 350."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "cardio_hf",
"specialty": "Cardiology",
"title": "Acute Decompensated Heart Failure",
"expected_keywords": ["heart failure", "HFrEF", "diuretic", "volume overload", "BNP", "ejection fraction"],
"patient_text": (
"58-year-old male presents with progressive dyspnea over 1 week, now orthopneic "
"with 3-pillow requirement. Reports 10-lb weight gain. PMH: ischemic cardiomyopathy "
"(EF 25% 6 months ago), HTN, CKD stage 3b (baseline Cr 2.1). Medications: carvedilol "
"25mg BID, sacubitril-valsartan 97/103mg BID, spironolactone 25mg daily, furosemide "
"40mg BID, dapagliflozin 10mg daily. Vitals: BP 98/62, HR 98, RR 26, SpO2 89% on RA. "
"Exam: JVD to earlobes, bilateral crackles to mid-lung, S3, 3+ bilateral LE edema. "
"Labs: BNP 2,800, Cr 2.8 (from 2.1), K+ 5.4, Na+ 128, troponin I 0.08."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Emergency Medicine ──
{
"id": "em_stroke",
"specialty": "Emergency Medicine",
"title": "Acute Ischemic Stroke — tPA Window",
"expected_keywords": ["stroke", "tPA", "alteplase", "NIHSS", "CT", "thrombolysis"],
"patient_text": (
"68-year-old female found by husband at 7:15 AM with right-sided weakness and difficulty "
"speaking. Last known well at 11 PM the night before (went to bed normal). PMH: "
"atrial fibrillation (not on anticoagulation — patient declined), HTN, "
"hyperlipidemia. Medications: metoprolol 50mg BID, atorvastatin 20mg daily. "
"Vitals: BP 182/105, HR 88 (irregular), RR 16, SpO2 97%. "
"Exam: NIHSS 14 — right hemiparesis (arm 4/5, leg 3/5), expressive aphasia, "
"right facial droop, neglect. CT head: no hemorrhage. CTA: left M1 occlusion."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "em_sepsis",
"specialty": "Emergency Medicine",
"title": "Sepsis from Urinary Source",
"expected_keywords": ["sepsis", "antibiotics", "lactate", "fluid resuscitation", "vasopressor", "cultures"],
"patient_text": (
"82-year-old female nursing home resident brought to ED with confusion, fever, and "
"decreased oral intake for 2 days. PMH: Alzheimer dementia, type 2 DM, recurrent "
"UTIs, HTN, CKD stage 3a. Medications: donepezil 10mg daily, glipizide 5mg BID, "
"lisinopril 10mg daily, cranberry supplement. "
"Vitals: BP 85/50, HR 115, RR 26, SpO2 92% on RA, Temp 39.4°C (103°F). "
"Exam: confused (GCS 13), dry mucous membranes, suprapubic tenderness. "
"Labs: WBC 18.5K, lactate 4.2, Cr 2.4 (baseline 1.3), glucose 45, "
"UA positive for nitrites, leukocyte esterase 3+, bacteria many."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "em_anaphylaxis",
"specialty": "Emergency Medicine",
"title": "Anaphylaxis — Peanut Allergy",
"expected_keywords": ["anaphylaxis", "epinephrine", "airway", "allergic reaction", "antihistamine"],
"patient_text": (
"22-year-old male brought by EMS after eating a cookie at a party that contained "
"peanuts. Known severe peanut allergy. Symptoms started 15 minutes after ingestion: "
"diffuse urticaria, lip and tongue swelling, throat tightness, wheezing, "
"lightheadedness. EpiPen administered by friend at scene. PMH: peanut allergy, "
"asthma (mild intermittent). Medications: albuterol PRN, carries EpiPen. "
"Vitals: BP 88/52, HR 130, RR 28, SpO2 89%, Temp 37.0°C. "
"Exam: diffuse urticaria, angioedema of lips and tongue, stridor, bilateral wheezing, "
"use of accessory muscles."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "em_trauma",
"specialty": "Emergency Medicine",
"title": "Polytrauma — MVC with Hemorrhagic Shock",
"expected_keywords": ["trauma", "hemorrhage", "transfusion", "FAST", "ABC", "resuscitation"],
"patient_text": (
"35-year-old male restrained driver in high-speed MVC, significant front-end damage, "
"prolonged extrication (30 min). GCS 12 (E3V4M5) in the field. C-collar in place. "
"PMH: healthy, no medications, NKDA. Social: occasional alcohol (denies today). "
"Vitals: BP 78/45, HR 135, RR 30, SpO2 88%, Temp 35.8°C. "
"Primary survey: A — speaking in short sentences. B — decreased breath sounds left, "
"subcutaneous crepitus left chest wall. C — tachycardic, thready pulses, no external "
"hemorrhage, abdomen distended and tender. D — GCS 12. E — left leg deformity, "
"pelvis unstable on compression. "
"FAST: positive for free fluid in Morrison's pouch and pelvis. "
"CXR: left hemopneumothorax. Labs pending."
),
"include_drug_check": False,
"include_guidelines": True,
},
# ── Endocrinology ──
{
"id": "endo_dka",
"specialty": "Endocrinology",
"title": "Diabetic Ketoacidosis — New-Onset T1DM",
"expected_keywords": ["DKA", "insulin", "ketoacidosis", "potassium", "fluid resuscitation", "anion gap"],
"patient_text": (
"19-year-old male college student brought by roommate with nausea, vomiting, and "
"confusion. Reports 3-week history of polyuria, polydipsia, and 15-lb weight loss. "
"Denies alcohol or drug use. No significant PMH. Family history of type 1 diabetes "
"(mother). No medications. "
"Vitals: BP 100/60, HR 120, RR 32 (Kussmaul), SpO2 99%, Temp 37.1°C. "
"Exam: dry mucous membranes, fruity breath odor, diffusely tender abdomen, "
"lethargic but arousable (GCS 14). "
"Labs: glucose 485 mg/dL, pH 7.12, bicarb 8, K+ 5.8, Na+ 131 (corrected 138), "
"anion gap 28, BUN 32, Cr 1.6, serum ketones strongly positive, "
"urine ketones 3+, A1C 13.2%."
),
"include_drug_check": False,
"include_guidelines": True,
},
{
"id": "endo_thyroid_storm",
"specialty": "Endocrinology",
"title": "Thyroid Storm",
"expected_keywords": ["thyroid storm", "PTU", "propylthiouracil", "beta-blocker", "hyperthyroidism", "Graves"],
"patient_text": (
"42-year-old female presents with high fever, agitation, tachycardia, and vomiting. "
"She was recently diagnosed with Graves disease 2 months ago but ran out of "
"methimazole 3 weeks ago and did not refill. She had a tooth extraction yesterday. "
"PMH: Graves disease, anxiety. Medications: none (ran out of methimazole). "
"Vitals: BP 160/70, HR 168, RR 28, SpO2 95%, Temp 40.2°C (104.4°F). "
"Exam: agitated, tremulous, diaphoretic, exophthalmos, diffuse goiter with bruit, "
"hyperactive bowel sounds, fine tremor, warm and flushed skin. "
"Labs: TSH <0.01, free T4 7.8 (normal 0.8-1.7), free T3 22 (normal 2-4.4), "
"WBC 11.2, AST 95, ALT 82, total bilirubin 2.1. "
"Burch-Wartofsky Point Scale: 65 (highly suggestive of thyroid storm)."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "endo_adrenal_crisis",
"specialty": "Endocrinology",
"title": "Adrenal Crisis",
"expected_keywords": ["adrenal crisis", "hydrocortisone", "Addison", "hypotension", "cortisol"],
"patient_text": (
"38-year-old female with known primary adrenal insufficiency (Addison disease) presents "
"with 2-day history of GI illness (vomiting and diarrhea) and progressive weakness. "
"She continued her usual hydrocortisone dose but could not keep medication down due to "
"vomiting. Found by partner lying on bathroom floor, minimally responsive. "
"PMH: Addison disease, hypothyroidism. Medications: hydrocortisone 15mg AM/5mg PM, "
"fludrocortisone 0.1mg daily, levothyroxine 75mcg. "
"Vitals: BP 72/40 (despite 1L NS in the field), HR 128, RR 22, SpO2 96%, Temp 38.5°C. "
"Labs: Na+ 122, K+ 6.3, glucose 52, Cr 1.8, random cortisol 1.2 mcg/dL. "
"Exam: obtunded (GCS 10), no focal neuro deficits, hyperpigmented skin creases."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Pulmonology ──
{
"id": "pulm_pe",
"specialty": "Pulmonology",
"title": "Massive Pulmonary Embolism",
"expected_keywords": ["pulmonary embolism", "anticoagulation", "thrombolysis", "D-dimer", "CTPA", "tPA"],
"patient_text": (
"45-year-old female presents with sudden-onset severe dyspnea and near-syncope while "
"at work. She had right calf pain for 3 days. PMH: obesity (BMI 38), oral "
"contraceptive use, recent 8-hour flight 1 week ago. Medications: combined OCP. "
"Vitals: BP 82/50, HR 130, RR 32, SpO2 84% on high-flow O2, Temp 37.3°C. "
"Exam: distressed, diaphoretic, JVD, loud P2, right calf swelling and tenderness. "
"ECG: sinus tachycardia, S1Q3T3 pattern, right axis deviation. "
"Labs: troponin I 1.2 (elevated), BNP 890, D-dimer >5000. "
"Bedside echo: RV dilation with septal bowing, McConnell's sign. "
"CTPA: bilateral extensive PE with saddle embolus at main PA bifurcation."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "pulm_asthma_exacerbation",
"specialty": "Pulmonology",
"title": "Severe Asthma Exacerbation (Status Asthmaticus)",
"expected_keywords": ["asthma", "bronchodilator", "albuterol", "corticosteroid", "magnesium", "intubation"],
"patient_text": (
"28-year-old male with history of poorly controlled asthma presents to ED with severe "
"dyspnea that started 6 hours ago. He ran out of his controller inhaler (fluticasone) "
"2 weeks ago and has been using albuterol 6-8 times daily. Reports URI symptoms for "
"3 days. PMH: asthma (2 ICU admissions, 1 intubation last year), allergic rhinitis, "
"GERD. Medications: albuterol MDI PRN (using heavily), montelukast 10mg daily. "
"Vitals: BP 140/88, HR 125, RR 36, SpO2 87% on NRB, Temp 37.4°C. "
"Exam: tripod position, accessory muscle use, speaking in 1-2 word sentences, "
"bilateral inspiratory and expiratory wheezes with decreased air entry at bases, "
"pulsus paradoxus 22 mmHg. PEFR: unable to perform. "
"ABG: pH 7.28, pCO2 52, pO2 58. "
"Given 3 rounds of continuous albuterol/ipratropium, IV methylprednisolone — minimal improvement."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Gastroenterology ──
{
"id": "gi_ugib",
"specialty": "Gastroenterology",
"title": "Upper GI Bleeding — Peptic Ulcer",
"expected_keywords": ["GI bleeding", "hematemesis", "PPI", "endoscopy", "transfusion", "H. pylori"],
"patient_text": (
"65-year-old male presents with 3 episodes of hematemesis (bright red blood) over "
"the past 4 hours. Also reports melena for 2 days. Mild epigastric pain. "
"PMH: osteoarthritis, atrial fibrillation on warfarin. Medications: warfarin 5mg daily, "
"naproxen 500mg BID (started 2 weeks ago for arthritis flare), omeprazole 20mg daily "
"(ran out 1 month ago). Allergies: sulfa. "
"Vitals: BP 92/58, HR 118, RR 20, SpO2 97%, Temp 36.8°C. "
"Exam: pale, diaphoretic, epigastric tenderness without rebound, "
"rectal exam positive for melena. "
"Labs: Hb 6.8 (from 13.2 three months ago), INR 3.8, BUN 45, Cr 1.0, "
"platelets 195K. Glasgow-Blatchford Score: 14."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "gi_pancreatitis",
"specialty": "Gastroenterology",
"title": "Acute Gallstone Pancreatitis",
"expected_keywords": ["pancreatitis", "lipase", "gallstone", "fluid resuscitation", "cholecystectomy"],
"patient_text": (
"48-year-old female presents with severe epigastric pain radiating to the back for "
"12 hours, worsening after a fatty meal. Nausea and vomiting x6 episodes. "
"PMH: cholelithiasis (known but declined surgery), obesity (BMI 34), "
"hyperlipidemia. Medications: simvastatin 20mg daily. "
"Vitals: BP 105/70, HR 108, RR 22, SpO2 96%, Temp 38.1°C. "
"Exam: moderate distress, epigastric tenderness with guarding, decreased bowel sounds, "
"positive Murphy's sign. "
"Labs: lipase 2,850 U/L (normal <60), amylase 1,200, WBC 15.2K, "
"total bilirubin 3.2, direct bilirubin 2.5, ALT 285, AST 312, "
"Alk phos 380, triglycerides 220, Cr 0.9. "
"CT abdomen: diffuse pancreatic edema and peripancreatic fluid, "
"gallbladder with multiple stones, dilated CBD 10mm."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Neurology ──
{
"id": "neuro_seizure",
"specialty": "Neurology",
"title": "Status Epilepticus",
"expected_keywords": ["seizure", "status epilepticus", "benzodiazepine", "lorazepam", "antiepileptic", "levetiracetam"],
"patient_text": (
"34-year-old male with known epilepsy brought by EMS with continuous seizure activity "
"for >10 minutes that has not stopped. Bystander reports he was walking, stiffened, "
"and fell with tonic-clonic activity. EMS gave midazolam 10mg IM 5 minutes ago with "
"brief pause, but seizure activity resumed. PMH: focal epilepsy (left temporal lesion), "
"prior breakthrough seizures (non-compliant with meds). Medications: levetiracetam "
"750mg BID (admits he hasn't taken it in 1 week). "
"Vitals: BP 175/95, HR 130, RR 8 (postictal), SpO2 85%, Temp 38.2°C. "
"Exam: continuous left-gaze deviation with rhythmic bilateral limb jerking, "
"no verbal response, bite mark on tongue, urinary incontinence."
),
"include_drug_check": True,
"include_guidelines": True,
},
{
"id": "neuro_meningitis",
"specialty": "Neurology",
"title": "Bacterial Meningitis",
"expected_keywords": ["meningitis", "lumbar puncture", "ceftriaxone", "vancomycin", "dexamethasone", "CSF"],
"patient_text": (
"20-year-old male college student presents with 18-hour history of severe headache, "
"fever, and neck stiffness. Roommate reports he's been confused for the past 3 hours. "
"Recently had an upper respiratory infection. Lives in a dormitory. "
"PMH: healthy, no medications, vaccinations up to date (received meningococcal "
"conjugate vaccine but not serogroup B vaccine). "
"Vitals: BP 98/62, HR 112, RR 24, SpO2 96%, Temp 39.8°C (103.6°F). "
"Exam: appears toxic, positive Kernig and Brudzinski signs, photophobia, "
"no focal neurologic deficits, GCS 12 (E3V4M5), petechial rash on trunk and extremities. "
"Labs: WBC 22K with 92% PMNs, lactate 3.1, Cr 1.0, platelets 120K."
),
"include_drug_check": False,
"include_guidelines": True,
},
# ── Psychiatry ──
{
"id": "psych_suicide",
"specialty": "Psychiatry",
"title": "Acute Suicidal Ideation with Plan",
"expected_keywords": ["suicide", "safety", "psychiatr", "risk assessment", "lethal means", "hospitalization"],
"patient_text": (
"45-year-old male veteran brought to ED by wife after she found a note. He reports "
"active suicidal ideation with plan to use his firearm (has access at home). States "
"he's been increasingly hopeless since job loss 3 months ago, not sleeping, lost "
"20 lbs, withdrawn from family and friends. Drinks 6-8 beers daily (increased from "
"occasional). PMH: PTSD (combat-related), major depressive disorder, chronic back pain. "
"Medications: sertraline 100mg daily, trazodone 50mg QHS, ibuprofen 600mg TID. "
"Denies prior suicide attempts. Father completed suicide at age 50. "
"Vitals: stable. Exam: sad affect, poor eye contact, psychomotor retardation, "
"coherent but hopeless in content. PHQ-9 score: 24 (severe). "
"Columbia Suicide Severity Rating Scale: active ideation with specific plan and intent."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Pediatrics ──
{
"id": "peds_fever",
"specialty": "Pediatrics",
"title": "Febrile Neonate — 21-day-old",
"expected_keywords": ["neonate", "fever", "sepsis workup", "lumbar puncture", "ampicillin", "cefotaxime"],
"patient_text": (
"21-day-old male infant brought by concerned mother for fever. Temperature measured "
"at home: 38.4°C (101.1°F) rectal. Born full-term via uncomplicated vaginal delivery, "
"GBS negative. Breastfeeding well until today — decreased feeds for past 6 hours, "
"seems more sleepy than usual. No cough, rash, or vomiting. "
"One older sibling with runny nose. "
"Vitals: Temp 38.6°C (101.5°F) rectal, HR 180 (normal 100-160 for age), "
"RR 44, SpO2 98%. Birth weight 3.4 kg, current weight 3.7 kg. "
"Exam: slightly lethargic, soft fontanelle, no bulging, no rash, "
"cap refill 3 seconds, mottled skin, abdomen soft, no organomegaly. "
"Labs: WBC 22K, ANC 6500, CRP 35 mg/L, procalcitonin 0.8 ng/mL, "
"urinalysis negative."
),
"include_drug_check": False,
"include_guidelines": True,
},
{
"id": "peds_dehydration",
"specialty": "Pediatrics",
"title": "Severe Dehydration — Pediatric Gastroenteritis",
"expected_keywords": ["dehydration", "fluid", "oral rehydration", "IV fluid", "bolus", "electrolyte"],
"patient_text": (
"2-year-old female brought by parents for 3 days of watery diarrhea (8-10 episodes/day) "
"and vomiting (4-5 times/day). Decreased oral intake, last wet diaper >12 hours ago. "
"Daycare contacts have similar illness. PMH: healthy, vaccinations up to date including "
"rotavirus. No medications. Allergies: NKDA. "
"Vitals: HR 170 (tachycardic for age), BP 72/45, RR 30, SpO2 99%, "
"Temp 38.3°C, Weight 10.5 kg (12 kg at well child visit 1 month ago — 12.5% weight loss). "
"Exam: lethargic but arousable, sunken eyes, sunken anterior fontanelle, "
"dry oral mucosa with no tears, decreased skin turgor (tenting >2 seconds), "
"cap refill 4 seconds, cool extremities, abdomen diffusely tender, "
"hyperactive bowel sounds."
),
"include_drug_check": False,
"include_guidelines": True,
},
# ── Nephrology ──
{
"id": "renal_hyperkalemia",
"specialty": "Nephrology",
"title": "Severe Hyperkalemia with ECG Changes",
"expected_keywords": ["hyperkalemia", "calcium", "insulin", "dextrose", "dialysis", "potassium", "ECG"],
"patient_text": (
"72-year-old male with ESRD on hemodialysis (missed last 2 sessions due to "
"transportation issues) presents with generalized weakness and palpitations. "
"PMH: ESRD on MWF HD, type 2 DM, HTN, peripheral vascular disease. "
"Medications: sevelamer 800mg TID, calcitriol 0.25mcg daily, EPO injection "
"at dialysis, amlodipine 10mg, insulin glargine 20 units nightly, insulin lispro "
"sliding scale. "
"Vitals: BP 190/105, HR 52 (bradycardic), RR 22, SpO2 94%, Temp 36.5°C. "
"ECG: widened QRS (140ms), peaked T waves globally, loss of P waves, "
"junctional bradycardia. "
"Labs: K+ 7.8, BUN 98, Cr 11.2, bicarb 14, pH 7.22, glucose 220."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Infectious Disease ──
{
"id": "id_pneumonia",
"specialty": "Infectious Disease",
"title": "Community-Acquired Pneumonia — Moderate Severity",
"expected_keywords": ["pneumonia", "antibiotic", "CURB-65", "ceftriaxone", "azithromycin", "chest x-ray"],
"patient_text": (
"58-year-old male presents with 4-day history of productive cough (yellow-green sputum), "
"fever, chills, and pleuritic right-sided chest pain. Reports dyspnea with minimal "
"exertion. PMH: COPD (FEV1 55% predicted), type 2 DM, moderate alcohol use "
"(2-3 drinks daily). Medications: tiotropium 18mcg inhaled daily, albuterol PRN, "
"metformin 1000mg BID. Former 20 pack-year smoker, quit 5 years ago. "
"Vitals: BP 128/78, HR 102, RR 26, SpO2 90% on RA, Temp 39.2°C (102.6°F). "
"Exam: appears ill, right basilar crackles with bronchial breath sounds, "
"egophony right base, no wheezing currently. "
"CXR: right lower lobe consolidation with air bronchograms. "
"Labs: WBC 16.8K, BUN 28, Cr 1.0, procalcitonin 3.2, lactate 1.8. "
"CURB-65 score: 2 (confusion absent, urea elevated, RR ≥30, BP normal, age <65)."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Hematology ──
{
"id": "heme_dvt_pe",
"specialty": "Hematology",
"title": "DVT with Moderate PE — Cancer Patient",
"expected_keywords": ["DVT", "PE", "anticoagulation", "LMWH", "cancer", "thrombosis"],
"patient_text": (
"62-year-old female with recently diagnosed pancreatic adenocarcinoma (on chemotherapy, "
"cycle 2 of gemcitabine/nab-paclitaxel) presents with 5-day left leg swelling and "
"new-onset dyspnea on exertion since yesterday. No chest pain, hemoptysis, or syncope. "
"PMH: pancreatic cancer stage III (diagnosed 6 weeks ago), HTN, hypothyroidism. "
"Medications: gemcitabine/nab-paclitaxel q28d, ondansetron PRN, levothyroxine 88mcg, "
"lisinopril 10mg. "
"Vitals: BP 118/72, HR 98, RR 22, SpO2 93% on RA, Temp 37.0°C. "
"Exam: left leg circumference 4cm greater than right, tender calf, "
"Homan's sign positive. Lungs CTA bilaterally. "
"Labs: D-dimer 8,500, Hb 10.2, plt 85K, INR 1.0, Cr 0.8. "
"LE Doppler: occlusive thrombus left femoral and popliteal veins. "
"CTPA: segmental PE in right lower lobe pulmonary artery. "
"Troponin normal, BNP 120, RV normal on echo."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── OB/GYN ──
{
"id": "obgyn_preeclampsia",
"specialty": "OB/GYN",
"title": "Severe Preeclampsia at 33 Weeks",
"expected_keywords": ["preeclampsia", "magnesium sulfate", "blood pressure", "HELLP", "delivery"],
"patient_text": (
"28-year-old G2P1 at 33 weeks gestation presents with severe headache unresponsive "
"to acetaminophen, visual disturbances (scotomata), and right upper quadrant pain "
"for 6 hours. First pregnancy was uncomplicated. This pregnancy: mild chronic HTN, "
"started on labetalol 200mg BID at 16 weeks. Started low-dose aspirin at 12 weeks. "
"Medications: labetalol 200mg BID, aspirin 81mg daily, prenatal vitamins. "
"Vitals: BP 178/112 (confirmed on repeat after 15 min: 174/108), HR 92, "
"RR 18, SpO2 98%, Temp 37.0°C. "
"Exam: 3+ pitting edema bilateral LE, RUQ tenderness, brisk reflexes (3+) "
"with 2 beats of clonus bilaterally. Fetal heart tones 140s, reassuring. "
"Labs: PLT 82K, AST 220, ALT 195, LDH 650, Cr 1.3, uric acid 8.2, "
"protein/creatinine ratio 4.8, peripheral smear: schistocytes present."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Toxicology ──
{
"id": "tox_acetaminophen",
"specialty": "Toxicology",
"title": "Acetaminophen Overdose",
"expected_keywords": ["acetaminophen", "NAC", "N-acetylcysteine", "Rumack", "liver", "antidote"],
"patient_text": (
"24-year-old female brought to ED by friend 6 hours after intentionally ingesting "
"~30 tablets of Extra Strength Tylenol (500mg each = ~15g total). She now has nausea "
"and vomiting. Reports this was a suicide attempt after breakup. Currently expressing "
"regret and requesting help. PMH: depression, anxiety. "
"Medications: escitalopram 10mg daily. No OTC meds regularly. "
"Vitals: BP 108/68, HR 92, RR 16, SpO2 99%, Temp 37.0°C. "
"Exam: mild epigastric tenderness, otherwise unremarkable. "
"Labs: APAP level at 6 hours: 180 mcg/mL (treatment line at 4h is 150), "
"AST 42, ALT 38, INR 1.0, Cr 0.7, glucose 95. "
"Above treatment line on Rumack-Matthew nomogram."
),
"include_drug_check": True,
"include_guidelines": True,
},
# ── Multi-system / Complex ──
{
"id": "complex_polypharmacy",
"specialty": "Geriatrics",
"title": "Elderly Polypharmacy with Falls and AKI",
"expected_keywords": ["fall", "polypharmacy", "kidney", "AKI", "dehydration", "medication review"],
"patient_text": (
"84-year-old female brought from assisted living after being found on the floor this "
"morning. Staff reports she's had decreased oral intake for 3 days due to 'stomach bug.' "
"History of 2 falls in the past month. PMH: HTN, CHF (EF 45%), atrial fibrillation, "
"type 2 DM, CKD stage 3 (baseline Cr 1.4), osteoporosis, depression, insomnia, "
"osteoarthritis. "
"Medications: metoprolol succinate 100mg daily, apixaban 5mg BID, lisinopril 20mg, "
"furosemide 40mg daily, metformin 500mg BID, spironolactone 25mg, amlodipine 5mg, "
"mirtazapine 15mg QHS, zolpidem 5mg QHS, alendronate 70mg weekly, "
"calcium/vitamin D, aspirin 81mg, acetaminophen 650mg TID. "
"Vitals: BP 88/52 (lying), 62/40 (sitting — orthostatic), HR 48 (irregular), "
"RR 18, SpO2 95%, Temp 36.2°C. "
"Exam: dry mucous membranes, irregular irregularly rhythm, clear lungs, "
"mild confusion (baseline oriented x3, now x1), right hip tenderness. "
"Labs: Na+ 126, K+ 6.1, BUN 62, Cr 3.2 (from 1.4), glucose 52, TSH 4.5, "
"Hb 10.2, WBC 9.8, INR 1.0 (on apixaban). "
"Hip XR: right intertrochanteric hip fracture."
),
"include_drug_check": True,
"include_guidelines": True,
},
]
# ─────────────────────────────────────────────────
# Test Runner
# ─────────────────────────────────────────────────
async def run_case(client: httpx.AsyncClient, case: dict, verbose: bool = True) -> dict:
"""Submit a case and poll until done. Returns result dict with timing."""
case_id_label = case["id"]
title = case["title"]
if verbose:
print(f"\n{'='*70}")
print(f" [{case_id_label}] {title}")
print(f" Specialty: {case['specialty']}")
print(f"{'='*70}")
start = time.time()
# Submit
body = {
"patient_text": case["patient_text"],
"include_drug_check": case.get("include_drug_check", True),
"include_guidelines": case.get("include_guidelines", True),
}
r = await client.post(f"{API}/api/cases/submit", json=body)
if r.status_code != 200:
return {"case_id": case_id_label, "error": f"Submit failed: {r.status_code} {r.text}", "elapsed": 0}
data = r.json()
server_case_id = data["case_id"]
if verbose:
print(f" Submitted: {server_case_id}")
# Poll
result = None
steps = []
for i in range(90): # up to 7.5 minutes
await asyncio.sleep(5)
r = await client.get(f"{API}/api/cases/{server_case_id}")
result = r.json()
state = result.get("state", {})
steps = state.get("steps", [])
if verbose and i % 3 == 0:
statuses = [f"{s['step_id']}={s['status']}" for s in steps]
print(f" [{i*5}s] {', '.join(statuses)}")
all_done = all(s["status"] in ("completed", "failed", "skipped") for s in steps)
if all_done:
break
elapsed = round(time.time() - start, 1)
# Analyze results
step_summary = {}
for s in steps:
step_summary[s["step_id"]] = {
"status": s["status"],
"duration_ms": s.get("duration_ms", 0),
"error": s.get("error", ""),
}
report = result.get("report") if result else None
all_passed = all(s["status"] == "completed" for s in steps)
any_failed = any(s["status"] == "failed" for s in steps)
# Check expected keywords in report
keyword_hits = []
keyword_misses = []
if report:
report_text = json.dumps(report).lower()
for kw in case.get("expected_keywords", []):
if kw.lower() in report_text:
keyword_hits.append(kw)
else:
keyword_misses.append(kw)
result_data = {
"case_id": case_id_label,
"title": title,
"specialty": case["specialty"],
"all_passed": all_passed,
"any_failed": any_failed,
"elapsed_seconds": elapsed,
"steps": step_summary,
"keyword_hits": keyword_hits,
"keyword_misses": keyword_misses,
"keyword_coverage": (
f"{len(keyword_hits)}/{len(keyword_hits) + len(keyword_misses)}"
if (keyword_hits or keyword_misses) else "N/A"
),
"report": report,
}
if verbose:
print(f"\n Results ({elapsed}s total):")
for sid, info in step_summary.items():
status_icon = "✓" if info["status"] == "completed" else ("✗" if info["status"] == "failed" else "○")
print(f" {status_icon} {sid:12s} {info['status']:10s} ({info['duration_ms']}ms)")
if info["error"]:
print(f" ERROR: {info['error'][:120]}")
if report:
print(f"\n Keywords found: {', '.join(keyword_hits) if keyword_hits else 'none'}")
if keyword_misses:
print(f" Keywords missing: {', '.join(keyword_misses)}")
print(f" Keyword coverage: {result_data['keyword_coverage']}")
# Print condensed report
if verbose:
print(f"\n --- Report Summary ---")
print(f" Patient: {report.get('patient_summary', 'N/A')[:200]}")
dx = report.get("differential_diagnosis", [])
if dx:
print(f" Top diagnosis: {dx[0].get('diagnosis', 'N/A')} ({dx[0].get('likelihood', 'N/A')})")
warnings = report.get("drug_interaction_warnings", [])
if warnings:
print(f" Drug warnings: {len(warnings)}")
recs = report.get("guideline_recommendations", [])
if recs:
print(f" Guideline recs: {len(recs)}")
steps_rec = report.get("suggested_next_steps", [])
if steps_rec:
print(f" Next steps: {len(steps_rec)}")
else:
print(f" ⚠ No report generated")
return result_data
async def main():
parser = argparse.ArgumentParser(description="CDS Agent Clinical Test Suite")
parser.add_argument("--case", help="Run a single test case by ID")
parser.add_argument("--specialty", help="Run all cases for a specialty (partial match)")
parser.add_argument("--list", action="store_true", help="List available test cases")
parser.add_argument("--quiet", action="store_true", help="Minimal output")
parser.add_argument("--report", help="Save results to JSON file")
args = parser.parse_args()
if args.list:
print(f"\nAvailable test cases ({len(TEST_CASES)} total):\n")
by_specialty = {}
for tc in TEST_CASES:
by_specialty.setdefault(tc["specialty"], []).append(tc)
for spec, cases in sorted(by_specialty.items()):
print(f" {spec}:")
for c in cases:
print(f" {c['id']:30s} {c['title']}")
return
# Filter cases
cases_to_run = TEST_CASES
if args.case:
cases_to_run = [c for c in TEST_CASES if c["id"] == args.case]
if not cases_to_run:
print(f"Case '{args.case}' not found. Use --list to see available cases.")
return
elif args.specialty:
cases_to_run = [c for c in TEST_CASES if args.specialty.lower() in c["specialty"].lower()]
if not cases_to_run:
print(f"No cases found for specialty '{args.specialty}'. Use --list.")
return
verbose = not args.quiet
print(f"\n{'#'*70}")
print(f" CDS Agent — Clinical Test Suite")
print(f" Running {len(cases_to_run)} test case(s)")
print(f" API: {API}")
print(f"{'#'*70}")
# Check health
async with httpx.AsyncClient(timeout=120) as client:
try:
health = await client.get(f"{API}/api/health")
if health.status_code != 200:
print(f"\n ✗ Backend health check failed: {health.status_code}")
return
print(f" ✓ Backend healthy\n")
except Exception as e:
print(f"\n ✗ Cannot reach backend at {API}: {e}")
return
results = []
for case in cases_to_run:
try:
result = await run_case(client, case, verbose=verbose)
results.append(result)
except Exception as e:
print(f"\n ✗ Exception running case {case['id']}: {e}")
results.append({
"case_id": case["id"],
"title": case["title"],
"error": str(e),
"all_passed": False,
})
# Summary
print(f"\n\n{'#'*70}")
print(f" SUMMARY — {len(results)} cases")
print(f"{'#'*70}\n")
passed = sum(1 for r in results if r.get("all_passed"))
failed = sum(1 for r in results if r.get("any_failed"))
total_time = sum(r.get("elapsed_seconds", 0) for r in results)
for r in results:
icon = "✓" if r.get("all_passed") else "✗"
kw = r.get("keyword_coverage", "N/A")
elapsed = r.get("elapsed_seconds", 0)
print(f" {icon} [{r['case_id']:30s}] {elapsed:6.1f}s keywords:{kw:>5s} {r.get('title', '')}")
print(f"\n Passed: {passed}/{len(results)}")
print(f" Failed: {failed}/{len(results)}")
print(f" Total time: {total_time:.0f}s ({total_time/60:.1f} min)")
# Save report
if args.report:
with open(args.report, "w") as f:
json.dump(results, f, indent=2, default=str)
print(f"\n Results saved to {args.report}")
if __name__ == "__main__":
asyncio.run(main())