update docs and prep for frontend

This commit is contained in:
voltsrage
2026-06-19 15:44:58 +08:00
parent abc781c9c0
commit 49973271e5
20 changed files with 1071 additions and 26 deletions
@@ -1,5 +1,7 @@
# Medication Correlation Design Decisions
**Status:** Implemented (Phase 15). Medication administration CRUD, `MedicationCorrelationHelper`, and integration with `WarningEvaluator` and `News2Detector` are in production. Verification: `./scripts/run-phase15-verification.sh` and `MedicationCorrelationTests`. The simulator scenario `VigilCare.Simulator/Scenarios/List/medication-false-alarm-01.json` exercises the end-to-end flow.
## The problem this solves
A patient with a blood pressure of 140/90 receives metoprolol (a beta-blocker that
@@ -12,7 +14,7 @@ Clinicians who see these false positives repeatedly stop trusting the alert syst
that point, the system is worse than useless — it trains people to ignore alerts,
including the real ones.
Phase 15 solves this by **annotating** alerts with medication context. The alert still
Phase 15 addresses this by **annotating** alerts with medication context. The alert still
fires (the BP is genuinely low and may need monitoring), but the details say:
> SYSTOLIC_BP value 95 is below warning low of 90. — note: metoprolol 25mg (PO)
@@ -25,7 +27,7 @@ medication is working — keep monitoring."
## How the pieces fit together
There are six components in Phase 15. Here is how a request flows through them, starting
There are six components. Here is how a request flows through them, starting
from when a nurse records a medication and ending when an annotated alert is created.
```
@@ -61,7 +63,7 @@ New observation arrives (e.g. SYSTOLIC_BP = 95)
│ 1. Load threshold │ (from Redis cache)
│ 2. Check breach │ (is 95 < warningLow of 90?)
│ 3. Build details │ ("SYSTOLIC_BP value 95 is below warning low of 90.")
│ 4. ► Annotate ◄ │ NEW in Phase 15
│ 4. ► Annotate ◄ │ MedicationCorrelationHelper
│ 5. INSERT alert │ (idempotent — skips if one already open)
└────────┬────────────┘
@@ -163,6 +165,10 @@ Drug names in clinical systems come in all forms — "Metoprolol", "METOPROLOL",
lookups, the correlation works regardless of how the nurse typed the drug name. This
avoids a class of bugs where correlation silently fails because the case doesn't match.
The shipped `appsettings.json` includes mappings for common cardiovascular, vasopressor,
opioid, sedative, diuretic, and antibiotic agents — not just metoprolol. Add or adjust
entries under `MedicationCorrelation:DrugVitalMappings` without a migration.
---
### 3. MedicationService
@@ -307,7 +313,7 @@ Both `WarningEvaluator` and `News2Detector` already follow a pattern:
2. Build a details string describing the breach
3. INSERT the alert into the database
Phase 15 adds one step between 2 and 3:
Phase 15 inserts one step between 2 and 3:
```
2. Build details string
@@ -315,7 +321,7 @@ Phase 15 adds one step between 2 and 3:
3. INSERT the alert (with the possibly-annotated details)
```
This minimal insertion point means no changes to the threshold logic, the idempotent
This insertion point required no changes to the threshold logic, the idempotent
INSERT pattern, the outbox event publishing, or the alert suppression logic. Each of
those systems continues to work exactly as before.
@@ -384,7 +390,7 @@ pharmaceutical review.
## Testing strategy
The tests are structured in three files, each targeting a different layer:
Twelve tests across three files cover the medication subsystem:
**MedicationServiceTests (4 tests)** — tests the service layer directly. Can a medication
be created on an active encounter? Does a discharged encounter get rejected? Does
@@ -393,14 +399,17 @@ pagination work? Does the time-window filter exclude old records? These tests ca
**MedicationCorrelationTests (5 tests)** — tests the full integration from medication
recording through alert creation. These seed a medication into the database, then invoke
`WarningEvaluator.EvaluateAsync` and check whether the resulting alert's `details` field
contains the medication annotation. This is the most important test file because it
verifies the end-to-end behavior that Phase 15 exists to provide.
`WarningEvaluator.EvaluateAsync` (and NEWS2 paths where applicable) and check whether the
resulting alert's `details` field contains the medication annotation. This is the most
important test file because it verifies the end-to-end behavior the feature exists to provide.
**MedicationValidationTests (3 tests)** — tests the HTTP validation layer. These send
invalid requests via `HttpClient` and assert 400 responses. They don't seed encounters
because the validator rejects the request before the service layer runs.
Run with `dotnet test --filter "FullyQualifiedName~Medication"` or
`./scripts/run-phase15-verification.sh` (requires API + Docker Compose).
All tests run against a real PostgreSQL database and real Redis instance (using test
containers on different ports). No mocking. This means the tests catch real issues like
SQL translation failures, index problems, and configuration registration mistakes that
+400
View File
@@ -0,0 +1,400 @@
# 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
1. [Prerequisites](#1-prerequisites)
2. [Quick Start](#2-quick-start)
3. [Available Commands](#3-available-commands)
4. [Understanding Scenarios](#4-understanding-scenarios)
5. [Available Scenarios](#5-available-scenarios)
6. [Controlling Replay Speed](#6-controlling-replay-speed)
7. [Reading the Output](#7-reading-the-output)
8. [Creating Your Own Scenarios](#8-creating-your-own-scenarios)
9. [Troubleshooting](#9-troubleshooting)
---
## 1. Prerequisites
You need two things installed:
- **.NET 8 SDK** -- Download from [dotnet.microsoft.com](https://dotnet.microsoft.com/download/dotnet/8.0)
- **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:
```bash
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](docker-compose-usage-and-troubleshooting.md) if you have trouble with Docker.
---
## 2. Quick Start
Open a terminal in the project root and run:
```bash
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 four commands. All are run with `dotnet run --project VigilCare.Simulator -- <command>`.
### replay -- Run a scenario against the API
```bash
dotnet run --project VigilCare.Simulator -- replay <scenario-file> [options]
```
**Options:**
| Option | Default | Description |
|--------|---------|-------------|
| `--speed <number>` | 60 | How fast to run (see [Speed](#6-controlling-replay-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 |
**Example -- run the stable baseline scenario in real-time with polling:**
```bash
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
```bash
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:**
```bash
dotnet run --project VigilCare.Simulator -- replay-all \
VigilCare.Simulator/Scenarios/List --speed 60
```
### validate -- Check a scenario file for errors
```bash
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:**
```bash
dotnet run --project VigilCare.Simulator -- validate \
VigilCare.Simulator/Scenarios/List/stable-baseline-01.json
```
### dry-run -- Preview the timeline without touching the API
```bash
dotnet run --project VigilCare.Simulator -- dry-run <scenario-file>
```
Prints exactly what *would* happen (every vital sign, medication, and order) without sending anything.
**Example:**
```bash
dotnet run --project VigilCare.Simulator -- dry-run \
VigilCare.Simulator/Scenarios/List/cardiac-arrest-post-mi-01.json
```
---
## 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:
```json
{
"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" |
### 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 |
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 10` with `--poll` so people can follow along
- **For quick testing:** Use `--speed 60` or `--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
1. Copy an existing scenario from `VigilCare.Simulator/Scenarios/List/` as a template
2. Modify the patient, encounter, and events to match your clinical story
3. Make sure `offsetMinutes` values are in ascending order
4. Validate it:
```bash
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 `offsetMinutes` in 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 cultures`
- `SEP-1: Serum lactate`
- `SEP-1: Broad-spectrum antibiotics`
- `SEP-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:
```bash
docker compose up -d
dotnet run --project VigilCare.Api
```
### "scenario.json has N error(s)"
Run `validate` to see what's wrong:
```bash
dotnet run --project VigilCare.Simulator -- validate ./your-scenario.json
```
Common issues:
- Invalid department name (must match exact casing: `Icu`, not `ICU`)
- `offsetMinutes` out 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](https://dotnet.microsoft.com/download/dotnet/8.0).
### I want to point the simulator at a different API
Use `--base-url`:
```bash
dotnet run --project VigilCare.Simulator -- replay scenario.json \
--base-url http://192.168.1.50:5270
```