update readme and create gap document
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
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-five planned phases are complete through Phase 30 — 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**, and the **FHIR R4 Inbound Facade** for EHR integration. 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-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**. 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
|
||||
|
||||
In a hospital, a patient presents for care and an encounter is opened. Bedside monitors and lab systems post observations continuously against that encounter — either directly via the REST API or through the FHIR R4 inbound facade that maps HL7 FHIR resources from integration engines (Mirth Connect, Rhapsody) into the internal domain. A rules engine evaluates each observation against configured thresholds and flags abnormal values as clinical alerts. Composite scoring engines (NEWS2, GCS, SOFA, qSOFA) aggregate multiple vitals and labs into acuity scores. The sepsis pathway follows Sepsis-3 consensus: qSOFA ≥ 2 creates a bedside screening alert recommending SOFA labs; when SOFA delta ≥ 2 from baseline confirms organ dysfunction, a `SOFA_SEPSIS` alert triggers the treatment bundle. Clinicians acknowledge and resolve alerts. If a critical alert goes unacknowledged for five minutes, the system escalates to the on-call backup. All events flow through Kafka so the Elasticsearch dashboard, scoring engines, and data lake writer consume the same stream independently.
|
||||
In a hospital, a patient presents for care and an encounter is opened. Bedside monitors and lab systems post observations continuously against that encounter — either directly via the REST API or through the FHIR R4 inbound facade that maps HL7 FHIR resources from integration engines (Mirth Connect, Rhapsody) into the internal domain. A rules engine evaluates each observation against configured thresholds and flags abnormal values as clinical alerts. Composite scoring engines (NEWS2, GCS, SOFA, qSOFA) aggregate multiple vitals and labs into acuity scores. The sepsis pathway follows Sepsis-3 consensus: qSOFA ≥ 2 creates a bedside screening alert recommending SOFA labs; when SOFA delta ≥ 2 from baseline confirms organ dysfunction, a `SOFA_SEPSIS` alert triggers the treatment bundle. Clinicians authenticate via JWT, and role-based access control (RBAC) gates every endpoint by clinical role (Nurse, Physician, Admin, Integration). Clinicians acknowledge and resolve alerts. If a critical alert goes unacknowledged for five minutes, the system escalates to the on-call backup. All clinical write actions — patient registration, alert acknowledgment, threshold changes, encounter transitions — are recorded in an append-only audit log with user identity, IP address, correlation ID, and before/after state. All events flow through Kafka so the Elasticsearch dashboard, scoring engines, and data lake writer consume the same stream independently.
|
||||
|
||||
```
|
||||
Patient ─────────────────────────── one patient = one MRN, many lifetime encounters
|
||||
@@ -74,6 +74,8 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
|
||||
- **Ward Dashboard APIs** — `GET /encounters` returns paginated `WardEncounterSummary` rows (patient name/MRN, room/bed, department, status, latest NEWS2 score, live qSOFA criteria count from Redis, sepsis bundle status, open alert count); filterable by `status` and `department`; `GET /encounters/:id/qsofa/current` exposes Redis-backed qSOFA state; CORS policy `Dashboard` allows configured origins (default `http://localhost:5173`)
|
||||
- **Ward Dashboard Frontend** — Vue 3 SPA (`vigilcare-dashboard/`) with virtual ward table (NEWS2-sorted), patient detail (vitals, scores, alerts, orders, sepsis bundle, GCS entry form, SOFA score panel), alert center (global acknowledge/resolve), vital sign trend charts with local replay scrubbing, NEWS2 history chart, alert reasoning with optional medication context, and clinician feedback on every alert; polls API every 5–10 s; guides in `docs/dashboard-guide.md` and `docs/clinical-testing-guide.md`
|
||||
- **FHIR R4 Inbound Facade** — `POST /fhir/R4/{Patient,Encounter,Observation,MedicationAdministration}` accepts FHIR R4 JSON resources (`application/fhir+json`); `POST /fhir/R4` processes transaction Bundles (Patient → Encounter → Observation in dependency order); `GET /fhir/R4/metadata` returns a CapabilityStatement; LOINC-to-internal code mapping (19 observation codes + SNOMED CT fallbacks); Fahrenheit-to-Celsius unit conversion; `ExternalResourceIdentifier` table links hospital MRNs and visit numbers to internal UUIDs for idempotent upserts; `FhirApiKeyMiddleware` authenticates via `X-Api-Key` header; `FhirExceptionFilter` returns FHIR `OperationOutcome` on errors; configurable identifier systems, department codes, and encounter class mappings via `Fhir` config section; Prometheus `fhir_ingest_total` and `fhir_mapping_errors_total`; integration guide for Mirth Connect HL7v2→FHIR channels in `docs/integration/mirth-fhir-channels.md`
|
||||
- **Role-Based Access Control (RBAC)** — JWT bearer authentication (`POST /auth/login`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 16 granular permissions (`patients:read`, `alerts:acknowledge`, `thresholds:write`, `fhir:ingest`, `audit:read`, etc.); `AuthorizePermission` attribute on every controller action; `PermissionAuthorizationHandler` resolves role → permission at runtime from `ClinicalRolePermissionMap`; `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`, `audit:read`, and `users:admin`; integration accounts get write-only access for FHIR ingest; 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; 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; eight audit actions (`THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`); `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
|
||||
- **Clinician Feedback Mode** — six quick ratings per alert (useful, too early, too late, false positive, missing context, would act); optional notes; Feedback Summary with aggregate stats and JSON/CSV export; client-side persistence for product research
|
||||
- **Console Replay Simulator** — standalone `VigilCare.Simulator` .NET console app replays JSON scenario files against the live API with configurable speed (`--speed 0` instant, `60` = 60× faster); commands: `replay`, `replay-all`, `validate`, `dry-run`; optional `--poll` shows alerts, NEWS2, GCS, SOFA, and sepsis bundle state during replay; eleven sample scenarios in `VigilCare.Simulator/Scenarios/List/` (including GCS neurological decline, SOFA sepsis progression, and SpO₂/FiO₂ fallback); user guide in `docs/simulator-guide.md`
|
||||
- **RabbitMQ Notification Workers** — `NotificationPublisherService` reads `alert.generated` from Kafka and publishes paging jobs to `alerts.paging.queue`; `PagingWorkerService` sends the page and waits for acknowledgment; if no ack arrives before timeout it NACKs to `alerts.paging.dlq` with `x-message-ttl = 300000ms`; if the host is stopping, in-flight paging messages are NACKed with `requeue=true` so they are retried after restart and do not false-escalate; `EscalationWorkerService` pages the on-call backup and sets alert status to `escalated`; `DischargeSummaryWorkerService` reads `encounter.status.changed`, generates a discharge summary, and stores it in MinIO under `/discharge-summaries/{encounterId}/summary.pdf`
|
||||
@@ -90,11 +92,14 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
|
||||
|
||||
```
|
||||
HTTP request
|
||||
→ FhirApiKeyMiddleware (X-Api-Key guard for /fhir/* routes)
|
||||
→ FhirApiKeyOrJwtMiddleware (X-Api-Key or JWT bearer for /fhir/* routes)
|
||||
→ CorrelationIdMiddleware
|
||||
→ ExceptionHandlerMiddleware
|
||||
→ JWT Authentication + RBAC (PermissionAuthorizationHandler)
|
||||
→ Controllers (REST API + FHIR R4 ingest)
|
||||
→ Services
|
||||
├── CurrentUserService (authenticated user identity from JWT claims)
|
||||
├── AuditService (append-only clinical_audit_logs on write actions)
|
||||
├── PostgreSQL (EF Core — writes, keyed reads)
|
||||
├── Redis (threshold cache, qSOFA state, NEWS2 parameter state, GCS state, SOFA lab cache, trend history, alert suppression keys)
|
||||
└── OutboxEvent (same transaction as domain write)
|
||||
@@ -145,6 +150,8 @@ IHostedServices (background):
|
||||
| Metrics | prometheus-net.AspNetCore (`GET /metrics`) |
|
||||
| Dashboards | Prometheus 2.52 + Grafana 10.4 |
|
||||
| Data lake format | Parquet.Net 4.x |
|
||||
| Authentication | JWT Bearer (Microsoft.AspNetCore.Authentication.JwtBearer) |
|
||||
| Password hashing | BCrypt.Net-Next |
|
||||
| FHIR | Hl7.Fhir.R4 (Firely SDK — parsing, serialization, model) |
|
||||
| Docs | Swagger / OpenAPI (Swashbuckle) |
|
||||
| Validation | FluentValidation.AspNetCore |
|
||||
@@ -160,6 +167,8 @@ VigilCareClinicalAPI/
|
||||
├── Program.cs # Service registration, middleware, seed on startup
|
||||
├── appsettings.json # Connection strings, Kafka, Elasticsearch, RabbitMQ, MinIO, Serilog, ReconciliationJobs
|
||||
├── Controllers/
|
||||
│ ├── AuthController.cs # JWT login + authenticated user profile (GET /auth/me)
|
||||
│ ├── AuditLogsController.cs # Clinical audit log query (Admin only)
|
||||
│ ├── PatientsController.cs # Patient CRUD, search by name/MRN
|
||||
│ ├── EncountersController.cs # Encounter list (ward summary), get, status PATCH, timeline
|
||||
│ ├── MedicationsController.cs # Medication administration create, list, get
|
||||
@@ -191,9 +200,13 @@ VigilCareClinicalAPI/
|
||||
│ │ ├── SepsisBundle.cs # Four-element treatment bundle with 1-hour compliance deadline
|
||||
│ │ ├── 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
|
||||
│ │ ├── ExternalResourceIdentifier.cs # Links external system identifiers (MRN, visit#) to internal UUIDs
|
||||
│ │ ├── 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/
|
||||
│ ├── EncounterStatus.cs # Scheduled, Active, Discharged, Cancelled
|
||||
│ ├── ClinicalRole.cs # Nurse, Physician, Admin, Integration
|
||||
│ ├── AuditAction.cs # ThresholdCreated/Updated, AlertAcknowledged/Resolved, EncounterStatusChanged, PatientRegistered, SuppressionWindowSet, UserLogin
|
||||
│ ├── EncounterType.cs # Inpatient, Outpatient, Emergency
|
||||
│ ├── AlertSeverity.cs # Warning, Critical
|
||||
│ ├── AlertStatus.cs # Open, Acknowledged, Resolved, Escalated
|
||||
@@ -225,8 +238,18 @@ VigilCareClinicalAPI/
|
||||
│ ├── FhirExceptionFilter.cs # Converts exceptions to FHIR OperationOutcome responses
|
||||
│ ├── FhirMappingException.cs # Typed exception for FHIR mapping failures
|
||||
│ └── FhirOperationOutcomeBuilder.cs # Builds FHIR OperationOutcome from exceptions and error codes
|
||||
├── Authorization/
|
||||
│ ├── AuthorizePermissionAttribute.cs # [AuthorizePermission("patients:read")] attribute
|
||||
│ ├── ClinicalPermissions.cs # 16 permission constants (patients:read, thresholds:write, audit:read, …)
|
||||
│ ├── ClinicalRolePermissionMap.cs # Role → permission set (Nurse, Physician, Admin, Integration)
|
||||
│ ├── 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
|
||||
├── Services/
|
||||
│ ├── Interfaces/ # IPatientService, IEncounterService, …
|
||||
│ ├── Interfaces/ # IPatientService, IEncounterService, IAuditService, IAuthService, ICurrentUserService, …
|
||||
│ ├── 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)
|
||||
│ ├── PatientService.cs
|
||||
│ ├── EncounterService.cs # Status state machine + ConflictException on invalid transitions
|
||||
│ ├── AlertThresholdService.cs # CRUD + Redis write-through invalidation
|
||||
@@ -289,6 +312,7 @@ VigilCareClinicalAPI/
|
||||
│ ├── ReconciliationJobOptions.cs
|
||||
│ ├── MedicationCorrelationOptions.cs # Drug-vital mappings + correlation window
|
||||
│ ├── FhirOptions.cs # API key, identifier systems, department/class maps, defaults
|
||||
│ ├── JwtOptions.cs # Issuer, audience, signing key, expiration (default 8 hours)
|
||||
│ └── DashboardOptions.cs # CORS origins for ward dashboard frontend
|
||||
├── Sepsis/
|
||||
│ ├── AlertCreationGuard.cs # Prevents creation of deprecated alert types (SEPSIS_WARNING)
|
||||
@@ -329,8 +353,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; ElasticsearchOptions, ElasticIndexOptions
|
||||
│ └── Seed/DataSeeder.cs # Seeds patients, encounters, thresholds, observations
|
||||
│ ├── Configurations/ # IEntityTypeConfiguration per entity; ClinicalUserConfiguration, ClinicalAuditLogConfiguration; ElasticsearchOptions, ElasticIndexOptions
|
||||
│ └── Seed/
|
||||
│ ├── DataSeeder.cs # Seeds patients, encounters, thresholds, observations
|
||||
│ └── UserSeeder.cs # Seeds four demo users (nurse, physician, admin, integration)
|
||||
├── Common/
|
||||
│ ├── ApiResponse.cs # { success, statusCode, data, error } envelope
|
||||
│ ├── PagedResult.cs / CursorPage.cs
|
||||
@@ -340,7 +366,7 @@ VigilCareClinicalAPI/
|
||||
│ ├── DomainException.cs
|
||||
│ └── ValidationException.cs
|
||||
├── Middlewares/
|
||||
│ ├── FhirApiKeyMiddleware.cs # X-Api-Key guard for /fhir/* routes; returns OperationOutcome on 401
|
||||
│ ├── FhirApiKeyOrJwtMiddleware.cs # Dual auth for /fhir/* routes: JWT bearer or X-Api-Key → Integration identity
|
||||
│ ├── CorrelationIdMiddleware.cs
|
||||
│ └── ExceptionHandlerMiddleware.cs
|
||||
└── Migrations/
|
||||
@@ -382,6 +408,8 @@ 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
|
||||
├── 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
|
||||
|
||||
@@ -395,13 +423,13 @@ VigilCare.Simulator/ # Phase 16 — console replay
|
||||
├── Scenarios/ # schema.json, ScenarioLoader, ScenarioValidator
|
||||
└── Scenarios/List/ # Eleven sample scenarios (sepsis, GCS, SOFA, NEWS2, stable, …)
|
||||
|
||||
vigilcare-dashboard/ # Phases 17–19, 27–28 — Vue 3 ward dashboard SPA
|
||||
vigilcare-dashboard/ # Phases 17–19, 27–28, 31 — Vue 3 ward dashboard SPA
|
||||
├── src/
|
||||
│ ├── api/ # HTTP client, encounters, clinical (GCS, SOFA), alerts, normalize
|
||||
│ ├── api/ # HTTP client (auto Bearer header), encounters, clinical (GCS, SOFA), alerts, normalize
|
||||
│ ├── components/ # charts, replay, alerts, feedback, patient (GcsEntryForm, SofaScorePanel), ward, layout, ui
|
||||
│ ├── composables/ # useChartData, useReplayControls, usePolling, useFeedback, useGcs, useSofa, chartFormat
|
||||
│ ├── stores/ # Pinia — ward, alerts, settings, feedback, scoring (localStorage)
|
||||
│ ├── views/ # WardDashboard, PatientDetail, AlertCenter, FeedbackSummary
|
||||
│ ├── stores/ # Pinia — ward, alerts, settings, feedback, scoring, auth (localStorage token + user)
|
||||
│ ├── views/ # LoginView, WardDashboard, PatientDetail, AlertCenter, FeedbackSummary
|
||||
│ └── __tests__/ # Vitest — tests (store, feedback, replay, charts, alerts, ward, GCS, SOFA)
|
||||
├── vite.config.js
|
||||
└── README.md # Dev quick start → docs/dashboard-guide.md
|
||||
@@ -426,7 +454,8 @@ scripts/
|
||||
├── 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-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-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
|
||||
|
||||
docs/
|
||||
├── plans/ # Phase implementation and verification guides
|
||||
@@ -571,7 +600,7 @@ dotnet run
|
||||
|
||||
On startup the application:
|
||||
1. Runs EF Core migrations
|
||||
2. Seeds two patients (with blood type, allergies, emergency contact), one active inpatient encounter each (with room/bed and admission reason), twelve alert thresholds, and sample observations
|
||||
2. Seeds two patients (with blood type, allergies, emergency contact), one active inpatient encounter each (with room/bed and admission reason), twelve alert thresholds, sample observations, and four clinical users (nurse, physician, admin, integration)
|
||||
3. Pre-loads all thresholds into Redis
|
||||
4. Provisions Kafka topics and Elasticsearch indices
|
||||
5. Declares the RabbitMQ exchange and queue topology
|
||||
@@ -602,7 +631,7 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open `http://localhost:5173` — **Virtual Ward** lists active patients sorted by NEWS2 score. Click a row for patient detail (vitals, alerts, charts, replay scrubbing, alert reasoning). Use **Alert Center** for hospital-wide triage. After reviewing alerts, rate them with the six feedback buttons and export results from **Feedback Summary** (`/feedback`).
|
||||
Open `http://localhost:5173` — log in with a demo account (e.g. `nurse.demo` / `DemoNurse1!`). **Virtual Ward** lists active patients sorted by NEWS2 score. Click a row for patient detail (vitals, alerts, charts, replay scrubbing, alert reasoning). Use **Alert Center** for hospital-wide triage. After reviewing alerts, rate them with the six feedback buttons and export results from **Feedback Summary** (`/feedback`).
|
||||
|
||||
Replay a simulator scenario in another terminal to watch charts and alerts populate in real time. Run dashboard tests with `cd vigilcare-dashboard && npm test`. See `docs/dashboard-guide.md` for technical documentation and `docs/clinical-testing-guide.md` for structured clinician evaluation sessions.
|
||||
|
||||
@@ -645,6 +674,7 @@ Integration tests use `WebApplicationFactory` with a `Testing` environment and T
|
||||
| `GcsScoringTests` | 25 | GCS component scoring, classification, alerts, CNS integration with SOFA |
|
||||
| `SofaScoringTests` | 26 | SOFA organ scores, baseline eligibility, delta alerts, carry-forward, vasopressors |
|
||||
| `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 |
|
||||
|
||||
### Verification Scripts
|
||||
|
||||
@@ -661,6 +691,7 @@ With the API running (`dotnet run`) and Docker Compose up:
|
||||
./scripts/run-phase15-verification.sh # Medication administration + correlation annotation pipeline
|
||||
./scripts/run-phase27-verification.sh # Sepsis-3 refactor: SIRS removal, QSOFA_SCREEN, SOFA bundle trigger
|
||||
./scripts/run-phase30-verification.sh # FHIR R4 ingest integration tests + manual bundle/metadata checks
|
||||
./scripts/run-phase31-verification.sh # RBAC integration tests + JWT login + audit log query
|
||||
```
|
||||
|
||||
Phase 25 — GCS scoring (requires running API + Docker Compose; set an active encounter UUID):
|
||||
@@ -691,6 +722,12 @@ Phase 15 unit/integration tests only:
|
||||
dotnet test --filter "FullyQualifiedName~Medication"
|
||||
```
|
||||
|
||||
Phase 31 RBAC tests only:
|
||||
|
||||
```bash
|
||||
dotnet test --filter "FullyQualifiedName~Rbac"
|
||||
```
|
||||
|
||||
Per-phase test runners (subset of `dotnet test`):
|
||||
|
||||
```bash
|
||||
@@ -1066,9 +1103,78 @@ 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`.
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| POST | `/auth/login` | Authenticate with username/password; returns JWT bearer token |
|
||||
| GET | `/auth/me` | Returns the authenticated user's profile (user ID, username, display name, role) |
|
||||
|
||||
**POST `/auth/login` body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `username` | string | yes | Username |
|
||||
| `password` | string | yes | Password |
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `accessToken` | string | JWT bearer token |
|
||||
| `expiresAt` | DateTimeOffset | Token expiration (default 8 hours) |
|
||||
| `userId` | Guid | User ID |
|
||||
| `username` | string | Username |
|
||||
| `displayName` | string | Display name |
|
||||
| `role` | string | `NURSE`, `PHYSICIAN`, `ADMIN`, `INTEGRATION` |
|
||||
|
||||
**Seeded demo users:**
|
||||
|
||||
| Username | Password | Role |
|
||||
|---|---|---|
|
||||
| `nurse.demo` | `DemoNurse1!` | Nurse |
|
||||
| `physician.demo` | `DemoPhysician1!` | Physician |
|
||||
| `admin.demo` | `DemoAdmin1!` | Admin |
|
||||
| `integration.mirth` | `MirthIntegration1!` | Integration |
|
||||
|
||||
**RBAC permission matrix:**
|
||||
|
||||
| Permission | Nurse | Physician | Admin | Integration |
|
||||
|---|---|---|---|---|
|
||||
| `patients:read` | yes | yes | yes | — |
|
||||
| `patients:write` | yes | yes | yes | yes |
|
||||
| `encounters:read` | yes | yes | yes | — |
|
||||
| `encounters:write` | yes | yes | yes | yes |
|
||||
| `observations:ingest` | yes | yes | yes | yes |
|
||||
| `alerts:read` | yes | yes | yes | — |
|
||||
| `alerts:acknowledge` | yes | yes | yes | — |
|
||||
| `alerts:resolve` | yes | yes | yes | — |
|
||||
| `thresholds:read` | yes | yes | yes | — |
|
||||
| `thresholds:write` | — | — | yes | — |
|
||||
| `analytics:read` | yes | yes | yes | — |
|
||||
| `orders:write` | yes | yes | yes | — |
|
||||
| `medications:write` | yes | yes | yes | yes |
|
||||
| `fhir:ingest` | — | — | yes | yes |
|
||||
| `audit:read` | — | — | yes | — |
|
||||
| `users:admin` | — | — | yes | — |
|
||||
|
||||
All endpoints except `POST /auth/login` and `GET /fhir/R4/metadata` require authentication. Unauthenticated requests receive `401`. Authenticated requests without the required permission receive `403`.
|
||||
|
||||
### Audit Logs
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| GET | `/audit-logs` | Query clinical audit logs (Admin only — requires `audit:read` permission) |
|
||||
|
||||
**GET `/audit-logs` query params:** `entityType`, `entityId`, `userId`, `action`, `from`, `to`, `page`, `pageSize`
|
||||
|
||||
**Audit actions:** `THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`
|
||||
|
||||
Each audit log entry includes `action`, `entityType`, `entityId`, `userId`, `userDisplayName`, `previousValueJson` (JSONB), `newValueJson` (JSONB), `reason`, `ipAddress`, `correlationId`, and `createdAt`.
|
||||
|
||||
### FHIR R4 Ingest
|
||||
|
||||
All FHIR endpoints are under `/fhir/R4`, accept `application/fhir+json`, and return FHIR R4 JSON responses. Authentication is via `X-Api-Key` header (configured in `Fhir:ApiKey`; disabled when blank). Errors return a FHIR `OperationOutcome` with appropriate issue codes.
|
||||
All FHIR endpoints are under `/fhir/R4`, accept `application/fhir+json`, and return FHIR R4 JSON responses. Authentication is via JWT bearer token or `X-Api-Key` header (configured in `Fhir:ApiKey`; disabled when blank). When a valid JWT is present, the API key check is skipped — this allows both integration engines (API key) and authenticated admin users (JWT) to ingest FHIR resources. Errors return a FHIR `OperationOutcome` with appropriate issue codes.
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
@@ -1323,6 +1429,40 @@ createdAt DateTimeOffset
|
||||
|
||||
Unique index: `(resource_type, system, value)` — one mapping per external identifier
|
||||
|
||||
### ClinicalUser
|
||||
|
||||
```
|
||||
id Guid PK
|
||||
username string required, unique (max 100)
|
||||
passwordHash string required (BCrypt)
|
||||
displayName string required (max 200)
|
||||
role string NURSE | PHYSICIAN | ADMIN | INTEGRATION
|
||||
isActive bool default true
|
||||
createdAt DateTimeOffset
|
||||
lastLoginAt DateTimeOffset?
|
||||
```
|
||||
|
||||
### ClinicalAuditLog
|
||||
|
||||
```
|
||||
id Guid PK
|
||||
action string required (max 50) — THRESHOLD_CREATED | THRESHOLD_UPDATED | ALERT_ACKNOWLEDGED | ALERT_RESOLVED | ENCOUNTER_STATUS_CHANGED | PATIENT_REGISTERED | SUPPRESSION_WINDOW_SET | USER_LOGIN
|
||||
entityType string required (max 100) — e.g. AlertThreshold, ClinicalAlert, Encounter, Patient, ClinicalUser
|
||||
entityId Guid required
|
||||
userId Guid? FK → ClinicalUser (null for system-initiated actions)
|
||||
userDisplayName string? (max 200)
|
||||
previousValueJson jsonb? state before the action
|
||||
newValueJson jsonb? state after the action
|
||||
reason string? optional clinician-provided reason
|
||||
ipAddress string? (max 45) — IPv4 or IPv6
|
||||
correlationId string? (max 100) — links to request correlation header
|
||||
createdAt DateTimeOffset
|
||||
```
|
||||
|
||||
Append-only — no UPDATE or DELETE from application code.
|
||||
|
||||
Indexes: `(entity_type)`, `(entity_id)`, `(user_id)`, `(created_at)`
|
||||
|
||||
### ReconciliationAlert
|
||||
|
||||
```
|
||||
@@ -1591,7 +1731,7 @@ Observation history uses cursor pagination on `(recorded_at DESC, id DESC)`. Off
|
||||
|
||||
## Implemented Phases
|
||||
|
||||
Twenty-five phases from the project roadmap are implemented and verified, including the **Sepsis-3 clinical refactor** (Phases 27–29) and the **FHIR R4 Inbound Facade** (Phase 30). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 8–15, 25–30. Phases 17–19 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
|
||||
Twenty-six phases from the project roadmap are implemented and verified, including the **Sepsis-3 clinical refactor** (Phases 27–29), 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 8–15, 25–31. Phases 17–19 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
|
||||
|
||||
| Phase | Feature | Status |
|
||||
|---|---|---|
|
||||
@@ -1620,6 +1760,7 @@ Twenty-five phases from the project roadmap are implemented and verified, includ
|
||||
| 28 | **Frontend GCS + SOFA + sepsis UI refactor** — `GcsEntryForm.vue` (bedside GCS component entry); `SofaScorePanel.vue` (organ-system breakdown with staleness indicators); `useGcs` / `useSofa` composables; `scoring` Pinia store; `ScoresPanel` updated with GCS/SOFA display; `SepsisBundlePanel` and `AlertReasoning` refactored for Sepsis-3 alert types; Vitest tests for GCS entry, SOFA panel, scores panel, alert labels; `run-phase28-verification.sh` | Done |
|
||||
| 29 | **Simulator scenario expansion + clinical validation** — three new scenarios (`neurological-decline-gcs-01`, `sepsis-sofa-progression-01`, `sofa-partial-spo2-fallback-01`); existing scenarios enriched with GCS/SOFA observations; `ScenarioReplayHelper` for end-to-end test replay; `ClinicalRefactorEndToEndTests` validates qSOFA screen → SOFA labs → bundle workflow; simulator polls GCS/SOFA scores; `run-phase29-verification.sh` | Done |
|
||||
| 30 | **FHIR R4 Inbound Facade** — `FhirIngestController` (`POST /fhir/R4/{Patient,Encounter,Observation,MedicationAdministration}`); `FhirMetadataController` (CapabilityStatement); `FhirBundleProcessor` (transaction Bundles in dependency order); `LoincCodeMapper` (19 LOINC + 3 SNOMED CT → internal codes); `FhirUnitConverter` (°F→°C); `ExternalResourceIdentifier` table + `ExternalIdentifierService` for hospital MRN/visit number ↔ internal UUID linking; `FhirApiKeyMiddleware` (`X-Api-Key` auth); `FhirExceptionFilter` (→ OperationOutcome); `PatientFhirMapper`, `EncounterFhirMapper`, `ObservationFhirMapper`, `MedicationAdministrationFhirMapper`, `FhirReferenceResolver`; idempotent patient/encounter upserts (`RegisterOrUpdateByIdentifierAsync`, `OpenOrUpdateByIdentifierAsync`); configurable identifier systems, department codes, encounter class maps (`FhirOptions`); Prometheus `fhir_ingest_total`, `fhir_mapping_errors_total`; Mirth Connect integration guide; `FhirIngestTests`; `run-phase30-verification.sh` | Done |
|
||||
| 31 | **RBAC + Clinical Audit Logging** — JWT bearer authentication (`AuthService`, `AuthController`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 16 granular permissions; `AuthorizePermission` attribute on all controller actions; `PermissionAuthorizationHandler` + `PermissionPolicyProvider` resolve `perm:*` policies; `CurrentUserService` extracts identity from JWT claims; `ClinicalUser` entity with BCrypt password hashing; `ClinicalAuditLog` append-only table with before/after JSONB, user identity, IP, and correlation ID; `AuditService` writes log entries on clinical write actions (8 audit actions); `AuditLogsController` admin-only query with filters; `FhirApiKeyOrJwtMiddleware` dual auth for FHIR routes (JWT or X-Api-Key); alert `acknowledgedBy` set from authenticated user, not request body; four seeded demo users; frontend `LoginView` + `auth` Pinia store with `localStorage` token persistence; Vue router auth guard; `RbacTests`; `run-phase31-verification.sh` | Done |
|
||||
|
||||
**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).
|
||||
|
||||
@@ -1629,4 +1770,6 @@ Twenty-five phases from the project roadmap are implemented and verified, includ
|
||||
|
||||
**FHIR R4 integration (Phase 30):** Inbound facade accepts FHIR R4 JSON from integration engines (Mirth Connect, Rhapsody). Supports per-resource endpoints and transaction Bundles for ADT admit workflows. LOINC/SNOMED code mapping, Fahrenheit conversion, and external identifier linking enable drop-in EHR integration without changing the internal clinical pipeline.
|
||||
|
||||
**RBAC + audit logging (Phase 31):** JWT authentication with role-based permission gating on every endpoint. Four clinical roles with granular permissions. Append-only audit logging records who did what, when, and why — with before/after state snapshots for compliance and incident review. Frontend login page with token-based session management.
|
||||
|
||||
**Optional follow-up:** execute and document the Kafka replay demonstration for the data lake (reset `data-lake-writer` offsets, clear MinIO prefixes, restart API, confirm Parquet rebuild). See `docs/plans/phase-9-plan.md` § Replay demonstration.
|
||||
|
||||
Reference in New Issue
Block a user