File size: 39,547 Bytes
f2c113d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 | """
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())
|