chore: update readme.md

This commit is contained in:
voltsrage
2026-06-23 04:39:16 +08:00
parent efe5419da4
commit 383df0dde5
+124 -8
View File
@@ -2,7 +2,7 @@
A production-quality clinical backend built with ASP.NET Core 8, PostgreSQL, Apache Kafka, RabbitMQ, Elasticsearch, Redis, and MinIO. The domain models the observe-alert-acknowledge lifecycle at the center of any clinical monitoring system: patient encounters, continuous vital sign and lab result ingest, real-time sepsis and NEWS2 scoring, and clinician notification with automatic escalation.
**Implementation status:** Twenty-six planned phases are complete through Phase 31 — from schema and CRUD through Kafka, Elasticsearch CQRS, sepsis detection, RabbitMQ paging with DLQ escalation, reconciliation jobs, Prometheus/Grafana observability, the MinIO Parquet data lake, clinical data model expansion, warning alerts and orders, the NEWS2 composite scoring engine, trend detection with alert suppression, qSOFA bedside screening, medication administration with alert correlation annotations, the console replay simulator, the **Vue 3 ward dashboard**, clinician feedback mode, **Glasgow Coma Scale (GCS) scoring**, **SOFA organ-dysfunction scoring with baseline tracking and delta sepsis alerts**, the **Sepsis-3 clinical refactor** (SIRS removed, qSOFA repositioned as screening, SOFA delta ≥ 2 triggers bundles), **frontend GCS entry and SOFA display**, **expanded simulator scenarios with clinical validation**, the **FHIR R4 Inbound Facade** for EHR integration, and **Role-Based Access Control (RBAC) with clinical audit logging**. Post-phase hardening includes health check endpoints, Kafka poison pill protection, outbox dead-letter with retry tracking, data lake partial-commit safety, MRN sequence-based generation, FHIR bundle transaction rollback, **FHIR R4 read/search endpoints** (Patient and Encounter), **alert threshold deletion with audit trail**, **FHIR API key rotation** (constant-time multi-key validation), **authorization failure logging** with Prometheus metrics, and **JWT signing key validation** at startup. See [Implemented Phases](#implemented-phases) for the full breakdown. Guides: [dashboard-guide.md](docs/dashboard-guide.md) (technical), [clinical-testing-guide.md](docs/clinical-testing-guide.md) (doctors & nurses).
**Implementation status:** Twenty-seven planned phases are complete through Phase 31 (plus Phase 20) — from schema and CRUD through Kafka, Elasticsearch CQRS, sepsis detection, RabbitMQ paging with DLQ escalation, reconciliation jobs, Prometheus/Grafana observability, the MinIO Parquet data lake, clinical data model expansion, warning alerts and orders, the NEWS2 composite scoring engine, trend detection with alert suppression, qSOFA bedside screening, medication administration with alert correlation annotations, the console replay simulator, the **Vue 3 ward dashboard**, clinician feedback mode, **Glasgow Coma Scale (GCS) scoring**, **SOFA organ-dysfunction scoring with baseline tracking and delta sepsis alerts**, the **Sepsis-3 clinical refactor** (SIRS removed, qSOFA repositioned as screening, SOFA delta ≥ 2 triggers bundles), **frontend GCS entry and SOFA display**, **expanded simulator scenarios with clinical validation**, the **Site & Gateway Registry** with dual authentication, shared clinical sync contracts, and fleet health Prometheus gauges, the **FHIR R4 Inbound Facade** for EHR integration, and **Role-Based Access Control (RBAC) with clinical audit logging**. Post-phase hardening includes health check endpoints, Kafka poison pill protection, outbox dead-letter with retry tracking, data lake partial-commit safety, MRN sequence-based generation, FHIR bundle transaction rollback, **FHIR R4 read/search endpoints** (Patient and Encounter), **alert threshold deletion with audit trail**, **FHIR API key rotation** (constant-time multi-key validation), **authorization failure logging** with Prometheus metrics, and **JWT signing key validation** at startup. See [Implemented Phases](#implemented-phases) for the full breakdown. Guides: [dashboard-guide.md](docs/dashboard-guide.md) (technical), [clinical-testing-guide.md](docs/clinical-testing-guide.md) (doctors & nurses).
## Domain Model — How It Maps to a Real Clinical System
@@ -77,6 +77,7 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
- **FHIR R4 Read/Search** — `GET /fhir/R4/Patient/{id}` reads a Patient by internal ID; `GET /fhir/R4/Patient` searches by `identifier` (system|value) or lists all patients; `GET /fhir/R4/Encounter/{id}` reads an Encounter by internal ID; `GET /fhir/R4/Encounter` searches by `patient` (UUID) and/or `status` (`in-progress`, `finished`, `cancelled`); all return FHIR R4 JSON (`application/fhir+json`); search endpoints return `Bundle.type=searchset`; requires `fhir:read` permission (Admin and Integration roles); internal resources mapped back to FHIR via `PatientFhirMapper.ToFhirResponse` / `EncounterFhirMapper.ToFhirResponse` with hospital identifier resolution; Prometheus `fhir_read_total` counter with `resource_type`, `interaction`, `outcome` labels
- **Role-Based Access Control (RBAC)** — JWT bearer authentication (`POST /auth/login`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 17 granular permissions (`patients:read`, `alerts:acknowledge`, `thresholds:write`, `fhir:ingest`, `fhir:read`, `audit:read`, etc.); `AuthorizePermission` attribute on every controller action; `PermissionAuthorizationHandler` resolves role → permission at runtime from `ClinicalRolePermissionMap` and logs authorization failures with structured details (user, role, permission, endpoint) plus `authorization_failures_total` Prometheus counter; `CurrentUserService` exposes authenticated identity (user ID, display name, role, IP address) to services; nurses and physicians get clinical read/write permissions; admins additionally get `thresholds:write`, `fhir:read`, `audit:read`, and `users:admin`; integration accounts get FHIR ingest and read access; FHIR endpoints accept both JWT and `X-Api-Key` authentication via `FhirApiKeyOrJwtMiddleware`; alert `acknowledgedBy` is set from the authenticated user identity, not the request body; startup validates JWT signing key is at least 256 bits (HMAC-SHA256 minimum); four seeded demo users (`nurse.demo`, `physician.demo`, `admin.demo`, `integration.mirth`); frontend login page with `localStorage` token persistence and automatic `Authorization: Bearer` header injection
- **Clinical Audit Logging** — append-only `clinical_audit_logs` table records clinical write actions with user identity, entity type/ID, before/after state (JSONB), reason, IP address, and correlation ID; ten audit actions (`THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `THRESHOLD_DELETED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`, `AUTHORIZATION_DENIED`); `AuditService` writes log entries inline with domain operations; `GET /audit-logs` admin-only query endpoint with filters by entity type, entity ID, user ID, action, and time range; indexed on entity type, entity ID, user ID, and timestamp
- **Site & Gateway Registry** — `ClinicalSite` and `WardGateway` domain entities model ward edge nodes that buffer clinical data during connectivity loss; `POST /sites` creates clinical sites; `POST /sites/{siteId}/gateways` registers gateways under a site; `PATCH /gateways/{gatewayId}/heartbeat` (gateway API key auth) updates status (`ONLINE`, `DEGRADED`, `OFFLINE`) and reported buffer depth; `GET /sites/{siteId}/gateways` lists gateways with optional `?department=` filter; dual authentication — JWT + RBAC (`users:admin`) for admin CRUD, `GatewayApiKeyAuthenticationHandler` (`X-Api-Key` + `X-Gateway-Id`) for gateway heartbeat and future sync upload; constant-time key comparison via `CryptographicOperations.FixedTimeEquals`; `VigilCare.ClinicalContracts` shared class library with sync DTOs (`ClinicalSyncBatchRequest`, `SyncedObservation`, `SyncedAlertEvent`, `GatewayHeartbeatRequest`) consumed by both central API and ward gateway projects; Prometheus `ward_gateways_offline_gauge` and `ward_gateway_buffer_depth` via `WardGatewayMetricsCollector` (60s periodic); `GatewayRegistrySeeder` provides demo site and gateway for Docker Compose and tests; FluentValidation on all request DTOs; `GatewayRegistryTests` and `ClinicalContractsTests` integration tests
- **Health Check Endpoints** — `GET /health/live` (liveness — always returns 200 if the process is running) and `GET /health/ready` (readiness — checks PostgreSQL, Redis, Kafka, RabbitMQ, and Elasticsearch connectivity); both return structured JSON with per-check status and duration; anonymous access; suitable for Kubernetes probes and load balancer health checks
- **Kafka Poison Pill Protection** — `PoisonPillGuard` prevents a single un-processable message from blocking a consumer partition forever; permanent errors (malformed JSON, bad format) are skipped immediately; transient errors are retried up to `MaxPoisonRetries` (default 5) before the offset is committed and the message is abandoned; all eight Kafka consumers use the guard; skipped messages are logged at CRITICAL with full payload and tracked by Prometheus `kafka_poison_pills_skipped_total` (labeled by consumer group and topic)
- **Outbox Dead-Letter with Retry Tracking** — `OutboxRelayService` tracks `RetryCount`, `LastError`, and `FailedAt` per event; events that fail `OutboxMaxRetries` (default 10) Kafka produce attempts are marked permanently failed (`FailedAt` set) and excluded from future relay polls; uses `FOR UPDATE SKIP LOCKED` for safe concurrent relay instances; idempotent Kafka producer (`EnableIdempotence = true`) prevents duplicate messages from network-level retries
@@ -103,8 +104,8 @@ HTTP request
→ FhirApiKeyOrJwtMiddleware (X-Api-Key or JWT bearer for /fhir/* routes)
→ CorrelationIdMiddleware
→ ExceptionHandlerMiddleware
→ JWT Authentication + RBAC (PermissionAuthorizationHandler)
→ Controllers (REST API + FHIR R4 ingest)
→ JWT Authentication + RBAC (PermissionAuthorizationHandler) / GatewayApiKeyAuthenticationHandler (X-Api-Key for gateway routes)
→ Controllers (REST API + FHIR R4 ingest + Site/Gateway registry)
→ Services
├── CurrentUserService (authenticated user identity from JWT claims)
├── AuditService (append-only clinical_audit_logs on write actions)
@@ -137,6 +138,7 @@ IHostedServices (background):
AlertsUnacknowledgedCollector → polls PostgreSQL every 30s → alerts_unacknowledged_gauge
OutboxPendingCollector → polls outbox every 30s → outbox_pending_events
KafkaConsumerLagCollector → polls four consumer groups every 30s → kafka_consumer_lag
WardGatewayMetricsCollector → polls gateway status/buffer depth every 60s → ward_gateways_offline_gauge, ward_gateway_buffer_depth
ClinicalMetrics (singleton) → inline counters/histogram from ingest, qSOFA, NEWS2, GCS, SOFA, trend, suppression, bundle compliance, escalation paths
```
@@ -190,6 +192,8 @@ VigilCareClinicalAPI/
│ ├── GcsController.cs # Latest GCS score per encounter
│ ├── SofaController.cs # Current SOFA score and cursor-paginated history
│ ├── SepsisBundlesController.cs # Current bundle per encounter, bundle detail by ID
│ ├── SitesController.cs # Clinical site CRUD (create, list, get)
│ ├── GatewaysController.cs # Gateway register, list, get, heartbeat (dual auth: JWT + API key)
│ ├── FhirIngestController.cs # FHIR R4 ingest: Patient, Encounter, Observation, MedicationAdministration, Bundle
│ ├── FhirReadController.cs # FHIR R4 read/search: GET Patient/{id}, GET Patient, GET Encounter/{id}, GET Encounter
│ ├── FhirMetadataController.cs # FHIR R4 CapabilityStatement (GET /fhir/R4/metadata)
@@ -211,6 +215,8 @@ VigilCareClinicalAPI/
│ │ ├── SepsisBundleElement.cs # Individual bundle element linked to a clinical order
│ │ ├── MedicationAdministration.cs # Drug administration record per encounter
│ │ ├── ExternalResourceIdentifier.cs # Links external system identifiers (MRN, visit#) to internal UUIDs
│ │ ├── ClinicalSite.cs # Hospital site with site code, name, address
│ │ ├── WardGateway.cs # Ward edge node with status, buffer depth, heartbeat, sync timestamps
│ │ ├── ClinicalUser.cs # Username, BCrypt password hash, display name, role, active flag
│ │ └── ClinicalAuditLog.cs # Append-only audit trail: action, entity, user, before/after JSONB, IP, correlation ID
│ └── Enums/
@@ -221,6 +227,7 @@ VigilCareClinicalAPI/
│ ├── AlertSeverity.cs # Warning, Critical
│ ├── AlertStatus.cs # Open, Acknowledged, Resolved, Escalated
│ ├── AlertType.cs # Threshold breach, QSOFA_SCREEN, SOFA_SEPSIS, warning*, NEWS2_*, GCS_*, …
│ ├── GatewayStatus.cs # Online, Degraded, Offline with ToDbString/FromDbString
│ ├── BloodType.cs # A+, O-, AB-, … with ToDbString/FromDbString
│ ├── ObservationSource.cs # Device, Manual, Lab
│ ├── SepsisBundleComplianceStatus.cs # InProgress, Compliant, NonCompliant
@@ -255,8 +262,10 @@ VigilCareClinicalAPI/
│ ├── PermissionAuthorizationHandler.cs # ASP.NET Core authorization handler resolving role claims
│ ├── PermissionPolicyProvider.cs # Dynamic policy provider for perm:* policies
│ └── PermissionRequirement.cs # IAuthorizationRequirement for a single permission string
├── Authentication/
│ └── GatewayApiKeyAuthenticationHandler.cs # X-Api-Key + X-Gateway-Id auth for gateway heartbeat/sync routes
├── Services/
│ ├── Interfaces/ # IPatientService, IEncounterService, IAuditService, IAuthService, ICurrentUserService, …
│ ├── Interfaces/ # IPatientService, IEncounterService, IAuditService, IAuthService, ICurrentUserService, ISiteService, IGatewayRegistryService,
│ ├── AuthService.cs # Login (BCrypt verify), JWT generation, login audit log
│ ├── AuditService.cs # Append-only clinical audit log writer (user, entity, before/after, IP, correlation ID)
│ ├── CurrentUserService.cs # Extracts authenticated user identity from JWT claims (HttpContext)
@@ -275,6 +284,8 @@ VigilCareClinicalAPI/
│ ├── MedicationService.cs # Medication CRUD; GetRecentForEncounterAsync for correlation
│ ├── QsofaService.cs # Redis-backed qSOFA criteria count for API/dashboard
│ ├── ExternalIdentifierService.cs # Links/resolves external system identifiers to internal UUIDs
│ ├── SiteService.cs # Clinical site CRUD
│ ├── GatewayRegistryService.cs # Gateway register, list, heartbeat, mark offline
│ ├── WarningEvaluator.cs # Warning-range evaluation; suppression + medication annotation; idempotent INSERT
│ ├── AnalyticsService.cs # Elasticsearch query wrappers
│ └── PlausibilityValidator.cs # Per-code numeric range guard
@@ -283,7 +294,7 @@ VigilCareClinicalAPI/
│ └── TrendDetector.cs # Redis history + RAPID_DETERIORATION alert creation
├── Medication/
│ └── MedicationCorrelationHelper.cs # Appends drug context to warning/NEWS2 alert details
├── Validators/ # FluentValidation — RegisterPatient, OpenEncounter, IngestObservation, CreateMedicationAdministration, …
├── Validators/ # FluentValidation — RegisterPatient, OpenEncounter, IngestObservation, CreateMedicationAdministration, CreateSiteRequest, RegisterGatewayRequest, GatewayHeartbeatRequest,
├── Observability/
│ └── Metrics/
│ └── ClinicalMetrics.cs # Prometheus metric families (counters, histograms, gauges); includes FHIR read and authorization failure metrics
@@ -294,7 +305,8 @@ VigilCareClinicalAPI/
│ ├── Metrics/
│ │ ├── AlertsUnacknowledgedCollector.cs # Polls open CRITICAL alerts > 5 min → alerts_unacknowledged_gauge
│ │ ├── OutboxPendingCollector.cs # Polls unprocessed outbox rows → outbox_pending_events
│ │ ── KafkaConsumerLagCollector.cs # Lag for es-indexer, sepsis-engine, notification-publisher, data-lake-writer
│ │ ── KafkaConsumerLagCollector.cs # Lag for es-indexer, sepsis-engine, notification-publisher, data-lake-writer
│ │ └── WardGatewayMetricsCollector.cs # Polls gateway status/buffer depth every 60s → offline gauge, buffer depth
│ ├── ElasticsSearch/
│ │ ├── ElasticIndexProvisioner.cs # Creates patient_encounters, observations, clinical_alerts indices
│ │ └── EsIndexerService.cs # consumer group: es-indexer; upserts Elasticsearch documents
@@ -371,9 +383,10 @@ VigilCareClinicalAPI/
│ └── Sepsis/QsofaResult.cs, QsofaOutcome.cs # qSOFA detector result and screening outcome enum
├── Data/
│ ├── AppDbContext.cs # EF Core context — entity configs, indexes, constraints
│ ├── Configurations/ # IEntityTypeConfiguration per entity; ClinicalUserConfiguration, ClinicalAuditLogConfiguration; ElasticsearchOptions, ElasticIndexOptions
│ ├── Configurations/ # IEntityTypeConfiguration per entity; ClinicalUserConfiguration, ClinicalAuditLogConfiguration, ClinicalSiteConfiguration, WardGatewayConfiguration; ElasticsearchOptions, ElasticIndexOptions
│ └── Seed/
│ ├── DataSeeder.cs # Seeds patients, encounters, thresholds, observations
│ ├── GatewayRegistrySeeder.cs # Seeds demo site (SITE-DEMO) and gateway (GW-ICU-3B) with fixed GUIDs
│ └── UserSeeder.cs # Seeds four demo users (nurse, physician, admin, integration)
├── Common/
│ ├── ApiResponse.cs # { success, statusCode, data, error } envelope
@@ -426,11 +439,26 @@ tests/
├── QsofaCurrentTests.cs # qSOFA current API — Redis state, criteria breakdown
├── GcsScoringTests.cs # GCS component scoring, alerts, NEWS2/qSOFA integration paths
├── SofaScoringTests.cs # SOFA organ scores, baseline, delta alerts, carry-forward, vasopressors
├── GatewayRegistryTests.cs # Gateway register, heartbeat, API key auth, department filter
├── Helpers/GatewayAuthHelper.cs # WithGatewayApiKey extension method for test clients
├── Auth/
│ └── RbacTests.cs # RBAC — unauthenticated 401, nurse 403 on threshold write, admin audit log creation
└── Fhir/
└── FhirIngestTests.cs # FHIR R4 patient upsert idempotency, observation LOINC mapping, unknown code 422, transaction bundle
VigilCare.ClinicalContracts/ # Phase 20 — shared sync DTOs (no ASP.NET dependency)
├── VigilCare.ClinicalContracts.csproj # net8.0 class library, no NuGet packages
└── Sync/
├── ClinicalSyncBatchRequest.cs # Batch upload envelope (batchReference, gatewayId, siteId, items)
├── SyncedObservation.cs # Per-observation sync item with idempotency key
├── SyncedAlertEvent.cs # Client-generated alert event
├── SyncedAlertAcknowledgment.cs # Alert acknowledgment from ward
├── SyncedAlertResolution.cs # Alert resolution from ward
└── GatewayHeartbeatRequest.cs # Status + buffer depth + timestamp
VigilCare.ClinicalContracts.Tests/ # Contracts round-trip serialization tests
└── ClinicalContractsTests.cs
VigilCare.Simulator/ # Phase 16 — console replay simulator (HTTP-only, no direct DB/Kafka)
├── Program.cs # CLI: replay, replay-all, validate, dry-run
├── Commands/ # System.CommandLine command handlers
@@ -471,6 +499,7 @@ scripts/
├── run-phase26-verification.sh # Phase 26 — SOFA scoring integration tests + baseline/delta API checks
├── run-phase27-verification.sh # Phase 27 — Sepsis-3 refactor: SIRS removal, QSOFA_SCREEN, SOFA bundle trigger
├── run-phase28-verification.sh # Phase 28 — Frontend GCS entry + SOFA display + sepsis UI refactor
├── run-phase20-verification.sh # Phase 20 — Gateway registry tests + site/gateway/heartbeat curl checks
├── run-phase29-verification.sh # Phase 29 — Simulator scenario expansion + clinical validation
├── run-phase30-verification.sh # Phase 30 — FHIR R4 ingest integration tests + manual bundle/metadata checks
└── run-phase31-verification.sh # Phase 31 — RBAC integration tests + JWT login + audit log query
@@ -695,6 +724,8 @@ Integration tests use `WebApplicationFactory` with a `Testing` environment and T
| `QsofaCurrentTests` | — | `GET /qsofa/current` — criteria count and breakdown from Redis |
| `GcsScoringTests` | 25 | GCS component scoring, classification, alerts, CNS integration with SOFA |
| `SofaScoringTests` | 26 | SOFA organ scores, baseline eligibility, delta alerts, carry-forward, vasopressors |
| `GatewayRegistryTests` | 20 | Gateway register, heartbeat status/buffer, API key auth 401, degraded status, department filter |
| `ClinicalContractsTests` | 20 | ClinicalSyncBatchRequest JSON round-trip serialization |
| `FhirIngestTests` | 30 | FHIR R4 patient upsert idempotency, LOINC observation mapping, unknown code 422, transaction bundle |
| `RbacTests` | 31 | Unauthenticated 401, nurse 403 on threshold write, admin threshold update with audit log creation, alert acknowledge uses authenticated user |
@@ -703,6 +734,7 @@ Integration tests use `WebApplicationFactory` with a `Testing` environment and T
With the API running (`dotnet run`) and Docker Compose up:
```bash
./scripts/run-phase20-verification.sh # Gateway registry tests, site/gateway/heartbeat API verification
./scripts/run-phase8-verification.sh # Prometheus target UP, ten metrics, alerts_unacknowledged_gauge live update
./scripts/run-phase9-verification.sh # DataLakePhase9Tests, Kafka consumer group, MinIO Parquet, DuckDB schema
./scripts/run-phase10-verification.sh # 12 Redis thresholds, clinical enrichment, ES pipeline, Phase 10 integration tests
@@ -744,6 +776,12 @@ Phase 15 unit/integration tests only:
dotnet test --filter "FullyQualifiedName~Medication"
```
Phase 20 gateway tests only:
```bash
dotnet test --filter "FullyQualifiedName~GatewayRegistry|FullyQualifiedName~ClinicalContracts"
```
Phase 31 RBAC tests only:
```bash
@@ -805,6 +843,8 @@ See `docs/plans/phase-8-plan.md` through `docs/plans/phase-12-plan.md` for manua
| `fhir_read_total` | Counter | `resource_type`, `interaction`, `outcome` | `FhirReadController` — per resource type (`Patient`, `Encounter`) with interaction (`read`, `search`) and `success` outcome |
| `authorization_failures_total` | Counter | `permission`, `role` | `PermissionAuthorizationHandler` — authorization denials by required permission and user role |
| `fhir_mapping_errors_total` | Counter | `resource_type` | `FhirExceptionFilter` — mapping/validation failures by resource type |
| `ward_gateways_offline_gauge` | Gauge | `site_code` | `WardGatewayMetricsCollector` — count of gateways with status OFFLINE or DEGRADED per site |
| `ward_gateway_buffer_depth` | Gauge | `gateway_code`, `department` | `WardGatewayMetricsCollector` — reported unsynced event count per gateway |
| `kafka_poison_pills_skipped_total` | Counter | `consumer_group`, `topic` | `PoisonPillGuard` — messages skipped as permanently un-processable (malformed JSON, format errors, or transient failures exceeding MaxPoisonRetries) |
Prometheus scrapes the API via `infra/prometheus/prometheus.yml` (`job: vigilcare_api` → `host.docker.internal:5270`). Grafana loads the clinical dashboard from `infra/grafana/dashboards/vigilcare.json`.
@@ -1129,6 +1169,48 @@ Bundles are created automatically by `SepsisAlertHandler` when a `SOFA_SEPSIS` a
When a correlated drug was given within the `MedicationCorrelation.CorrelationWindowMinutes` window (default 90), subsequent warning and NEWS2 alerts for affected vitals include an annotation in `details` — e.g. `— note: metoprolol 25mg (PO) administered 45 min ago`. See `docs/decisions/medication-correlation-design.md`.
### Sites
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | `/sites` | JWT + `users:admin` | Create a clinical site |
| GET | `/sites` | JWT + `users:admin` | List all sites |
| GET | `/sites/{siteId}` | JWT + `users:admin` | Site detail |
**POST body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `siteCode` | string | yes | Uppercase alphanumeric with hyphens (max 32) |
| `name` | string | yes | Hospital/site name (max 200) |
| `address` | string | no | Site address (max 500) |
### Gateways
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | `/sites/{siteId}/gateways` | JWT + `users:admin` | Register a gateway under a site |
| GET | `/sites/{siteId}/gateways` | JWT + `users:admin` | List gateways; optional `?department=` filter |
| GET | `/gateways/{gatewayId}` | JWT + `users:admin` | Gateway detail |
| PATCH | `/gateways/{gatewayId}/heartbeat` | Gateway API key | Update status and buffer depth |
**POST `/sites/{siteId}/gateways` body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `gatewayCode` | string | yes | Unique per site (max 64) |
| `department` | string | yes | Department assignment (max 100) |
**PATCH `/gateways/{gatewayId}/heartbeat` body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `status` | string | yes | `ONLINE`, `DEGRADED`, or `OFFLINE` |
| `bufferDepth` | int | yes | Unsynced event count (≥ 0) |
| `reportedAtUtc` | DateTimeOffset | yes | Gateway-reported timestamp |
**Gateway API key auth:** Set `X-Api-Key` header (configured in `ApiKey:Gateway`) and `X-Gateway-Id` header. The handler validates the key with constant-time comparison and confirms the gateway ID in the URL matches the header.
### Authentication
| Method | Path | Description |
@@ -1504,6 +1586,37 @@ Append-only — no UPDATE or DELETE from application code.
Indexes: `(entity_type)`, `(entity_id)`, `(user_id)`, `(created_at)`
### ClinicalSite
```
id Guid PK
siteCode string required, unique (max 32) — uppercase alphanumeric with hyphens
name string required (max 200)
address string? free text
active bool default true
createdAt DateTimeOffset
```
Indexes: unique `(site_code)`
### WardGateway
```
id Guid PK
siteId Guid FK → ClinicalSite (RESTRICT)
gatewayCode string required (max 64)
department string required (max 100)
status string ONLINE | DEGRADED | OFFLINE (default: OFFLINE)
reportedBufferDepth int default 0
lastHeartbeatAt DateTimeOffset?
lastSyncAt DateTimeOffset?
createdAt DateTimeOffset
```
Indexes: unique `(site_id, gateway_code)`, `(site_id, department)`, partial `(status) WHERE status != 'ONLINE'`
Check constraint: `status IN ('ONLINE', 'DEGRADED', 'OFFLINE')`
### ReconciliationAlert
```
@@ -1772,7 +1885,7 @@ Observation history uses cursor pagination on `(recorded_at DESC, id DESC)`. Off
## Implemented Phases
Twenty-six phases from the project roadmap are implemented and verified, including the **Sepsis-3 clinical refactor** (Phases 2729), the **FHIR R4 Inbound Facade** (Phase 30), and **RBAC with clinical audit logging** (Phase 31). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 815, 2531. Phases 1719 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
Twenty-seven phases from the project roadmap are implemented and verified, including the **Site & Gateway Registry** (Phase 20), the **Sepsis-3 clinical refactor** (Phases 2729), the **FHIR R4 Inbound Facade** (Phase 30), and **RBAC with clinical audit logging** (Phase 31). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 815, 20, 2531. Phases 1719 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
| Phase | Feature | Status |
|---|---|---|
@@ -1792,6 +1905,7 @@ Twenty-six phases from the project roadmap are implemented and verified, includi
| 14 | qSOFA scoring engine (`QsofaCalculator`, `QsofaDetector`); sepsis bundle compliance (`SepsisBundle`, `SepsisBundleElement`, `SepsisBundleService`); auto-created treatment orders with 1-hour deadline; `SepsisAlertHandler` bridge; `SepsisBundleMonitorService` (5-min overdue scan); `SepsisBundlesController` API; ES projection of bundle status; Kafka topics `sepsis.bundle.created` / `sepsis.bundle.updated`; Prometheus `qsofa_detections_total` and `sepsis_bundle_compliance_total`; `QsofaCalculatorTests`, `QsofaDetectorTests`, `SepsisBundleTests` (bundle trigger updated to SOFA_SEPSIS in Phase 27) | Done |
| 15 | Medication administration (`MedicationAdministration`, `MedicationsController`, `MedicationService`); drug-vital correlation config (`MedicationCorrelationOptions`); `MedicationCorrelationHelper` annotates `WarningEvaluator` and `News2Detector` alert details; `medication_administrations` table + migration; `MedicationServiceTests`, `MedicationCorrelationTests`, `MedicationValidationTests`; `run-phase15-verification.sh`; design doc in `docs/decisions/medication-correlation-design.md` | Done |
| 16 | Console replay simulator (`VigilCare.Simulator`); scenario JSON schema; CLI commands `replay`, `replay-all`, `validate`, `dry-run`; speed multiplier and optional API polling; eight sample scenarios; `docs/simulator-guide.md` | Done |
| 20 | **Site & Gateway Registry + Clinical Sync Contracts** — `VigilCare.ClinicalContracts` shared class library with sync DTOs (`ClinicalSyncBatchRequest`, `SyncedObservation`, `SyncedAlertEvent`, `GatewayHeartbeatRequest`); `ClinicalSite` and `WardGateway` domain entities with EF Core configurations and migrations; `GatewayApiKeyAuthenticationHandler` (constant-time `X-Api-Key` validation + `X-Gateway-Id` claim) registered alongside JWT bearer; `SitesController` (create, list, get) and `GatewaysController` (register, list, get, heartbeat) with dual auth — JWT + `users:admin` for admin CRUD, gateway API key for heartbeat; `SiteService`, `GatewayRegistryService`; FluentValidation on `CreateSiteRequest`, `RegisterGatewayRequest`, `GatewayHeartbeatRequest`; `GatewayRegistrySeeder` with fixed GUIDs for demo site and gateway; `WardGatewayMetricsCollector` (60s periodic) exports `ward_gateways_offline_gauge` and `ward_gateway_buffer_depth`; `GatewayRegistryTests` (register, heartbeat, API key 401, degraded status, department filter); `ClinicalContractsTests` (JSON round-trip); `run-phase20-verification.sh` | Done |
| 17 | Ward dashboard shell — Vue 3 + Vite + Pinia + Tailwind; virtual ward table (NEWS2-sorted, department filter); patient detail (vitals, scores, alerts, orders, sepsis bundle); alert center (global acknowledge/resolve); API polling; CORS-backed `GET /encounters` ward list | Done |
| 18 | Clinical review mode — Chart.js vital sign trends (5 charts), NEWS2 history chart, local replay controls, alert reasoning panel, medication context on alerts; `fetchNews2History` / `fetchMedications`; Vitest composable and component tests; `docs/dashboard-guide.md` | Done |
| 19 | Clinician feedback mode — six rating buttons per alert, optional notes, Feedback Summary with aggregate stats, JSON/CSV export, localStorage persistence; `docs/clinical-testing-guide.md` for doctor/nurse evaluation sessions | Done |
@@ -1805,6 +1919,8 @@ Twenty-six phases from the project roadmap are implemented and verified, includi
**Ward dashboard:** backend APIs (`GET /encounters` ward list, `GET /qsofa/current`, CORS) and frontend SPA — `EncountersListTests`, `QsofaCurrentTests`, `vigilcare-dashboard` Vitest suite (replay scrubbing, feedback store, FeedbackButtons, FeedbackSummary, alert components, charts, ward table, GCS entry, SOFA panel, scores panel, alert labels).
**Site & Gateway Registry (Phase 20):** Central API manages clinical sites and ward edge nodes (gateways). Gateways authenticate via API key for heartbeat and future sync upload. Shared `VigilCare.ClinicalContracts` class library defines sync DTOs consumed by both central API and ward gateway projects. Prometheus fleet health gauges track offline gateways and buffer depth per site. Foundation for Phase 21 (WardGateway standalone service) and Phase 22 (sync batch upload).
**Scoring pipeline (Phases 2526):** GCS components → `gcs_scores` + `gcs.scored` → SOFA CNS organ system; SOFA lab/vital observations → `sofa_scores` with baseline tracking → delta sepsis alerts when organ dysfunction worsens.
**Sepsis-3 refactor (Phases 2729):** SIRS removed; qSOFA repositioned as bedside screening (`QSOFA_SCREEN`); SOFA delta ≥ 2 triggers `SOFA_SEPSIS` → sepsis bundle. Frontend gains GCS entry form and SOFA score panel. Eleven simulator scenarios validate the full clinical pipeline end-to-end.