An LLM-driven patient simulator for emergency-department triage training.
⚠️ This is an educational training tool, not a medical device. It must not be used for real clinical decision-making. All patient data is de-identified and open-access or synthetic.
Medical students, nursing staff, and other ED trainees under-triage patients at more than twice the acceptable rate. Existing tools rely on scripted, non-interactive scenarios. This simulator gives realistic, conversational practice: the trainee takes a history from an LLM-driven patient, measures vitals, assigns an Emergency Severity Index (ESI) level 1–5, and orders critical interventions — then gets immediate, specific feedback scored against expert labels and real outcomes.
A single encounter is a strict workflow:
CASE_LOAD → HISTORY (chat with LLM patient) → VITALS → ESI (1–5) → INTERVENTIONS → FEEDBACK
The backend enforces the workflow and hides expert labels until feedback. Scoring is deterministic and rule-based (the LLM only writes the narrative), and it penalizes under-triage more heavily than over-triage — the specific safety gap this tool targets.
Contract-first, language-split: Python owns data + clinical logic, TypeScript
owns the UI, and they meet only at the JSON-Schema contract in shared/schemas/.
shared/schemas/ The cross-language contract (TriageCase, Encounter, ScoreReport)
backend/ FastAPI · Pydantic · SQLite — loaders, LLM, state machine, scoring
frontend/ React · Vite · TypeScript · Zustand — the trainee UI
docs/ Design spec + data/ethics docs
See docs/superpowers/specs/2026-06-09-ed-triage-trainer-design.md for the full
design, and AGENTS.md / CLAUDE.md for contributor rules.
# Backend
cd backend && python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
uvicorn app.main:app --reload # http://localhost:8000
# Frontend (separate terminal)
cd frontend && npm install && npm run dev # http://localhost:5173Runs with no API key and no network out of the box: it uses the bundled
synthetic generator and a scripted local patient stub. Set ANTHROPIC_API_KEY
(and LLM_PROVIDER=anthropic) for real LLM-driven patients.
docker compose up --build # then open http://localhost:8080The frontend (nginx) serves the built SPA and reverse-proxies /api to the backend
container. Configure via environment (or a .env file compose reads):
LLM_PROVIDER(localdefault;anthropic/openaifor cloud) + the matching*_API_KEYCORS_ALLOW_ORIGINS— set to your frontend's public URL when deploying beyond localhost (defaults tohttp://localhost:8080under compose)ENABLED_SOURCES— e.g.syntheticormimic_demo,synthetic
| Source | Access | Status |
|---|---|---|
| Synthetic generator + seed cases | None | Ships now (the only corpus committed) |
| MIMIC-IV-ED Demo | Open-access (~100 ED stays) | Fetch locally: python backend/scripts/fetch_mimic_demo.py (not committed) |
| MIMIC-IV-ED Full | PhysioNet DUA + CITI training | Loader path; data git-ignored |
| MIETIC | PhysioNet credentialing | Loader path; data git-ignored |
Out of the box the app runs on the synthetic corpus. The open-access MIMIC-IV-ED
Demo is an opt-in local fetch (no credentialing); the full dataset and MIETIC are
documented loader paths for credentialed users. All sources normalize to one
TriageCase; no data is committed. See docs/DATA_CARD.md for
provenance, the ESI-label-validity caveat, and which scoring dimensions each source
supports, and backend/data/sources/*/README.md for per-source setup.
See CONTRIBUTING.md for setup and the quality bars, and
AGENTS.md for the engineering rules. Security reports:
SECURITY.md.
Code is licensed under the MIT License (see LICENSE). Clinical data
retains its original PhysioNet license and must be cited per PhysioNet terms — see
docs/ATTRIBUTION.md and the per-source README files. To cite
this software, use CITATION.cff (GitHub renders a "Cite this
repository" button).