From 584d1edd586250d7150bb1bfa3750c39e1b3e718 Mon Sep 17 00:00:00 2001 From: voltsrage Date: Sat, 20 Jun 2026 14:32:51 +0800 Subject: [PATCH] feature: Doctor Feedback Mode --- README.md | 39 +- docs/clinical-testing-guide.md | 357 ++++++++++++++++++ docs/dashboard-guide.md | 312 +++++++++++++++ vigilcare-dashboard/README.md | 101 ++++- .../src/__tests__/AlertCard.test.js | 14 +- .../src/__tests__/AlertReasoning.test.js | 7 +- .../src/__tests__/FeedbackButtons.test.js | 84 +++++ .../src/__tests__/FeedbackSummary.test.js | 44 +++ .../src/__tests__/useFeedbackStore.test.js | 98 +++++ .../src/components/alerts/AlertCard.vue | 9 + .../src/components/alerts/AlertReasoning.vue | 9 + .../components/feedback/FeedbackButtons.vue | 91 +++++ .../src/components/layout/AppSidebar.vue | 18 +- .../src/composables/useFeedback.js | 16 + vigilcare-dashboard/src/router/index.js | 6 + vigilcare-dashboard/src/stores/feedback.js | 88 +++++ .../src/views/FeedbackSummary.vue | 103 +++++ 17 files changed, 1385 insertions(+), 11 deletions(-) create mode 100644 docs/clinical-testing-guide.md create mode 100644 docs/dashboard-guide.md create mode 100644 vigilcare-dashboard/src/__tests__/FeedbackButtons.test.js create mode 100644 vigilcare-dashboard/src/__tests__/FeedbackSummary.test.js create mode 100644 vigilcare-dashboard/src/__tests__/useFeedbackStore.test.js create mode 100644 vigilcare-dashboard/src/components/feedback/FeedbackButtons.vue create mode 100644 vigilcare-dashboard/src/composables/useFeedback.js create mode 100644 vigilcare-dashboard/src/stores/feedback.js create mode 100644 vigilcare-dashboard/src/views/FeedbackSummary.vue diff --git a/README.md b/README.md index 8dd8a83..aa25790 100644 --- a/README.md +++ b/README.md @@ -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:** Sixteen planned phases are complete — 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 (patient demographics, encounter enrichment, 12 observation codes), warning alerts and orders, the NEWS2 composite scoring engine, trend detection with alert suppression, qSOFA scoring with sepsis bundle compliance tracking, medication administration with alert correlation annotations, and the console replay simulator. Ward-dashboard backend APIs (`GET /encounters` list with clinical summaries, `GET /qsofa/current`, CORS for a frontend on port 5173) are also in place. See [Implemented Phases](#implemented-phases) for the full breakdown. +**Implementation status:** Nineteen planned phases are complete — 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 (patient demographics, encounter enrichment, 12 observation codes), warning alerts and orders, the NEWS2 composite scoring engine, trend detection with alert suppression, qSOFA scoring with sepsis bundle compliance tracking, medication administration with alert correlation annotations, the console replay simulator, the **Vue 3 ward dashboard** (virtual ward, patient detail, alert center, vital sign charts, NEWS2 history, replay controls, alert reasoning), and **clinician feedback mode** (structured alert ratings, feedback summary, JSON/CSV export). 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 @@ -70,6 +70,8 @@ An `OutboxEvent` is written in the same transaction as any observation or alert, - **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` - **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), alert center (global acknowledge/resolve), vital sign trend charts, NEWS2 history chart, local replay controls, 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` +- **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, and sepsis bundle state during replay; eight sample scenarios in `VigilCare.Simulator/Scenarios/List/`; 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 after at least one successful upload; shutdown flush uses an uncanceled token so MinIO writes complete on Ctrl+C; `kafka_partition` and `kafka_offset` columns provide audit lineage @@ -140,6 +142,7 @@ IHostedServices (background): | Docs | Swagger / OpenAPI (Swashbuckle) | | Validation | FluentValidation.AspNetCore | | Testing | xUnit + Testcontainers + WebApplicationFactory | +| Ward dashboard | Vue 3 + Vite + Pinia + Tailwind CSS v4 + Chart.js (`vigilcare-dashboard/`) | --- @@ -340,6 +343,17 @@ VigilCare.Simulator/ # Phase 16 — console replay ├── Scenarios/ # schema.json, ScenarioLoader, ScenarioValidator └── Scenarios/List/ # Eight sample scenarios (sepsis, NEWS2, stable, medication, …) +vigilcare-dashboard/ # Phases 17–19 — Vue 3 ward dashboard SPA +├── src/ +│ ├── api/ # HTTP client, encounters, clinical, alerts, normalize +│ ├── components/ # charts, replay, alerts, feedback, patient, ward, layout, ui +│ ├── composables/ # useChartData, useReplayControls, usePolling, useFeedback, chartFormat +│ ├── stores/ # Pinia — ward, alerts, settings, feedback (localStorage) +│ ├── views/ # WardDashboard, PatientDetail, AlertCenter, FeedbackSummary +│ └── __tests__/ # Vitest — 38 tests (store, feedback, charts, alerts, ward) +├── vite.config.js +└── README.md # Dev quick start → docs/dashboard-guide.md + scripts/ ├── run-api-redis-tests.sh # Phase 1 — patient/encounter/threshold + Redis cache ├── run-kafka-outbox-tests.sh # Phase 3 — outbox relay and Kafka topics @@ -358,6 +372,8 @@ scripts/ docs/ ├── plans/ # Phase implementation and verification guides +├── clinical-testing-guide.md # Doctor/nurse guide — alert review & feedback sessions +├── dashboard-guide.md # VigilCare Dashboard user guide (ward, patient detail, charts) ├── simulator-guide.md # VigilCare.Simulator user guide ├── decisions/ │ ├── data-lake-design.md # Parquet vs JSON, partitioning, replay rationale @@ -504,6 +520,20 @@ dotnet run --project VigilCare.Simulator -- replay \ Other commands: `validate `, `dry-run `, `replay-all `. See `docs/simulator-guide.md` for the full user guide. +### Run the Dashboard + +With the API running, start the Vue frontend: + +```bash +cd vigilcare-dashboard +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, alert reasoning). Use **Alert Center** for hospital-wide triage. After reviewing alerts, rate them with the six feedback buttons (useful, too early, too late, false positive, missing context, would act) 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. + ### Run Tests ```bash @@ -1382,7 +1412,7 @@ Observation history uses cursor pagination on `(recorded_at DESC, id DESC)`. Off ## Implemented Phases -Sixteen phases from the project roadmap are implemented and verified. Integration tests (`dotnet test` — 116 test methods) and per-phase verification scripts cover Phases 8–15. +Nineteen phases from the project roadmap are implemented and verified. Integration tests (`dotnet test` — 116 test methods) and per-phase verification scripts cover Phases 8–15. Phases 17–19 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`). | Phase | Feature | Status | |---|---|---| @@ -1402,7 +1432,10 @@ Sixteen phases from the project roadmap are implemented and verified. Integratio | 14 | qSOFA scoring engine (`QsofaCalculator`, `QsofaDetector`); `QSOFA_WARNING` alert type; 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` | 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 | +| 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 | -**Ward dashboard backend (in progress):** `GET /encounters` ward list with `WardEncounterSummary`, `GET /encounters/{id}/qsofa/current`, `DashboardOptions` CORS — `EncountersListTests`, `QsofaCurrentTests`. +**Ward dashboard:** backend APIs (`GET /encounters` ward list, `GET /qsofa/current`, CORS) and frontend SPA — `EncountersListTests`, `QsofaCurrentTests`, `vigilcare-dashboard` Vitest suite (38 tests: feedback store, FeedbackButtons, FeedbackSummary, alert components, charts, ward table). **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. diff --git a/docs/clinical-testing-guide.md b/docs/clinical-testing-guide.md new file mode 100644 index 0000000..6c4b2e0 --- /dev/null +++ b/docs/clinical-testing-guide.md @@ -0,0 +1,357 @@ +# VigilCare Clinical Testing Guide + +**For doctors and nurses evaluating the alerting dashboard** + +This guide walks you through testing the VigilCare monitoring dashboard. You will review simulated patient scenarios, decide whether alerts are clinically meaningful, and record structured feedback that helps the team improve the system. + +No programming knowledge is required. A facilitator will start the backend services and open the dashboard in your browser. + +--- + +## Table of Contents + +1. [What you are testing](#1-what-you-are-testing) +2. [Important notes before you start](#2-important-notes-before-you-start) +3. [Getting to the dashboard](#3-getting-to-the-dashboard) +4. [Tour of the application](#4-tour-of-the-application) +5. [Your core task — review and rate alerts](#5-your-core-task--review-and-rate-alerts) +6. [Understanding the six feedback ratings](#6-understanding-the-six-feedback-ratings) +7. [Recommended testing sessions](#7-recommended-testing-sessions) +8. [Scenario scripts](#8-scenario-scripts) +9. [Session checklist](#9-session-checklist) +10. [Submitting your feedback](#10-submitting-your-feedback) +11. [Frequently asked questions](#11-frequently-asked-questions) + +--- + +## 1. What you are testing + +VigilCare is a **clinical decision support prototype**. It watches vital signs and lab-style observations for hospitalized patients and raises alerts when something looks wrong — for example: + +- A single vital sign in a warning range (heart rate, blood pressure, temperature) +- A **NEWS2** early warning score crossing medium or high risk +- **SIRS / sepsis** or **qSOFA** patterns suggesting infection or organ dysfunction +- **Rapid deterioration** — a vital changing quickly even if still “in range” +- **Sepsis bundle** tracking — whether time-critical treatments were ordered and completed + +The **dashboard** is where you, as a clinician, would see those alerts and decide what to do. This testing round adds **structured feedback**: after reviewing each alert, you tell us whether it would help or hinder real clinical work. + +Your ratings are the primary output of this study. There are no right or wrong answers — we want your honest clinical judgment on simulated cases. + +--- + +## 2. Important notes before you start + +### Simulated patients only + +All patients in this test are **fictional**. Data is generated by a replay simulator, not from real bedside monitors. Treat it like a training exercise, not live clinical work. + +### Not a production EHR + +This dashboard is a **research and evaluation tool**. It does not replace your hospital’s charting system, does not send real pages, and does not write orders to a live pharmacy or lab. + +### Acknowledge and resolve are practice actions + +You can tap **Ack** and **Resolve** on alerts to walk through the workflow. In this environment those actions update the test database only. + +### Feedback stays in your browser (for now) + +Your ratings are saved in the browser on the computer you use. Export them at the end of the session (see [§10](#10-submitting-your-feedback)) and send the file to the study facilitator. If you switch computers or clear browser data without exporting, ratings may be lost. + +### Default clinician ID + +Acknowledging alerts uses a demo identifier (`DR-DEMO`) unless the facilitator configures yours. This does not affect your feedback ratings. + +--- + +## 3. Getting to the dashboard + +The facilitator will ensure the following are running. You only need the browser URL. + +| What | Where | +|---|---| +| Dashboard | **http://localhost:5173** (or URL provided by facilitator) | +| Your role | Review alerts and submit feedback | + +**On first open** you land on **Virtual Ward** — a list of active simulated patients sorted by acuity (NEWS2 score). + +If the ward list is empty, ask the facilitator to start or replay a scenario (see [§8](#8-scenario-scripts)). + +--- + +## 4. Tour of the application + +Use the sidebar (desktop) or bottom navigation (mobile) to move between screens. + +### Virtual Ward + +**Purpose:** See who is on the floor and who needs attention first. + +- Patients sorted by **NEWS2 score** (higher = higher concern on this board). +- Badge shows count of patients with NEWS2 ≥ 7. +- Filter by department if asked (ICU, General Medicine, Surgery). +- **Click a patient row** to open their detail page. + +**What to notice:** Does the sort order match how you would prioritize a real ward round? + +--- + +### Patient Detail + +**Purpose:** Deep review of one patient — vitals, scores, alerts, trends, and why alerts fired. + +| Section | What it shows | +|---|---| +| **Scores** | Current NEWS2 total and risk level | +| **Latest Vitals** | Most recent heart rate, RR, BP, SpO₂, temperature, etc. | +| **Active Alerts** | Open alerts for this patient — **click a row** to see reasoning | +| **Alert reasoning** | Plain-language explanation of why the alert fired; may show recent medications | +| **Orders** | Clinical orders (labs, antibiotics, fluids, etc.) | +| **Sepsis bundle** | If sepsis was suspected — four time-critical elements and compliance status | +| **Vital sign charts** | Trends for HR, RR, systolic BP, SpO₂, temperature | +| **NEWS2 history** | How the early warning score changed over time | +| **Replay controls** | Local timeline bar (pause, speed, **Next Alert →**) to step through the case | + +**What to notice:** Would you trust these charts and explanations during a real handoff? Is anything missing? + +--- + +### Alert Center + +**Purpose:** Hospital-wide inbox — all alerts across patients, not just one encounter. + +- Tabs: **Open**, **Acknowledged**, **Resolved**, **Escalated**. +- Each card shows severity, type, details, and time. +- **Ack** / **Resolve** for workflow practice. +- **Feedback buttons** on every card (see [§5](#5-your-core-task--review-and-rate-alerts)). + +**What to notice:** Is it easy to triage multiple patients from one screen? Would you use this during a shift? + +--- + +### Feedback Summary + +**Purpose:** Aggregate view of all ratings you (and others on the same browser) have submitted. + +- **Total ratings**, **% useful / would act**, **% false positive**, **timing issues**. +- Breakdown **by alert type** (e.g. sepsis, NEWS2, heart rate warning). +- **Recent feedback** list with your notes. +- **Export JSON** and **Export CSV** — use CSV for spreadsheets. + +Open this at the end of a session to sanity-check your work before export. + +--- + +## 5. Your core task — review and rate alerts + +For **each alert** you review, complete this short workflow: + +``` +1. Read the alert (severity, type, details, time) +2. Optional: open Patient Detail → click the alert → read "why it fired" +3. Optional: look at vitals, charts, medications, sepsis bundle +4. Choose ONE feedback rating (required for that alert) +5. Optional: tap "+ Note" and add a short clinical comment +6. Move to the next alert +``` + +### Where feedback buttons appear + +- **Alert Center** — below each alert card. +- **Patient Detail** — below the alert reasoning panel (after you click an alert). +- **Active Alerts list** — on expanded reasoning view when integrated. + +You can rate alerts in any status (Open, Acknowledged, Resolved, Escalated). You may **change your rating** by selecting a different button — the latest choice replaces the previous one. + +### Tips for consistent ratings + +- Rate based on what you knew **at the time the alert fired**, using the charts and reasoning as context — not hindsight after the full scenario finished. +- If an alert was technically correct but **not actionable**, consider **Too early**, **Too late**, or **Missing context** rather than Useful. +- If the patient was stable and the alert reflected expected treatment (e.g. beta-blocker bradycardia), **False positive** or **Missing context** may fit better than Useful. +- Use **Would act** when you would genuinely change management based on that notification alone. + +--- + +## 6. Understanding the six feedback ratings + +| Button | When to use it | Example | +|---|---|---| +| **Useful** | Clinically appropriate alert; right concern at right time | NEWS2 rises to 6 as RR and HR worsen in sepsis scenario | +| **Would act** | You would change assessment, monitoring, or treatment because of this alert | qSOFA alert on a patient you would escalate to senior review | +| **Too early** | Directionally right but fired before you would act | Warning HR while patient still asymptomatic and trending stable | +| **Too late** | Real problem existed but alert came after you would have intervened | Deterioration alert after you would already have called a rapid response | +| **False positive** | Alert should not have fired for this patient context | Bradycardia warning on a patient on scheduled metoprolol with baseline low HR | +| **Missing context** | Alert may be valid but message lacks information you need | HR warning without noting recent beta-blocker dose | + +### Optional notes — what to write + +Short phrases are enough. Examples: + +- *"Expected bradycardia — patient on metoprolol 25 mg PO"* +- *"Would have acted on NEWS2 ≥ 7 sooner if bundle status visible"* +- *"Useful but duplicate of HR warning 10 min earlier"* +- *"Needed lactate trend, not just single value"* + +--- + +## 7. Recommended testing sessions + +### Session A — Quick orientation (20–30 minutes) + +**Goal:** Learn the UI and rate at least 5 alerts. + +1. Facilitator replays `stable-baseline-01` — confirm ward stays quiet or low acuity. +2. Facilitator replays `uti-sepsis-elderly-01` at faster speed. +3. You: Virtual Ward → open patient → review 2–3 alerts with reasoning + charts. +4. You: Alert Center → rate remaining alerts. +5. Feedback Summary → export CSV. + +### Session B — Alert quality deep dive (45–60 minutes) + +**Goal:** Compare alert types across scenarios. + +1. `medication-false-alarm-01` — focus on false positives and missing context. +2. `uti-sepsis-elderly-01` — sepsis, NEWS2, bundle panel. +3. `respiratory-failure-asthma-01` or `post-op-hemorrhage-01` — deterioration patterns. +4. Export CSV with notes on at least 10 alerts. + +### Session C — Ward workflow (30 minutes) + +**Goal:** Test prioritization and handoff usability. + +1. Facilitator runs `replay-all` on the scenario folder (or 2–3 scenarios back-to-back). +2. You: Stay on Virtual Ward — note sort order as new patients appear. +3. Round on each high-NEWS2 patient — detail page only, no Alert Center until end. +4. Document: *Would this order match your morning ward round?* + +--- + +## 8. Scenario scripts + +The facilitator runs these from a separate terminal. You watch the dashboard update. + +| Scenario | Clinical story | What to evaluate | +|---|---|---| +| `stable-baseline-01.json` | Stable inpatient, minimal abnormal vitals | Alert noise — should few or no alerts fire? | +| `medication-false-alarm-01.json` | Beta-blocker, baseline low HR, boundary vitals | False alarms, alert fatigue, medication context in reasoning | +| `uti-sepsis-elderly-01.json` | Elderly UTI progressing to sepsis | SIRS/qSOFA, NEWS2 trend, sepsis bundle, usefulness of escalation | +| `respiratory-failure-asthma-01.json` | Asthma exacerbation | RR/SpO₂ warnings, NEWS2, rapid deterioration | +| `post-op-hemorrhage-01.json` | Post-operative bleeding | BP/HR trends, critical vs warning timing | +| `cardiac-arrest-post-mi-01.json` | Post-MI deterioration | High-acuity alerts, would-act vs too-late | +| `dka-electrolyte-01.json` | Metabolic emergency | Multi-parameter scoring, order visibility | +| `hypothermia-elderly-01.json` | Temperature-driven risk | Temp warnings, NEWS2 contribution | + +**Facilitator command (example):** + +```bash +dotnet run --project VigilCare.Simulator -- replay \ + VigilCare.Simulator/Scenarios/List/uti-sepsis-elderly-01.json \ + --speed 60 +``` + +`--speed 60` means 60× real time — a 3-hour scenario completes in a few minutes. Ask the facilitator to pause between scenarios if you need more review time. + +--- + +## 9. Session checklist + +Use this during or after your session. + +### Virtual Ward +- [ ] Patient list loads and refreshes +- [ ] NEWS2 sort order feels clinically sensible +- [ ] Department filter works (if tested) +- [ ] Patient detail opens from row click + +### Patient Detail +- [ ] Latest vitals match what you expect for the scenario +- [ ] NEWS2 score and risk level display correctly +- [ ] Clicking an alert shows reasoning panel +- [ ] Medication context appears when relevant (beta-blocker scenario) +- [ ] Vital sign charts show sensible trends +- [ ] NEWS2 history chart updates over time +- [ ] Sepsis bundle panel appears when sepsis alerts fire +- [ ] **Next Alert →** scrolls to review section and selects alerts + +### Alert Center +- [ ] Open / Acknowledged / Resolved tabs filter correctly +- [ ] Acknowledge and resolve workflow understandable +- [ ] Feedback buttons visible on every card + +### Feedback (Phase 19) +- [ ] Rated at least **5 alerts** (more is better) +- [ ] Used more than one rating category (not all “Useful”) +- [ ] Added notes on at least **2** alerts where context mattered +- [ ] Changed a rating intentionally — confirm new selection sticks after refresh +- [ ] Feedback Summary totals look correct +- [ ] Exported **CSV** (and JSON if requested) + +### Overall clinical judgment +- [ ] Which alert types were most trustworthy? +- [ ] Which caused unnecessary noise? +- [ ] What information was missing from alert text or reasoning? +- [ ] Would you want this on a real ward? Why or why not? + +--- + +## 10. Submitting your feedback + +At the end of your session: + +1. Open **Feedback Summary** (sidebar → Feedback). +2. Review totals and recent entries. +3. Click **Export CSV**. +4. Save the file (e.g. `vigilcare-feedback-dr-smith-2026-06-20.csv`). +5. Send to the facilitator via the method they specify (email, shared drive, study portal). + +The CSV contains: alert ID, alert type, severity, your rating, notes, and timestamp. The research team aggregates exports from all participants. + +**Optional:** Export JSON if the facilitator requests machine-readable format. + +**Same computer:** Ratings persist if you refresh the page on the same browser. **Different computer:** Export before switching devices. + +--- + +## 11. Frequently asked questions + +**I don’t see any patients.** +The simulator may not have run yet, or all encounters are discharged. Ask the facilitator to replay a scenario. + +**Charts are empty but vitals show data.** +Wait a few seconds — the page polls every 5 seconds. If charts stay empty, tell the facilitator. + +**Can I rate the same alert twice?** +You can change your rating; only the latest is kept per alert. + +**Do I have to acknowledge before rating?** +No. Rate any alert in any status. + +**What’s the difference between Useful and Would act?** +*Useful* = good alert clinically. *Would act* = you would specifically change care because of it. An alert can be useful information but not change your plan — use the button that best matches your reasoning. + +**The replay bar doesn’t pause the simulator.** +Correct — replay controls only scrub data already loaded in the browser. The facilitator controls simulator speed separately. + +**Dark mode?** +Toggle in the header if your eyes prefer it; all screens support dark mode. + +**Who do I contact with problems?** +Speak to your session facilitator. Technical issues (blank screen, errors) may need them to restart the API or dashboard. + +--- + +## Quick reference card + +| I want to… | Go to… | +|---|---| +| See all patients by acuity | Virtual Ward | +| Deep-dive one patient | Click patient → Patient Detail | +| Triage all hospital alerts | Alert Center | +| Understand why an alert fired | Patient Detail → click alert → Reasoning | +| Rate an alert | Feedback buttons under alert card or reasoning | +| See my ratings aggregate | Feedback Summary | +| Submit results | Feedback Summary → Export CSV | + +--- + +*Thank you for participating. Your clinical feedback directly shapes whether VigilCare alerts help or harm real ward workflows.* diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md new file mode 100644 index 0000000..6fda3c6 --- /dev/null +++ b/docs/dashboard-guide.md @@ -0,0 +1,312 @@ +# VigilCare Dashboard — User Guide + +The **VigilCare Dashboard** (`vigilcare-dashboard/`) is a Vue 3 single-page application that gives clinicians a live view of the virtual ward: who is on the floor, which patients are deteriorating, what alerts need action, and — on the patient detail screen — vital sign trends, NEWS2 history, and plain-language alert reasoning. + +It reads from the VigilCare Clinical API over HTTP. It does **not** connect to Kafka, RabbitMQ, or PostgreSQL directly. Polling keeps the UI fresh while the API and background consumers do the clinical work. + +--- + +## Table of Contents + +1. [What the dashboard does](#1-what-the-dashboard-does) +2. [Prerequisites](#2-prerequisites) +3. [Quick start](#3-quick-start) +4. [Screens](#4-screens) +5. [Patient detail — clinical review mode](#5-patient-detail--clinical-review-mode) +6. [Working with the simulator](#6-working-with-the-simulator) +7. [Configuration](#7-configuration) +8. [Development](#8-development) +9. [Troubleshooting](#9-troubleshooting) + +--- + +## 1. What the dashboard does + +In a real hospital, nurses and physicians need a ward board: acuity at a glance, drill-down into one patient, and a way to act on alerts. This frontend implements that workflow against the VigilCare API. + +| Capability | Where in the app | API backing | +|---|---|---| +| Ward overview — active patients sorted by NEWS2 risk | **Virtual Ward** (`/ward`) | `GET /encounters?status=ACTIVE` | +| Department filter | Virtual Ward toolbar | Same list endpoint with `department` | +| Patient drill-down | **Patient Detail** (`/patients/:encounterId`) | `GET /encounters/{id}`, observations, scores, orders | +| Latest vitals snapshot | Patient Detail — Vitals panel | `GET /encounters/{id}/observations` | +| NEWS2 current score | Patient Detail — Scores panel | `GET /encounters/{id}/news2/current` | +| Open alerts per patient | Patient Detail — Active Alerts | `GET /encounters/{id}/alerts` | +| Acknowledge / resolve alerts | Patient Detail or Alert Center | `PATCH /alerts/{id}/acknowledge`, `/resolve` | +| Sepsis bundle status | Patient Detail — Sepsis Bundle panel | `GET /encounters/{id}/sepsis-bundle/current` | +| Clinical orders | Patient Detail — Orders panel | `GET /encounters/{id}/orders` | +| Vital sign trend charts (HR, RR, BP, SpO₂, temp) | Patient Detail — Clinical review section | Observations (Chart.js) | +| NEWS2 score over time | Patient Detail — NEWS2 history chart | `GET /encounters/{id}/news2/history` | +| Alert reasoning (“why did this fire?”) | Patient Detail — click an alert row | Client-side rules + alert `details` | +| Medication context on alerts | Alert reasoning panel | `GET /encounters/{id}/medications` | +| Local replay controls | Patient Detail — replay bar | Scrubs fetched data (not live simulator control) | +| Global alert inbox | **Alert Center** (`/alerts`) | `GET /alerts` with status filter | +| Clinician feedback on alerts | Alert Center, Patient Detail reasoning | Pinia + `localStorage` (client-side) | +| Feedback summary & export | **Feedback Summary** (`/feedback`) | Aggregate stats; export JSON/CSV | + +**What it is not:** a full EHR, authentication server, or simulator remote control. Replay controls scrub through data already loaded from the API; they do not pause or speed up the .NET simulator process. Alert feedback is stored in the browser until exported — see [clinical-testing-guide.md](clinical-testing-guide.md) for the clinician workflow. + +--- + +## 2. Prerequisites + +- **Node.js 20+** and npm +- **VigilCare Clinical API** running at `http://localhost:5270` (see root [README](../README.md) — Getting Started) +- **CORS:** API `Dashboard` policy must allow the dev origin (`http://localhost:5173` by default) + +Optional but recommended for demo data: + +- **VigilCare Simulator** — replay a scenario so observations and alerts populate while you watch the dashboard update. See [simulator-guide.md](simulator-guide.md). + +--- + +## 3. Quick start + +From the repository root: + +```bash +# Terminal 1 — infrastructure + API +docker compose up -d +dotnet run --project VigilCareClinicalAPI + +# Terminal 2 — dashboard +cd vigilcare-dashboard +npm install +npm run dev +``` + +Open **http://localhost:5173**. The app redirects to **Virtual Ward**. + +To populate live data, run a simulator scenario in a third terminal: + +```bash +dotnet run --project VigilCare.Simulator -- replay \ + VigilCare.Simulator/Scenarios/List/uti-sepsis-elderly-01.json \ + --speed 60 +``` + +Return to the dashboard, click a patient row, and watch vitals, charts, and alerts update every few seconds. + +--- + +## 4. Screens + +### Virtual Ward (`/ward`) + +The home screen lists **active encounters** as a responsive table (desktop) or card list (mobile). + +- Patients are sorted by **NEWS2 score** (highest risk first). +- A badge shows how many patients have NEWS2 ≥ 7. +- Filter by department: All, ICU, General Medicine, Surgery. +- Click a row to open **Patient Detail**. + +Data refreshes every **10 seconds** via polling. + +### Alert Center (`/alerts`) + +A hospital-wide alert inbox independent of any single patient. + +- Filter tabs: **Open**, **Acknowledged**, **Resolved**, **Escalated**. +- Each card shows severity, alert type, details, and triggered time. +- **Ack** opens a confirmation modal (uses clinician ID from settings — default `DR-DEMO`). +- **Resolve** is available after acknowledge. + +Use this when you need to triage alerts across the ward without opening each patient first. + +### Feedback Summary (`/feedback`) + +**Purpose:** Research and evaluation view for structured clinician ratings on alerts. + +- Aggregate stats: total ratings, % useful/would act, % false positive, timing issues. +- Breakdown by alert type (sepsis, NEWS2, heart rate warning, etc.). +- Recent feedback entries with notes. +- **Export JSON** / **Export CSV** for study analysis. + +Ratings are collected via six buttons on each alert (see [clinical-testing-guide.md](clinical-testing-guide.md) for definitions aimed at doctors and nurses). + +### Patient Detail (`/patients/:encounterId`) + +The clinical review screen. See [§5](#5-patient-detail--clinical-review-mode) for the full breakdown. + +--- + +## 5. Patient detail — clinical review mode + +Layout top to bottom: + +``` +┌─────────────────────────────────────────────────────────┐ +│ ← Ward Patient Name │ +├─────────────────────────────────────────────────────────┤ +│ Scores (NEWS2) │ Latest Vitals │ Active Alerts │ +├─────────────────────────────────────────────────────────┤ +│ Alert reasoning panel (after clicking an alert) │ +├─────────────────────────────────────────────────────────┤ +│ Orders │ Sepsis Bundle (if present) │ +├─────────────────────────────────────────────────────────┤ +│ Vital sign trend charts (5) │ +│ NEWS2 score history chart │ +│ Replay controls │ +└─────────────────────────────────────────────────────────┘ +``` + +#### Scores panel + +Shows the current **NEWS2** total, risk level, and encounter metadata (room/bed, department). If no score has been computed yet, displays `—`. + +#### Vitals panel + +Latest value per observation code (heart rate, respiratory rate, blood pressure, SpO₂, temperature, etc.), deduplicated to the most recent reading per code. + +#### Active alerts + +Open alerts for this encounter. Click a row to open the **reasoning panel** below the grid. + +- **Ack** / **Resolve** buttons use `@click.stop` so they do not trigger row selection. +- Selected row is highlighted. + +#### Alert reasoning + +Explains **why** an alert fired in plain language: + +| Alert type | Explanation shown | +|---|---| +| `WarningHeartRate`, `WarningSystolicBp`, `WarningTempC` | Threshold range + extracted value from `details` | +| `SepsisWarning` | SIRS criteria summary | +| `QsofaWarning` | qSOFA criteria summary | +| `News2Warning` / `News2Emergency` | NEWS2 risk tier | +| `RapidDeterioration` | Trajectory / rate-of-change | +| Unknown types | Falls back to raw `details` string | + +If medications were administered within **90 minutes** before the alert, a **Recent medications** block appears (supports reviewing `medication-false-alarm-01` and similar scenarios). + +#### Orders and sepsis bundle + +- **Orders** — pending and resulted clinical orders for the encounter. +- **Sepsis bundle** — only rendered when a bundle exists; shows compliance status, deadline, and four treatment elements. + +#### Clinical review charts + +Five **vital sign trend** line charts (Chart.js via `vue-chartjs`): + +- Heart rate, respiratory rate, systolic BP, SpO₂, temperature +- Responsive grid: 1 column (mobile) → 2 (`sm`) → 3 (`xl`) +- Respects `prefers-reduced-motion` (animations disabled when OS setting is on) + +**NEWS2 history** chart plots `totalScore` over time with per-point fill colors for risk bands (low / medium / high). + +#### Replay controls + +Local playback UI for reviewing scenario timelines: + +- Pause / resume, speed presets (1×, 60×, 360×, Instant) +- Progress bar and elapsed time display +- **Next Alert →** cycles through open alerts chronologically, selects each for reasoning, and scrolls to the chart section + +> Replay state is **client-side only**. It does not send commands to the simulator. + +Patient detail data polls every **5 seconds**. + +#### Clinician feedback (on alerts) + +Below each alert in **Alert Center** and below the **reasoning panel** on Patient Detail: + +| Rating | Meaning | +|---|---| +| Useful | Clinically appropriate alert | +| Would act | Would change management based on this alert | +| Too early | Right direction, wrong timing (too soon) | +| Too late | Problem existed; alert came too late | +| False positive | Should not have fired for this context | +| Missing context | Valid concern but insufficient information | + +Tap **+ Note** for optional free text. Ratings save automatically in the browser. Export from **Feedback Summary** when the session ends. + +--- + +## 6. Working with the simulator + +Typical demo flow: + +1. Start API + dashboard (§3). +2. Replay a scenario at moderate speed (`--speed 60`). +3. Open **Virtual Ward** — new or updated encounters appear as the simulator creates them. +4. Click the patient — watch vitals and charts fill in as observations ingest. +5. When alerts fire, click an alert row to read reasoning; try **Next Alert →** on the replay bar. +6. Use **Alert Center** to acknowledge alerts globally. + +**Suggested scenarios:** + +| Scenario file | What to observe in the dashboard | +|---|---| +| `uti-sepsis-elderly-01.json` | SIRS/qSOFA alerts, sepsis bundle panel, rising acuity | +| `medication-false-alarm-01.json` | HR warnings with medication context in reasoning | +| `news2-deterioration-01.json` (if present) | NEWS2 history chart and score panel | + +--- + +## 7. Configuration + +| Setting | Location | Default | +|---|---|---| +| API base URL | `vigilcare-dashboard/.env` → `VITE_API_URL` | `http://localhost:5270` | +| Clinician ID (acknowledge) | Pinia `settings` store / `localStorage` key `clinicianId` | `DR-DEMO` | +| Dark mode | Header toggle / `localStorage` key `darkMode` | System preference | +| Ward poll interval | `usePolling` in `WardDashboard.vue` | 10 s | +| Patient detail poll | `usePolling` in `PatientDetail.vue` | 5 s | + +Example `.env`: + +```env +VITE_API_URL=http://localhost:5270 +``` + +--- + +## 8. Development + +```bash +cd vigilcare-dashboard +npm install +npm run dev # http://localhost:5173 +npm test # Vitest — composables + component tests +npm run build # production bundle +npm run preview # preview production build +``` + +**Stack:** Vue 3 (` + + + + \ No newline at end of file diff --git a/vigilcare-dashboard/src/components/layout/AppSidebar.vue b/vigilcare-dashboard/src/components/layout/AppSidebar.vue index 63a4a82..429a17b 100644 --- a/vigilcare-dashboard/src/components/layout/AppSidebar.vue +++ b/vigilcare-dashboard/src/components/layout/AppSidebar.vue @@ -6,6 +6,7 @@ const route = useRoute() const links = [ { to: '/ward', label: 'Virtual Ward', icon: 'ward' }, { to: '/alerts', label: 'Alert Center', icon: 'alerts' }, + { to: '/feedback', label: 'Feedback Summary', icon: 'feedback' }, ] function linkClasses(path) { @@ -45,7 +46,7 @@ function linkClasses(path) { /> + {{ link.label }} diff --git a/vigilcare-dashboard/src/composables/useFeedback.js b/vigilcare-dashboard/src/composables/useFeedback.js new file mode 100644 index 0000000..ed93008 --- /dev/null +++ b/vigilcare-dashboard/src/composables/useFeedback.js @@ -0,0 +1,16 @@ +import { computed } from 'vue' +import { useFeedbackStore } from '@/stores/feedback' + +export function useFeedback(alertId) { + const store = useFeedbackStore() + + const feedback = computed(() => store.getFeedback(alertId)) + const hasRating = computed(() => !!feedback.value) + const rating = computed(() => feedback.value?.rating ?? null) + + function rate(alertType, severity, ratingValue, notes = '') { + store.addFeedback(alertId, alertType, severity, ratingValue, notes) + } + + return { feedback, hasRating, rating, rate } +} \ No newline at end of file diff --git a/vigilcare-dashboard/src/router/index.js b/vigilcare-dashboard/src/router/index.js index f48a60c..883b950 100644 --- a/vigilcare-dashboard/src/router/index.js +++ b/vigilcare-dashboard/src/router/index.js @@ -23,6 +23,12 @@ const routes = [ component: () => import('@/views/AlertCenter.vue'), meta: { title: 'Alert Center', layout: 'default' }, }, + { + path: '/feedback', + name: 'FeedbackSummary', + component: () => import('@/views/FeedbackSummary.vue'), + meta: { title: 'Feedback Summary', layout: 'default' }, + }, ] const router = createRouter({ diff --git a/vigilcare-dashboard/src/stores/feedback.js b/vigilcare-dashboard/src/stores/feedback.js new file mode 100644 index 0000000..5e6ce9f --- /dev/null +++ b/vigilcare-dashboard/src/stores/feedback.js @@ -0,0 +1,88 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useFeedbackStore = defineStore('feedback', () => { + const entries = ref(JSON.parse(localStorage.getItem('vigilcare-feedback') || '[]')) + + const stats = computed(() => { + const total = entries.value.length + if (total === 0) return { total: 0, useful: 0, falsePositive: 0, usefulPct: 0, fpPct: 0 } + + const useful = entries.value.filter(e => e.rating === 'useful' || e.rating === 'would-act').length + const fp = entries.value.filter(e => e.rating === 'false-positive').length + + return { + total, + useful, + falsePositive: fp, + usefulPct: Math.round((useful / total) * 100), + fpPct: Math.round((fp / total) * 100), + } + }) + + const byAlertType = computed(() => { + const map = {} + for (const entry of entries.value) { + if (!map[entry.alertType]) map[entry.alertType] = [] + map[entry.alertType].push(entry) + } + return map + }) + + function addFeedback(alertId, alertType, severity, rating, notes = '') { + const existing = entries.value.findIndex(e => e.alertId === alertId) + const entry = { + alertId, + alertType, + severity, + rating, + notes: notes.trim(), + timestamp: new Date().toISOString(), + } + + if (existing >= 0) { + entries.value[existing] = entry + } else { + entries.value.push(entry) + } + + persist() + } + + function getFeedback(alertId) { + return entries.value.find(e => e.alertId === alertId) ?? null + } + + function persist() { + localStorage.setItem('vigilcare-feedback', JSON.stringify(entries.value)) + } + + function exportAsJson() { + const blob = new Blob([JSON.stringify(entries.value, null, 2)], { type: 'application/json' }) + downloadBlob(blob, 'vigilcare-feedback.json') + } + + function exportAsCsv() { + const headers = ['alertId', 'alertType', 'severity', 'rating', 'notes', 'timestamp'] + const rows = entries.value.map(e => headers.map(h => `"${(e[h] ?? '').toString().replace(/"/g, '""')}"`).join(',')) + const csv = [headers.join(','), ...rows].join('\n') + const blob = new Blob([csv], { type: 'text/csv' }) + downloadBlob(blob, 'vigilcare-feedback.csv') + } + + function downloadBlob(blob, filename) { + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = filename + a.click() + URL.revokeObjectURL(url) + } + + function clearAll() { + entries.value = [] + persist() + } + + return { entries, stats, byAlertType, addFeedback, getFeedback, exportAsJson, exportAsCsv, clearAll } +}) \ No newline at end of file diff --git a/vigilcare-dashboard/src/views/FeedbackSummary.vue b/vigilcare-dashboard/src/views/FeedbackSummary.vue new file mode 100644 index 0000000..f22b420 --- /dev/null +++ b/vigilcare-dashboard/src/views/FeedbackSummary.vue @@ -0,0 +1,103 @@ + + + \ No newline at end of file