23 KiB
VigilCare Clinical Simulator -- User Guide
Welcome! The VigilCare Simulator lets you replay realistic hospital patient scenarios against the VigilCare Clinical API. You can watch a patient's vitals change over time, see alerts fire (NEWS2, SIRS/qSOFA, sepsis bundles), and observe how the system detects clinical deterioration -- all without real patients.
Think of it as a flight simulator, but for clinical decision support.
Table of Contents
- Prerequisites
- Quick Start
- Available Commands
- Understanding Scenarios
- Available Scenarios
- Controlling Replay Speed
- Reading the Output
- Creating Your Own Scenarios
- Troubleshooting
- Ward Outage Scenario (Climate Resilience)
- MIMIC-IV Real Patient Data
1. Prerequisites
You need two things installed:
- .NET 8 SDK -- Download from dotnet.microsoft.com
- The VigilCare API running locally -- The simulator sends data to the API, so it must be up first
Starting the API
From the project root directory, run:
docker compose up -d
dotnet run --project VigilCare.Api
The API starts on http://localhost:5270 by default.
See docker-compose-usage-and-troubleshooting.md if you have trouble with Docker.
2. Quick Start
Open a terminal in the project root and run:
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/uti-sepsis-elderly-01.json \
--speed 60 --poll
This replays a 4-hour UTI-to-sepsis progression in about 4 seconds, showing alerts as they fire.
That's it! Read on for more detail.
3. Available Commands
The simulator has six commands. All are run with dotnet run --project VigilCare.Simulator -- <command>.
replay -- Run a scenario against the API
dotnet run --project VigilCare.Simulator -- replay <scenario-file> [options]
Options:
| Option | Default | Description |
|---|---|---|
--speed <number> |
60 | How fast to run (see Speed) |
--base-url <url> |
http://localhost:5270 |
API address (change if your API runs elsewhere) |
--poll |
off | Show alerts and scores after each set of vitals |
--poll-interval <seconds> |
5 | How often to check for alerts when polling |
--username <name> |
physician.demo |
API login username |
--password <secret> |
(demo default) | API login password |
--gateway |
off | Target the ward gateway API at http://localhost:5081 |
--encounter-id <guid> |
— | Use an existing encounter (required with --gateway; also used with --skip-setup) |
--skip-setup |
off | Skip patient/encounter registration — requires --encounter-id |
--gateway-token <jwt> |
$GATEWAY_JWT |
Bearer token for gateway replay (required with --gateway; use ./scripts/mint-gateway-jwt.sh) |
Example -- run the stable baseline scenario in real-time with polling:
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/stable-baseline-01.json \
--speed 1 --poll
replay-all -- Run every scenario in a folder
dotnet run --project VigilCare.Simulator -- replay-all <directory> [options]
Runs all .json scenario files in the given directory, one after another. Accepts --speed and --base-url.
Example:
dotnet run --project VigilCare.Simulator -- replay-all \
VigilCare.Simulator/Scenarios/List --speed 60
validate -- Check a scenario file for errors
dotnet run --project VigilCare.Simulator -- validate <scenario-file>
Checks that the JSON is well-formed and all fields are valid. Does not contact the API.
Example:
dotnet run --project VigilCare.Simulator -- validate \
VigilCare.Simulator/Scenarios/List/stable-baseline-01.json
dry-run -- Preview the timeline without touching the API
dotnet run --project VigilCare.Simulator -- dry-run <scenario-file>
Prints exactly what would happen (every vital sign, medication, and order) without sending anything.
Example:
dotnet run --project VigilCare.Simulator -- dry-run \
VigilCare.Simulator/Scenarios/List/cardiac-arrest-post-mi-01.json
mimic-list -- Browse available MIMIC-IV patients and ICU stays
dotnet run --project VigilCare.Simulator -- mimic-list <mimic-data-dir> [options]
Displays a table of all ICU stays in the MIMIC-IV dataset with patient demographics, care unit, length of stay, and outcome. Does not contact the API.
Options:
| Option | Description |
|---|---|
--subject-id <int> |
Filter to a specific patient |
--stay-id <int> |
Filter to a specific ICU stay |
Example:
dotnet run --project VigilCare.Simulator -- mimic-list docs/MIMIC-IV/
mimic-generate -- Generate a scenario from real MIMIC-IV data
dotnet run --project VigilCare.Simulator -- mimic-generate <mimic-data-dir> --stay-id <int> [options]
Reads MIMIC-IV CSV files and generates a VigilCare scenario JSON from a specific ICU stay. The generated file is fully compatible with replay, validate, and dry-run. Does not contact the API.
Options:
| Option | Default | Description |
|---|---|---|
--stay-id <int> |
(required) | ICU stay ID to generate scenario for |
--max-hours <int> |
full stay | Limit scenario duration (real ICU stays can be days/weeks) |
--no-medications |
off | Exclude medication events |
--no-labs |
off | Exclude lab observations |
--output <path> |
Scenarios/List/mimic-s{stayId}.json |
Custom output file path |
--validate |
off | Run scenario validation after generation |
Example -- generate a 24-hour CVICU scenario:
dotnet run --project VigilCare.Simulator -- mimic-generate docs/MIMIC-IV/ \
--stay-id 32604416 --max-hours 24 --validate
Then replay it:
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/mimic-s32604416.json --speed 0 --poll
See MIMIC-IV Real Patient Data for the full guide.
4. Understanding Scenarios
Each scenario is a JSON file that tells a clinical story. It contains:
- Patient -- Name, date of birth, gender
- Encounter -- Department, encounter type, attending physician, room/bed
- Events -- A timeline of observations (vitals, labs), medications, and orders
Here is what a simplified scenario looks like:
{
"scenario": {
"id": "stable-baseline-01",
"name": "Stable Baseline -- Routine Inpatient Monitoring",
"description": "52-year-old female admitted for elective cholecystectomy...",
"durationMinutes": 480,
"tags": ["stable", "baseline", "control", "surgery"]
},
"patient": {
"firstName": "Linda",
"lastName": "Weston",
"dateOfBirth": "1974-02-18",
"gender": "Female"
},
"encounter": {
"department": "Surgery",
"encounterType": "Inpatient",
"attendingPhysician": "Dr. James Nakamura",
"roomBed": "SURG-204B",
"admissionReason": "Elective laparoscopic cholecystectomy"
},
"events": [
{
"offsetMinutes": 0,
"type": "observation",
"data": {
"code": "HEART_RATE",
"value": 72,
"unit": "bpm"
},
"note": "Post-op arrival, patient alert and comfortable"
},
{
"offsetMinutes": 60,
"type": "medication",
"data": {
"drugName": "acetaminophen",
"dose": 1000,
"doseUnit": "mg",
"route": "PO",
"administeredBy": "RN Davis"
}
}
]
}
The key concept is offsetMinutes -- each event happens at a certain number of minutes after the scenario starts. The simulator waits the appropriate amount of time (adjusted by your speed setting) before sending each event.
Event Types
| Type | What It Represents | Example |
|---|---|---|
observation |
A vital sign or lab result | Heart rate 110 bpm, Temperature 39.2 C |
medication |
A drug being administered | Ceftriaxone 1g IV |
order |
A clinical order being placed | "Blood cultures", "Chest X-ray" |
order_result |
Result of a prior order | "Positive for E. coli" |
alert_ack |
Acknowledgement of an open alert | RN acknowledges critical potassium alert |
alert_ack events require alertType and clinicianId in data; optional note. On gateway replay (--gateway), the simulator sends clinicianId in the acknowledge request so the ward records the bedside nurse label (e.g. RN-Wu) and syncs it to central. On central replay, attribution comes from the logged-in API user.
Vital Sign Codes
These are the observation codes used in scenarios:
| Code | What It Measures | Unit | Normal Range |
|---|---|---|---|
HEART_RATE |
Heart rate | bpm | 51--90 |
RESP_RATE |
Respiratory rate | /min | 12--20 |
SYSTOLIC_BP |
Systolic blood pressure | mmHg | 111--219 |
DIASTOLIC_BP |
Diastolic blood pressure | mmHg | 60--90 |
TEMP_C |
Temperature | C | 36.1--38.0 |
SPO2 |
Oxygen saturation | % | 96--100 |
AVPU |
Consciousness level | score | 0 = Alert |
SUPPLEMENTAL_O2 |
On supplemental oxygen? | flag | 0 = No |
WBC_K_UL |
White blood cell count | x10^3/uL | 4.5--11.0 |
LACTATE_MMOL_L |
Serum lactate | mmol/L | 0.5--1.5 |
POTASSIUM_MEQ_L |
Potassium | mEq/L | 3.5--5.0 |
GLUCOSE_MG_DL |
Blood glucose | mg/dL | 70--140 |
5. Available Scenarios
The simulator ships with 8 scenarios covering different clinical situations:
| Scenario | Clinical Story | Duration |
|---|---|---|
| stable-baseline-01 | Post-op cholecystectomy, all vitals normal. Control case -- no alerts should fire. | 8 hours |
| uti-sepsis-elderly-01 | 78-year-old with UTI progressing to sepsis. SIRS criteria met, sepsis bundle triggered. | 4 hours |
| cardiac-arrest-post-mi-01 | Post-MI patient deteriorating into cardiogenic shock. Rapid HR/BP changes. | Varies |
| post-op-hemorrhage-01 | Surgical patient with internal bleeding. Rising HR, falling BP and SpO2. | Varies |
| respiratory-failure-asthma-01 | Asthma exacerbation progressing to respiratory failure. Falling SpO2, rising RR. | Varies |
| dka-electrolyte-01 | Diabetic ketoacidosis with potassium and glucose derangement. | Varies |
| hypothermia-elderly-01 | Elderly patient with severe hypothermia. Slow HR, dropping temperature. | Varies |
| medication-false-alarm-01 | Beta-blocker causing bradycardia. Tests whether the system correctly handles medication-induced vital changes. | 3 hours |
| ward-outage-reconnect-01 | ICU patient with critical hyperkalemia during simulated central outage. Validates gateway-local alerting and alert acknowledgement sync. | 90 minutes |
All scenario files are in: VigilCare.Simulator/Scenarios/List/
6. Controlling Replay Speed
The --speed option controls how fast simulated time passes:
| Speed | Meaning | A 4-hour scenario takes... |
|---|---|---|
0 |
Instant -- no waiting, all events fire immediately | < 1 second |
1 |
Real-time -- 1 simulated minute = 1 real minute | 4 hours |
10 |
10x -- 1 simulated minute = 6 real seconds | 24 minutes |
60 |
60x (default) -- 1 simulated minute = 1 real second | 4 minutes |
120 |
120x -- 1 simulated minute = 0.5 real seconds | 2 minutes |
Recommendations:
- For demos / presentations: Use
--speed 10with--pollso people can follow along - For quick testing: Use
--speed 60or--speed 0 - For the most realistic experience: Use
--speed 1(real-time, a 4-hour scenario takes 4 hours)
7. Reading the Output
When you run a replay, the simulator prints a colored timeline. Here's what to look for:
────────────────── Stable Baseline -- Routine Inpatient Monitoring ──────────────────
52-year-old female admitted for elective cholecystectomy...
Patient registered: 3fa85f64-... (MRN-12345)
Encounter opened: 7c9e6679-... (active)
[00:00] HEART_RATE 72 bpm
[00:00] RESP_RATE 14 /min
[00:00] SYSTOLIC_BP 124 mmHg
[00:00] SPO2 98 %
... waiting 60m simulated (1.0s real) ...
[01:00] HEART_RATE 74 bpm
[01:00] medication: acetaminophen 1000 mg PO
With --poll enabled, you also see clinical scoring:
[01:30] NEWS2 = 5 (Medium)
[01:30] ALERT SEPSIS_WARNING (Critical)
[01:30] SEPSIS BUNDLE InProgress (2/4 completed)
At the end, a summary table appears:
Metric Value
-----------------------------------
Scenario uti-sepsis-elderly-01
Observations sent 42
Medications sent 2
Orders placed 3
Wall-clock time 12.3s
What the Alerts Mean
| Alert | Meaning |
|---|---|
NEWS2_LOW |
NEWS2 score 1--4: Low risk, routine monitoring |
NEWS2_MEDIUM |
NEWS2 score 5--6 or single parameter score of 3: Urgent review needed |
NEWS2_HIGH |
NEWS2 score 7+: Emergency response needed |
SEPSIS_WARNING |
SIRS criteria met (2+ of: temp, HR, RR, WBC abnormal) |
QSOFA_WARNING |
qSOFA criteria met (2+ of: altered mentation, RR >= 22, SBP <= 100) |
RAPID_DETERIORATION |
Sudden significant change in vital signs |
8. Creating Your Own Scenarios
You can create new clinical scenarios by writing a JSON file. There are two ways:
Option A: Write it manually
- Copy an existing scenario from
VigilCare.Simulator/Scenarios/List/as a template - Modify the patient, encounter, and events to match your clinical story
- Make sure
offsetMinutesvalues are in ascending order - Validate it:
dotnet run --project VigilCare.Simulator -- validate ./my-scenario.json
Option B: Use AI to generate it
There is a generation prompt file at VigilCare.Simulator/Scenarios/GENERATE_PROMPT.md. You can paste its contents into ChatGPT, Claude, or another AI assistant, describe the clinical case you want, and it will generate a valid scenario JSON file for you.
Validation Rules
The simulator enforces these rules on scenario files:
- scenario.id and scenario.name are required
- encounter.department must be one of:
Icu,GeneralMedicine,Emergency,Cardiology,Surgery,Pediatrics - encounter.encounterType must be one of:
Inpatient,Outpatient,Emergency - events must be non-empty, with
offsetMinutesin ascending order - observation codes must match one of the 12 valid codes listed above
- Maximum 10 observations per time point (API batch limit)
- order_result events must reference a matching prior order (or a sepsis bundle auto-order)
Sepsis Bundle Auto-Orders
When the system detects sepsis (SIRS or qSOFA criteria met), it automatically creates four orders:
SEP-1: Blood culturesSEP-1: Serum lactateSEP-1: Broad-spectrum antibioticsSEP-1: IV fluid bolus
You do not need to create order events for these. Just add order_result events referencing them to simulate bundle completion.
9. Troubleshooting
"Connection refused" error
The API is not running. Start it first:
docker compose up -d
dotnet run --project VigilCare.Api
"scenario.json has N error(s)"
Run validate to see what's wrong:
dotnet run --project VigilCare.Simulator -- validate ./your-scenario.json
Common issues:
- Invalid department name (must match exact casing:
Icu, notICU) offsetMinutesout of order- Missing required fields (
scenario.id,scenario.name,encounter.attendingPhysician)
Medications show "skipped"
The medication tracking endpoint may not be available. This is non-fatal -- the scenario continues.
"dotnet" command not found
Install .NET 8 SDK from dotnet.microsoft.com.
I want to point the simulator at a different API
Use --base-url:
dotnet run --project VigilCare.Simulator -- replay scenario.json \
--base-url http://192.168.1.50:5270
10. Ward Outage Scenario (Climate Resilience)
The ward-outage-reconnect-01 scenario validates Tier 1 safety during a central API outage. Observations and alerts continue on the ward gateway; acknowledgements are recorded locally and synced when the uplink returns.
Automated verification (recommended)
From the repo root, with Docker running:
./scripts/run-phase24-verification.sh
This script runs all three phases automatically: central baseline replay, gateway replay during simulated central outage, and post-reconnect sync checks. Logs go to /tmp/vigilcare-phase24-*.
Useful flags:
| Env var | Effect |
|---|---|
SKIP_DOCKER=1 |
Assume docker compose stack is already up |
SKIP_PHASE_A=1 |
Skip central replay; use an existing ICU encounter on the gateway |
Helper for manual gateway API calls (ward gateway has no login endpoint):
export GATEWAY_JWT=$(./scripts/mint-gateway-jwt.sh nurse.demo NURSE)
Manual procedure
- Full stack running with the ward gateway profile:
docker compose --profile ward-gateway up -d
- Central API running and the gateway encounter replica synced
- Note an active ICU encounter id from the gateway:
curl "http://localhost:5081/api/v1/encounters?status=ACTIVE&department=ICU" \
-H "Authorization: Bearer $JWT"
Procedure
Phase A — Baseline on central (optional):
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/ward-outage-reconnect-01.json \
--speed 0 --base-url http://localhost:5270
Phase B — Stop central, replay against gateway:
# Stop central API process/container
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/ward-outage-reconnect-01.json \
--gateway --encounter-id <ENCOUNTER-GUID> --speed 60 --poll
The --gateway flag targets http://localhost:5081 automatically. --encounter-id must reference an encounter already replicated on the gateway. The alert_ack event at T+50 min sends clinicianId: "RN-Wu" from the scenario; the gateway honors this in acknowledged_by and in the sync buffer (login user is only used when clinicianId is omitted).
Phase A note: Central replay attributes acks to the JWT user (physician.demo by default). Use --username nurse.demo if you want a nurse role on central; gateway Phase B is the authoritative climate-resilience path for scenario attribution.
Phase C — Restart central, verify sync:
- Wait for
SyncUploaderServiceto drain the buffer - Poll
GET /api/v1/operations/gateways— buffer depth should reach 0 - Confirm observations on central with preserved
recorded_attimestamps
Success criteria
- Critical potassium alert created on gateway at T+45 min while central is down
- Ack recorded locally at T+50 min with
acknowledged_by=RN-Wu - After reconnect: central has observations, alert, and ack; no duplicate paging logs
11. MIMIC-IV Real Patient Data
The simulator can generate scenarios from real de-identified ICU data from MIT's MIMIC-IV dataset (PhysioNet). This bridges the gap from synthetic scenarios to actual clinical records — a critical validation step for scoring engines and alert logic.
The MIMIC-IV CSV files in docs/MIMIC-IV/ contain 100 patients, 140 ICU stays, 668K chart events, and 107K lab events. The generator reads these files and produces standard scenario JSON files that replay through VigilCare's full scoring pipeline (NEWS2, SOFA, GCS, qSOFA, trend detection, alerting).
Quick start
# 1. Browse available ICU stays
dotnet run --project VigilCare.Simulator -- mimic-list docs/MIMIC-IV/
# 2. Generate a 24-hour scenario from a CVICU patient
dotnet run --project VigilCare.Simulator -- mimic-generate docs/MIMIC-IV/ \
--stay-id 32604416 --max-hours 24 --validate
# 3. Preview the timeline
dotnet run --project VigilCare.Simulator -- dry-run \
VigilCare.Simulator/Scenarios/List/mimic-s32604416.json
# 4. Replay against the live API
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/mimic-s32604416.json --speed 0 --poll
What gets generated
The generator maps MIMIC-IV data to VigilCare observations:
| MIMIC Source | VigilCare Codes |
|---|---|
| Vital signs (chartevents) | HEART_RATE, RESP_RATE, SYSTOLIC_BP, DIASTOLIC_BP, TEMP_C, SPO2, FIO2_PCT |
| GCS (chartevents, text labels) | GCS_EYE, GCS_VERBAL, GCS_MOTOR |
| Labs (labevents) | CREATININE_MG_DL, PLATELET_K_UL, BILIRUBIN_MG_DL, LACTATE_MMOL_L, WBC_K_UL, POTASSIUM_MEQ_L, GLUCOSE_MG_DL, PAO2_MMHG |
| Prescriptions | Medication events (drug name, dose, route) |
The generated scenario has no expectedOutcomes — these are exploratory replays. The alerts and scores that fire are the real output, driven by actual patient trajectories.
Useful options
| Option | Effect |
|---|---|
--max-hours 24 |
Cap the scenario at 24 hours (real ICU stays can be weeks) |
--no-medications |
Exclude medication events for a cleaner vitals-only replay |
--no-labs |
Exclude lab observations (vitals + GCS only) |
--validate |
Run ScenarioValidator after generation and print results |
--output my-file.json |
Write to a custom path instead of Scenarios/List/ |
Data handling notes
- GCS: MIMIC stores GCS as text labels ("Obeys Commands", "To Speech", etc.). The generator reads the numeric
valuenumcolumn; if missing, falls back to a text-to-numeric lookup dictionary. - Temperature: Fahrenheit readings (item 223761) are converted to Celsius. Celsius readings (item 223762) pass through.
- Blood pressure: When both non-invasive (NBP) and arterial (ABP) readings exist at the same time, the generator keeps non-invasive and discards arterial. ABP is used only when no NBP is available.
- Cluster limits: The VigilCare API accepts at most 10 observations per batch. When a MIMIC timestamp has more than 10 readings, the generator keeps vital signs first and spills labs to the next minute offset.
- Patient identity: De-identified names (
MIMIC-{subjectId}/S{hadmId}). Date of birth approximated fromanchor_ageandanchor_year.
Finding interesting patients
Use mimic-list with filters to find specific cases:
# Find all stays for a specific patient
dotnet run --project VigilCare.Simulator -- mimic-list docs/MIMIC-IV/ --subject-id 10005817
# Look for a specific stay
dotnet run --project VigilCare.Simulator -- mimic-list docs/MIMIC-IV/ --stay-id 32604416
The table shows care unit, length of stay, and whether the patient expired -- useful for finding clinically interesting trajectories to replay.