Skip to content

Triage: Wire TriageEngine to API endpoint + ESI v4 clinical hardening #27

@DeepRatAI

Description

@DeepRatAI

Summary

The TriageEngine in src/medex/medical/triage.py is a complete 567-line rule-based ESI triage engine that is never called by the API. The /api/v1/triage/assess endpoint in run_api.py ignores the engine entirely — it sends a free-text prompt to the LLM, attempts fragile regex parsing of the response, and returns hardcoded empty arrays for red_flags and vital_concerns. This makes triage non-functional.

Beyond wiring, a clinical audit against the ESI Handbook v4 (Gilboy et al.) reveals significant protocol deviations that must be corrected for the engine to produce clinically valid assessments.

Current Behavior

  • User fills in triage form (chief complaint, vitals, pain level) → UI POSTs to /api/v1/triage/assess
  • Endpoint converts structured vitals to a text string, sends to medex_app.query() (generic LLM call)
  • Attempts re.search(r"esi[:\s-]*(\d)", response_text) to extract ESI level — fails because the LLM responds in Spanish and rarely outputs "ESI" literally
  • Falls back to keyword matching ("resucitación", "crítico") — equally unreliable
  • red_flags: [] and vital_concerns: [] are always hardcoded empty
  • Response takes 5-30 seconds (LLM round-trip) for what should be an instantaneous deterministic assessment

Expected Behavior

  • Endpoint instantiates TriageEngine and calls engine.assess() directly
  • Returns structured TriageAssessment.to_dict() with real red flags, discriminators, disposition recommendations
  • Assessment is deterministic, instantaneous, and clinically aligned with ESI v4

Technical Tasks

A. Wire TriageEngine to API Endpoint

  • Replace LLM call in /api/v1/triage/assess (run_api.py L696-747) with direct TriageEngine.assess() call
  • Map UI vital field names to VitalSigns dataclass fields:
    • bp_systolicblood_pressure_systolic
    • bp_diastolicblood_pressure_diastolic
    • spo2oxygen_saturation
  • Return full TriageAssessment.to_dict() (includes red_flags, discriminators, disposition, recommended_action)
  • Fix MedicalService.is_emergency() signature mismatch — calls TriageEngine.is_emergency(chief_complaint=..., vital_signs=...) but actual signature is (self, text: str)

B. Clinical Hardening — ESI v4 Alignment

  1. Fix ESI algorithm flow — The real ESI algorithm verifies vital signs in "danger zone" after estimating ≥2 resources (ESI 3), and upgrades to ESI 2 if vitals are abnormal. The current engine does not perform this post-resource vital sign recheck. Ref: ESI Handbook v4, Decision Point D.

  2. Align vital sign thresholds to ESI danger zone:

    Vital Sign Current (Critical) ESI Danger Zone
    Heart Rate >150 / <40 >100 / <50
    Systolic BP <80 <90
    SpO₂ <88% <92%
    Resp Rate >35 / <8 >20 / <10
  3. GCS 9-13 → ESI 2 — Currently only GCS <9 triggers Level 1. GCS 9-13 (altered sensorium, responds to stimuli) should classify as ESI 2 per protocol.

  4. Pain ≥7 as independent ESI 2 discriminator — ESI: "high risk / confused / lethargic / disoriented OR severe pain/distress." Currently pain_scale is only checked inside _check_high_risk_vitals. Should be an independent Decision Point B check.

  5. Resource estimation: clinical heuristics, not patient keywords — The current engine searches the patient's text for words like "radiografía" or "laboratorio." The patient doesn't know what resources they need. Implement clinical heuristic mapping:

    • Headache + fever → labs + imaging = 2 resources = ESI 3
    • Laceration → suture = 1 resource = ESI 4
    • Sore throat (no red flags, normal vitals) → 0 resources = ESI 5
  6. Improve red flag matching robustness — Current if flag in text misses "me duele el pecho" (only matches "dolor de pecho"). Add synonym lists, partial matching, or at minimum expand the sets.

  7. Age-adjusted vital sign thresholds — HR 140 in a 2-year-old is normal; in a 70-year-old it's critical. At minimum add adult vs. pediatric threshold sets.

  8. Missing ESI 2 presentations — Add: acute psychiatric danger to self/others, sexual assault, fever + immunosuppression, acute abdomen in >65y, obstetric emergencies (eclampsia, ectopic, abruption).

Files Affected

File Change
run_api.py (L696-747) Replace LLM call with TriageEngine
src/medex/medical/triage.py Clinical hardening (thresholds, flow, resource heuristics)
src/medex/medical/models.py Add age-based threshold support to VitalSigns if needed
src/medex/medical/service.py (L228) Fix is_emergency() signature
tests/ New tests validating ESI classification against known clinical scenarios

Clinical References

  • Gilboy N, Tanabe T, Travers D, Rosenau AM. Emergency Severity Index (ESI): A Triage Tool for Emergency Department Care. Version 4. AHRQ Publication No. 12-0014. 2012.
  • ESI Implementation Handbook, Chapter 2: The ESI Algorithm.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions