Skip to content

evanqua/scenesim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

SceneSim

A turn-based, deterministic EMS training simulator with LLM-rendered roleplay.

License: AGPL v3 Live Demo DOI

Live demo: emt-sim.vercel.app

The demo is a hosted instance for evaluation purposes. Depending on how it's currently configured, it may run with a mock LLM provider (deterministic template text instead of real generated dialogue) or a live provider with rate limits. See Configuration if you want your own instance with full LLM roleplay.


What is SceneSim?

SceneSim puts a learner through a prehospital EMS call — dispatch, scene arrival, patient assessment, treatment, transport, and radio handoff — as a turn-based text simulation. The core design principle:

A deterministic engine owns all patient truth. Vitals, deterioration, contraindications, and scoring are computed by plain Python logic against scenario data. The LLM is used only to render that engine-computed state as natural-language dialogue (patient, caregiver, proctor). It never decides what happens medically, and it cannot reveal information the engine hasn't authorized.

This means:

  • Reproducible — the same scenario, seed, and actions always produce the same clinical outcome.
  • Gradeable — scoring is a deterministic function of what the learner actually did, not an LLM's opinion.
  • Safe to extend without an LLM — the entire engine is unit-testable, and a mock LLM mode renders template text with no external API calls at all.
  • Content-driven — new scenarios and county protocols are JSON files validated against schemas. No code changes are needed to add a case.

Important

SceneSim is an educational training tool. It is not a certified medical device, not a source of medical advice, and must not be used to guide real patient care.

Table of contents

Architecture

┌──────────────────────────────────────────────┐
│                  Frontend (Next.js)           │
│          Chat UI / Action Palette / Panels    │
├──────────────────────────────────────────────┤
│                  API Layer (FastAPI)          │
│  Routes: /scenarios, /sessions, /protocols    │
├──────────────────────────────────────────────┤
│              Services Layer                   │
│  scenario_loader │ protocol_resolver │ llm    │
│  evaluation │ transcript │ report │ intent    │
├──────────────────────────────────────────────┤
│            Engine Layer (Deterministic)       │
│  sim_engine │ state_machine │ action_resolver │
│  reveal_rules │ deterioration │ guardrails    │
│  scoring │ randomizer │ vitals_model          │
├──────────────────────────────────────────────┤
│          Models Layer (Pydantic)              │
│  scenario │ protocol │ session │ action       │
│  turn │ review                                │
├──────────────────────────────────────────────┤
│          DB / Repository Layer                │
│  SQLite (dev) → Postgres (prod)               │
├──────────────────────────────────────────────┤
│          Content Layer (JSON files)           │
│  scenarios │ protocols │ action_catalog       │
│  schemas │ prompts                            │
└──────────────────────────────────────────────┘

A learner turn flows: frontend → POST /sessions/{id}/turns → deterministic engine resolves the action, checks contraindications, reveals findings, applies deterioration → the engine's facts are handed to the LLM only to be phrased as dialogue → response returned and persisted. See docs/architecture.md for the full breakdown and docs/prompt_design.md for exactly what the LLM is and isn't allowed to see.

Repository layout

This is a two-app repo developed and run independently:

Path What it is
app/, components/, lib/, hooks/ (repo root) The Next.js 16 / React 19 frontend
apps/api/ The FastAPI + Pydantic v2 backend (the simulation engine lives here)
content/ Scenarios, county protocols, the action catalog, and their JSON Schemas
scripts/ Dev utility scripts (content validation, dev server, session seeding)
docs/ Architecture, API, authoring, and evaluation-rubric guides

apps/web is a vestigial placeholder package (not the real frontend) — ignore it; the working Next.js app is at the repo root. See CLAUDE.md for a deeper architectural tour aimed at contributors working in the codebase.

Getting started

Prerequisites

  • Node.js 20+ and npm (or pnpm)
  • Python 3.12+
  • Optionally, a Together.ai API key if you want real LLM-rendered dialogue instead of the deterministic mock provider

1. Clone and configure

git clone https://github.com/evanqua/scenesim.git
cd scenesim
cp .env.example .env

The defaults in .env.example run entirely locally: SQLite database, LLM_PROVIDER=mock (no API key required). Edit .env if you want to point at Together.ai or Postgres — see Configuration.

2. Run the backend

cd apps/api
pip install -e ".[dev]"
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Or, from the repo root, use the convenience wrapper: python scripts/dev_server.py.

The API is now at http://localhost:8000 (interactive docs at http://localhost:8000/docs).

3. Run the frontend

In a second terminal, from the repo root:

npm install
npm run dev

Open http://localhost:3000. The frontend calls the backend at NEXT_PUBLIC_API_URL (defaults to http://localhost:8000 if unset — see Configuration).

4. Try it without the UI (optional)

With the backend running, seed and run a scripted session directly against the API:

python scripts/seed_session.py chf_pulmonary_edema_01

This creates a session, performs a few actions (bsi, scene_safe, obtain_vitals), and ends it — useful as a smoke test or as a reference for calling the API yourself. See docs/api_reference.md for every endpoint.

Configuration

All configuration is environment variables, loaded from .env at the repo root (see .env.example for the full, documented list). The ones you're most likely to change:

Variable Purpose
LLM_PROVIDER mock (default, no external calls) or together (real LLM roleplay via Together.ai)
TOGETHER_API_KEY, TOGETHER_MODEL, TOGETHER_BASE_URL Together.ai credentials/model, used when LLM_PROVIDER=together
DATABASE_URL SQLite by default; swap for a Postgres DSN in production
ALLOWED_ORIGINS Comma-separated CORS allowlist for production deployments
RANDOM_SEED Fix the engine's randomizer seed for reproducible runs (e.g. in tests)
NEXT_PUBLIC_API_URL Frontend-only, not in .env.example (Next.js env conventions differ from the backend's). Set this to your backend's URL when frontend and backend aren't both on localhost:8000 — e.g. in Vercel project settings for a deployed frontend.

Testing

# Backend — pytest, from apps/api/
cd apps/api
pytest

# Backend — lint / format / typecheck
ruff check .
black .
mypy .

# Frontend — vitest, from the repo root
npm run test          # single run
npm run test:watch    # watch mode
npx vitest run path/to/file.test.ts   # a single test file

# Content — validate every scenario/protocol JSON file against its schema
python scripts/validate_content.py

Authoring content

Scenarios, protocols, and actions are plain JSON validated against schemas in content/schemas/ — you can add a new clinical case without touching any Python or TypeScript:

Always run python scripts/validate_content.py and cd apps/api && pytest after adding or editing content.

Deployment

The frontend is deployed on Vercel (the live demo). The backend (FastAPI) is a standard ASGI app and can be deployed anywhere that runs Python 3.12 (a container, a VM, Fly.io, Render, Railway, etc.) — this repo doesn't prescribe a specific backend host. Wherever you deploy it:

  • Set ALLOWED_ORIGINS to your frontend's origin(s) for CORS.
  • Set DATABASE_URL to a persistent database (Postgres recommended) — the default SQLite file is fine for local dev but not for most hosted setups.
  • Set LLM_PROVIDER=together plus TOGETHER_API_KEY if you want real LLM roleplay in production.
  • Point the frontend's NEXT_PUBLIC_API_URL at the deployed backend's URL.
  • No database migrations exist yet (see CLAUDE.md); the schema is currently created via SQLAlchemy create_all at startup.

Documentation index

Doc Covers
CLAUDE.md Codebase tour for contributors: turn-processing data flow, key modules, known quirks
docs/architecture.md System layers and the turn lifecycle
docs/api_reference.md Every REST endpoint, request/response shapes
docs/prompt_design.md What the LLM sees and the rules it must follow
docs/evaluation_rubric.md How deterministic scoring and debriefs work
docs/scenario_authoring_guide.md How to write a new scenario
docs/protocol_authoring_guide.md How to write a new county protocol pack

Contributing

Contributions are welcome — see CONTRIBUTING.md for dev setup, coding conventions, and the PR process, and CODE_OF_CONDUCT.md for community expectations. For security issues, see SECURITY.md.

Citing this project

If you use SceneSim in research, teaching, or as a basis for other software, please cite it — see CITATION.cff for structured citation metadata (GitHub renders a "Cite this repository" button from this file automatically).

DOI

Related work

SceneSim is an independent project. Readers interested in LLM-based Socratic tutoring for case-based clinical education may also be interested in related prior work by the same author:

Golchini, N., Passalacqua, E., Vaughn, L., & Abdulnour, R.-E. E. (2025). Socratic AI: An Adaptive Tutor for Clinical Case Based Learning. medRxiv. https://doi.org/10.1101/2025.06.22.25329661

License

SceneSim is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later). In short: you're free to use, modify, and redistribute this software, including running it as a network service — but if you do run a modified version as a service, you must make your modified source available to that service's users. See the LICENSE file for the full terms.

About

A turn-based, deterministic EMS training simulator with LLM-rendered roleplay.

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors