Files
vigilcare-clinical/docs/patient-encounter-api-lifecycle.md
T

44 KiB
Raw Blame History

Patient Encounter API Lifecycle

Audience: API integrators, simulator authors, dashboard developers, and anyone who needs to understand how a patient moves through VigilCare from registration to discharge.

What is VigilCare? VigilCare is a clinical monitoring system that tracks patients in a hospital. It continuously watches vital signs (like heart rate, blood pressure, and temperature), calculates safety scores, and fires alerts when something looks wrong — so doctors and nurses can respond quickly.

Companion docs:


What this document covers

A hospital stay in VigilCare is called an encounter, and it belongs to a patient. Think of it this way: a patient is a person, and an encounter is one visit to the hospital. The same patient can have many encounters over their lifetime.

Everything clinical — measurements, alerts, medication records, lab orders, safety scores, sepsis bundles — is attached to an encounter while it is ACTIVE (meaning the patient is still in the hospital).

This guide walks through the lifecycle in order: what API calls to make, what happens right away in the response, what happens a few seconds later in the background, and what stops working after the patient leaves.

There is no single "admit patient" button. Instead, you combine the standard REST endpoints below.


Quick glossary of medical terms

Before we dive in, here are the medical terms you will see throughout this document:

Term What it means
Vital signs Basic body measurements that show how a patient is doing — heart rate, blood pressure, temperature, breathing rate, oxygen level
Observation Any measurement recorded for a patient (vitals, lab results, consciousness checks)
Sepsis A life-threatening condition where the body's response to an infection starts damaging its own organs. It can kill quickly if not treated
NEWS2 National Early Warning Score 2 — a scoring system that combines 7 vital signs into a single number (020) to detect patients who are getting sicker
SOFA Sequential Organ Failure Assessment — a score (024) that measures how well 6 organ systems are working. Used to detect organ failure and sepsis
qSOFA Quick SOFA — a fast bedside check using just 3 things (breathing rate, blood pressure, consciousness). Used as an early screen: "should we look more closely for sepsis?"
GCS Glasgow Coma Scale — measures how conscious/awake a patient is (315). Lower is worse. A score of 3 means completely unresponsive, 15 means fully alert
AVPU A simpler consciousness scale: Alert, responds to Voice, responds to Pain, Unresponsive
Threshold A boundary value. If a measurement crosses a threshold, it triggers an alert
Alert A notification to clinical staff that something needs attention — like an alarm going off
Sepsis bundle A checklist of 4 things that must be done within 1 hour when sepsis is suspected (blood tests, antibiotics, IV fluids)
MRN Medical Record Number — the human-readable ID for a patient (e.g. MRN-000042)

Conventions

Item Value
Base path /api/v1
Response envelope { success, statusCode, data, error }
Correlation Optional X-Correlation-Id request header; echoed on the response
Active encounter guard POST observations, medications, and orders return 409 (ENCOUNTER_NOT_ACTIVE) when the encounter is discharged or cancelled
Async latency Warning alerts, NEWS2 scores, qSOFA, SOFA, trend alerts, and sepsis bundles are created by background workers — allow a few seconds after recording a measurement before polling for results

Lifecycle overview

flowchart LR
    subgraph admission [Admission]
        A1[POST /patients]
        A2[POST /patients/id/encounters]
    end
    subgraph monitoring [Active monitoring]
        M1[POST /encounters/id/observations]
        M2[POST /encounters/id/medications]
        M3[POST /encounters/id/orders]
    end
    subgraph async [Background scoring engines]
        K1[warning-evaluator]
        K2[news2-scoring]
        K3[sepsis-engine]
        K4[trend-analyzer]
        K5[sofa-scoring]
        K6[gcs-scoring]
    end
    subgraph triage [Clinician response]
        T1[GET /alerts]
        T2[POST /alerts/id/acknowledge]
        T3[POST /alerts/id/resolve]
        T4[PATCH /orders/id/result]
    end
    subgraph discharge [Discharge]
        D1[PATCH /encounters/id/status]
        D2[encounter.status.changed → discharge summary]
    end

    A1 --> A2 --> M1
    M1 --> K1 & K2 & K3 & K4 & K5 & K6
    K1 & K2 & K3 & K4 & K5 & K6 --> T1
    M2 & M3 --> T1
    T1 --> T2 --> T3
    M3 --> T4
    T3 --> D1 --> D2

How encounter status works (enforced by PATCH /encounters/{id}/status):

scheduled → active → discharged
          → cancelled

POST /patients/{id}/encounters creates encounters directly in ACTIVE. Illegal transitions (like going from discharged back to active) return 409 (ILLEGAL_STATUS_TRANSITION).


All measurements the system tracks

VigilCare tracks measurements (called "observations") across three categories: vital signs, lab results, and consciousness assessments. Each measurement has a plausible range — values outside this range are rejected as likely errors.

Vital signs (measured at the bedside)

These are the basic measurements that nurses and devices collect regularly:

Code What it measures Unit Normal range (approx.) Plausible range Example
HEART_RATE How fast the heart beats bpm (beats per minute) 60100 1300 A resting adult is ~70 bpm. Over 150 or under 30 is critical
RESP_RATE How many breaths per minute breaths/min 1220 180 Normal adults breathe 1220 times/min. Over 30 is critical
SYSTOLIC_BP Blood pressure (the top number) — pressure when the heart squeezes mmHg 90140 40300 120/80 is "normal." Under 70 systolic is critical (not enough blood reaching organs)
DIASTOLIC_BP Blood pressure (the bottom number) — pressure when the heart relaxes mmHg 6090 20200 The lower number in a blood pressure reading
TEMP_C Body temperature °C (Celsius) 36.137.2 1550 Normal is ~37°C (98.6°F). Over 40°C or under 35°C is critical
SPO2 Oxygen saturation — how much oxygen is in the blood % 95100 50100 Measured with a clip on the finger. Below 88% is critical
SUPPLEMENTAL_O2 Whether the patient is receiving extra oxygen flag (0 or 1) 0 (room air) 01 0 = breathing normal air, 1 = on supplemental oxygen. Being on oxygen adds points to the NEWS2 score

Lab results (from blood tests and other samples)

These require a blood draw or lab analysis — they take longer to get but reveal what is happening inside the body:

Code What it measures Unit Normal range (approx.) Plausible range Why it matters
POTASSIUM_MEQ_L Potassium level in blood mEq/L 3.55.0 0.112 Too high or too low can cause dangerous heart rhythms
WBC_K_UL White blood cell count k/uL (thousands per microliter) 4.012.0 0.1500 White blood cells fight infection. Very high = possible infection. Very low = weak immune system
GLUCOSE_MG_DL Blood sugar level mg/dL 70100 (fasting) 101000 Too low (hypoglycemia) can cause seizures. Too high (hyperglycemia) damages organs over time
LACTATE_MMOL_L Lactic acid in blood mmol/L 0.52.0 0.130 High lactate means tissues are not getting enough oxygen — a key sign of sepsis or shock
PAO2_MMHG Partial pressure of oxygen in arterial blood mmHg 80100 20600 Requires an arterial blood draw. Shows how well lungs transfer oxygen. Used in SOFA respiratory scoring
FIO2_PCT Fraction of inspired oxygen — how concentrated the oxygen the patient breathes is % 21 (room air) 21100 Room air is 21%. A patient on a ventilator might be at 40100%. Used with PaO2 to calculate SOFA respiratory score
PLATELET_K_UL Platelet count k/uL 150400 11500 Platelets help blood clot. Very low platelets = bleeding risk. Used in SOFA coagulation scoring
BILIRUBIN_MG_DL Bilirubin level mg/dL 0.11.2 0.150 Produced when the liver breaks down old blood cells. High bilirubin = liver is struggling. Used in SOFA liver scoring
CREATININE_MG_DL Creatinine level mg/dL 0.61.2 0.120 A waste product filtered by the kidneys. High creatinine = kidneys are not filtering properly. Used in SOFA renal scoring
URINE_OUTPUT_ML_H How much urine the patient produces per hour mL/h >50 0500 Low urine output = kidneys may be failing. Used in SOFA renal scoring

Consciousness assessments

These measure how awake and responsive the patient is:

Code What it measures Range What the numbers mean
AVPU Quick consciousness check 03 0 = Alert (awake and talking), 1 = responds to Voice, 2 = responds to Pain only, 3 = Unresponsive. Used in NEWS2 scoring
GCS_EYE Eye opening response 14 4 = opens eyes on own, 3 = opens to voice, 2 = opens to pain, 1 = no eye opening
GCS_VERBAL Verbal response 15 5 = oriented/normal conversation, 4 = confused, 3 = inappropriate words, 2 = incomprehensible sounds, 1 = none
GCS_MOTOR Motor (movement) response 16 6 = obeys commands, 5 = localizes pain, 4 = withdraws from pain, 3 = abnormal flexion, 2 = extension, 1 = none

GCS total (315) is the sum of all three components. A GCS of 15 means fully alert. A GCS of 3 means completely unresponsive.


Alert thresholds — when do alarms go off?

Every measurement is checked against configured thresholds. There are two levels:

  • Critical — something is dangerously wrong right now. The alert fires immediately (in the same HTTP response). These are never suppressed.
  • Warning — something is concerning and needs attention. The alert fires in the background (via Kafka). Warnings can be temporarily suppressed after a clinician acknowledges them (30-minute window).
Measurement Critical Low Warning Low Warning High Critical High
Heart Rate (bpm) 30 50 100 150
Temperature (°C) 35.0 36.0 38.3 40.0
Potassium (mEq/L) 2.5 3.5 5.0 6.5
Oxygen Saturation (%) 88 92
Respiratory Rate (breaths/min) 12 20 30
White Blood Cells (k/uL) 2.0 4.0 12.0 20.0
Systolic BP (mmHg) 70 90 160 180
Diastolic BP (mmHg) 40 60 90 110
Lactate (mmol/L) 2.0 4.0
AVPU 2
Glucose (mg/dL) 40 70 180 400
PaO2 (mmHg) 60 80
Platelets (k/uL) 20 50
Bilirubin (mg/dL) 2.0 6.0
Creatinine (mg/dL) 2.0 3.5

Example: A heart rate of 155 bpm crosses the critical high threshold (150), so a CRITICAL_HEART_RATE alert fires immediately. A heart rate of 105 bpm crosses only the warning high threshold (100), so a WARNING_HEART_RATE alert fires in the background.

A dash (—) means there is no threshold configured on that side. For example, oxygen saturation has no high threshold because high oxygen is generally not dangerous in this context.


Clinical scoring systems explained

VigilCare calculates several clinical scores automatically. Each score looks at patient data from a different angle. Together, they give clinicians a complete picture of how the patient is doing.

NEWS2 — National Early Warning Score 2

What it does: Combines 7 vital signs into a single number (020) that tells staff "how sick is this patient right now?" It is the most widely used early warning system in UK hospitals.

Why it matters: A rising NEWS2 score often means the patient is getting worse — even if no single measurement has crossed a critical threshold yet. It catches deterioration early.

The 7 required measurements:

All 7 must be recorded within a 4-hour window for a score to be calculated. If any are missing, no score is produced.

Measurement Score 3 Score 2 Score 1 Score 0 (normal) Score 1 Score 2 Score 3
Respiratory Rate ≤8 911 1220 2124 ≥25
Oxygen Saturation (%) ≤91 9293 9495 ≥96
Systolic BP (mmHg) ≤90 91100 101110 111219 ≥220
Heart Rate (bpm) ≤40 4150 5190 91110 111130 ≥131
Consciousness (AVPU) Alert Any other (V, P, or U)
Temperature (°C) ≤35.0 35.136.0 36.138.0 38.139.0 ≥39.1
Supplemental O2 On oxygen (2) Room air (0)

How the total score maps to risk levels:

Total Score Risk Level What it means Alert type
04 LOW Routine monitoring. Patient is stable No alert
04 but any single parameter scores 3 LOW-MEDIUM One vital sign is individually concerning, even though the total looks OK NEWS2_WARNING
56 MEDIUM Patient may be deteriorating. Increase monitoring frequency NEWS2_WARNING
7 or higher HIGH Urgent — patient is at significant risk. Senior clinician review needed NEWS2_EMERGENCY

Example: A patient has: respiratory rate 24 (score 2), SpO2 94% (score 1), systolic BP 115 (score 0), heart rate 115 (score 2), AVPU alert (score 0), temperature 38.5°C (score 1), on room air (score 0). Total = 6 → MEDIUM risk → NEWS2_WARNING alert fires.


SOFA — Sequential Organ Failure Assessment

What it does: Scores how well 6 organ systems are functioning, on a scale of 04 each (total 024). Higher scores mean worse organ function. It is the gold standard for detecting sepsis-related organ damage.

Why it matters: Sepsis kills by damaging organs. SOFA catches this damage early. A jump of 2+ points from the patient's baseline is the clinical definition of sepsis (per Sepsis-3 guidelines).

The 6 organ systems scored:

Organ System What it measures Data needed Score 0 (normal) Score 1 Score 2 Score 3 Score 4 (worst)
Respiratory (lungs) How well lungs transfer oxygen PaO2 and FiO2 (or SpO2 if PaO2 unavailable) PaO2/FiO2 ≥400 300399 200299 100199 <100
Coagulation (blood clotting) Platelet count Platelet count ≥150 k/uL 100149 5099 2049 <20
Liver Liver function Bilirubin <1.2 mg/dL 1.21.9 2.05.9 6.011.9 ≥12.0
Cardiovascular (heart/circulation) Blood pressure support needed MAP (mean arterial pressure) MAP ≥70 MAP <70 Low-dose vasopressor Moderate vasopressor High-dose vasopressor
CNS (brain) Consciousness level GCS total score 15 1314 1012 69 <6
Renal (kidneys) Kidney function Creatinine and/or urine output Creatinine <1.2 1.21.9 2.03.4 3.54.9 ≥5.0 or urine <200 mL/day

How SOFA alerts work:

The system establishes a "baseline" SOFA score once at least 4 of the 6 organ systems have data. Then it watches for changes:

Change from baseline Alert Severity What it means
Increase of 2+ points SOFA_SEPSIS CRITICAL Meets the Sepsis-3 definition of sepsis. Immediate clinical response needed
Increase of 1 point SOFA_WARNING WARNING Organs are trending worse. Watch closely

Lab staleness: SOFA uses lab results that may not be measured frequently. The system considers lab values stale after 12 hours and expired after 24 hours, meaning the score becomes less reliable over time without fresh data.

Example: A patient has a baseline SOFA of 3. New labs come in showing worsening kidney function and lower platelets, pushing the SOFA to 6. That is a jump of 3 points → SOFA_SEPSIS alert fires.


qSOFA — Quick SOFA (bedside screening)

What it does: A fast, simple check using just 3 things you can measure at the bedside — no lab work needed. It asks: "should we be worried about sepsis?"

Why it matters: Lab results take time. qSOFA gives an answer in seconds using measurements that are already being collected. It is a screening tool — it does not diagnose sepsis, but it flags patients who need deeper evaluation (like a full SOFA score).

The 3 criteria:

Criteria Threshold What it means
Respiratory rate ≥22 breaths/min Patient is breathing faster than normal — body may be compensating for something
Systolic blood pressure ≤100 mmHg Blood pressure is dropping — organs may not be getting enough blood
Altered mental status AVPU ≥1 (not fully alert) or GCS <15 Patient is confused or not fully conscious

Scoring: Each criterion met = 1 point. Range is 03. When 2 or more criteria are met, a QSOFA_SCREEN alert fires.

Important: qSOFA is a screen, not a diagnosis. It says "look closer" — it does not by itself trigger a sepsis bundle. The full SOFA score is used for sepsis determination.

Example: A patient has a respiratory rate of 24 (meets criterion), systolic BP of 95 (meets criterion), and is alert (AVPU = 0, does not meet criterion). Score = 2 → QSOFA_SCREEN alert fires.


GCS — Glasgow Coma Scale

What it does: Measures how conscious a patient is by testing three types of responses: eye opening, verbal response, and motor (movement) response. Total score ranges from 3 (completely unresponsive) to 15 (fully alert).

Why it matters: Changes in consciousness can signal brain injury, stroke, medication effects, or worsening illness. A dropping GCS is an emergency.

The three components:

Component Best response (highest score) Worst response (lowest score)
Eye opening 4 — opens eyes spontaneously 1 — no eye opening
Verbal response 5 — oriented, normal conversation 1 — no verbal response
Motor response 6 — obeys commands 1 — no movement

Severity classification:

GCS Total Classification Alert What it means
1315 Mild No alert Patient is mostly or fully alert
912 Moderate GCS_WARNING Significant impairment — needs monitoring
38 Severe (coma) GCS_CRITICAL Patient is in or near coma — urgent intervention needed

How GCS connects to other scores:

  • NEWS2: GCS 15 maps to AVPU = "Alert" (score 0). Any GCS below 15 maps to "not alert" (score 3 in NEWS2). This is why even a small drop in consciousness adds 3 points to NEWS2.
  • qSOFA: GCS below 15 counts as "altered mental status" — one of the 3 qSOFA criteria.
  • SOFA CNS component: GCS maps directly to the SOFA brain score (15→0, 1314→1, 1012→2, 69→3, <6→4).

Trend detection — Rapid Deterioration

What it does: Watches how fast certain vital signs are changing over time, not just whether they have crossed a threshold. Even if a value is still in a "normal" range, a rapid change can signal trouble.

Why it matters: A heart rate going from 70 to 100 in 30 minutes is more alarming than a stable heart rate of 100. The trend tells you the patient is getting worse fast.

Monitored vital signs and velocity thresholds:

The system looks at changes over a 30-minute sliding window:

Vital Sign Trigger rate In plain terms Alert
Heart Rate ≥0.5 bpm/min Rising by 15+ bpm in 30 minutes RAPID_DETERIORATION
Respiratory Rate ≥0.3 breaths/min Rising by 9+ breaths/min in 30 minutes RAPID_DETERIORATION
Systolic BP ≥1.0 mmHg/min decline Dropping by 30+ mmHg in 30 minutes RAPID_DETERIORATION
Temperature ≥0.05 °C/min Rising by 1.5+°C in 30 minutes RAPID_DETERIORATION
Oxygen Saturation ≥0.2 %/min decline Dropping by 6+% in 30 minutes RAPID_DETERIORATION

Example: A patient's heart rate readings: 75 bpm at 2:00 PM, 82 bpm at 2:10 PM, 92 bpm at 2:25 PM. That is a rise of 17 bpm in 25 minutes (0.68 bpm/min) — above the 0.5 threshold → RAPID_DETERIORATION alert fires, even though 92 bpm is not above the warning threshold (100) yet.

RAPID_DETERIORATION alerts are never suppressed — every rapid change triggers a new alert.


Phase 0 — Platform prerequisites (before any patient)

These are typically set up once at deploy time, not called per patient.

What How Why it matters
Alert thresholds Seeded in PostgreSQL; loaded into Redis on startup; manageable via POST/GET/PUT /alert-thresholds Every measurement is validated against configured thresholds to decide if alerts fire
Kafka topics Provisioned by KafkaTopicProvisioner observation.recorded, alert.generated, encounter.status.changed (+ sepsis bundle topics)
Drug-vital mappings MedicationCorrelation section in appsettings.json Links medications to vital signs so alerts can include context like "heart rate may be elevated due to epinephrine given 20 min ago"

Phase 1 — Patient arrives (registration and admission)

1.1 Register the patient

POST /api/v1/patients
Field Required Notes
firstName, lastName, dateOfBirth, gender yes
bloodType, allergies, emergencyContactName, emergencyContactPhone no Stored for ward context

Response: 201 Createddata includes a system-generated mrn (e.g. MRN-000042) and id (UUID). Save both; the MRN is what nurses see on wristbands, the UUID is used in all API paths.

Later lookups:

Need Endpoint
Search by name or MRN GET /patients?q=...
Demographics + active encounter summary GET /patients/{id}

1.2 Open an encounter (admission)

POST /api/v1/patients/{patientId}/encounters
Field Required Notes
encounterType yes INPATIENT, OUTPATIENT, EMERGENCY
department yes e.g. ICU, GENERAL_MEDICINE, SURGERY
attendingPhysician yes
roomBed no Ward assignment (e.g. ICU-1A)
admissionReason no Why the patient was admitted

Response: 201 Created — encounter id, status: ACTIVE, admittedAt.

Side effects:

  • Event encounter.status.changed → Kafka → updates Elasticsearch ward index, data lake, and discharge-summary queue
  • Only one active encounter per patient per encounter type — trying to create a duplicate returns 409 (DUPLICATE_ACTIVE_ENCOUNTER)

From this point, encounterId is the key for all clinical writes.


Phase 2 — Ward visibility (who is on the floor)

While the patient is active, ward systems poll aggregated state.

Virtual ward board

GET /api/v1/encounters?status=ACTIVE&department=ICU&page=1&pageSize=20

Each row (WardEncounterSummary) includes:

Field What it shows
encounterId, patientId, mrn, firstName, lastName, roomBed, department, status Patient and encounter info
news2Score, news2RiskLevel Latest NEWS2 score and risk level (LOW, LOW_MEDIUM, MEDIUM, HIGH)
qsofaScore Current qSOFA criteria count (03)
sepsisActive Whether the patient has a sepsis investigation in progress
sepsisBundleStatus IN_PROGRESS, COMPLIANT, or NON_COMPLIANT
openAlertCount How many unresolved alerts exist for this patient

The dashboard sorts patients by NEWS2 score — sickest patients appear first.

Single-patient chart header

GET /api/v1/encounters/{id}

Returns the encounter with the patient's info, the 10 most recent observations, and open alerts. Use this for a quick summary; use the dedicated list endpoints for full history.


Phase 3 — Continuous monitoring (recording measurements)

Observations are the heartbeat of the system. Bedside devices, manual nurse entries, and lab interfaces all use the same endpoint.

Record measurements

POST /api/v1/encounters/{encounterId}/observations

Body: { "observations": [ ... ] }1 to 10 measurements per call.

Observation field Required Notes
observationCode yes One of the measurement codes listed above (e.g. HEART_RATE, RESP_RATE)
value, unit yes The numeric value and its unit. Checked for plausibility (e.g. a heart rate of 500 would be rejected)
recordedAt yes When the measurement was actually taken
source no DEVICE (default), MANUAL, LAB

Optional header: Idempotency-Key — if you accidentally send the same measurement twice, the system returns the original record without creating a duplicate.

What happens immediately (in the HTTP response)

  1. Encounter must be ACTIVE — otherwise 409
  2. Idempotency check — skip if already recorded
  3. Plausibility validation — reject impossible values with 422
  4. Save the measurement
  5. Check against critical thresholds from Redis
  6. If a critical threshold is breached: create an alert immediately in the same database transaction
  7. Queue the measurement for background processing
  8. Commit

HTTP response (201): for each measurement, you get observation, alertGenerated (true only for critical alerts), alertId, and duplicate.

What happens in the background (not in the HTTP response)

Every recorded measurement is processed independently by these background engines:

Engine What it does Alert produced
warning-evaluator Checks if the value crosses a warning threshold. Optionally adds medication context (e.g. "patient was given morphine 30 min ago") WARNING_* alerts
news2-scoring Recalculates NEWS2 if all 7 parameters are available within 4 hours NEWS2_WARNING or NEWS2_EMERGENCY
sepsis-engine Recalculates qSOFA criteria count QSOFA_SCREEN (when 2+ criteria met)
sofa-scoring Recalculates SOFA organ scores using latest vitals and labs SOFA_WARNING or SOFA_SEPSIS
gcs-scoring Recalculates GCS when eye/verbal/motor components are recorded GCS_WARNING or GCS_CRITICAL
trend-analyzer Checks if the vital sign is changing too fast (rate of change over 30-min window) RAPID_DETERIORATION
es-indexer Updates Elasticsearch for search and analytics (no alert)
data-lake-writer Writes Parquet files to MinIO for long-term analysis (no alert)

Important for integrators: After posting measurements, poll GET /encounters/{id}/alerts or score endpoints. Warning and scoring alerts are NOT in the initial HTTP response — they arrive 13 seconds later.

Read measurement history

GET /api/v1/encounters/{encounterId}/observations?code=HEART_RATE&from=...&to=...&limit=50&cursor=...

Cursor-paginated (newest first). Use code to filter by measurement type.


Phase 4 — Clinical interventions (medications and orders)

These run in parallel with monitoring; they do not replace recording measurements.

Medication administrations

POST /api/v1/encounters/{encounterId}/medications
GET  /api/v1/encounters/{encounterId}/medications?since=...&page=1&pageSize=20
GET  /api/v1/medications/{id}

Recording a drug does not create alerts on its own. Instead, when a warning or NEWS2 alert fires later, the system checks: "was a relevant drug given recently?" If so, it adds context to the alert details. For example, a high heart rate warning might include: "Note: epinephrine administered 20 minutes ago."

The correlation window is 90 minutes by default — only drugs given within the last 90 minutes are linked.

Clinical orders

POST  /api/v1/encounters/{encounterId}/orders
GET   /api/v1/encounters/{encounterId}/orders?status=PENDING
GET   /api/v1/orders/{id}
PATCH /api/v1/orders/{id}/status
PATCH /api/v1/orders/{id}/result

Order status flow:

pending → in_progress → resulted
        → cancelled

PATCH .../result is how clinicians record that an order is complete — for example, recording that a blood culture was collected. For orders that are part of a sepsis bundle, completing the order also marks that bundle element as done.


Phase 5 — Alerts fire (detection to triage)

Alerts are created by multiple engines (critical thresholds, warning thresholds, NEWS2, qSOFA, SOFA, GCS, trend detection). All share the same read and lifecycle API.

List and inspect

Endpoint Use
GET /encounters/{id}/alerts?status=OPEN Alerts for one patient
GET /alerts?status=OPEN&severity=CRITICAL&department=ICU Hospital-wide alert center
GET /alerts/{id} Single alert with full details

Each alert includes alertType, severity, details (with medication context when applicable), status, triggeredAt, and optional observationId (which measurement triggered it).

All alert types:

Alert Type What triggered it Severity Can be suppressed?
CRITICAL_* (e.g. CRITICAL_HEART_RATE) A measurement crossed a critical threshold CRITICAL No
WARNING_* (e.g. WARNING_HEART_RATE) A measurement crossed a warning threshold WARNING Yes (30 min)
NEWS2_WARNING NEWS2 score is 56, or any single parameter scores 3 WARNING Yes (30 min)
NEWS2_EMERGENCY NEWS2 score is 7 or higher CRITICAL No
QSOFA_SCREEN 2 or more qSOFA criteria met WARNING Yes (30 min)
SOFA_SEPSIS SOFA score increased 2+ points from baseline CRITICAL No
SOFA_WARNING SOFA score increased 1 point from baseline WARNING Yes (30 min)
GCS_CRITICAL GCS total is 8 or below (coma) CRITICAL No
GCS_WARNING GCS total is 912 (moderate impairment) WARNING Yes (30 min)
RAPID_DETERIORATION A vital sign is changing too fast WARNING No

Acknowledge and resolve

POST /api/v1/alerts/{id}/acknowledge
Body: { "clinicianId": "DR-SMITH", "note": "optional" }

POST /api/v1/alerts/{id}/resolve

Alert lifecycle:

open → acknowledged → resolved
     → escalated   (unacknowledged CRITICAL after 5 min → pages on-call staff)

When a clinician acknowledges a suppressible warning, a 30-minute suppression window starts in Redis. During that window, duplicate warnings of the same type for the same patient will not fire again. Critical and SOFA-sepsis alerts are never suppressed.

Escalation (no HTTP endpoint)

Critical alerts that remain unacknowledged for 5+ minutes are automatically escalated — they are routed to an on-call paging queue. This happens at the infrastructure level; integrators see it through the alert's status field changing to escalated.


Phase 6 — Composite scores (NEWS2, SOFA, qSOFA, GCS)

NEWS2

Requires all 7 parameters in Redis within a 4-hour window: RESP_RATE, SPO2, SYSTOLIC_BP, HEART_RATE, AVPU, TEMP_C, SUPPLEMENTAL_O2.

GET /api/v1/encounters/{id}/news2/current     → latest score (404 if not yet computed)
GET /api/v1/encounters/{id}/news2/history     → score history over time

Scores and NEWS2 alerts are calculated in the background — record the 7th parameter, wait a few seconds, then poll.

SOFA

Requires lab results and vitals across 6 organ systems. Not all systems need data immediately — the score is computed with whatever is available once at least 4 systems have data.

GET /api/v1/encounters/{id}/sofa/current      → latest SOFA score with per-organ breakdown
GET /api/v1/encounters/{id}/sofa/history      → score history

The response includes per-organ scores (respiratory, coagulation, liver, cardiovascular, CNS, renal) so clinicians can see which organ systems are struggling.

qSOFA

Live criteria count from Redis (no stored history — just the current state):

GET /api/v1/encounters/{id}/qsofa/current

Returns how many of the 3 criteria are currently met (03). When the count reaches 2, a QSOFA_SCREEN alert fires.

GCS

GET /api/v1/encounters/{id}/gcs/current       → latest GCS with component breakdown

Returns the total GCS score, individual component scores (eye, verbal, motor), and severity classification (mild/moderate/severe).


Phase 7 — Sepsis bundle (when infection is suspected)

A sepsis bundle is a checklist of things that must be done within 1 hour when sepsis is suspected. VigilCare creates bundles automatically when a SOFA_SEPSIS alert fires — not by a direct API call.

GET /api/v1/encounters/{id}/sepsis-bundle/current
GET /api/v1/sepsis-bundles/{id}

The 4 bundle elements (each backed by an auto-created clinical order):

Element What it is Why within 1 hour
Blood cultures Draw blood samples to identify which bacteria is causing the infection Need to identify the infection before antibiotics potentially mask it
Serum lactate Measure lactic acid level in blood High lactate means organs are not getting enough oxygen — guides how aggressive treatment needs to be
Broad-spectrum antibiotics Give antibiotics that cover many types of bacteria Every hour of delay increases mortality risk. Start broad, then narrow down when culture results come back
IV fluid resuscitation Give fluids through an IV to restore blood pressure and organ perfusion Sepsis causes blood vessels to leak — fluids keep blood pressure up and organs working

Compliance flow:

  1. Bundle is created with complianceStatus: IN_PROGRESS and deadlineAt set to 1 hour from detection
  2. Clinicians complete orders via PATCH /orders/{id}/result
  3. Each completed order marks its bundle element COMPLETED
  4. When all 4 elements are done:
    • Within deadlineCOMPLIANT (good)
    • Past deadlineNON_COMPLIANT (needs review — delay may have worsened outcome)
  5. A background monitor checks every 5 minutes and marks overdue bundles as NON_COMPLIANT
GET /api/v1/encounters/{id}/orders

Use this to see bundle-linked orders and their status during the stay.


Phase 8 — Review during the stay (timeline and analytics)

Encounter timeline

GET /api/v1/encounters/{id}/timeline

A combined chronological stream of observations and alerts — like a scrolling history of everything that has happened to the patient. Useful for reviewing events during ward rounds or shift handovers.

Elasticsearch analytics (read-only, cross-encounter)

Endpoint Purpose
GET /analytics/patients?q=...&department=... Search patients and encounters
GET /analytics/observations/trend?encounterId=...&code=... Hourly avg/min/max time series for a measurement
GET /analytics/alerts/summary?severity=...&department=... Alert volume by department
GET /analytics/population?code=...&threshold=... Find patients above/below a measurement threshold

These read from a search index fed by Kafka — not from the live database. There may be a few seconds of delay.


Phase 9 — Discharge (end of encounter)

Close the encounter

PATCH /api/v1/encounters/{encounterId}/status
Body: { "status": "DISCHARGED", "dischargeDiagnosis": "optional free text" }

Response: 200 OK{ encounterId, newStatus, dischargeDiagnosis }.

Side effects:

  • dischargedAt is set to the current time
  • Event fires → Kafka → Elasticsearch status update, data lake, and a discharge summary worker that generates a PDF stored at /discharge-summaries/{encounterId}/summary.pdf

What stops working after discharge

Action Result
POST .../observations 409 ENCOUNTER_NOT_ACTIVE — cannot record new measurements
POST .../medications 409 ENCOUNTER_NOT_ACTIVE — cannot record new medications
POST .../orders 409 ENCOUNTER_NOT_ACTIVE — cannot create new orders
GET .../alerts, GET .../observations, GET .../news2/history Still work — you can read historical data
GET /encounters?status=ACTIVE Patient drops off the active ward list

Cancellation: PATCH .../status with { "status": "CANCELLED" } from ACTIVE (e.g. an admission was created by mistake). Same write restrictions apply afterward.

A patient may later return and receive a new encounter via POST /patients/{id}/encounters. Each hospital visit is independent; the patient's MRN stays the same across their lifetime.


Complete endpoint index by lifecycle phase

Phase Method Path
0 — Config GET/POST/PUT /alert-thresholds, /alert-thresholds/{id}
1 — Admission POST /patients
POST /patients/{id}/encounters
GET /patients, /patients/{id}
2 — Ward GET /encounters, /encounters/{id}
GET /encounters/{id}/qsofa/current
3 — Monitoring POST /encounters/{id}/observations
GET /encounters/{id}/observations
4 — Interventions POST/GET /encounters/{id}/medications, /medications/{id}
POST/GET/PATCH /encounters/{id}/orders, /orders/{id}, /orders/{id}/status, /orders/{id}/result
5 — Alerts GET /encounters/{id}/alerts, /alerts, /alerts/{id}
POST /alerts/{id}/acknowledge, /alerts/{id}/resolve
6 — Scores GET /encounters/{id}/news2/current, /encounters/{id}/news2/history
GET /encounters/{id}/sofa/current, /encounters/{id}/sofa/history
GET /encounters/{id}/qsofa/current
GET /encounters/{id}/gcs/current
7 — Sepsis GET /encounters/{id}/sepsis-bundle/current, /sepsis-bundles/{id}
8 — Review GET /encounters/{id}/timeline
GET /analytics/patients, /analytics/observations/trend, /analytics/alerts/summary, /analytics/population
9 — Discharge PATCH /encounters/{id}/status

Operational (not encounter-scoped): GET /metrics (Prometheus), Swagger UI (development only).


Reference walkthrough — inpatient stay (step by step)

Here is the minimum sequence an integrator or simulator follows for a typical hospital stay:

# 1. Admit the patient
POST /api/v1/patients                                    ← register the person
POST /api/v1/patients/{patientId}/encounters             ← start their hospital visit

# 2. Record baseline vitals (first set of measurements)
POST /api/v1/encounters/{encounterId}/observations       ← heart rate, BP, temp, etc.

# 3. Give medications and order tests
POST /api/v1/encounters/{encounterId}/medications        ← record drugs given
POST /api/v1/encounters/{encounterId}/orders              ← order blood tests, cultures, etc.

# 4. Ongoing monitoring (repeat during the stay)
POST /api/v1/encounters/{encounterId}/observations       ← new vitals every few hours
GET  /api/v1/encounters/{encounterId}/alerts             ← check for alerts (wait 1-3s after posting vitals)
GET  /api/v1/encounters/{encounterId}/news2/current      ← check NEWS2 score
GET  /api/v1/encounters/{encounterId}/sofa/current       ← check SOFA score
GET  /api/v1/encounters/{encounterId}/qsofa/current      ← check qSOFA screen
GET  /api/v1/encounters/{encounterId}/gcs/current        ← check consciousness level
GET  /api/v1/encounters/{encounterId}/sepsis-bundle/current  ← check sepsis bundle if active
POST /api/v1/alerts/{alertId}/acknowledge                ← clinician acknowledges an alert
PATCH /api/v1/orders/{orderId}/result                    ← record order completion

# 5. Ward round (overview)
GET  /api/v1/encounters?status=ACTIVE                   ← see all active patients
GET  /api/v1/encounters/{encounterId}                    ← patient summary
GET  /api/v1/encounters/{encounterId}/timeline           ← full event history

# 6. Discharge
PATCH /api/v1/encounters/{encounterId}/status  { "status": "DISCHARGED", "dischargeDiagnosis": "..." }

For realistic clinical narratives (UTI progressing to sepsis, medication-related false alarms, rapid deterioration), replay the JSON scenarios in VigilCare.Simulator/Scenarios/List/ — each scenario file walks through this lifecycle with real clinical data.


Dashboard mapping

The Vue ward dashboard (vigilcare-dashboard/) implements this lifecycle through polling:

UI screen Primary API calls
Virtual Ward GET /encounters?status=ACTIVE
Patient Detail GET /encounters/{id}, GET .../news2/current, GET .../sofa/current, GET .../medications, GET .../orders, GET .../sepsis-bundle/current, GET .../observations, GET .../news2/history
Alert Center GET /alerts?status=OPEN
Alert actions POST /alerts/{id}/acknowledge, POST /alerts/{id}/resolve
Alert reasoning alert.details from alert list + client-side medication window from GET .../medications

Sync vs async quick reference

Event In the HTTP response? Where to check afterward
Measurement saved Yes (201) GET .../observations
Critical threshold alert Yes (alertGenerated: true) GET .../alerts
Warning threshold alert No — background (Kafka) GET .../alerts (after ~13 s)
NEWS2 score / alert No — background (Kafka) GET .../news2/current, GET .../alerts
SOFA score / alert No — background (Kafka) GET .../sofa/current, GET .../alerts
qSOFA screen No — background (Kafka) GET .../alerts, GET .../qsofa/current
GCS score / alert No — background (Kafka) GET .../gcs/current, GET .../alerts
Trend alert No — background (Kafka) GET .../alerts
Medication annotation on alert No — applied at alert creation GET .../alerts → read details field
Discharge summary PDF No — background (RabbitMQ) MinIO /discharge-summaries/{encounterId}/summary.pdf

How it all fits together — a real example

Imagine a patient named Maria arrives at the Emergency Department with a suspected urinary tract infection (UTI):

  1. Admission: A clerk registers Maria (POST /patients) and opens an emergency encounter (POST /patients/{id}/encounters with encounterType: EMERGENCY).

  2. First vitals: A nurse records heart rate 95, respiratory rate 20, BP 115/75, temperature 38.8°C, SpO2 97%, AVPU = Alert, room air. No critical thresholds are crossed, so no immediate alerts. In the background, NEWS2 calculates: total = 3 (temp score 1 + heart rate score 1 + resp rate score 0 + others 0 + temperature gives 1) → LOW risk, no alert.

  3. Labs ordered: The doctor orders blood cultures, lactate, and a CBC (POST /encounters/{id}/orders). Broad-spectrum antibiotics are administered (POST /encounters/{id}/medications).

  4. Two hours later — getting worse: New vitals come in: heart rate 118, respiratory rate 24, BP 95/60, temperature 39.5°C, SpO2 94%. The WARNING_SYSTOLIC_BP fires (below 100). NEWS2 recalculates to 8 → HIGH risk → NEWS2_EMERGENCY alert fires. qSOFA sees respiratory rate ≥22 and systolic BP ≤100 → 2 criteria → QSOFA_SCREEN fires.

  5. Lab results arrive: Lactate comes back at 3.2 mmol/L (high — tissue hypoxia). Creatinine is 2.1 mg/dL (elevated — kidneys struggling). SOFA score jumps by 3 points from baseline → SOFA_SEPSIS alert fires. A sepsis bundle is automatically created with a 1-hour deadline.

  6. Sepsis bundle completion: The team draws blood cultures, gives IV fluids, and confirms antibiotics were already given. Each order is completed via PATCH /orders/{id}/result. All 4 elements complete within the deadline → bundle status: COMPLIANT.

  7. Stabilization and discharge: Over the next 48 hours, vitals normalize. NEWS2 drops back to 2. SOFA returns to baseline. Maria is discharged with PATCH /encounters/{id}/status.

Throughout this story, the ward board showed Maria's deterioration in real time — her NEWS2 score rising, qSOFA flagging, SOFA alerting — giving the clinical team the information they needed to act fast.