add patient lifecycle doc
This commit is contained in:
@@ -374,6 +374,7 @@ docs/
|
|||||||
├── plans/ # Phase implementation and verification guides
|
├── plans/ # Phase implementation and verification guides
|
||||||
├── clinical-testing-guide.md # Doctor/nurse guide — alert review & feedback sessions
|
├── clinical-testing-guide.md # Doctor/nurse guide — alert review & feedback sessions
|
||||||
├── dashboard-guide.md # VigilCare Dashboard user guide (ward, patient detail, charts)
|
├── dashboard-guide.md # VigilCare Dashboard user guide (ward, patient detail, charts)
|
||||||
|
├── patient-encounter-api-lifecycle.md # Full API walkthrough: registration → active stay → discharge
|
||||||
├── simulator-guide.md # VigilCare.Simulator user guide
|
├── simulator-guide.md # VigilCare.Simulator user guide
|
||||||
├── decisions/
|
├── decisions/
|
||||||
│ ├── data-lake-design.md # Parquet vs JSON, partitioning, replay rationale
|
│ ├── data-lake-design.md # Parquet vs JSON, partitioning, replay rationale
|
||||||
|
|||||||
@@ -23,6 +23,77 @@ fires (the BP is genuinely low and may need monitoring), but the details say:
|
|||||||
That single line changes the clinical interpretation from "something is wrong" to "the
|
That single line changes the clinical interpretation from "something is wrong" to "the
|
||||||
medication is working — keep monitoring."
|
medication is working — keep monitoring."
|
||||||
|
|
||||||
|
The authoritative endpoint list lives in the [API Reference](../../README.md#api-reference)
|
||||||
|
(`/api/v1` prefix, standard `{ success, statusCode, data, error }` envelope). For the
|
||||||
|
full patient journey from registration through discharge — not only medication correlation —
|
||||||
|
see [Patient Encounter API Lifecycle](../patient-encounter-api-lifecycle.md). This
|
||||||
|
document explains how medication correlation fits into that surface — which endpoints
|
||||||
|
write administrations, which paths produce annotated alerts, and which endpoints are
|
||||||
|
deliberately outside the correlation scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relationship to the full API
|
||||||
|
|
||||||
|
Medication correlation is not a standalone micro-API. It is a cross-cutting concern that
|
||||||
|
connects medication write endpoints, observation ingest, asynchronous alert consumers, and
|
||||||
|
alert read endpoints. The table below maps every `/api/v1` resource group to its role.
|
||||||
|
|
||||||
|
| Resource group | Endpoints | Role in medication correlation |
|
||||||
|
|---|---|---|
|
||||||
|
| **Patients** | `POST/GET /patients`, `GET /patients/{id}` | Prerequisite only — patients must exist before encounters are opened. No correlation logic. |
|
||||||
|
| **Encounters** | `GET/POST /encounters`, `PATCH /encounters/{id}/status`, `GET /encounters/{id}/timeline`, `GET /encounters/{id}/qsofa/current` | Medications and observations are scoped to an **active** encounter. `GET /encounters/{id}` and `GET /encounters/{id}/timeline` return open alerts whose `details` may already include server-side annotations; the timeline does **not** include medication administrations. |
|
||||||
|
| **Alert thresholds** | `POST/GET/PUT /alert-thresholds` | Defines the warning/critical bands that `WarningEvaluator` compares against. Threshold changes invalidate the Redis cache but do not affect drug-vital mappings (`MedicationCorrelation` config is separate). |
|
||||||
|
| **Observations** | `POST/GET /encounters/{id}/observations` | **Write path** that eventually triggers correlation. `POST` returns `alertGenerated` only for **critical** breaches created synchronously (never annotated). Warning and NEWS2 alerts are created asynchronously by Kafka consumers and include annotations when applicable. |
|
||||||
|
| **Clinical alerts** | `GET /encounters/{id}/alerts`, `GET /alerts`, `GET /alerts/{id}`, `POST /alerts/{id}/acknowledge`, `POST /alerts/{id}/resolve` | **Read path** where annotations surface. The `details` field on `ClinicalAlert` carries the `— note: …` suffix. Acknowledge/resolve lifecycle is unchanged. |
|
||||||
|
| **Orders** | `POST/GET /encounters/{id}/orders`, `GET/PATCH /orders/{id}` | Unrelated to correlation. Sepsis-bundle orders are auto-created by the sepsis engine, not by medication recording. |
|
||||||
|
| **Analytics** | `GET /analytics/patients`, `/observations/trend`, `/alerts/summary`, `/population` | Elasticsearch-backed reporting. Alert summaries index the annotated `details` text but do not run correlation themselves. |
|
||||||
|
| **NEWS2** | `GET /encounters/{id}/news2/current`, `GET /encounters/{id}/news2/history` | Scores are computed asynchronously (`news2-scoring` consumer). When a NEWS2 warning/emergency alert is created, its `details` may be annotated the same way as threshold warnings. Score history endpoints return numeric components only — annotations live on the alert, not the score row. |
|
||||||
|
| **Sepsis bundles** | `GET /encounters/{id}/sepsis-bundle/current`, `GET /sepsis-bundles/{id}` | Deliberately excluded from annotation (see [What is NOT annotated](#what-is-not-annotated-and-why)). |
|
||||||
|
| **Medications** | `POST/GET /encounters/{id}/medications`, `GET /medications/{id}` | **Write and read path** for administrations. Correlation reads via `MedicationService.GetRecentForEncounterAsync`; HTTP clients (dashboard, simulator) use the list endpoint to show recent drugs alongside alert reasoning. |
|
||||||
|
|
||||||
|
There is no dedicated "correlate" or "annotate" endpoint. Correlation runs inside background
|
||||||
|
consumers at alert-creation time and is visible only through alert `details` and
|
||||||
|
medication list responses.
|
||||||
|
|
||||||
|
### End-to-end API choreography (metoprolol example)
|
||||||
|
|
||||||
|
A typical integration or simulator run exercises these endpoints in order:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. POST /api/v1/patients → register patient (MRN assigned)
|
||||||
|
2. POST /api/v1/patients/{id}/encounters → open encounter (status: active)
|
||||||
|
3. POST /api/v1/encounters/{id}/medications → record metoprolol 25mg PO
|
||||||
|
4. POST /api/v1/encounters/{id}/observations → ingest SYSTOLIC_BP = 95
|
||||||
|
│
|
||||||
|
├─ HTTP 201: observation row created; alertGenerated = false
|
||||||
|
│ (warning path is async — no annotation in this response)
|
||||||
|
│
|
||||||
|
└─ Kafka observation.recorded
|
||||||
|
→ WarningAlertService (consumer group: warning-evaluator)
|
||||||
|
→ MedicationCorrelationHelper annotates details
|
||||||
|
→ clinical_alerts row + alert.generated outbox event
|
||||||
|
5. GET /api/v1/encounters/{id}/alerts → poll until WARNING_SYSTOLIC_BP appears
|
||||||
|
details: "SYSTOLIC_BP value 95 is below warning low of 90. — note: metoprolol 25mg (PO) administered 45 min ago"
|
||||||
|
6. GET /api/v1/encounters/{id}/medications → optional; dashboard uses this for the
|
||||||
|
"Recent medications" panel in alert reasoning (client-side 90-min window)
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow a few seconds between steps 4 and 5 for the Kafka consumer to process the
|
||||||
|
`observation.recorded` event. NEWS2 follows the same pattern via `news2-scoring` after
|
||||||
|
all seven parameters are present.
|
||||||
|
|
||||||
|
### Where annotations appear (and where they do not)
|
||||||
|
|
||||||
|
| Response field / endpoint | Contains annotation? | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST …/observations` → `alertGenerated` / inline alert | No (warnings) | Only critical alerts return synchronously; critical path never calls `MedicationCorrelationHelper`. |
|
||||||
|
| `GET …/alerts`, `GET /alerts/{id}`, `GET /encounters/{id}` (embedded alerts) | Yes (warning + NEWS2) | Primary consumer surface. `details` is plain text with appended `— note: …`. |
|
||||||
|
| `GET …/encounters/{id}/timeline` | Yes (on alert events) | Timeline merges observations and alerts; alert events include annotated `details`. |
|
||||||
|
| `alert.generated` Kafka / outbox payload | Yes | `details` in the event matches the persisted alert row. |
|
||||||
|
| `GET …/news2/current`, `GET …/news2/history` | No | Scores only; read the corresponding NEWS2 alert for annotated context. |
|
||||||
|
| `GET …/medications` | No | Returns raw administration rows; dashboard correlates client-side for the reasoning panel. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How the pieces fit together
|
## How the pieces fit together
|
||||||
@@ -36,8 +107,9 @@ Nurse records medication
|
|||||||
▼
|
▼
|
||||||
┌─────────────────────┐
|
┌─────────────────────┐
|
||||||
│ MedicationsController│ ◄── thin HTTP layer, no business logic
|
│ MedicationsController│ ◄── thin HTTP layer, no business logic
|
||||||
│ POST /encounters/ │
|
│ POST /api/v1/ │
|
||||||
│ {id}/medications │
|
│ encounters/{id}/ │
|
||||||
|
│ medications │
|
||||||
└────────┬────────────┘
|
└────────┬────────────┘
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
@@ -54,11 +126,17 @@ Nurse records medication
|
|||||||
|
|
||||||
... time passes, vital signs arrive ...
|
... time passes, vital signs arrive ...
|
||||||
|
|
||||||
New observation arrives (e.g. SYSTOLIC_BP = 95)
|
POST /api/v1/encounters/{id}/observations (e.g. SYSTOLIC_BP = 95)
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌─────────────────────┐
|
┌─────────────────────┐
|
||||||
│ WarningEvaluator │ ◄── or News2Detector for composite scores
|
│ ObservationService │ ◄── sync path: critical alerts only (never annotated)
|
||||||
|
│ COMMIT + outbox │ always emits observation.recorded → Kafka
|
||||||
|
└────────┬────────────┘
|
||||||
|
│
|
||||||
|
▼ (async, consumer group: warning-evaluator)
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ WarningEvaluator │ ◄── or News2Detector (consumer group: news2-scoring)
|
||||||
│ │
|
│ │
|
||||||
│ 1. Load threshold │ (from Redis cache)
|
│ 1. Load threshold │ (from Redis cache)
|
||||||
│ 2. Check breach │ (is 95 < warningLow of 90?)
|
│ 2. Check breach │ (is 95 < warningLow of 90?)
|
||||||
@@ -87,6 +165,10 @@ New observation arrives (e.g. SYSTOLIC_BP = 95)
|
|||||||
│ which drugs are "relevant"│
|
│ which drugs are "relevant"│
|
||||||
│ to which vital signs │
|
│ to which vital signs │
|
||||||
└────────────────────────────┘
|
└────────────────────────────┘
|
||||||
|
|
||||||
|
... client reads result ...
|
||||||
|
|
||||||
|
GET /api/v1/encounters/{id}/alerts → annotated details in response
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -208,11 +290,51 @@ do what it's good at.
|
|||||||
|
|
||||||
### 4. MedicationsController
|
### 4. MedicationsController
|
||||||
|
|
||||||
**What it is:** Three HTTP endpoints:
|
**What it is:** Three HTTP endpoints under `/api/v1` (see also
|
||||||
- `POST /api/v1/encounters/{encounterId}/medications` — record a new administration
|
[Medications in the API Reference](../../README.md#medications)):
|
||||||
- `GET /api/v1/encounters/{encounterId}/medications` — list with pagination and optional
|
|
||||||
`since` filter
|
| Method | Path | Purpose |
|
||||||
- `GET /api/v1/medications/{id}` — look up a single record
|
|---|---|---|
|
||||||
|
| `POST` | `/encounters/{encounterId}/medications` | Record a new administration |
|
||||||
|
| `GET` | `/encounters/{encounterId}/medications` | List with pagination and optional `since` filter |
|
||||||
|
| `GET` | `/medications/{id}` | Look up a single record (includes nested `encounter`) |
|
||||||
|
|
||||||
|
**POST body** (`CreateMedicationAdministrationRequest`):
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `drugName` | string | yes | Trimmed on persist; case-insensitive for `DrugVitalMappings` lookup |
|
||||||
|
| `dose` | decimal | yes | Must be > 0 |
|
||||||
|
| `doseUnit` | string | yes | e.g. `mg`, `g`, `mcg`, `units` |
|
||||||
|
| `route` | string | yes | e.g. `PO`, `IV`, `SubQ` |
|
||||||
|
| `administeredAt` | DateTimeOffset | no | Defaults to server UTC time if omitted |
|
||||||
|
| `administeredBy` | string | yes | Clinician or nurse identifier |
|
||||||
|
|
||||||
|
**POST response:** `201 Created` with `ApiResponse<MedicationAdministration>` in the
|
||||||
|
standard envelope. The `data` object includes `id`, `encounterId`, `drugName`, `dose`,
|
||||||
|
`doseUnit`, `route`, `administeredAt`, `administeredBy`.
|
||||||
|
|
||||||
|
**POST status codes:**
|
||||||
|
|
||||||
|
| Code | When |
|
||||||
|
|---|---|
|
||||||
|
| `201` | Administration recorded |
|
||||||
|
| `400` | FluentValidation failure (empty drug name, zero dose, `administeredAt` > 5 min in future, field length exceeded) |
|
||||||
|
| `404` | Encounter not found (`ENCOUNTER_NOT_FOUND`) |
|
||||||
|
| `409` | Encounter not active (`ENCOUNTER_NOT_ACTIVE`) |
|
||||||
|
|
||||||
|
**GET list response:** `200 OK` with paginated `{ items, page, pageSize, totalCount, totalPages }`.
|
||||||
|
Query params: `since` (ISO 8601 — administrations at or after this time), `page` (default 1),
|
||||||
|
`pageSize` (default 20). The dashboard's `fetchMedications` helper pages through this
|
||||||
|
endpoint (default `pageSize` 50) to populate the alert-reasoning panel.
|
||||||
|
|
||||||
|
**GET by id response:** `200 OK` with full `MedicationAdministration` (404 if not found).
|
||||||
|
|
||||||
|
**Why medication POST does not trigger correlation:**
|
||||||
|
Recording a drug does not create or modify alerts. Correlation is evaluated only when a
|
||||||
|
warning or NEWS2 alert is **created** — triggered by observation ingest (async) or NEWS2
|
||||||
|
scoring (async). This keeps the medication write path fast and idempotent with no
|
||||||
|
side effects beyond the `medication_administrations` row.
|
||||||
|
|
||||||
**Why the controller is almost empty:**
|
**Why the controller is almost empty:**
|
||||||
The controller's only job is to translate HTTP concepts (route parameters, query strings,
|
The controller's only job is to translate HTTP concepts (route parameters, query strings,
|
||||||
@@ -329,6 +451,12 @@ The annotated details string is also included in the outbox event payload, so do
|
|||||||
consumers (notifications, dashboards) receive the medication context without needing
|
consumers (notifications, dashboards) receive the medication context without needing
|
||||||
their own correlation logic.
|
their own correlation logic.
|
||||||
|
|
||||||
|
Clients that poll `GET /api/v1/encounters/{id}/alerts` or `GET /api/v1/alerts/{id}` read
|
||||||
|
the same annotated `details` string. The ward dashboard (`vigilcare-dashboard`) adds a
|
||||||
|
second layer: `AlertReasoning.vue` fetches `GET …/medications` and shows administrations
|
||||||
|
within 90 minutes before `alert.triggeredAt`, independent of whether the server appended
|
||||||
|
the `— note:` suffix (e.g. when the drug name is not in `DrugVitalMappings`).
|
||||||
|
|
||||||
### News2Detector annotation strategy
|
### News2Detector annotation strategy
|
||||||
|
|
||||||
NEWS2 is different from single-vital warnings because it's a composite score of 7
|
NEWS2 is different from single-vital warnings because it's a composite score of 7
|
||||||
@@ -349,7 +477,7 @@ pharmaceutical review.
|
|||||||
## Data flow summary
|
## Data flow summary
|
||||||
|
|
||||||
```
|
```
|
||||||
HTTP POST
|
HTTP POST /api/v1/encounters/{id}/medications
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌────────────────┐
|
┌────────────────┐
|
||||||
@@ -364,26 +492,45 @@ pharmaceutical review.
|
|||||||
└────────────────┘ │ administrations │
|
└────────────────┘ │ administrations │
|
||||||
└────────┬────────┘
|
└────────┬────────┘
|
||||||
│
|
│
|
||||||
... later, observation arrives ... │
|
HTTP POST /api/v1/encounters/{id}/observations
|
||||||
│
|
(observation row + observation.recorded outbox)
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
┌────────────────┐ │
|
┌────────────────┐ │
|
||||||
│ Warning │ │
|
│ Kafka │ │
|
||||||
│ Evaluator │ │
|
│ observation. │ │
|
||||||
|
│ recorded │ │
|
||||||
|
└───────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
┌────────────┴────────────┐ │
|
||||||
|
▼ ▼ │
|
||||||
|
┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ WarningAlert │ │ News2Scoring │ │
|
||||||
|
│ Service │ │ Service │ │
|
||||||
|
└──────┬───────┘ └──────┬───────┘ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ builds details│ │
|
▼ ▼ │
|
||||||
│ │ │ │
|
┌────────────────┐ ┌────────────────┐ │
|
||||||
│ ▼ │ │
|
│ Warning │ │ News2 │ │
|
||||||
│ Correlation │◄───────────────┘
|
│ Evaluator │ │ Detector │ │
|
||||||
│ Helper │ queries recent meds
|
│ │ │ │ │ │ │
|
||||||
│ │ │ for this encounter +
|
│ ▼ │ │ ▼ │ │
|
||||||
│ ▼ │ observation code
|
│ Correlation │◄──────┴───────┘ │
|
||||||
|
│ Helper │◄────────────────────────┘
|
||||||
|
│ │ │ queries recent meds
|
||||||
|
│ ▼ │ for encounter + code
|
||||||
│ annotated │
|
│ annotated │
|
||||||
│ details │
|
│ details │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ INSERT alert │──────► PostgreSQL clinical_alerts
|
│ INSERT alert │──────► PostgreSQL clinical_alerts
|
||||||
│ + outbox event│──────► outbox_events → Kafka
|
│ + outbox event│──────► outbox_events → Kafka alert.generated
|
||||||
└────────────────┘
|
└────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
GET /api/v1/encounters/{id}/alerts
|
||||||
|
GET /api/v1/alerts/{id}
|
||||||
|
(annotated details in JSON response)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,524 @@
|
|||||||
|
# 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 — not from one subsystem's perspective, but across the full `/api/v1` surface.
|
||||||
|
|
||||||
|
**Companion docs:**
|
||||||
|
|
||||||
|
- [API Reference](../README.md#api-reference) — authoritative endpoint contracts and request bodies
|
||||||
|
- [Medication correlation design](decisions/medication-correlation-design.md) — how drug context annotates warning/NEWS2 alerts
|
||||||
|
- [Sepsis engine design](decisions/sepsis-engine-design.md) — SIRS/qSOFA Redis state and bundle creation
|
||||||
|
- [Simulator guide](simulator-guide.md) — replay JSON scenarios against this lifecycle
|
||||||
|
- [Dashboard guide](dashboard-guide.md) — which endpoints the Vue ward UI polls
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What this document covers
|
||||||
|
|
||||||
|
A hospital stay in VigilCare is an **encounter** scoped to a **patient**. Everything clinical — observations, alerts, orders, medications, NEWS2 scores, sepsis bundles — hangs off that encounter while it is `ACTIVE`. This guide walks through the lifecycle in chronological order: what to call, what happens synchronously in the HTTP response, what happens asynchronously via Kafka consumers, and what stops working after discharge.
|
||||||
|
|
||||||
|
There is no single "admit patient" orchestration endpoint. Integrators compose the standard REST resources below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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, SIRS/qSOFA, trend alerts, and sepsis bundles are created by Kafka consumers — allow a few seconds after observation ingest before polling read endpoints |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lifecycle overview
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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 [Async engines via Kafka]
|
||||||
|
K1[warning-evaluator]
|
||||||
|
K2[news2-scoring]
|
||||||
|
K3[sepsis-engine]
|
||||||
|
K4[trend-analyzer]
|
||||||
|
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
|
||||||
|
K1 & K2 & K3 & K4 --> T1
|
||||||
|
M2 & M3 --> T1
|
||||||
|
T1 --> T2 --> T3
|
||||||
|
M3 --> T4
|
||||||
|
T3 --> D1 --> D2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Encounter status machine** (enforced by `PATCH /encounters/{id}/status`):
|
||||||
|
|
||||||
|
```
|
||||||
|
scheduled → active → discharged
|
||||||
|
→ cancelled
|
||||||
|
```
|
||||||
|
|
||||||
|
`POST /patients/{id}/encounters` creates encounters directly in `ACTIVE` (the `scheduled` state exists in the model but is not used by the open-encounter endpoint). Illegal transitions return **409** (`ILLEGAL_STATUS_TRANSITION`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Platform prerequisites (before any patient)
|
||||||
|
|
||||||
|
These are typically seeded at deploy time, not called per patient.
|
||||||
|
|
||||||
|
| What | How | Why it matters |
|
||||||
|
|---|---|---|
|
||||||
|
| Alert thresholds (12 observation codes) | Seeded in PostgreSQL; loaded into Redis on startup; manageable via `POST/GET/PUT /alert-thresholds` | Every observation ingest validates against a configured code; thresholds drive critical (sync) and warning (async) alerts |
|
||||||
|
| Kafka topics | Provisioned by `KafkaTopicProvisioner` | `observation.recorded`, `alert.generated`, `encounter.status.changed` (+ sepsis bundle topics) |
|
||||||
|
| Drug-vital mappings | `MedicationCorrelation` section in `appsettings.json` | Medication context on warning/NEWS2 alert `details` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 on the patient record for ward context |
|
||||||
|
|
||||||
|
**Response:** `201 Created` — `data` includes system-generated `mrn` (e.g. `MRN-000042`) and `id` (UUID). Save both; the MRN is the human-facing identifier, the UUID is used in all subsequent 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 | Clinical context for admission |
|
||||||
|
|
||||||
|
**Response:** `201 Created` — encounter `id`, `status: ACTIVE`, `admittedAt`.
|
||||||
|
|
||||||
|
**Side effects:**
|
||||||
|
|
||||||
|
- Outbox event `encounter.status.changed` → Kafka → Elasticsearch ward index, data lake, discharge-summary queue listener
|
||||||
|
- Only **one active encounter per patient per encounter type** — duplicate returns **409** (`DUPLICATE_ACTIVE_ENCOUNTER`)
|
||||||
|
|
||||||
|
From this point, `encounterId` is the primary key for all clinical writes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Ward visibility (who is on the floor)
|
||||||
|
|
||||||
|
While the patient is active, ward systems poll aggregated state rather than joining tables client-side.
|
||||||
|
|
||||||
|
### Virtual ward board
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/encounters?status=ACTIVE&department=ICU&page=1&pageSize=20
|
||||||
|
```
|
||||||
|
|
||||||
|
Each row (`WardEncounterSummary`) includes:
|
||||||
|
|
||||||
|
| Field | Source |
|
||||||
|
|---|---|
|
||||||
|
| `encounterId`, `patientId`, `mrn`, `firstName`, `lastName`, `roomBed`, `department`, `status` | Encounter + patient |
|
||||||
|
| `news2Score`, `news2RiskLevel` | Latest `news2_scores` row |
|
||||||
|
| `qsofaScore` | Live Redis count (`GET /encounters/{id}/qsofa/current` uses the same backing store) |
|
||||||
|
| `sepsisActive` | Whether a non-compliant sepsis bundle exists |
|
||||||
|
| `sepsisBundleStatus` | `IN_PROGRESS`, `COMPLIANT`, `NON_COMPLIANT` |
|
||||||
|
| `openAlertCount` | Open clinical alerts for the encounter |
|
||||||
|
|
||||||
|
The dashboard sorts this list by NEWS2 score for acuity-first display.
|
||||||
|
|
||||||
|
### Single-patient chart header
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/encounters/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the encounter with nested `patient`, the **10 most recent observations**, and **open alerts**. Use this for a chart summary; use dedicated list endpoints for full history.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Continuous monitoring (observation ingest)
|
||||||
|
|
||||||
|
Observations are the heartbeat of the system. Bedside devices, manual entry, and lab interfaces all use the same endpoint.
|
||||||
|
|
||||||
|
### Record measurements
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/encounters/{encounterId}/observations
|
||||||
|
```
|
||||||
|
|
||||||
|
Body: `{ "observations": [ … ] }` — **1 to 10** objects per call.
|
||||||
|
|
||||||
|
| Observation field | Required | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `observationCode` | yes | Must match a configured alert threshold (12 seeded codes) |
|
||||||
|
| `value`, `unit` | yes | Plausibility-checked per code |
|
||||||
|
| `recordedAt` | yes | When the measurement was taken |
|
||||||
|
| `source` | no | `DEVICE` (default), `MANUAL`, `LAB` |
|
||||||
|
|
||||||
|
Optional header: `Idempotency-Key` — retries with the same key return the original row without duplicate insert.
|
||||||
|
|
||||||
|
### What happens inside one ingest (synchronous HTTP path)
|
||||||
|
|
||||||
|
1. Encounter must be `ACTIVE`
|
||||||
|
2. Idempotency check
|
||||||
|
3. Plausibility validation → **422** if out of range
|
||||||
|
4. Insert `observations` row
|
||||||
|
5. Load threshold from Redis
|
||||||
|
6. **If CRITICAL breach:** insert `clinical_alert` + `alert.generated` outbox event **in the same transaction**
|
||||||
|
7. Always insert `observation.recorded` outbox event
|
||||||
|
8. `COMMIT`
|
||||||
|
|
||||||
|
**HTTP response (`201`):** per-observation result with `observation`, `alertGenerated` (true only for **critical** sync alerts), `alertId`, `duplicate`.
|
||||||
|
|
||||||
|
| Status | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `201` | Recorded |
|
||||||
|
| `200` | Idempotency key matched existing row |
|
||||||
|
| `404` | Encounter not found |
|
||||||
|
| `409` | Encounter not active |
|
||||||
|
| `422` | Plausibility failure or unknown observation code |
|
||||||
|
|
||||||
|
### What happens after commit (asynchronous — not in the HTTP response)
|
||||||
|
|
||||||
|
Every `observation.recorded` Kafka message is consumed independently:
|
||||||
|
|
||||||
|
| Consumer group | Produces |
|
||||||
|
|---|---|
|
||||||
|
| `warning-evaluator` | `WARNING_*` threshold alerts (with optional medication annotation) |
|
||||||
|
| `news2-scoring` | `news2_scores` row; `NEWS2_WARNING` / `NEWS2_EMERGENCY` when all 7 parameters present |
|
||||||
|
| `sepsis-engine` | `SEPSIS_WARNING` (SIRS ≥2) or `QSOFA_WARNING` (qSOFA ≥2); triggers sepsis bundle |
|
||||||
|
| `trend-analyzer` | `RAPID_DETERIORATION` when rate-of-change exceeds configured velocity |
|
||||||
|
| `es-indexer` | Elasticsearch projection for ward search and analytics |
|
||||||
|
| `data-lake-writer` | Parquet files in MinIO |
|
||||||
|
|
||||||
|
**Integrator pattern:** after `POST …/observations`, poll `GET /encounters/{id}/alerts` or use the simulator's `--poll` flag. Do not expect warning or NEWS2 alerts in the ingest response body.
|
||||||
|
|
||||||
|
### Read observation history
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/encounters/{encounterId}/observations?code=HEART_RATE&from=…&to=…&limit=50&cursor=…
|
||||||
|
```
|
||||||
|
|
||||||
|
Cursor-paginated on `(recorded_at DESC, id DESC)` — preferred over offset pagination for live streams.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — Clinical interventions (medications and orders)
|
||||||
|
|
||||||
|
These run in parallel with monitoring; they do not replace observation ingest.
|
||||||
|
|
||||||
|
### 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. When a later warning or NEWS2 alert fires, `MedicationCorrelationHelper` may append context to `details` if a mapped drug was given within the correlation window (default 90 min). See [medication-correlation-design.md](decisions/medication-correlation-design.md).
|
||||||
|
|
||||||
|
### 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 machine:**
|
||||||
|
|
||||||
|
```
|
||||||
|
pending → in_progress → resulted
|
||||||
|
→ cancelled
|
||||||
|
```
|
||||||
|
|
||||||
|
`PATCH …/result` is the clinical completion path — it transitions to `Resulted` and, for sepsis-bundle-linked orders, marks the corresponding bundle element complete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 — Alerts fire (detection to triage)
|
||||||
|
|
||||||
|
Alerts are created by multiple engines (sync critical threshold, async warning, NEWS2, SIRS/qSOFA, trend). All share the same read and lifecycle API.
|
||||||
|
|
||||||
|
### List and inspect
|
||||||
|
|
||||||
|
| Endpoint | Use |
|
||||||
|
|---|---|
|
||||||
|
| `GET /encounters/{id}/alerts?status=OPEN` | Per-patient alert list |
|
||||||
|
| `GET /alerts?status=OPEN&severity=CRITICAL&department=ICU` | Hospital-wide alert center |
|
||||||
|
| `GET /alerts/{id}` | Alert detail with encounter |
|
||||||
|
|
||||||
|
Each `ClinicalAlert` includes `alertType`, `severity`, `details`, `status`, `triggeredAt`, and optional `observationId`.
|
||||||
|
|
||||||
|
**Alert types you will see during an active stay:**
|
||||||
|
|
||||||
|
| Type | Engine | Severity | Medication annotation |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `CRITICAL_*` | Sync ingest | CRITICAL | Never |
|
||||||
|
| `WARNING_*` | `warning-evaluator` | WARNING | Yes, when correlated |
|
||||||
|
| `NEWS2_WARNING`, `NEWS2_EMERGENCY` | `news2-scoring` | WARNING / CRITICAL | Yes (warnings) |
|
||||||
|
| `SEPSIS_WARNING`, `QSOFA_WARNING` | `sepsis-engine` | WARNING / CRITICAL | Never |
|
||||||
|
| `RAPID_DETERIORATION` | `trend-analyzer` | WARNING | Never |
|
||||||
|
|
||||||
|
### Acknowledge and resolve
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/alerts/{id}/acknowledge
|
||||||
|
Body: { "clinicianId": "DR-SMITH", "note": "optional" }
|
||||||
|
|
||||||
|
POST /api/v1/alerts/{id}/resolve
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lifecycle:**
|
||||||
|
|
||||||
|
```
|
||||||
|
open → acknowledged → resolved
|
||||||
|
→ escalated (unacknowledged CRITICAL > 5 min → RabbitMQ DLQ → on-call page)
|
||||||
|
```
|
||||||
|
|
||||||
|
Acknowledging a suppressible warning (`WARNING_*`, `NEWS2_WARNING`) sets a Redis suppression window (default 30 min) so duplicate warnings for the same encounter/type do not fire repeatedly. Critical and sepsis alerts are never suppressed.
|
||||||
|
|
||||||
|
### Notification path (no HTTP endpoint)
|
||||||
|
|
||||||
|
`alert.generated` → Kafka → RabbitMQ `alerts.paging.queue` → clinician page simulation. Critical unacknowledged alerts escalate via DLQ. This is infrastructure-side; integrators observe it through alert `status` and Prometheus `escalations_total`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6 — Composite scores (NEWS2 and qSOFA)
|
||||||
|
|
||||||
|
### NEWS2
|
||||||
|
|
||||||
|
Requires all seven 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 → cursor-paginated score history
|
||||||
|
```
|
||||||
|
|
||||||
|
Scores and NEWS2 alerts are **async** — ingest the seventh parameter, wait a few seconds, then poll.
|
||||||
|
|
||||||
|
### qSOFA
|
||||||
|
|
||||||
|
No dedicated score history table. Live criteria count from Redis:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/encounters/{id}/qsofa/current
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns active criteria count (0–3). When count reaches 2, `sepsis-engine` creates a `QSOFA_WARNING` alert and a sepsis bundle (same as SIRS).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7 — Sepsis bundle (when infection is suspected)
|
||||||
|
|
||||||
|
Triggered automatically when `SEPSIS_WARNING` or `QSOFA_WARNING` fires — not by a direct API call.
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/encounters/{id}/sepsis-bundle/current
|
||||||
|
GET /api/v1/sepsis-bundles/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bundle contents:** four elements (blood cultures, serum lactate, broad-spectrum antibiotics, IV fluid resuscitation), each backed by an auto-created order (`orderedBy: sepsis-bundle-engine`).
|
||||||
|
|
||||||
|
**Compliance flow:**
|
||||||
|
|
||||||
|
1. Bundle created with `complianceStatus: IN_PROGRESS`, `deadlineAt` = recognition + 1 hour
|
||||||
|
2. Clinician (or simulator) results orders via `PATCH /orders/{id}/result`
|
||||||
|
3. Each result marks the matching bundle element `COMPLETED`
|
||||||
|
4. When all four complete: `COMPLIANT` (within deadline) or `NON_COMPLIANT` (past deadline)
|
||||||
|
5. `SepsisBundleMonitorService` marks overdue in-progress bundles `NON_COMPLIANT` every 5 minutes
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Merged chronological stream of **observations** and **alerts** (not medications, orders, or status changes). Useful for audit-style review and dashboard replay alignment.
|
||||||
|
|
||||||
|
### Elasticsearch analytics (read-only, cross-encounter)
|
||||||
|
|
||||||
|
| Endpoint | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `GET /analytics/patients?q=…&department=…` | Patient/encounter search |
|
||||||
|
| `GET /analytics/observations/trend?encounterId=…&code=…` | Hourly avg/min/max time series |
|
||||||
|
| `GET /analytics/alerts/summary?severity=…&department=…` | Alert volume by department |
|
||||||
|
| `GET /analytics/population?code=…&threshold=…` | Patients above/below threshold in a window |
|
||||||
|
|
||||||
|
These read from the CQRS projection fed by Kafka — not from live PostgreSQL ingest tables.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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` set to server UTC time
|
||||||
|
- Outbox `encounter.status.changed` → Kafka → Elasticsearch status update, data lake, **discharge summary worker** (PDF to MinIO at `/discharge-summaries/{encounterId}/summary.pdf`)
|
||||||
|
|
||||||
|
### What stops working after discharge
|
||||||
|
|
||||||
|
| Action | Result |
|
||||||
|
|---|---|
|
||||||
|
| `POST …/observations` | **409** `ENCOUNTER_NOT_ACTIVE` |
|
||||||
|
| `POST …/medications` | **409** `ENCOUNTER_NOT_ACTIVE` |
|
||||||
|
| `POST …/orders` | **409** `ENCOUNTER_NOT_ACTIVE` |
|
||||||
|
| `GET …/alerts`, `GET …/observations`, `GET …/news2/history` | Still work — historical read |
|
||||||
|
| `GET /encounters?status=ACTIVE` | Patient drops off the active ward list |
|
||||||
|
|
||||||
|
**Cancellation path:** `PATCH …/status` with `{ "status": "CANCELLED" }` from `ACTIVE` (e.g. admission error). Same write restrictions apply afterward.
|
||||||
|
|
||||||
|
A patient may later receive a new encounter via `POST /patients/{id}/encounters` — each episode is independent; MRN persists across the 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` |
|
||||||
|
| **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 (curl-style sequence)
|
||||||
|
|
||||||
|
Minimal happy-path sequence an integrator or simulator follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 1. Admit
|
||||||
|
POST /api/v1/patients
|
||||||
|
POST /api/v1/patients/{patientId}/encounters
|
||||||
|
|
||||||
|
# 2. Baseline vitals (may not trigger alerts)
|
||||||
|
POST /api/v1/encounters/{encounterId}/observations
|
||||||
|
|
||||||
|
# 3. Treat
|
||||||
|
POST /api/v1/encounters/{encounterId}/medications
|
||||||
|
POST /api/v1/encounters/{encounterId}/orders
|
||||||
|
|
||||||
|
# 4. Monitor & respond (repeat during stay)
|
||||||
|
POST /api/v1/encounters/{encounterId}/observations
|
||||||
|
GET /api/v1/encounters/{encounterId}/alerts
|
||||||
|
GET /api/v1/encounters/{encounterId}/news2/current
|
||||||
|
GET /api/v1/encounters/{encounterId}/sepsis-bundle/current
|
||||||
|
POST /api/v1/alerts/{alertId}/acknowledge
|
||||||
|
PATCH /api/v1/orders/{orderId}/result
|
||||||
|
|
||||||
|
# 5. Ward round
|
||||||
|
GET /api/v1/encounters?status=ACTIVE
|
||||||
|
GET /api/v1/encounters/{encounterId}
|
||||||
|
GET /api/v1/encounters/{encounterId}/timeline
|
||||||
|
|
||||||
|
# 6. Discharge
|
||||||
|
PATCH /api/v1/encounters/{encounterId}/status { "status": "DISCHARGED", "dischargeDiagnosis": "…" }
|
||||||
|
```
|
||||||
|
|
||||||
|
For a worked clinical narrative (UTI → sepsis, medication false alarm, rapid deterioration), replay the JSON scenarios in `VigilCare.Simulator/Scenarios/List/` — each scenario file is a scripted instance of this lifecycle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 …/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` |
|
||||||
|
|
||||||
|
Clinician feedback ratings are stored in the browser only (no API endpoint) — see [clinical-testing-guide.md](clinical-testing-guide.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sync vs async quick reference
|
||||||
|
|
||||||
|
| Event | Visible in HTTP response? | Poll these endpoints |
|
||||||
|
|---|---|---|
|
||||||
|
| Observation saved | Yes (`201`) | `GET …/observations` |
|
||||||
|
| Critical alert | Yes (`alertGenerated: true`) | `GET …/alerts` |
|
||||||
|
| Warning alert | No — Kafka | `GET …/alerts` (after ~1–3 s) |
|
||||||
|
| NEWS2 score / alert | No — Kafka | `GET …/news2/current`, `GET …/alerts` |
|
||||||
|
| SIRS / qSOFA / sepsis bundle | No — Kafka | `GET …/alerts`, `GET …/sepsis-bundle/current`, `GET …/qsofa/current` |
|
||||||
|
| Trend alert | No — Kafka | `GET …/alerts` |
|
||||||
|
| Medication annotation on alert | No — applied at alert insert | `GET …/alerts` → read `details` |
|
||||||
|
| Discharge summary PDF | No — RabbitMQ worker | MinIO `/discharge-summaries/{encounterId}/summary.pdf` |
|
||||||
Reference in New Issue
Block a user