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
+45 -22
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:** Thirty-one planned phases are complete through Phase 33 (plus Phases 2023) — 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 **Ward Gateway Service** (local-first clinical path with offline buffering and central sync), the **FHIR R4 Inbound Facade** for EHR integration, **Role-Based Access Control (RBAC) with clinical audit logging**, the **Dashboard Gap Analysis Fixes** (SOFA/GCS/qSOFA history charts, patient banner, encounter timeline, medication markers on vital charts), the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry form, sortable/filterable ward table), **Degraded Operations Visibility** (gateway fleet operations panel, stale gateway auto-detection, discharge summary API, admin panels for user/threshold/audit/reconciliation management, degraded-mode banner), and **Alert Quality Analytics** (server-side clinician feedback with `AlertFeedback` entity, `AlertQualityAggregatorService` background metrics, quality metrics API, Grafana alert quality dashboard). 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, **JWT signing key validation** at startup, and **concurrency hardening** (transactional sepsis bundle creation, unique active encounter constraint). 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:** Thirty-two planned phases are complete through Phase 34 (plus Phases 2023) — 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 **Ward Gateway Service** (local-first clinical path with offline buffering and central sync), the **FHIR R4 Inbound Facade** for EHR integration, **Role-Based Access Control (RBAC) with clinical audit logging**, the **Dashboard Gap Analysis Fixes** (SOFA/GCS/qSOFA history charts, patient banner, encounter timeline, medication markers on vital charts), the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry form, sortable/filterable ward table), **Degraded Operations Visibility** (gateway fleet operations panel, stale gateway auto-detection, discharge summary API, admin panels for user/threshold/audit/reconciliation management, degraded-mode banner), **Alert Quality Analytics** (server-side clinician feedback with `AlertFeedback` entity, `AlertQualityAggregatorService` background metrics, quality metrics API, Grafana alert quality dashboard), and **Explainable Alerts** (immutable JSONB `explanation` on composite alerts with score contributors, trend context, structured medication context, and bedside `NarrativeSummary`; `AlertResponse` DTO on GET/list/acknowledge/resolve; dashboard `AlertReasoning.vue`; ES indexer and data lake propagation; ward gateway sync). 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, **JWT signing key validation** at startup, and **concurrency hardening** (transactional sepsis bundle creation, unique active encounter constraint). 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
@@ -38,7 +38,7 @@ An `idempotencyKey` (partial unique index) prevents duplicate observations when
### ClinicalAlert
A `ClinicalAlert` is generated when an observation breaches a threshold, when the qSOFA engine detects two or more organ-dysfunction criteria (screening), when SOFA delta ≥ 2 from baseline confirms sepsis, or when the NEWS2 engine computes a medium/high-risk composite score (or a single-parameter score of 3). Lifecycle: `open → acknowledged → resolved` (or `escalated` after a five-minute NACK cycle through the RabbitMQ dead-letter queue). Alerts carry an audit trail: who acknowledged, when, and with what note. Only `SOFA_SEPSIS` alerts trigger automatic sepsis bundle creation.
A `ClinicalAlert` is generated when an observation breaches a threshold, when the qSOFA engine detects two or more organ-dysfunction criteria (screening), when SOFA delta ≥ 2 from baseline confirms sepsis, or when the NEWS2 engine computes a medium/high-risk composite score (or a single-parameter score of 3). Lifecycle: `open → acknowledged → resolved` (or `escalated` after a five-minute NACK cycle through the RabbitMQ dead-letter queue). Alerts carry an audit trail: who acknowledged, when, and with what note. Composite alerts from NEWS2, SOFA, GCS, and trend detection also carry an immutable JSONB `explanation` snapshot — score contributors, trend context, medication context, and a bedside narrative — frozen at alert creation time. The human-readable `details` string remains for backward compatibility. Only `SOFA_SEPSIS` alerts trigger automatic sepsis bundle creation.
### SepsisBundle
@@ -70,9 +70,9 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
- **Trend Detection Engine** — `TrendAnalyzerService` Kafka consumer (`trend-analyzer`) tracks rate-of-change for five vital parameters (`HEART_RATE`, `RESP_RATE`, `SYSTOLIC_BP`, `TEMP_C`, `SPO2`) using Redis sliding-window history; when velocity exceeds configured thresholds (e.g. 72→95 bpm in 30 min), creates a `RAPID_DETERIORATION` alert even if the current value is below warning thresholds; Prometheus `trend_alerts_total` and `trend_analysis_duration_seconds`
- **Alert Suppression Windows** — acknowledging a suppressible alert (`WARNING_*`, `NEWS2_WARNING`) sets a Redis key `suppress:{encounterId}:{alertType}` with a configurable TTL (default 30 min from `AlertSuppression` config; optional per-code override via `alert_thresholds.suppression_window_minutes`); `WarningEvaluator` and `News2Detector` check suppression before creating new warning alerts; critical alerts (`CRITICAL_*`, `NEWS2_EMERGENCY`, `SEPSIS_WARNING`, `RAPID_DETERIORATION`) are never suppressed; observations and NEWS2 scores continue to persist during suppression; Prometheus `alert_suppressions_total`
- **Medication Administration** — `POST /encounters/:id/medications` records drug administrations (name, dose, route, timestamp, administered-by); `GET /encounters/:id/medications` lists with optional `since` filter; `GET /medications/:id` detail; active-encounter guard; FluentValidation on request DTOs
- **Medication Correlation Annotations** — `MedicationCorrelationHelper` appends medication context to warning and NEWS2 alert details when a mapped drug was administered within the correlation window (default 90 min); drug-to-vital mappings in `MedicationCorrelation` config (`appsettings.json`); annotates rather than suppresses — alerts still fire; sepsis, trend, and critical sync-path alerts are never annotated; design rationale in `docs/decisions/medication-correlation-design.md`
- **Medication Correlation Annotations** — `MedicationCorrelationHelper` appends medication context to warning alert `details` when a mapped drug was administered within the correlation window (default 90 min); explainable alerts (NEWS2, SOFA, GCS, rapid deterioration) receive structured `MedicationContext` in the JSONB `explanation` via `TryGetContextAsync()`; drug-to-vital mappings in `MedicationCorrelation` config (`appsettings.json`); annotates rather than suppresses — alerts still fire; sepsis, trend, and critical sync-path alerts are never annotated; design rationale in `docs/decisions/medication-correlation-design.md`
- **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, SOFA score/delta, GCS score/classification, attending physician, admitted-at, last observation time); filterable by `status` and `department`; `GET /encounters/:id/qsofa/current` exposes Redis-backed qSOFA state; `GET /sepsis-bundles` lists bundles hospital-wide with optional `status` filter (returns `SepsisBundleSummary` with patient demographics, elements, and deadlines); CORS policy `Dashboard` allows configured origins (default `http://localhost:5173`)
- **Ward Dashboard Frontend** — Vue 3 SPA (`vigilcare-dashboard/`) with virtual ward table (multi-column sortable, patient search, quick-filters for critical/alerts/sepsis), patient detail (vitals, scores, alerts, orders, sepsis bundle, GCS entry form, SOFA score panel, patient banner with demographics/allergies/emergency contact, encounter timeline, vitals entry form for manual observation recording, discharge summary panel), alert center (global acknowledge/resolve with role-aware modal and acknowledgment note preview), department overview (unit-level snapshot cards with acuity bars, patient/alert/bundle counts per department), sepsis bundle board (real-time countdown timers, on-track/at-risk/overdue urgency sorting), critical alert banner with browser notifications and audible tone, shift handoff report generator (SBAR format with ward summary, exportable via print/PDF), vital sign trend charts with medication administration markers and local replay scrubbing, NEWS2 history chart, SOFA history chart with organ-system breakdown, GCS history chart with component tracking, qSOFA evaluation history, alert reasoning with optional medication context, clinician feedback on every alert, admin panels (threshold management, user management, audit log viewer, reconciliation viewer), gateway operations dashboard with degraded-mode banner, and alert quality analytics with quality charts; role-aware sidebar navigation; polls API every 510 s; guides in `docs/dashboard-guide.md` and `docs/clinical-testing-guide.md`
- **Ward Dashboard Frontend** — Vue 3 SPA (`vigilcare-dashboard/`) with virtual ward table (multi-column sortable, patient search, quick-filters for critical/alerts/sepsis), patient detail (vitals, scores, alerts, orders, sepsis bundle, GCS entry form, SOFA score panel, patient banner with demographics/allergies/emergency contact, encounter timeline, vitals entry form for manual observation recording, discharge summary panel), alert center (global acknowledge/resolve with role-aware modal and acknowledgment note preview), department overview (unit-level snapshot cards with acuity bars, patient/alert/bundle counts per department), sepsis bundle board (real-time countdown timers, on-track/at-risk/overdue urgency sorting), critical alert banner with browser notifications and audible tone, shift handoff report generator (SBAR format with ward summary, exportable via print/PDF), vital sign trend charts with medication administration markers and local replay scrubbing, NEWS2 history chart, SOFA history chart with organ-system breakdown, GCS history chart with component tracking, qSOFA evaluation history, structured alert reasoning (`AlertReasoning.vue` — score contributors, trend context, medication context, narrative summary from `explanation`), clinician feedback on every alert, admin panels (threshold management, user management, audit log viewer, reconciliation viewer), gateway operations dashboard with degraded-mode banner, and alert quality analytics with quality charts; role-aware sidebar navigation; polls API every 510 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; `FhirApiKeyOrJwtMiddleware` authenticates via JWT bearer or `X-Api-Key` header (supports multiple keys via `Fhir:ApiKeys` array for zero-downtime rotation; constant-time comparison via `CryptographicOperations.FixedTimeEquals`); `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`
- **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 18 granular permissions (`patients:read`, `alerts:acknowledge`, `alerts:feedback`, `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
@@ -88,10 +88,11 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
- **FHIR Bundle Transaction Rollback** — `FhirBundleProcessor` wraps all bundle entry processing in a database transaction; on any entry failure, the transaction is rolled back and the response includes the `OperationOutcome` for the failed entry; prevents partial state from orphaned Patient/Encounter records
- **Clinician Feedback Mode** — six quick ratings per alert (useful, too early, too late, false positive, missing context, would act); optional notes; server-side `AlertFeedback` entity persisted per user per alert (`POST /alerts/{id}/feedback`); `alerts:feedback` permission for Nurse, Physician, and Admin roles; Feedback Summary with aggregate stats and JSON/CSV export; client-side persistence for product research
- **Alert Quality Analytics** — `AlertQualityAggregatorService` periodically computes per-alert-type quality metrics (acknowledgement rate, false positive rate, useful rate, would-act rate, avg seconds to acknowledge/resolve) into `alert_quality_metrics` table; `AlertQualityMetricsController` exposes `GET /alerts/quality-metrics` (time-range filterable, optional alert type) and `GET /alerts/quality-metrics/summary`; Grafana alert quality dashboard (`infra/grafana/dashboards/alert-quality-dashboard.json`); frontend `AlertQualityAnalytics.vue` with `AlertQualityChart.vue`; Prometheus `alert_quality_useful_rate` and `alert_quality_false_positive_rate` gauges
- **Explainable Alerts** — `AlertExplanation` value object (`ScoreContributor`, `TrendContext`, `MedicationContext`, `NarrativeSummary`) serialized as JSONB on `ClinicalAlert.Explanation` at creation time; `AlertExplanationBuilder` and contributor builders (NEWS2, SOFA, GCS, trend) assemble explanation from scoring outputs; `ClinicalAlertFactory` idempotent INSERT with explanation; NEWS2, SOFA, GCS, and `TrendDetector` wire explanation and include `explanation` in `alert.generated` outbox payloads; `AlertResponse` DTO exposes optional `Explanation` on GET/list/acknowledge/resolve; Elasticsearch indexes `NarrativeSummary`; data lake Parquet includes `explanation_json`; ward gateway `LocalClinicalAlert.ExplanationJson` synced via `ClinicalSyncBatchProcessor`; dashboard `AlertReasoning.vue` + `alertExplanation.js` composable render structured reasoning; simulator `ExpectedOutcomeValidator` supports `narrativeContains` on key scenarios; `ExplainableAlertsTests` (10 tests) + `run-phase34-verification.sh`
- **Degraded Operations Visibility** — `GatewayStaleDetectorService` auto-marks gateways OFFLINE when heartbeat exceeds configurable `StaleThresholdMinutes`; `OperationsController` (`GET /operations/gateways`, `GET /operations/gateways/{id}`, `GET /operations/sites/{siteId}/summary`) provides fleet management API; `DischargeSummaryService` with `GET /encounters/{id}/discharge-summary` (info) and `GET /encounters/{id}/discharge-summary/content` (MinIO PDF download); `DischargeSummaryPanel.vue` on patient detail; `DegradedModeBanner.vue` warns when gateways are offline; `GatewayOperations.vue` operations dashboard
- **User Management** — `UsersController` (`GET /users`, `POST /users`, `PATCH /users/{id}`) for admin user account CRUD; `UserService` with BCrypt password hashing; `UserManagementView.vue` with `UserFormModal.vue` (create/edit users, role assignment, active toggle)
- **Admin Dashboard Panels** — `ThresholdManagementView.vue` with `ThresholdFormModal.vue` (create/edit alert thresholds); `AuditLogView.vue` (filterable audit log viewer with action/entity/user/date filters); `ReconciliationView.vue` (safety finding viewer); sidebar navigation with role-aware admin section; `CollapsibleSection.vue` and `SeverityBadge.vue` UI components
- **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; `--gateway` targets the ward gateway (`http://localhost:5081`) with `--encounter-id`, `--skip-setup`, and `--gateway-token`; `alert_ack` events poll for open alerts on central before acknowledging (handles async alert pipeline at `--speed 0`); twelve sample scenarios in `VigilCare.Simulator/Scenarios/List/` (including ward outage reconnect, GCS neurological decline, SOFA sepsis progression, and SpO₂/FiO₂ fallback); user guide in `docs/simulator-guide.md`
- **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; `--gateway` targets the ward gateway (`http://localhost:5081`) with `--encounter-id`, `--skip-setup`, and `--gateway-token`; `alert_ack` events poll for open alerts on central before acknowledging (handles async alert pipeline at `--speed 0`); `ExpectedOutcomeValidator` validates alert `narrativeContains` on key scenarios; twelve sample scenarios in `VigilCare.Simulator/Scenarios/List/` (including ward outage reconnect, 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`
- **Data Lake Writer** — `DataLakeWriterService` (consumer group `data-lake-writer`) buffers `observation.recorded`, `alert.generated`, and `encounter.status.changed` events, flushes date-partitioned Parquet files to MinIO (`/observations/`, `/alerts/`, `/encounters/`), and commits Kafka offsets only for topic-partitions where all uploads succeeded; failed partition buffers are retained in memory and retried on the next flush cycle (prevents data loss from partial upload failures); shutdown flush uses an uncanceled token so MinIO writes complete on Ctrl+C; `kafka_partition` and `kafka_offset` columns provide audit lineage
- **Reconciliation Jobs** — three scheduled checks: (1) unacknowledged CRITICAL alerts older than 30 minutes, (2) pending orders without results after 4 hours, (3) active inpatients with no observation in 2 hours; each finding creates a `reconciliation_alerts` row and publishes to RabbitMQ
@@ -210,7 +211,7 @@ VigilCareClinicalAPI/
│ ├── QsofaController.cs # Current qSOFA criteria count (Redis-backed) + cursor-paginated evaluation history
│ ├── ObservationsController.cs # Ingest POST, cursor-paginated GET
│ ├── AlertThresholdsController.cs # Threshold CRUD + cache invalidation
│ ├── AlertsController.cs # Alert list (global + per-encounter), acknowledge, resolve
│ ├── AlertsController.cs # Alert list (global + per-encounter), get by ID, acknowledge, resolve → AlertResponse
│ ├── OrdersController.cs # Order create, list, get, status transition, record result
│ ├── News2Controller.cs # Current NEWS2 score and cursor-paginated history
│ ├── GcsController.cs # Latest GCS score and cursor-paginated history per encounter
@@ -231,7 +232,7 @@ VigilCareClinicalAPI/
│ │ ├── Encounter.cs # Status machine; SetStatus() enforces transition matrix
│ │ ├── AlertThreshold.cs
│ │ ├── Observation.cs # Append-only; IdempotencyKey; partial unique index
│ │ ├── ClinicalAlert.cs # open → acknowledged → resolved / escalated
│ │ ├── ClinicalAlert.cs # open → acknowledged → resolved / escalated; JSONB Explanation snapshot
│ │ ├── Order.cs
│ │ ├── News2Score.cs # Composite score with seven component scores + risk level
│ │ ├── GcsScore.cs # Eye/verbal/motor components, total, classification
@@ -249,6 +250,8 @@ VigilCareClinicalAPI/
│ │ ├── ClinicalAuditLog.cs # Append-only audit trail: action, entity, user, before/after JSONB, IP, correlation ID
│ │ ├── AlertFeedback.cs # Clinician feedback per alert (one per user per alert)
│ │ └── AlertQualityMetric.cs # Per-alert-type quality metric snapshots (acknowledgement/false-positive/useful rates)
│ ├── ValueObjects/
│ │ └── AlertExplanation.cs # ScoreContributor, TrendContext, MedicationContext, NarrativeSummary
│ └── Enums/
│ ├── EncounterStatus.cs # Scheduled, Active, Discharged, Cancelled
│ ├── ClinicalRole.cs # Nurse, Physician, Admin, Integration
@@ -305,7 +308,7 @@ VigilCareClinicalAPI/
│ ├── AlertThresholdService.cs # CRUD + Redis write-through invalidation
│ ├── ObservationService.cs # Ingest transaction: idempotency → plausibility → threshold → alert → outbox; emits Prometheus counters
│ ├── ObservationQueryService.cs # Cursor-paginated history
│ ├── AlertService.cs # Acknowledge (sets suppression), resolve, list
│ ├── AlertService.cs # Acknowledge (sets suppression), resolve, list → AlertResponse with optional Explanation
│ ├── AlertSuppressionService.cs # Redis suppress:{enc}:{type} TTL keys
│ ├── OrderService.cs # Order lifecycle; status machine; calls SepsisBundleService.OnOrderResultedAsync on result
│ ├── News2Service.cs # Current score + cursor-paginated history from PostgreSQL
@@ -324,11 +327,14 @@ VigilCareClinicalAPI/
│ ├── WarningEvaluator.cs # Warning-range evaluation; suppression + medication annotation; idempotent INSERT
│ ├── AnalyticsService.cs # Elasticsearch query wrappers
│ └── PlausibilityValidator.cs # Per-code numeric range guard
├── Alerts/
│ ├── ClinicalAlertFactory.cs # Idempotent alert INSERT with explanation JSON; outbox payload serialization
│ └── AlertExplanationBuilder.cs # Assembles explanation from contributors, trend, medication context
├── Trend/
│ ├── TrendCalculator.cs # Pure static rate-of-change logic
│ └── TrendDetector.cs # Redis history + RAPID_DETERIORATION alert creation
├── Medication/
│ └── MedicationCorrelationHelper.cs # Appends drug context to warning/NEWS2 alert details
│ └── MedicationCorrelationHelper.cs # String annotation on warning details; structured MedicationContext for explanations
├── Validators/ # FluentValidation — RegisterPatient, OpenEncounter, IngestObservation, CreateMedicationAdministration, CreateSiteRequest, RegisterGatewayRequest, GatewayHeartbeatRequest, …
├── Observability/
│ └── Metrics/
@@ -485,7 +491,8 @@ tests/
├── OperationsApiTests.cs # Operations fleet listing, gateway detail, site summary
├── Helpers/GatewayAuthHelper.cs # WithGatewayApiKey extension method for test clients
├── Alerts/
── AlertQualityAnalyticsTests.cs # Alert feedback submission, quality aggregation, metrics API
── AlertQualityAnalyticsTests.cs # Alert feedback submission, quality aggregation, metrics API
│ └── ExplainableAlertsTests.cs # Explanation JSONB, AlertResponse mapping, medication context, legacy null
├── Auth/
│ └── RbacTests.cs # RBAC — unauthenticated 401, nurse 403 on threshold write, admin audit log creation
└── Fhir/
@@ -562,22 +569,24 @@ VigilCare.WardGateway.Tests/ # Phase 21 — ward gateway i
├── WardGatewayLocalPathTests.cs # Local observation ingest, warning alerts, buffered sync items
└── WardGatewayPartitionTests.cs # Network partition simulation — offline buffering and sync upload
VigilCare.Simulator/ # Phase 16 — console replay simulator (HTTP-only, no direct DB/Kafka)
VigilCare.Simulator/ # Phase 16, 29, 34 — console replay simulator (HTTP-only, no direct DB/Kafka)
├── Program.cs # CLI: replay, replay-all, validate, dry-run
├── Commands/ # System.CommandLine command handlers
├── Client/VigilCareApiClient.cs # Typed HTTP client for all API endpoints (incl. GCS, SOFA)
├── Client/
│ ├── VigilCareApiClient.cs # Typed HTTP client for all API endpoints (incl. GCS, SOFA)
│ └── Models/AlertExplanation.cs # Explanation DTO for poll/validation
├── Engine/ReplayEngine.cs # Scenario replay with speed multiplier + event logging
├── Output/SimulatorConsole.cs # Colored output with GCS/SOFA score display
├── Output/SimulatorConsole.cs # Colored output with GCS/SOFA score + explanation display
├── Polling/ApiPoller.cs # Optional post-event alert/score/bundle/GCS/SOFA polling
├── Scenarios/ # schema.json, ScenarioLoader, ScenarioValidator
├── Scenarios/ # schema.json, ScenarioLoader, ScenarioValidator, ExpectedOutcomeValidator
└── Scenarios/List/ # Twelve sample scenarios (sepsis, GCS, SOFA, NEWS2, stable, ward outage, …)
vigilcare-dashboard/ # Phases 1719, 22, 23, 2728, 31, 33 — Vue 3 ward dashboard SPA
vigilcare-dashboard/ # Phases 1719, 22, 23, 2728, 31, 3334 — Vue 3 ward dashboard SPA
├── src/
│ ├── api/ # HTTP client (auto Bearer header), encounters, clinical (GCS, SOFA, qSOFA history), alerts, analytics, sepsis, thresholds, users, audit, reconciliation, operations, alertQuality, normalize
│ ├── components/
│ │ ├── admin/ # ThresholdFormModal, UserFormModal (admin CRUD modals)
│ │ ├── alerts/ # AlertCard, AcknowledgeModal (role-aware), CriticalAlertBanner (browser notifications + audible tone)
│ │ ├── alerts/ # AlertCard, AlertReasoning (structured explanation), AcknowledgeModal (role-aware), CriticalAlertBanner (browser notifications + audible tone)
│ │ ├── charts/ # SofaHistory, GcsHistory, QsofaHistory, VitalChart with medication markers, AlertQualityChart
│ │ ├── departments/ # DepartmentCard, AcuityBar (unit-level snapshot)
│ │ ├── feedback/ # FeedbackButtons, FeedbackSummary
@@ -587,7 +596,7 @@ vigilcare-dashboard/ # Phases 1719, 22, 23, 27
│ │ ├── sepsis/ # SepsisBundleTable, SepsisBundleRow, SepsisBundleCard (countdown timer)
│ │ ├── ward/ # WardTable (sortable headers), PatientRow, PatientCard, WardToolbar, SortableHeader, HandoffReport (SBAR + print)
│ │ └── ui/ # Button, Card, Badge, Skeleton, EmptyState, Modal, CollapsibleSection, SeverityBadge, DegradedModeBanner
│ ├── composables/ # useChartData, useReplayControls, usePolling, useFeedback, useGcs, useSofa, useApiMode, useChartTheme, useFocusTrap, chartFormat, patientFormat, timelineFormat, chartMedications, wardSort, wardFilter, departmentFormat, sepsisFormat, alertAcknowledge, criticalAlertDetect, useAlertNotification, useCriticalAlertPolling, handoffReport, vitalsForm, roleAccess, auditFormat, reconciliationFormat, thresholdForm, userForm
│ ├── composables/ # useChartData, useReplayControls, usePolling, useFeedback, useGcs, useSofa, useApiMode, useChartTheme, useFocusTrap, chartFormat, patientFormat, timelineFormat, chartMedications, wardSort, wardFilter, departmentFormat, sepsisFormat, alertAcknowledge, alertExplanation, criticalAlertDetect, useAlertNotification, useCriticalAlertPolling, handoffReport, vitalsForm, roleAccess, auditFormat, reconciliationFormat, thresholdForm, userForm
│ ├── plugins/ # medicationMarkerPlugin (Chart.js plugin for medication administration markers on vital charts)
│ ├── stores/ # Pinia — ward (sort + filter + search), alerts (banner + polling), settings (sort prefs + sound mute), feedback, scoring, auth, departments, sepsis, operationsStore, alertQuality
│ ├── views/ # LoginView, WardDashboard, PatientDetail, AlertCenter, FeedbackSummary, DepartmentOverviewView, SepsisBoardView, ThresholdManagementView, UserManagementView, AuditLogView, ReconciliationView, GatewayOperations, AlertQualityAnalytics
@@ -623,6 +632,7 @@ scripts/
├── run-phase31-verification.sh # Phase 31 — RBAC integration tests + JWT login + audit log query
├── run-phase23-verification.sh # Phase 23 — Degraded operations visibility + gateway fleet + admin panels
├── run-phase33-verification.sh # Phase 33 — Alert quality analytics integration tests
├── run-phase34-verification.sh # Phase 34 — Explainable alerts integration tests
├── demo-network-partition.sh # Gateway network partition demo script
└── mint-gateway-jwt.sh # JWT minting helper for gateway testing
@@ -914,6 +924,7 @@ With the API running (`dotnet run`) and Docker Compose up:
./scripts/run-phase31-verification.sh # RBAC integration tests + JWT login + audit log query
./scripts/run-phase23-verification.sh # Degraded operations visibility + gateway fleet + admin panels
./scripts/run-phase33-verification.sh # Alert quality analytics integration tests
./scripts/run-phase34-verification.sh # Explainable alerts integration tests
```
Phase 25 — GCS scoring (requires running API + Docker Compose; set an active encounter UUID):
@@ -980,6 +991,12 @@ Phase 33 alert quality analytics tests only:
dotnet test --filter "FullyQualifiedName~AlertQuality"
```
Phase 34 explainable alerts tests only:
```bash
dotnet test --filter "FullyQualifiedName~ExplainableAlerts"
```
Per-phase test runners (subset of `dotnet test`):
```bash
@@ -1231,11 +1248,13 @@ Uses cursor pagination on `(recorded_at DESC, id DESC)` — offset pagination wo
|---|---|---|
| GET | `/encounters/{id}/alerts` | Paginated alert list for an encounter |
| GET | `/alerts` | Global alert list; optional `status`, `severity`, `department` filter |
| GET | `/alerts/{id}` | Alert detail |
| POST | `/alerts/{id}/acknowledge` | Acknowledge with clinician ID and optional note |
| POST | `/alerts/{id}/resolve` | Resolve (must be acknowledged first) |
| GET | `/alerts/{id}` | Alert detail (`AlertResponse` with optional `explanation`) |
| POST | `/alerts/{id}/acknowledge` | Acknowledge with clinician ID and optional note; returns `AlertResponse` |
| POST | `/alerts/{id}/resolve` | Resolve (must be acknowledged first); returns `AlertResponse` |
| POST | `/alerts/{id}/feedback` | Submit clinician feedback (one per user per alert); requires `alerts:feedback` |
**Alert response shape:** list, get, acknowledge, and resolve endpoints return `AlertResponse` — alert fields plus optional `explanation` (`scoreContributors`, `trend`, `medicationContext`, `narrativeSummary`). Omitted on legacy and threshold-only alerts.
**Alert lifecycle:**
```
@@ -1656,6 +1675,7 @@ observationId Guid? FK → Observation (null for NEWS2, GCS, SOFA composite
alertType string e.g. CRITICAL_HEART_RATE, QSOFA_SCREEN, SOFA_SEPSIS, NEWS2_WARNING, NEWS2_EMERGENCY, GCS_CRITICAL
severity string WARNING | CRITICAL
details text required
explanation jsonb? immutable structured explanation snapshot (score contributors, trend, medication context, narrative); null on legacy/threshold-only alerts
observationCode string? observation code that triggered this alert (e.g. HEART_RATE) — enables direct lookups without LIKE pattern matching
status string open | acknowledged | resolved | escalated (default: open)
acknowledgedAt DateTimeOffset?
@@ -2206,7 +2226,7 @@ Observation history uses cursor pagination on `(recorded_at DESC, id DESC)`. Off
## Implemented Phases
Thirty-one phases from the project roadmap are implemented and verified, including the **Site & Gateway Registry** (Phase 20), the **Ward Gateway Service** (Phase 21), the **Dashboard Gap Analysis Fixes** (Phase 22), the **Degraded Operations Visibility** (Phase 23), the **Sepsis-3 clinical refactor** (Phases 2729), the **FHIR R4 Inbound Facade** (Phase 30), **RBAC with clinical audit logging** (Phase 31), the **Alert Quality Analytics** (Phase 33), and the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry, sortable/filterable ward table). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 815, 2023, 2531, 33. Phases 1719 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
Thirty-two phases from the project roadmap are implemented and verified, including the **Site & Gateway Registry** (Phase 20), the **Ward Gateway Service** (Phase 21), the **Dashboard Gap Analysis Fixes** (Phase 22), the **Degraded Operations Visibility** (Phase 23), the **Sepsis-3 clinical refactor** (Phases 2729), the **FHIR R4 Inbound Facade** (Phase 30), **RBAC with clinical audit logging** (Phase 31), the **Alert Quality Analytics** (Phase 33), the **Explainable Alerts** (Phase 34), and the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry, sortable/filterable ward table). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 815, 2023, 2531, 3334. Phases 1719 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
| Phase | Feature | Status |
|---|---|---|
@@ -2241,8 +2261,9 @@ Thirty-one phases from the project roadmap are implemented and verified, includi
| 31 | **RBAC + Clinical Audit Logging** — JWT bearer authentication (`AuthService`, `AuthController`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 18 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 (10 audit actions); `AuditLogsController` admin-only query with filters; `FhirApiKeyOrJwtMiddleware` dual auth for FHIR routes (JWT or X-Api-Key with multi-key rotation); 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 |
| 23 | **Degraded Operations Visibility** — `GatewayStaleDetectorService` background service auto-marks gateways OFFLINE when heartbeat exceeds configurable `StaleThresholdMinutes`; `OperationsController` exposes gateway fleet listing (`GET /operations/gateways` with status/site filters), gateway detail (`GET /operations/gateways/{id}`), and site summary (`GET /operations/sites/{siteId}/summary`); `DischargeSummaryService` with `GET /encounters/{id}/discharge-summary` (info) and `GET /encounters/{id}/discharge-summary/content` (MinIO PDF download); `UsersController` (`GET /users`, `POST /users`, `PATCH /users/{id}`) for admin user account management; frontend: `GatewayOperations.vue` operations dashboard, `DegradedModeBanner.vue` warning banner, `DischargeSummaryPanel.vue` on patient detail, `ThresholdManagementView.vue` with `ThresholdFormModal.vue`, `UserManagementView.vue` with `UserFormModal.vue`, `AuditLogView.vue`, `ReconciliationView.vue`; role-aware admin sidebar navigation; `roleAccess.js` composable; `useChartTheme.js`, `useFocusTrap.js`, `useApiMode.js` composables; `CollapsibleSection.vue`, `SeverityBadge.vue` UI components; `OperationsApiTests`; `run-phase23-verification.sh` | Done |
| 33 | **Alert Quality Analytics** — `AlertFeedback` entity with per-user-per-alert constraint; `POST /alerts/{id}/feedback` server-side feedback submission with `alerts:feedback` permission (Nurse, Physician, Admin); `AlertQualityMetric` entity stores per-alert-type quality snapshots (acknowledgement rate, false positive rate, useful rate, would-act rate, avg seconds to acknowledge/resolve); `AlertQualityAggregatorService` background service computes metrics periodically; `AlertQualityMetricsController` exposes `GET /alerts/quality-metrics` (time-range + alert type filter) and `GET /alerts/quality-metrics/summary`; `AlertFeedbackConfiguration` and `AlertQualityMetricConfiguration` EF Core configs; `SubmitAlertFeedbackRequestValidator`; Prometheus `alert_quality_useful_rate` and `alert_quality_false_positive_rate` gauges; Grafana `alert-quality-dashboard.json`; frontend `AlertQualityAnalytics.vue` with `AlertQualityChart.vue` and `alertQuality` Pinia store; `AlertQualityAnalyticsTests`; `run-phase33-verification.sh` | Done |
| 34 | **Explainable Alerts** — `AlertExplanation` value object (`ScoreContributor`, `TrendContext`, `MedicationContext`, `NarrativeSummary`); JSONB `ClinicalAlert.Explanation` column (immutable at creation); contributor builders for NEWS2, SOFA, GCS; `TrendContextBuilder`; `AlertExplanationBuilder` + `ClinicalAlertFactory`; NEWS2, SOFA, GCS, and `TrendDetector` wire explanation and include `explanation` in `alert.generated` outbox; `MedicationCorrelationHelper.TryGetContextAsync()` for structured medication context; `AlertResponse` DTO + `AlertResponseMapper`; GET/list/acknowledge/resolve return `AlertResponse`; ES indexer projects `NarrativeSummary`; data lake Parquet `explanation_json`; ward gateway `LocalClinicalAlert.ExplanationJson` + sync; dashboard `AlertReasoning.vue` + `alertExplanation.js`; simulator `ExpectedOutcomeValidator` with `narrativeContains`; `ExplainableAlertsTests`; `run-phase34-verification.sh` | Done |
**Ward dashboard:** backend APIs (`GET /encounters` ward list with extended summary fields including SOFA/GCS/attending/admitted-at, `GET /qsofa/current`, `GET /qsofa/history`, `GET /gcs/history`, `GET /sepsis-bundles` hospital-wide list, `GET /operations/gateways` fleet management, `GET /users` user management, `GET /alerts/quality-metrics` alert quality, CORS) and frontend SPA — `EncountersListTests`, `QsofaCurrentTests`, `GapAnalysisFixTests`, `OperationsApiTests`, `AlertQualityAnalyticsTests`, `vigilcare-dashboard` Vitest suite (replay scrubbing, feedback store, FeedbackButtons, FeedbackSummary, alert components, charts, ward table, ward sort, ward filter, department format, sepsis format, alert acknowledge, critical alert detect, handoff report, vitals form, GCS entry/history, SOFA panel/history, qSOFA history, scores panel, alert labels, PatientBanner, EncounterTimeline, medication chart markers, AcknowledgeModal, CriticalAlertBanner, DepartmentOverviewView, SepsisBoardView, VitalsEntryForm, useAlertStore, useWardStore, roleAccess, ThresholdManagementView, DischargeSummaryPanel, GatewayOperations, alertQuality).
**Ward dashboard:** backend APIs (`GET /encounters` ward list with extended summary fields including SOFA/GCS/attending/admitted-at, `GET /qsofa/current`, `GET /qsofa/history`, `GET /gcs/history`, `GET /sepsis-bundles` hospital-wide list, `GET /operations/gateways` fleet management, `GET /users` user management, `GET /alerts/quality-metrics` alert quality, `GET /alerts/{id}` with structured explanation, CORS) and frontend SPA — `EncountersListTests`, `QsofaCurrentTests`, `GapAnalysisFixTests`, `OperationsApiTests`, `AlertQualityAnalyticsTests`, `ExplainableAlertsTests`, `vigilcare-dashboard` Vitest suite (replay scrubbing, feedback store, FeedbackButtons, FeedbackSummary, alert components, AlertReasoning, charts, ward table, ward sort, ward filter, department format, sepsis format, alert acknowledge, critical alert detect, handoff report, vitals form, GCS entry/history, SOFA panel/history, qSOFA history, scores panel, alert labels, PatientBanner, EncounterTimeline, medication chart markers, AcknowledgeModal, CriticalAlertBanner, DepartmentOverviewView, SepsisBoardView, VitalsEntryForm, useAlertStore, useWardStore, roleAccess, ThresholdManagementView, DischargeSummaryPanel, GatewayOperations, alertQuality).
**Enhanced Dashboard (post-Phase 22):** Major dashboard feature expansion addressing clinical workflow gaps. **Department Overview** (`/departments`) — unit-level snapshot cards showing patient count, critical/alert/bundle totals per department with acuity distribution bars; click-through to ward filtered by department. **Sepsis Bundle Board** (`/sepsis`) — real-time bundle compliance tracking with countdown timers to 1-hour deadline, urgency-sorted (overdue → at-risk → on-track), live 1-second tick updates. **Critical Alert Notifications** — `CriticalAlertBanner` surfaces new critical alerts from polling cycle with audible 880Hz two-tone alert, browser title flash, and native `Notification` API integration; mute toggle persisted in settings. **Shift Handoff Report** — `HandoffReport.vue` generates SBAR-format (Situation, Background, Assessment, Recommendation) structured reports for all ward patients, enriched with latest vitals, open alerts, pending orders, and sepsis bundle status; ward summary with department stats; print/PDF export. **Vitals Entry Form** — `VitalsEntryForm.vue` on patient detail page enables manual observation recording (7 vital parameters with AVPU dropdown) with client-side plausibility validation matching server-side ranges. **Ward Table Enhancements** — multi-column sorting (room, patient, department, NEWS2, qSOFA, sepsis, alerts) with sortable column headers, debounced patient search (name/MRN), quick-filter toggles (critical, has alerts, active sepsis), clear-all filters. **Acknowledge Modal** — role-aware acknowledgment with clinician identity pre-populated from JWT, role-specific guidance text, and acknowledgment note preview. Backend additions: `GET /sepsis-bundles` paginated hospital-wide list with `SepsisBundleSummary` (patient demographics, elements, deadlines); `WardEncounterSummary` extended with `sofaScore`, `sofaDelta`, `gcsScore`, `gcsClassification`, `lastObservationAt`, `attendingPhysician`, `admittedAt`.
@@ -2264,6 +2285,8 @@ Thirty-one phases from the project roadmap are implemented and verified, includi
**Alert Quality Analytics (Phase 33):** Server-side clinician feedback persisted as `AlertFeedback` entities (one per user per alert, six feedback types). `AlertQualityAggregatorService` periodically computes per-alert-type quality metrics (acknowledgement rate, false positive rate, useful rate, would-act rate, response times). REST API exposes quality metric snapshots and aggregate summaries. Grafana dashboard visualizes alert quality trends. Frontend analytics view with quality charts.
**Explainable Alerts (Phase 34):** Composite alerts (NEWS2, SOFA, GCS, rapid deterioration) carry an immutable JSONB `explanation` snapshot at creation — score contributors with raw values and normal ranges, trend context (percent change, duration, direction), structured medication context, and a bedside `NarrativeSummary`. `AlertResponse` exposes explanation on GET/list/acknowledge/resolve. Downstream consumers (Elasticsearch indexer, data lake Parquet, ward gateway sync, Kafka `alert.generated`) propagate explanation without breaking legacy consumers. Dashboard `AlertReasoning.vue` renders structured reasoning. Simulator validates `narrativeContains` on key scenarios.
**Post-phase hardening (after Phase 31):**
- **FHIR R4 read/search** — `FhirReadController` adds `GET /fhir/R4/Patient/{id}`, `GET /fhir/R4/Patient` (search by `identifier`), `GET /fhir/R4/Encounter/{id}`, `GET /fhir/R4/Encounter` (search by `patient`/`status`); new `fhir:read` permission for Admin and Integration roles; CapabilityStatement updated to advertise `read` and `searchType` interactions for Patient and Encounter
- **Alert threshold deletion** — `DELETE /alert-thresholds/{id}` with `THRESHOLD_DELETED` audit action and Redis cache invalidation