chore: update docs

This commit is contained in:
voltsrage
2026-06-25 00:46:54 +08:00
parent 7bb9124230
commit df6fbed401
3 changed files with 132 additions and 63 deletions
+43 -26
View File
@@ -243,27 +243,55 @@ Grafana dashboard JSON template for alert quality overview.
| Clinical Usage | 10 |
| Composite | 9.0 |
> **Status:** Complete — see [phase-34-plan.md](plans/phase-34-plan.md) for full implementation detail, tests, and file inventory.
Architecture: Each alert carries a structured explanation payload alongside the existing `Details` string. A new `AlertExplanation` value object captures score contributors, trend context, and medication context at alert creation time. Scoring consumers (NEWS2, SOFA, GCS) emit contributor breakdowns. The `TrendDetector` attaches trend summaries. `MedicationCorrelationHelper` attaches recent medication context. All explanation data is serialized as JSONB on the `ClinicalAlert` row so the explanation is immutable — it reflects the state at alert time, not query time.
> **Prerequisite:** Phase 33 for feedback loop. No hard technical dependency but sequencing allows feedback data to inform which explanations clinicians value.
### What exists
After Phase 33:
After Phase 34 (implemented):
- `ClinicalAlert.Details` is a free-text string, sometimes containing score values
- `TrendDetector` returns `TrendOutcome` enum but no narrative description
- `MedicationCorrelationHelper.TryAnnotateDetailsAsync()` appends drug info to the details string
- NEWS2, SOFA, GCS scoring returns aggregate scores but not per-component breakdowns to the alert layer
- No structured explanation model
- `AlertExplanation` value object with `ScoreContributor`, `TrendContext`, `MedicationContext`, and `NarrativeSummary`
- `ClinicalAlert.Explanation` — nullable JSONB column, immutable at alert creation
- NEWS2, SOFA, GCS, and Trend detectors assemble explanation at alert creation; `alert.generated` outbox/Kafka payloads include `explanation`
- `MedicationCorrelationHelper.TryGetContextAsync()` — structured medication context (replaces string-append to `Details`)
- Scoring results carry contributors: `News2Result`, `SofaScoringResult`, `GcsResult`, `TrendResult`
- `AlertResponse` DTO with `Explanation`; GET/list/acknowledge/resolve endpoints return it
- Dashboard `AlertReasoning.vue` and alert surfaces consume structured explanation; legacy alerts fall back to `Details`
- Simulator `AlertResponse.Explanation` + `ExpectedOutcomeValidator` with `narrativeContains` on key scenarios
- Elasticsearch indexes `NarrativeSummary`; data lake Parquet includes `explanation_json`; ward gateway sync forwards explanation
- `ExplainableAlertsTests` (10 tests) + `scripts/run-phase34-verification.sh`
- `Details` string unchanged for backward compatibility with threshold-only and legacy alerts
### What needs to be built
Six steps, in order.
Nothing — Phase 34 is complete. **Next:** [Phase 35 — Alert Lifecycle Analytics](#phase-35--alert-lifecycle-analytics).
### Verification Checklist (Phase 34)
- [x] NEWS2 alert includes per-component score contributors in explanation
- [x] SOFA alert includes per-organ-system contributors
- [x] GCS alert includes Eye/Verbal/Motor breakdown
- [x] Trend-triggered alerts include trend context with percent change and duration
- [x] Medication-correlated alerts include drug context
- [x] Narrative summary is human-readable and accurate
- [x] Existing alerts with null explanation still serialize correctly
- [x] Alert GET endpoints return explanation object
- [x] No change to scoring algorithm outputs (same scores, same thresholds)
- [x] Migration applies cleanly on existing data
- [x] Dashboard and simulator consume explanation
- [x] Downstream consumers (ES, data lake, gateway sync) include explanation fields
---
#### Step 1 — Explanation Value Objects
#### Implementation reference (Steps 17)
The step-by-step design below is retained for interview prep and onboarding. All steps are implemented; see [phase-34-plan.md](plans/phase-34-plan.md) for code paths and tests.
<details>
<summary>Original step-by-step design (Steps 16)</summary>
**`Domains/ValueObjects/AlertExplanation.cs`** (NEW):
@@ -380,20 +408,7 @@ Extend alert GET endpoints to include the `Explanation` object. The explanation
}
```
---
### Verification Checklist (Phase 34)
- [ ] NEWS2 alert includes per-component score contributors in explanation
- [ ] SOFA alert includes per-organ-system contributors
- [ ] GCS alert includes Eye/Verbal/Motor breakdown
- [ ] Trend-triggered alerts include trend context with percent change and duration
- [ ] Medication-correlated alerts include drug context
- [ ] Narrative summary is human-readable and accurate
- [ ] Existing alerts with null explanation still serialize correctly
- [ ] Alert GET endpoints return explanation object
- [ ] No change to scoring algorithm outputs (same scores, same thresholds)
- [ ] Migration applies cleanly on existing data
</details>
---
@@ -415,6 +430,8 @@ After Phase 34:
- `ClinicalAlert` has `Status` transitions: Open → Acknowledged → Resolved, Open → Escalated → Acknowledged → Resolved
- `AcknowledgedAt`, `ResolvedAt` timestamps exist on the entity
- `ClinicalAlert.Explanation` JSONB with immutable score contributors, trend, and medication context at alert time
- `AlertResponse` exposes explanation to dashboard and simulator; `Details` retained for legacy consumers
- `ClinicalAuditLog` captures some transitions but is not structured for time-series analytics
- No dedicated lifecycle event log
- No computed lifecycle metrics (median ack time, escalation rate)
@@ -673,7 +690,7 @@ Authorized for `Admin` role only. PUT upserts a rule and invalidates the Redis c
Architecture: A correlation engine groups alerts that fire within a configurable time window for the same patient into a single clinical narrative. Correlated alerts are linked by a shared `CorrelationGroupId`. The first alert in a group becomes the primary; subsequent alerts within the window attach as secondary. The group carries a composite explanation built from Phase 34 individual explanations. Clinicians see one bundled notification with the full picture instead of multiple independent alerts.
> **Prerequisite:** Phase 34 (Explainable Alerts) for structured explanations to compose into bundles.
> **Prerequisite:** Phase 34 (Explainable Alerts) **complete** — structured explanations compose into bundle narratives.
### What exists
@@ -681,9 +698,9 @@ After Phase 36:
- Alerts fire independently per scoring consumer and trend detector
- No correlation between simultaneous alerts for the same patient
- Phase 34 `AlertExplanation` provides structured per-alert context
- Phase 34 `AlertExplanation` **shipped** — per-alert `NarrativeSummary`, score contributors, trend, and medication context available on `AlertResponse`
- `AlertSuppressionService` prevents duplicate alert types but not cross-type bundling
- Medication correlation annotates individual alerts but does not group them
- Medication correlation attaches structured context to individual alerts but does not group them
### What needs to be built
@@ -953,7 +970,7 @@ After Phase 38:
- qSOFA scoring implementation in dedicated service
- GCS scoring implementation in dedicated service
- Each has different input shapes, output shapes, and integration points
- Phase 34 added contributor extraction but each scoring system returns it differently
- Phase 34 added contributor extraction but each scoring system returns it differently (**implemented** — unify via `ScoreResult.Contributors` in Phase 39)
### What needs to be built
+44 -15
View File
@@ -2,7 +2,7 @@
## Overview
A production-quality clinical backend built with ASP.NET Core 8, PostgreSQL, Apache Kafka, RabbitMQ, Elasticsearch, Redis, and MinIO. The system models patient encounters, continuous observation ingest, and real-time clinical alerting with composite scoring engines (NEWS2, GCS, SOFA), Sepsis-3 two-tier detection (qSOFA screening → SOFA confirmation → treatment bundle), trend analysis, and alert suppression. It streams vital signs and lab results through Kafka, fans urgent notifications to clinicians through RabbitMQ with DLQ-based escalation, and maintains a searchable CQRS projection in Elasticsearch for ward dashboards and population analytics. A Vue 3 ward dashboard provides real-time clinical views. Ward gateway edge nodes buffer observations locally during connectivity loss. A FHIR R4 facade enables EHR integration. JWT-based RBAC with clinical audit logging gates every endpoint. Long-term data is archived as Parquet files in MinIO — a regulatory requirement in healthcare that has no equivalent in most other domains. Twenty-nine phases are implemented and verified.
A production-quality clinical backend built with ASP.NET Core 8, PostgreSQL, Apache Kafka, RabbitMQ, Elasticsearch, Redis, and MinIO. The system models patient encounters, continuous observation ingest, and real-time clinical alerting with composite scoring engines (NEWS2, GCS, SOFA), Sepsis-3 two-tier detection (qSOFA screening → SOFA confirmation → treatment bundle), trend analysis, alert suppression, and structured explainable alert payloads. It streams vital signs and lab results through Kafka, fans urgent notifications to clinicians through RabbitMQ with DLQ-based escalation, and maintains a searchable CQRS projection in Elasticsearch for ward dashboards and population analytics. A Vue 3 ward dashboard provides real-time clinical views with structured alert reasoning. Ward gateway edge nodes buffer observations locally during connectivity loss. A FHIR R4 facade enables EHR integration. JWT-based RBAC with clinical audit logging gates every endpoint. Long-term data is archived as Parquet files in MinIO — a regulatory requirement in healthcare that has no equivalent in most other domains. Thirty-two phases are implemented and verified.
The domain is deliberately different from the Digital Wallet API. Both projects use Kafka, RabbitMQ, and Elasticsearch, but the trade-off conversations are entirely different. In fintech the core question is "did the money move correctly?" In healthcare the core question is "did the right person get the right alert at the right time?" That distinction — correctness vs timeliness — produces different architectural decisions at every layer.
@@ -42,6 +42,7 @@ docker compose up -d
- Enable EHR integration via a FHIR R4 inbound facade with LOINC/SNOMED code mapping
- Support edge deployment via ward gateway nodes with offline buffering and central sync
- Secure all endpoints with JWT-based RBAC and maintain an append-only clinical audit trail
- Provide structured, immutable alert explanations (score contributors, trend context, medication context) so clinicians understand why composite alerts fired
- Produce a project that supports senior trade-off conversations in healthcare, medtech, and any domain where real-time alerting and long-term archival coexist
## Non-Goals
@@ -127,7 +128,7 @@ An **alert threshold** defines the numeric boundaries that trigger a clinical al
An **observation** is a single recorded measurement: a vital sign (heart rate, temperature, blood pressure), a lab value (potassium, glucose, white blood cell count), or a pulse oximetry reading. Observations are append-only. They are never updated or deleted. The observation stream is the primary input to both the alert engine and the Kafka pipeline.
A **clinical alert** is generated when an observation breaches a threshold or when the sepsis detection engine identifies a pattern across multiple recent observations. An alert has a lifecycle: `open``acknowledged``resolved`. An unacknowledged `CRITICAL` alert triggers the RabbitMQ escalation after five minutes.
A **clinical alert** is generated when an observation breaches a threshold or when the sepsis detection engine identifies a pattern across multiple recent observations. Composite alerts from NEWS2, SOFA, GCS, and trend detection carry an immutable JSONB `explanation` snapshot — score contributors, trend context, medication context, and a bedside narrative — frozen at alert creation time. An alert has a lifecycle: `open``acknowledged``resolved`. An unacknowledged `CRITICAL` alert triggers the RabbitMQ escalation after five minutes.
An **order** is a clinician's instruction: run this lab test, administer this medication, perform this imaging study. Orders have a status lifecycle and a `resulted_at` timestamp. The reconciliation job uses pending orders to detect cases where a result was never returned.
@@ -223,14 +224,17 @@ A `discharged` encounter triggers a RabbitMQ job to generate a discharge summary
### 4. Clinical Alert Lifecycle
**Description:** Alerts are the patient safety core of the system. Every open `CRITICAL` alert must be acknowledged by a clinician within five minutes or it escalates. Every alert has a documented audit trail: who acknowledged it, when, and with what note.
**Description:** Alerts are the patient safety core of the system. Every open `CRITICAL` alert must be acknowledged by a clinician within five minutes or it escalates. Every alert has a documented audit trail: who acknowledged it, when, and with what note. Composite alerts expose a structured `explanation` alongside the human-readable `details` string.
**Endpoints:**
- `GET /api/v1/encounters/:id/alerts` — paginated alert list for an encounter
- `GET /api/v1/encounters/:id/alerts` — paginated alert list for an encounter (`AlertResponse`)
- `GET /api/v1/alerts` — global alert list filterable by status, severity, department
- `GET /api/v1/alerts/:id` — alert detail
- `POST /api/v1/alerts/:id/acknowledge` — acknowledge with clinician ID and optional note
- `POST /api/v1/alerts/:id/resolve` — resolve (must be acknowledged first)
- `GET /api/v1/alerts/:id` — alert detail with optional `explanation`
- `POST /api/v1/alerts/:id/acknowledge` — acknowledge with clinician ID and optional note; returns `AlertResponse`
- `POST /api/v1/alerts/:id/resolve` — resolve (must be acknowledged first); returns `AlertResponse`
- `POST /api/v1/alerts/:id/feedback` — submit clinician feedback (Phase 33)
**Alert response shape:** optional `explanation` with `scoreContributors`, `trend`, `medicationContext`, and `narrativeSummary`. Omitted on legacy and threshold-only alerts.
**Alert lifecycle:**
```
@@ -529,9 +533,9 @@ Alert suppression windows prevent warning fatigue. Acknowledging a suppressible
---
### 15. Medication Administration and Correlation (Phase 15)
### 15. Medication Administration and Correlation (Phase 15, 34)
**Description:** Records drug administrations per encounter. `MedicationCorrelationHelper` annotates warning and NEWS2 alert details when a correlated drug was administered within a configurable window (default 90 min). Annotations provide clinical context — e.g. `— note: metoprolol 25mg (PO) administered 45 min ago` — but never suppress alerts.
**Description:** Records drug administrations per encounter. `MedicationCorrelationHelper` appends medication context to warning alert `details` when a correlated drug was administered within a configurable window (default 90 min). Explainable alerts (NEWS2, SOFA, GCS, rapid deterioration) receive structured `MedicationContext` in the JSONB `explanation` via `TryGetContextAsync()`. Annotations provide clinical context — e.g. `"metoprolol 25mg (PO) administered 45 min ago"` — but never suppress alerts.
---
@@ -565,19 +569,36 @@ Append-only `clinical_audit_logs` table records write actions with user identity
---
### 20. Ward Dashboard (Phases 1719, 22, 28)
### 20. Ward Dashboard (Phases 1719, 22, 28, 34)
**Description:** Vue 3 SPA (`vigilcare-dashboard/`) with Vite, Pinia, Tailwind CSS v4, and Chart.js. Virtual ward table (NEWS2-sorted, department filter), patient detail view (vitals, scores, alerts, orders, sepsis bundle, GCS entry form, SOFA score panel, patient banner with demographics, encounter timeline), and alert center (global acknowledge/resolve).
Chart components: five vital sign trend charts with medication administration markers, NEWS2 history, SOFA history with organ-system breakdown, GCS history with component tracking, qSOFA evaluation history. Local replay scrubbing, alert reasoning with medication context, and clinician feedback mode (six ratings per alert, Feedback Summary with JSON/CSV export).
Chart components: five vital sign trend charts with medication administration markers, NEWS2 history, SOFA history with organ-system breakdown, GCS history with component tracking, qSOFA evaluation history. Local replay scrubbing, structured alert reasoning (`AlertReasoning.vue` — score contributors, trend context, medication context, narrative summary from `explanation`), and clinician feedback mode (six ratings per alert, Feedback Summary with JSON/CSV export).
Phase 22 gap analysis fixes: `SofaHistory.vue`, `GcsHistory.vue`, `QsofaHistory.vue`, `PatientBanner.vue`, `EncounterTimeline.vue`, medication marker Chart.js plugin. Backend additions: `GET /gcs/history`, `GET /qsofa/history` APIs, `qsofa_evaluations` table.
---
### 21. Console Replay Simulator (Phase 16, 29)
### 21. Console Replay Simulator (Phase 16, 29, 34)
**Description:** Standalone `VigilCare.Simulator` .NET console app replays JSON scenario files against the live API with configurable speed. Commands: `replay`, `replay-all`, `validate`, `dry-run`. Optional `--poll` shows alerts, NEWS2, GCS, SOFA, and sepsis bundle state during replay. Eleven sample scenarios including GCS neurological decline, SOFA sepsis progression, and SpO₂/FiO₂ fallback.
**Description:** Standalone `VigilCare.Simulator` .NET console app replays JSON scenario files against the live API with configurable speed. Commands: `replay`, `replay-all`, `validate`, `dry-run`. Optional `--poll` shows alerts, NEWS2, GCS, SOFA, and sepsis bundle state during replay. `ExpectedOutcomeValidator` validates alert `narrativeContains` on key scenarios. Eleven sample scenarios including GCS neurological decline, SOFA sepsis progression, and SpO₂/FiO₂ fallback.
---
### 22. Explainable Alerts (Phase 34)
**Description:** Each composite alert carries a structured explanation payload alongside the existing `details` string. An `AlertExplanation` value object captures score contributors, trend context, and medication context at alert creation time. Scoring consumers (NEWS2, SOFA, GCS) emit contributor breakdowns. `TrendDetector` attaches trend summaries. `MedicationCorrelationHelper.TryGetContextAsync()` attaches recent medication context. Explanation data is serialized as JSONB on the `ClinicalAlert` row — immutable, reflecting state at alert time, not query time.
**Key components:**
- `AlertExplanation`, `ScoreContributor`, `TrendContext`, `MedicationContext` value objects
- Contributor builders: `News2ContributorBuilder`, `SofaContributorBuilder`, `GcsContributorBuilder`, `TrendContextBuilder`
- `AlertExplanationBuilder` + `ClinicalAlertFactory` — idempotent INSERT with explanation JSON
- `AlertResponse` DTO with optional `Explanation`; exposed on GET/list/acknowledge/resolve
- `alert.generated` outbox/Kafka payload includes optional `explanation` field
- Elasticsearch indexes `NarrativeSummary`; data lake Parquet includes `explanation_json`
- Ward gateway `LocalClinicalAlert.ExplanationJson` synced via `ClinicalSyncBatchProcessor`
**Concepts practiced:** Explainable AI / CDS transparency, immutable clinical snapshots, human factors / alert usability (`NarrativeSummary` for bedside readability), backward-compatible API evolution (legacy alerts return null explanation).
---
@@ -657,6 +678,7 @@ CREATE TABLE clinical_alerts (
alert_type VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL,
details TEXT NOT NULL,
explanation JSONB NULL, -- Phase 34: immutable structured explanation snapshot
observation_code VARCHAR(50) NULL, -- enables direct lookups without LIKE pattern matching
status VARCHAR(20) NOT NULL DEFAULT 'open',
acknowledged_at TIMESTAMPTZ NULL,
@@ -713,7 +735,7 @@ CREATE TABLE reconciliation_alerts (
);
```
**Tables added in Phases 1031** (managed by EF Core migrations — see `README.md` Data Models for full column definitions):
**Tables added in Phases 1034** (managed by EF Core migrations — see `README.md` Data Models for full column definitions):
| Table | Phase | Purpose |
|---|---|---|
@@ -729,6 +751,10 @@ CREATE TABLE reconciliation_alerts (
| `clinical_audit_logs` | 31 | Append-only audit trail: action, entity, user, before/after JSONB, IP, correlation ID |
| `clinical_sites` | 20 | Hospital sites with site code, name, address |
| `ward_gateways` | 20 | Ward edge nodes with status, buffer depth, heartbeat, sync timestamps |
| `alert_feedback` | 33 | Clinician feedback per user per alert (six feedback types) |
| `alert_quality_metrics` | 33 | Per-alert-type quality metric snapshots (acknowledgement/false-positive/useful rates) |
**Column additions:** `clinical_alerts.explanation` (JSONB, Phase 34) — immutable structured explanation on composite alerts.
---
@@ -810,6 +836,9 @@ Observations, alerts, and orders belong to an encounter, not directly to a patie
| 29 | Simulator scenario expansion + clinical validation (end-to-end qSOFA → SOFA → bundle) | Done |
| 30 | FHIR R4 Inbound Facade — per-resource ingest, transaction Bundles, read/search, LOINC mapping | Done |
| 31 | RBAC + Clinical Audit Logging — JWT auth, four roles, 17 permissions, append-only audit trail | Done |
| 23 | Degraded Operations Visibility — gateway fleet ops, stale detection, discharge summary, admin panels | Done |
| 33 | Alert Quality Analytics — server-side feedback, quality metrics API, Grafana dashboard | Done |
| 34 | Explainable Alerts — JSONB explanation, contributor builders, AlertResponse DTO, dashboard reasoning | Done |
---
@@ -928,4 +957,4 @@ Follow the same Prometheus/Grafana and MinIO/Parquet approach as described in th
### Phases 1031 — Extended Feature Phases
See the [Build Order](#build-order) table for all implemented phases and the [Features](#features) section (items 1221) for detailed descriptions. Per-phase implementation plans and verification guides are in `docs/plans/`. Integration tests and verification scripts cover all phases — see `README.md` for the full test table and script listing.
See the [Build Order](#build-order) table for all implemented phases and the [Features](#features) section (items 1222) for detailed descriptions. Per-phase implementation plans and verification guides are in `docs/plans/`. Integration tests and verification scripts cover all phases — see `README.md` for the full test table and script listing.