Files
vigilcare-clinical/docs/dashboard-gap-analysis.md
T
voltsrage 7751d6df06 fix:
No SOFA trend chart or organ-system timeline
No GCS trend chart or component history
No qSOFA history view
2026-06-23 18:00:43 +08:00

50 KiB

VigilCare Dashboard — Gap Analysis

Comprehensive gap analysis of the VigilCare clinical dashboard system, covering the Vue 3 frontend (vigilcare-dashboard/), the API endpoints that feed it, and the Grafana infrastructure dashboard (infra/grafana/). Items are assessed from the perspective of clinical usefulness — what a nurse, physician, charge nurse, or administrator needs at the bedside, ward board, or admin console that the current dashboard does not provide.

Each item includes who needs it (clinical role), why it matters for patient care or operational efficiency, and how to implement it at a component and API level.


Priority legend

Tier Meaning
P0 Missing capability that directly affects patient safety or clinical decision-making at the bedside
P1 Missing workflow that forces clinicians to leave the dashboard and use another tool (paper, EHR, phone)
P2 Missing view that degrades situational awareness for charge nurses, supervisors, or administrators
P3 UX/usability gap that slows clinical workflows or increases cognitive load
P4 Missing operational/admin dashboard capability for IT ops or compliance teams
P5 Polish, accessibility, and developer experience improvements

Part A — Patient Safety & Bedside Decision Support


P0 — No SOFA trend chart or organ-system timeline

Who needs it

Physicians (ICU attendings, hospitalists), advanced practice providers.

Problem

The Patient Detail view shows the current SOFA score in SofaScorePanel.vue with organ-system breakdowns and staleness flags, but there is no SOFA history chart. The backend exposes GET /api/v1/encounters/{id}/sofa/history with full component breakdowns, but the dashboard does not render it. NEWS2 has a dedicated News2History.vue chart — SOFA does not.

Why fix

SOFA delta (change from baseline) is the primary trigger for sepsis bundle initiation. A physician reviewing a sepsis case needs to see when organ dysfunction began, which organs are driving the score, and whether the trajectory is improving or worsening. Without this, they must mentally reconstruct the trajectory from individual vital signs and lab values — exactly the cognitive burden an early warning system should eliminate.

How to fix

  1. Create SofaHistory.vue chart component showing SOFA total score over time (line chart, matching News2History.vue pattern).
  2. Add optional stacked area or grouped bar overlay showing per-organ contributions (respiratory, coagulation, liver, cardiovascular, CNS, renal).
  3. Fetch GET /encounters/{id}/sofa/history in PatientDetail.vue (already cursor-paginated).
  4. Add a fetchSofaHistory(encounterId, { limit }) function to clinical.js.
  5. Place below SofaScorePanel in the patient detail layout.
  6. Color-code risk bands: 0-5 (green), 6-9 (amber), 10+ (red).

Frontend files: New components/charts/SofaHistory.vue, update views/PatientDetail.vue, update api/clinical.js.

Backend: Already complete — SofaController.History exists.

Dependency: None.


P0 — No GCS trend chart or component history

Who needs it

Nurses (neuro checks), physicians (neurology, trauma, ICU).

Problem

GCS is displayed as a single current value in ScoresPanel.vue with an inline entry form (GcsEntryForm.vue), but there is no history view. For patients with traumatic brain injury, stroke, or post-operative neurosurgery, GCS trend is a primary indicator of neurological deterioration. The backend has no GET /encounters/{id}/gcs/history endpoint — only GET /encounters/{id}/gcs (latest).

Why fix

A GCS drop from 14 to 10 over 4 hours is a neurosurgical emergency. Without a trend view, nurses performing hourly neuro checks cannot see the trajectory — they only see the current score and must remember or write down previous values. This defeats the purpose of electronic GCS tracking.

How to fix

  1. Backend: Add GET /api/v1/encounters/{encounterId}/gcs/history endpoint to GcsController with cursor pagination (mirror NEWS2/SOFA pattern). Query GcsScores table ordered by CalculatedAt DESC.
  2. Create GcsHistory.vue chart component showing total GCS and optionally eye/verbal/motor component lines.
  3. Add fetchGcsHistory(encounterId, { limit }) to clinical.js.
  4. Render below or alongside ScoresPanel in PatientDetail.vue.
  5. Highlight score boundaries: mild (13-15), moderate (9-12), severe (3-8).

Frontend files: New components/charts/GcsHistory.vue, update views/PatientDetail.vue, update api/clinical.js.

Backend files: GcsController.cs, GcsService.cs (new GetHistoryAsync).

Dependency: None.


P0 — No qSOFA history view

Who needs it

Physicians, nurse practitioners (sepsis screening).

Problem

QsofaController only exposes GET /encounters/{id}/qsofa/current (Redis-backed real-time state). There is no qSOFA history endpoint and no dashboard view of qSOFA criteria over time. The score panel shows the current ActiveCriteria count and individual criterion states — but a clinician cannot see when criteria first appeared or how the screen has evolved.

Why fix

qSOFA is a screening tool — it triggers the recommendation to order SOFA labs. Seeing when criteria first became positive (and whether they have resolved) is critical for assessing whether sepsis workup is still indicated or whether a false alarm resolved.

How to fix

  1. Backend: Add GET /api/v1/encounters/{encounterId}/qsofa/history with cursor pagination. Store qSOFA evaluations in the database (currently Redis-only with 30-min TTL) or derive from observation history.
  2. Create QsofaHistory.vue timeline or chart showing criteria count over time with markers for when alerts fired.
  3. Add fetchQsofaHistory(encounterId, { limit }) to clinical.js.
  4. Render in PatientDetail.vue clinical review section.

Frontend files: New components/charts/QsofaHistory.vue, update views/PatientDetail.vue, update api/clinical.js.

Backend files: QsofaController.cs, QsofaService.cs, potentially new QsofaEvaluation entity and migration.

Dependency: Backend persistence decision (Redis-only vs. database-backed history).


P0 — No medication timeline integrated with vital sign charts

Who needs it

Physicians, pharmacists, nurses.

Problem

Medications are fetched in PatientDetail.vue but only used in the AlertReasoning.vue panel (to show "recent medications" within 90 minutes of an alert). They are not displayed on vital sign trend charts as event markers. A nurse looking at a heart rate spike cannot see that a beta-blocker was administered 30 minutes prior.

Why fix

Medication-vital correlation is fundamental to clinical assessment. Without overlaying medication administration times on vital trends, clinicians cannot distinguish pharmacological effects from pathological deterioration. The medication-false-alarm-01 simulator scenario was specifically designed to test this, but the visual integration is incomplete.

How to fix

  1. Add vertical annotation lines or markers on VitalTrendChart.vue for medication administration timestamps.
  2. Filter medications to the same time window as the observation data.
  3. Show medication name, dose, and route on hover/tooltip.
  4. Use a distinct visual style (dashed vertical line with label) to avoid confusion with observation data points.

Frontend files: Update components/charts/VitalTrendChart.vue and TrendsGrid.vue to accept a medications prop. Update PatientDetail.vue to pass medications to chart grid.

Backend: Already complete — MedicationsController.List returns timestamped administrations.

Dependency: None.


P1 — No encounter timeline view in the dashboard

Who needs it

All clinicians (handoff, shift change, case review).

Problem

The backend has GET /api/v1/encounters/{id}/timeline which returns a merged chronological timeline of observations, alerts, orders, medications, and status changes. The dashboard does not render this view. Currently, observations, alerts, orders, and sepsis bundles are displayed in separate panels with no unified chronological view.

Why fix

During shift handoff, the outgoing clinician needs to tell a story: "at 14:00 the patient spiked a fever, at 14:15 qSOFA fired, at 14:30 we ordered labs, at 15:00 SOFA delta triggered a bundle, antibiotics were given at 15:20." The separate panels force the receiving clinician to mentally merge 4+ data streams. A timeline view is the standard handoff tool in clinical settings.

How to fix

  1. Create EncounterTimeline.vue component rendering a vertical chronological timeline.
  2. Each event type gets a distinct icon/color: observation (blue), alert (red/amber), order (purple), medication (green), status change (gray).
  3. Fetch GET /encounters/{id}/timeline — add fetchTimeline(encounterId) to encounters.js.
  4. Add as a tab or expandable section in PatientDetail.vue.
  5. Support time-range filter and event-type toggle.

Frontend files: New components/patient/EncounterTimeline.vue, update views/PatientDetail.vue, update api/encounters.js.

Backend: Already complete — EncountersController.Timeline exists.

Dependency: None.


P1 — No patient demographics or allergy display

Who needs it

All clinicians, especially nurses (medication administration safety check).

Problem

PatientDetail.vue shows encounter.patient?.firstName and encounter.patient?.lastName in the header, but does not display: date of birth, age, gender, blood type, allergies, emergency contact, MRN (except in the ward table), or admission reason. The Patient entity contains all of these fields. The Encounter entity contains room/bed, department, attending physician, and admission reason.

Why fix

Allergies are a patient safety field — a nurse confirming medication administration needs allergies visible. Age affects clinical scoring interpretation (NEWS2 and SOFA have different clinical implications for elderly patients). Blood type is critical during transfusion decisions. Emergency contact is needed when a patient cannot advocate for themselves.

How to fix

  1. Create PatientBanner.vue — a persistent header strip showing: name, MRN, DOB/age, gender, allergies (highlighted), blood type, room/bed, department, attending physician, admission reason.
  2. Use standard clinical banner pattern: red background for known allergies, "NKDA" (no known drug allergies) if allergies field is empty.
  3. Replace the simple name display in PatientDetail.vue header with PatientBanner.
  4. Data already available from GET /encounters/{id} response (includes nested patient).

Frontend files: New components/patient/PatientBanner.vue, update views/PatientDetail.vue.

Backend: Already complete — encounter detail includes patient demographics.

Dependency: None.


Part B — Ward Management & Situational Awareness


P1 — Ward table missing critical clinical columns

Who needs it

Charge nurses, nursing supervisors.

Problem

WardEncounterSummary provides: MRN, name, room/bed, department, status, NEWS2 score/risk, qSOFA score, sepsis active flag, sepsis bundle status, and open alert count. WardTable.vue and PatientRow.vue render these fields. Missing from the ward view:

  • SOFA score and delta
  • GCS score (critical for neuro patients)
  • Last observation time (how stale is the data?)
  • Attending physician name
  • Length of stay (days since admission)

Why fix

A charge nurse scanning the ward board needs to identify: (1) patients with active organ dysfunction (SOFA), (2) patients with neurological decline (GCS), (3) patients who haven't had vitals taken recently (stale data = missed deterioration), and (4) who is responsible for each patient. The current ward table is blind to SOFA, GCS, data freshness, and physician assignment.

How to fix

  1. Backend: Extend WardEncounterSummary record to include: int? SofaScore, int? SofaDelta, int? GcsScore, string? GcsClassification, DateTimeOffset? LastObservationAt, string? AttendingPhysician, DateTimeOffset OpenedAt.
  2. Update EncounterService.ListAsync to join/subquery for SOFA, GCS, and latest observation timestamp.
  3. Frontend: Add columns to WardTable.vue and data to PatientRow.vue / PatientCard.vue.
  4. Add "stale" visual indicator (amber/red text or icon) when LastObservationAt is > 2 hours old for active inpatients.
  5. Compute and display length of stay from OpenedAt.

Frontend files: Update components/ward/WardTable.vue, PatientRow.vue, PatientCard.vue, stores/ward.js.

Backend files: WardEncounterSummary.cs, EncounterService.cs.

Dependency: None.


P2 — No hospital-wide sepsis bundle compliance board

Who needs it

Charge nurses, sepsis coordinators, quality improvement teams.

Problem

Sepsis bundle status is only visible per-patient in SepsisBundlePanel.vue. There is no hospital-wide view showing all active bundles, their compliance status, time remaining, and which treatment elements are outstanding. The backend has GET /api/v1/sepsis-bundles?status=IN_PROGRESS which lists bundles across all encounters — the dashboard does not consume it.

Why fix

Sepsis bundle compliance (CMS SEP-1) is a hospital quality metric reported to regulatory bodies. A charge nurse or sepsis coordinator needs a single screen showing: "3 active bundles — 1 on track, 1 at risk (45 min remaining, antibiotics pending), 1 overdue." Without this, bundle compliance tracking requires opening each patient individually or running database queries.

How to fix

  1. Create SepsisBoardView.vue — new route at /sepsis.
  2. Display a table/card list of active bundles with: patient name/MRN, department, bundle creation time, deadline, time remaining (countdown), compliance status, completed elements, outstanding elements.
  3. Sort by urgency: overdue first, then by time remaining ascending.
  4. Add severity coloring: green (> 30 min remaining), amber (< 30 min), red (overdue or non-compliant).
  5. Click-through to PatientDetail for the encounter.
  6. Add fetchSepsisBundles({ status, page, pageSize }) to a new api/sepsis.js module.
  7. Add navigation link in AppSidebar.vue.

Frontend files: New views/SepsisBoardView.vue, new api/sepsis.js, update router/index.js, update components/layout/AppSidebar.vue.

Backend: Already complete — SepsisBundlesController.List exists.

Dependency: None.


P2 — No department/unit view with aggregate metrics

Who needs it

Charge nurses, nursing managers, hospital administrators.

Problem

The ward dashboard filters by department but shows a flat patient list. There is no department-level summary showing: total patients, acuity distribution (how many high/medium/low NEWS2), active alerts, active sepsis bundles, staffing context, or bed occupancy. The AnalyticsController has GET /analytics/alerts/summary (by department) and GET /analytics/population (by observation threshold) — neither is surfaced in the dashboard.

Why fix

A nursing manager responsible for 4 units needs a summary view, not 80 individual patient rows. "ICU: 12 patients, 4 critical (NEWS2 ≥7), 2 active sepsis bundles, 15 unacknowledged alerts" is actionable. Scrolling through 12 patient rows to derive the same information is not.

How to fix

  1. Create DepartmentOverviewView.vue — new route at /departments or modify /ward to show summary cards above the patient list.
  2. Show per-department cards with: patient count, acuity distribution (pie/bar), active bundle count, unacknowledged alert count, average NEWS2.
  3. Click a department card to filter the ward table to that department.
  4. Consume GET /analytics/alerts/summary and derive patient counts from GET /encounters?department=X&status=ACTIVE.
  5. Optionally add GET /api/v1/encounters/summary backend endpoint to return pre-aggregated counts by department.

Frontend files: New views/DepartmentOverviewView.vue or enhanced WardDashboard.vue, new API calls in api/encounters.js.

Backend files: Optionally new summary endpoint in EncountersController.

Dependency: None.


P2 — No sort controls on the ward table

Who needs it

All clinicians using the ward view.

Problem

WardTable.vue sorts exclusively by NEWS2 risk (highest first) via sortedByRisk computed property in the ward store. There is no UI to sort by: patient name, room/bed, department, alert count, qSOFA score, or sepsis bundle status. The table is virtual-scrolled (50-row window) which is good for performance but has no column header click-to-sort.

Why fix

A nurse doing room rounds wants to sort by room/bed. A physician looking for a specific patient wants to sort by name. A charge nurse wants to sort by alert count to prioritize triage. Fixed single-sort works for the "acuity first" workflow but blocks all others.

How to fix

  1. Add sortable column headers to WardTable.vue with visual sort indicators (arrow up/down).
  2. Extend ward.js store to support sortField and sortDirection state.
  3. Compute sorted list based on selected sort field.
  4. Default to NEWS2 risk (preserve current behavior).
  5. Persist sort preference to localStorage via settings store.

Frontend files: Update components/ward/WardTable.vue, update stores/ward.js.

Backend: No change needed — sorting is client-side on the full result set.

Dependency: None.


P2 — No search/filter on the ward table

Who needs it

All clinicians.

Problem

The ward table has department filter only (4 options: All, ICU, General Medicine, Surgery). There is no text search for patient name or MRN, no filter by acuity level, no filter by "has active alerts", no filter by "has active sepsis bundle." The backend AnalyticsController.PatientSearch provides full-text search via Elasticsearch — it is not used by the dashboard.

Why fix

On a 40-patient ward, finding a specific patient by name or MRN requires visual scanning. Filtering to "only patients with unacknowledged alerts" is the most common charge nurse workflow. Both are O(n) eye-scanning today vs. instant with a search box.

How to fix

  1. Add a search input to the ward toolbar (debounced, 300ms).
  2. Client-side filter for name/MRN matching (data already loaded).
  3. Add filter chips or toggles: "Has alerts", "Sepsis active", "Critical (NEWS2 ≥7)".
  4. Optionally integrate GET /analytics/patients for server-side full-text search when local filtering is insufficient.

Frontend files: Update views/WardDashboard.vue, update stores/ward.js.

Backend: No change for client-side filtering. Optional integration with existing AnalyticsController.

Dependency: None.


Part C — Clinical Workflow Gaps


P1 — No notification/sound for new critical alerts

Who needs it

Nurses at nurse stations, charge nurses with dashboard on wall display.

Problem

The dashboard polls every 5-10 seconds and silently updates. When a new CRITICAL alert fires (SOFA_SEPSIS, NEWS2_EMERGENCY, GCS_CRITICAL, CRITICAL_HEART_RATE), there is no audible alert, no browser notification, no visual flash. A nurse glancing at a wall-mounted dashboard will not notice a new critical alert unless they happen to look at the right moment.

Why fix

Clinical alerting is safety-critical. Wall-mounted dashboards are common in nurse stations. Without attention-grabbing notification on critical alerts, the dashboard provides less situational awareness than a traditional alarm bell. The entire alert pipeline (Kafka → alert creation → RabbitMQ paging) exists but the "last mile" to the dashboard screen is passive polling.

How to fix

  1. Track lastSeenAlertIds in the alert store.
  2. On each poll, detect new critical alerts by comparing IDs.
  3. Play an audible tone (configurable, with mute toggle) for new CRITICAL alerts.
  4. Show a browser notification (requires permission request) with patient name and alert type.
  5. Flash the browser tab title: "⚠ CRITICAL ALERT — VigilCare".
  6. Add a visual banner at the top of the page that persists until dismissed.
  7. Long-term: Replace polling with WebSocket/SSE for real-time push (see P4-07).

Frontend files: Update stores/alerts.js, new composables/useAlertNotification.js, update App.vue for banner.

Backend: No change for polling approach. Future: add SignalR hub or SSE endpoint.

Dependency: None for polling-based. SSE/WebSocket requires backend work.


P1 — No handoff report or shift summary

Who needs it

Nurses, physicians (shift change).

Problem

There is no printable or exportable shift summary. During handoff, a clinician needs a snapshot: "these are my patients, here are their current scores, here are the active alerts and pending actions." The dashboard is live and interactive — there is no "print this shift's state" or "generate a summary" feature.

Why fix

Shift handoff is the highest-risk moment for information loss. SBAR (Situation, Background, Assessment, Recommendation) handoff is standard practice. A one-page summary per patient (or one page for the entire ward) eliminates reliance on memory and verbal communication.

How to fix

  1. Add "Export Handoff Report" button to the ward toolbar.
  2. Generate a printable HTML view (or PDF via browser print) with:
    • Ward summary: department, patient count, critical count, active bundles.
    • Per-patient row: name, MRN, room, NEWS2, SOFA, GCS, active alerts summary, pending orders, key vitals (latest HR, RR, BP, SpO₂, temp).
  3. Use @media print CSS for clean print layout.
  4. Optionally add per-patient SBAR summary (Situation = chief complaint + admission reason, Background = allergies + history, Assessment = scores + trends, Recommendation = pending orders + bundle status).

Frontend files: New components/ward/HandoffReport.vue, print-specific CSS, button in WardDashboard.vue.

Backend: No change — all data already available from existing endpoints.

Dependency: P1 (patient demographics) for allergy display.


P2 — No discharge summary view

Who needs it

Physicians, case managers.

Problem

DischargeSummaryWorkerService generates PDF discharge summaries and stores them in MinIO, but there is no dashboard UI to view or download them. EncountersController.TransitionStatus handles discharge transitions, but the resulting PDF is only accessible via MinIO's S3 API.

Why fix

Discharge summaries are legally required documents. Physicians need to review the auto-generated summary, and case managers need to verify completeness before patient leaves. Without dashboard access, these documents require S3 bucket browsing or CLI tools.

How to fix

  1. Backend: Add GET /api/v1/encounters/{id}/discharge-summary endpoint that returns a pre-signed MinIO URL or streams the PDF.
  2. Frontend: Add "Discharge Summary" section to PatientDetail.vue (visible only for DISCHARGED encounters).
  3. Embed PDF viewer or download link.
  4. Show generation status if the async worker hasn't completed yet.

Frontend files: New components/patient/DischargeSummaryPanel.vue, update PatientDetail.vue.

Backend files: New endpoint in EncountersController, pre-signed URL generation for MinIO.

Dependency: None.


P3 — Alert acknowledgment lacks role context

Who needs it

All clinicians, audit/compliance teams.

Problem

AcknowledgeModal.vue uses the clinician ID from settings (default DR-DEMO) as a free-text string. There is no role context — the system does not distinguish between a nurse acknowledging an alert (documenting awareness) and a physician acknowledging it (confirming clinical assessment). The POST /alerts/{id}/acknowledge endpoint accepts acknowledgedByClinicianId as a string.

Why fix

Clinical audit trails require knowing who acknowledged an alert and in what capacity. A nurse acknowledging a critical alert means "I've seen this and will escalate to a physician." A physician acknowledging means "I've assessed this and am taking action." These are distinct clinical events.

How to fix

  1. Replace free-text clinician ID with the authenticated user's identity from the JWT token.
  2. Display the user's name and role in the acknowledgment confirmation modal.
  3. Include role in the acknowledgment note automatically.
  4. Update AlertCard.vue to show who acknowledged (name + role) instead of raw clinician ID.

Frontend files: Update components/alerts/AcknowledgeModal.vue, AlertCard.vue, stores/auth.js.

Backend: Minor — already receives user identity via JWT. Ensure AcknowledgedBy stores user ID, not free-text string.

Dependency: None.


P3 — No observation entry form (nurse charting)

Who needs it

Nurses (primary vital sign recorders).

Problem

The dashboard has GcsEntryForm.vue for GCS component entry but no form for standard vital signs (HR, RR, BP, SpO₂, temperature, AVPU). Nurses must enter vitals through another system or direct API calls. The POST /encounters/{id}/observations endpoint exists and accepts single or batch observations.

Why fix

If nurses cannot chart vitals in the dashboard, they need a separate system for data entry and only use the dashboard for viewing. This dual-system workflow increases error risk and reduces adoption. A single system for both entry and monitoring is the clinical standard.

How to fix

  1. Create VitalsEntryForm.vue — a structured form with fields for all standard vital signs.
  2. Pre-populate observation codes: HEART_RATE, RESP_RATE, SYSTOLIC_BP, DIASTOLIC_BP, SPO2, TEMP_C, AVPU.
  3. Add plausibility validation client-side (e.g., HR 20-300, temp 30-45°C) matching backend PlausibilityValidator.
  4. Submit as batch observation via POST /encounters/{id}/observations.
  5. Add to PatientDetail.vue as a collapsible form or modal triggered by a "Record Vitals" button.

Frontend files: New components/patient/VitalsEntryForm.vue, update views/PatientDetail.vue, update api/encounters.js.

Backend: Already complete — observation ingest endpoint exists with batch support.

Dependency: None.


Part D — Admin & Operational Dashboards


P2 — No alert threshold management UI

Who needs it

Clinical administrators, biomedical engineering.

Problem

Alert thresholds are managed via API only (AlertThresholdsController — full CRUD with audit trail and Redis cache invalidation). There is no dashboard UI for viewing or editing thresholds. A clinical administrator who needs to adjust the heart rate warning range from 50-100 to 50-110 must use curl or Postman.

Why fix

Threshold tuning is the primary mechanism for reducing alert fatigue — the #1 clinician complaint about clinical alerting systems. Without a UI, threshold adjustments require developer involvement, creating a bottleneck that prevents clinical teams from optimizing their own alert configuration.

How to fix

  1. Create ThresholdManagementView.vue — new route at /admin/thresholds.
  2. Display a table of all thresholds: observation code, warning low/high, critical low/high, unit.
  3. Inline editing or modal edit form with validation.
  4. Create/delete threshold support.
  5. Add to sidebar under an "Admin" section (visible only to Admin role).
  6. Consume GET /alert-thresholds, POST, PUT /alert-thresholds/{id}, DELETE /alert-thresholds/{id}.

Frontend files: New views/ThresholdManagementView.vue, new api/thresholds.js, update router/index.js, update AppSidebar.vue.

Backend: Already complete — full CRUD exists with DELETE support.

Dependency: Role-based navigation (P3-02).


P2 — No user management UI

Who needs it

Hospital IT administrators.

Problem

User creation and role assignment happen via API only (AuthController.Login issues JWT tokens, but user registration is seed-data or direct DB). There is no dashboard for managing clinical users, assigning roles, or deactivating accounts.

Why fix

A hospital with 200 clinicians cannot manage user accounts via database inserts. New staff onboarding, role changes (nurse → charge nurse), and account deactivation (termination, transfer) are daily IT operations that need a UI.

How to fix

  1. Backend first: Add UsersController with GET /api/v1/users (list), POST (create), PATCH /{id} (update role, deactivate), protected by UsersAdmin permission.
  2. Create UserManagementView.vue — new route at /admin/users.
  3. Table of users: username, role, email, last login, active status.
  4. Create/edit form: username, password (create only), role selection, active toggle.
  5. Restrict route to Admin role.

Frontend files: New views/UserManagementView.vue, new api/users.js, update router/index.js, update AppSidebar.vue.

Backend files: New UsersController.cs, new UserService.cs CRUD methods.

Dependency: Backend user management endpoints.


P2 — No gateway/site status dashboard

Who needs it

IT operations, hospital administrators.

Problem

The Ward Gateway system tracks site registration, gateway heartbeats (ONLINE/DEGRADED/OFFLINE), and buffer depth via GatewaysController and SitesController. There is no dashboard for monitoring gateway health. An IT operator who needs to see which ward gateways are offline must query the API directly.

Why fix

Gateway outages directly affect clinical data flow. If a ward gateway goes OFFLINE, observations from that ward stop syncing to the central system — scoring, alerting, and sepsis detection all stop for those patients. This is a patient safety concern that needs a visible status board.

How to fix

  1. Create GatewayStatusView.vue — new route at /admin/gateways.
  2. Display sites as collapsible sections, each containing its gateways.
  3. Per gateway: status badge (green/amber/red), last heartbeat time, buffer depth, department, last sync time.
  4. Auto-refresh every 30 seconds.
  5. Highlight gateways with buffer depth > 0 (data waiting to sync) or heartbeat older than 2 minutes.
  6. Consume GET /sites, GET /sites/{id}/gateways.

Frontend files: New views/GatewayStatusView.vue, new api/gateways.js, update router/index.js, update AppSidebar.vue.

Backend: Already complete — site and gateway endpoints exist.

Dependency: None.


P4 — No audit log viewer

Who needs it

Compliance officers, hospital administrators.

Problem

AuditLogsController and PhiAccessLogsController expose paginated, filterable audit trails — who did what, when, to which entity. The dashboard has no UI for browsing audit logs. Compliance reviews and incident investigations require API queries or direct database access.

Why fix

HIPAA requires demonstrable access controls and audit trails. During a compliance audit or security incident investigation, a compliance officer needs to answer: "Who accessed Patient X's records in the last 30 days?" and "Who modified alert threshold Y?" Without a UI, this requires technical staff to run queries.

How to fix

  1. Create AuditLogView.vue — new route at /admin/audit.
  2. Filterable table: date range, user, entity type, action type.
  3. Show before/after state for modifications (already stored in ClinicalAuditLog.OldValues / NewValues).
  4. Add PHI access log tab with patient-specific filtering.
  5. Restrict to AuditRead permission.
  6. Consume GET /audit-logs and GET /phi-access-logs.

Frontend files: New views/AuditLogView.vue, new api/audit.js, update router/index.js, update AppSidebar.vue.

Backend: Already complete — both endpoints exist with full filter support.

Dependency: Role-based navigation.


P4 — No reconciliation dashboard

Who needs it

Charge nurses, quality improvement teams.

Problem

ReconciliationScheduler runs periodic checks for data quality issues (unacknowledged alerts > 30 min, pending orders > 4 hours, stale observations > 2 hours for active inpatients). ReconciliationAlertsController exposes the findings. The dashboard does not display them.

Why fix

Reconciliation findings identify systemic workflow failures — not individual patient emergencies, but patterns like "3 patients on Ward B haven't had vitals recorded in 3 hours." Without a dashboard view, these operational insights are invisible.

How to fix

  1. Create ReconciliationView.vue — new route at /admin/reconciliation.
  2. Three sections (matching check types): unacknowledged alerts, pending orders, stale observations.
  3. Each item links to the relevant patient or encounter.
  4. Show resolved vs. unresolved toggle.
  5. Consume GET /reconciliation-alerts?checkType=X&resolved=false.

Frontend files: New views/ReconciliationView.vue, new api/reconciliation.js, update router/index.js, update AppSidebar.vue.

Backend: Already complete — controller and scheduler exist.

Dependency: None.


Part E — UX & Usability


P3 — No role-based navigation or view scoping

Who needs it

All users.

Problem

The dashboard shows the same 4 routes to all authenticated users regardless of role (Nurse, Physician, Admin, Integration). All navigation items are visible in AppSidebar.vue. Admin-only views (audit logs, threshold management, user management) do not exist yet, but when they do, they should be role-scoped. Even existing views should adapt — a physician may want SOFA/GCS prominent while a nurse needs vitals entry and alert acknowledgment prominent.

Why fix

Clinicians have limited screen time. Showing irrelevant navigation and features increases cognitive load and onboarding time. Admin features visible to bedside nurses create confusion and accidental access risk.

How to fix

  1. Read the user's role from the JWT token (already decoded in auth.js store).
  2. Add meta.requiredRole or meta.requiredPermission to route definitions.
  3. Filter sidebar navigation items by user role.
  4. Add v-if guards on role-specific components.
  5. Define role-view mapping: Nurse (ward, patients, alerts), Physician (ward, patients, alerts, feedback), Admin (all views + admin section), Integration (N/A — API-only role).

Frontend files: Update router/index.js, update components/layout/AppSidebar.vue, update stores/auth.js.

Backend: No change — role info already in JWT claims.

Dependency: None.


P3 — No dark mode consistency across all components

Who needs it

Clinicians in dimly lit environments (night shift, ICU).

Problem

Dark mode toggle exists (useDarkMode.js, header toggle, localStorage persistence), and Tailwind dark: classes are applied throughout. However, Chart.js charts (VitalChart.vue, VitalTrendChart.vue, News2History.vue) use hardcoded colors that do not adapt to dark mode — light gray gridlines and axis labels that become invisible on dark backgrounds.

Why fix

ICU and night-shift environments are deliberately dimly lit. A bright chart area within a dark dashboard creates eye strain and reduces readability. Clinical dashboards on wall-mounted monitors must be readable in both lighting conditions without manual adjustment.

How to fix

  1. Create a useChartTheme.js composable that returns Chart.js options based on current dark mode state.
  2. Dynamically set scales.x.ticks.color, scales.y.ticks.color, scales.x.grid.color, scales.y.grid.color, plugins.legend.labels.color based on dark/light mode.
  3. Apply to all chart components: VitalChart.vue, VitalTrendChart.vue, News2History.vue, and any new chart components (SofaHistory, GcsHistory).
  4. React to dark mode toggle via watch on the dark mode composable.

Frontend files: New composables/useChartTheme.js, update all chart components.

Backend: No change.

Dependency: None.


P3 — No keyboard shortcuts for alert triage

Who needs it

Nurses, charge nurses (high-volume alert triage).

Problem

Alert acknowledgment requires: click alert card → click Ack button → confirm in modal. During a high-alert-volume period (sepsis onset, code event), this three-click workflow repeated across 10+ alerts is slow. There are no keyboard shortcuts for common alert actions.

Why fix

Clinical alert triage during critical events needs to be as fast as possible. Keyboard shortcuts (e.g., A to acknowledge selected, R to resolve, J/K to navigate alerts) reduce triage time from seconds per alert to milliseconds.

How to fix

  1. Add keyboard event listeners in AlertCenter.vue and PatientDetail.vue.
  2. Implement navigation: J (next alert), K (previous alert), Enter (select).
  3. Implement actions: A (acknowledge selected), R (resolve selected), Esc (cancel modal).
  4. Show keyboard shortcut hints in the UI (small tooltip or ? help overlay).
  5. Respect focus context — only activate when no input/textarea is focused.

Frontend files: New composables/useAlertKeyboard.js, update views/AlertCenter.vue, components/patient/AlertsList.vue.

Backend: No change.

Dependency: None.


P3 — No responsive mobile optimization for patient detail

Who needs it

Nurses at bedside (tablet/phone).

Problem

WardDashboard.vue has mobile support (PatientCard.vue for mobile, PatientRow.vue for desktop). PatientDetail.vue uses a grid layout (sm:grid-cols-2 lg:grid-cols-3) but the clinical review section with 5 vital trend charts and replay controls is not optimized for mobile. Charts may overflow or be too small to read on a phone screen.

Why fix

Bedside clinicians increasingly use tablets and phones. A nurse confirming vital signs at the bedside needs to see the patient's current scores and trends on a handheld device. If charts are unreadable or the page requires horizontal scrolling, the nurse will revert to paper.

How to fix

  1. Audit all PatientDetail.vue sub-components at 320px, 768px, and 1024px viewports.
  2. Stack panels vertically on mobile (scores → vitals → alerts → orders).
  3. Make charts full-width on mobile with touch-friendly tooltips.
  4. Collapse less-critical sections (replay controls, feedback) behind expandable accordions on mobile.
  5. Ensure tap targets are ≥ 44px for all buttons (WCAG minimum).

Frontend files: Update views/PatientDetail.vue, all components/patient/*.vue, components/charts/*.vue.

Backend: No change.

Dependency: None.


P5 — No accessibility (WCAG) compliance

Who needs it

All users, particularly those with visual or motor impairments.

Problem

The dashboard has no documented accessibility testing. Common gaps observed: chart components lack aria-label descriptions, alert severity relies solely on color (no icon or text pattern for colorblind users), modal focus trapping may be incomplete, keyboard navigation through the ward table is not implemented.

Why fix

Healthcare facilities must comply with ADA/Section 508 accessibility requirements. Clinical staff may have color vision deficiency (8% of males) — relying on red/amber/green alone for severity classification excludes them from effective alert triage.

How to fix

  1. Add aria-label to all interactive elements and chart containers.
  2. Add severity icons alongside color badges (e.g., exclamation triangle for critical, warning triangle for warning).
  3. Ensure color contrast meets WCAG AA (4.5:1 for text, 3:1 for UI components).
  4. Add focus management: trap focus in modals, return focus on close.
  5. Add skip links and landmark roles (role="main", role="navigation").
  6. Test with screen reader (VoiceOver, NVDA) and keyboard-only navigation.

Frontend files: All component files, App.vue for landmarks.

Backend: No change.

Dependency: None.


Part F — Infrastructure & Real-Time Communication


P4 — Grafana dashboard limited to infrastructure metrics

Who needs it

IT operations, clinical informatics teams.

Problem

The Grafana dashboard (infra/grafana/dashboards/vigilcare.json) has 3 panels: unacknowledged critical alerts (stat), observation ingest rate (timeseries), and clinical alerts by type/severity (bar gauge). Missing: consumer lag per group, outbox pending depth, gateway status, sepsis bundle compliance rates, scoring latency, API endpoint latency, error rates by service.

Why fix

When a clinical alert is delayed, IT needs to diagnose whether the bottleneck is ingest (observations not arriving), scoring (Kafka consumer lag), alerting (outbox relay delay), or paging (RabbitMQ worker). The current 3 panels provide almost no diagnostic capability.

How to fix

  1. Add Consumer Lag panel: kafka_consumer_lag by consumer group.
  2. Add Outbox Depth panel: outbox_pending_events over time.
  3. Add Gateway Status panel: ward_gateway_status per gateway (from WardGatewayMetricsCollector).
  4. Add Sepsis Bundle Compliance panel: sepsis_bundle_compliance_total by status.
  5. Add Scoring Latency panels: processing time per scoring service (requires P5 metrics from platform gap analysis).
  6. Add API Latency panel: http_request_duration_seconds p50/p95/p99 (requires platform gap P5-01 fix).
  7. Organize into rows: Clinical, Infrastructure, API.

Files: infra/grafana/dashboards/vigilcare.json.

Backend: Some panels depend on metrics from BackgroundServices/Metrics/ collectors. Additional metrics require platform gap analysis P5 fixes.

Dependency: Platform gap P5-01 (request timing) and P5-02 (background service metrics) for full coverage.


P4 — No real-time push (WebSocket/SSE) — polling only

Who needs it

All dashboard users, especially for critical alerts.

Problem

All dashboard data is fetched via HTTP polling (10s ward, 5s patient detail). This introduces up to 10 seconds of latency between a critical alert firing and a nurse seeing it on screen. For a SOFA_SEPSIS alert where a 1-hour compliance clock starts immediately, 10 seconds of UI latency is acceptable but not ideal. More importantly, polling generates continuous HTTP traffic even when no data has changed.

Why fix

Real-time push reduces alert notification latency from 5-10 seconds to sub-second. It also reduces server load — instead of every connected dashboard client polling every 5 seconds, the server pushes only when state changes. For a hospital with 20 dashboard instances, that's 240 requests/minute replaced by event-driven pushes.

How to fix (phased)

Phase 1 — SSE for alerts:

  1. Add GET /api/v1/alerts/stream SSE endpoint that pushes new alert events.
  2. Backend consumes from Kafka alert.generated topic and writes to SSE connections.
  3. Frontend replaces alert polling with EventSource connection.
  4. Fall back to polling if SSE connection drops.

Phase 2 — SignalR for full real-time:

  1. Add SignalR hub for dashboard events: new observations, score updates, alert state changes.
  2. Backend bridges Kafka consumers → SignalR groups (by encounter, by department).
  3. Frontend uses SignalR client, falling back to SSE, then to polling.

Backend files: New Hubs/DashboardHub.cs or SSE controller, Kafka → push bridge service.

Frontend files: New composables/useRealTime.js, update all polling consumers.

Dependency: Significant backend work. Phase 1 (SSE) is simpler and covers the most critical use case (alerts).


Summary matrix

# Issue Priority Part Backend exists? Status
1 No SOFA trend chart P0 A Yes Open
2 No GCS trend chart / history endpoint P0 A Partial (no history endpoint) Open
3 No qSOFA history view P0 A No (Redis-only, no persistence) Open
4 No medication overlay on vital charts P0 A Yes Open
5 No encounter timeline view P1 A Yes Open
6 No patient demographics / allergy banner P1 A Yes Open
7 Ward table missing SOFA, GCS, staleness P1 B Partial (DTO needs extension) Open
8 No sepsis bundle compliance board P2 B Yes Open
9 No department overview with aggregates P2 B Partial Open
10 No sort controls on ward table P2 B N/A (client-side) Open
11 No search/filter on ward table P2 B Yes (analytics search) Open
12 No critical alert notification/sound P1 C No (needs push or client detect) Open
13 No handoff/shift summary report P1 C Yes (data available) Open
14 No discharge summary view P2 C Partial (needs download endpoint) Open
15 Alert ack lacks role context P3 C Partial Open
16 No vitals entry form P3 C Yes Open
17 No threshold management UI P2 D Yes Open
18 No user management UI P2 D No (needs backend endpoints) Open
19 No gateway status dashboard P2 D Yes Open
20 No audit log viewer P4 D Yes Open
21 No reconciliation dashboard P4 D Yes Open
22 No role-based navigation P3 E N/A (frontend-only) Open
23 Dark mode chart inconsistency P3 E N/A Open
24 No keyboard shortcuts P3 E N/A Open
25 No mobile optimization for patient detail P3 E N/A Open
26 No accessibility (WCAG) compliance P5 E N/A Open
27 Grafana dashboard incomplete P4 F Partial (needs more metrics) Open
28 No real-time push (polling only) P4 F No (needs SSE/SignalR) Open

Suggested implementation sequence

flowchart TD
    subgraph safety [Part A — Patient Safety]
        P0A[P0: SOFA history chart]
        P0B[P0: GCS history + endpoint]
        P0C[P0: qSOFA history + persistence]
        P0D[P0: Medication overlay on charts]
        P1A[P1: Encounter timeline view]
        P1B[P1: Patient demographics banner]
    end

    subgraph ward [Part B — Ward Awareness]
        P1C[P1: Ward table clinical columns]
        P2A[P2: Sepsis compliance board]
        P2B[P2: Department overview]
        P2C[P2: Sort controls]
        P2D[P2: Search/filter]
    end

    subgraph workflow [Part C — Clinical Workflows]
        P1D[P1: Critical alert notification]
        P1E[P1: Handoff report]
        P2E[P2: Discharge summary view]
        P3A[P3: Alert role context]
        P3B[P3: Vitals entry form]
    end

    subgraph admin [Part D — Admin Dashboards]
        P2F[P2: Threshold management UI]
        P2G[P2: User management UI]
        P2H[P2: Gateway status dashboard]
        P4A[P4: Audit log viewer]
        P4B[P4: Reconciliation dashboard]
    end

    subgraph ux [Part E — UX & Accessibility]
        P3C[P3: Role-based navigation]
        P3D[P3: Dark mode charts]
        P3E[P3: Keyboard shortcuts]
        P3F[P3: Mobile optimization]
        P5A[P5: WCAG compliance]
    end

    P3C --> P2F
    P3C --> P2G
    P3C --> P4A
    P0B --> P1C
    P0A --> P1C
    P1D --> P2A

Sprint-sized batches

Batch Items Outcome
1 — Bedside Decision Support P0 SOFA chart, P0 GCS history + chart, P0 medication overlay, P1 patient banner, P1 timeline Physicians and nurses see full scoring history and medication context for clinical decisions
2 — Ward Awareness P0 qSOFA history, P1 ward table columns, P2 sort/search, P1 critical alert notification, P2 sepsis board Charge nurses have complete situational awareness; critical alerts demand attention
3 — Clinical Workflows P1 handoff report, P2 discharge summary, P3 vitals entry form, P3 alert role context Nurses can chart vitals in-app; shift handoff is printable; discharge documents accessible
4 — Admin Tools P3 role-based nav, P2 threshold management, P2 gateway status, P2 user management, P2 department overview Admins manage thresholds without API calls; IT monitors gateway health; navigation is role-scoped
5 — Polish & Infrastructure P3 dark mode charts, P3 keyboard shortcuts, P3 mobile optimization, P4 audit/reconciliation views, P4 Grafana, P5 WCAG Night-shift readability, power-user efficiency, compliance audit trail, monitoring depth
6 — Real-Time P4 SSE/SignalR push Sub-second alert notification, reduced polling load

Backend work required

Most dashboard gaps are frontend-only — the API endpoints already exist but the dashboard does not consume them. The following items require backend changes:

Item Backend work needed
GCS history chart New GET /encounters/{id}/gcs/history endpoint
qSOFA history Decide persistence strategy (Redis → DB) + new history endpoint
Ward table columns Extend WardEncounterSummary DTO with SOFA/GCS/staleness
Discharge summary view New GET /encounters/{id}/discharge-summary download endpoint
User management New UsersController with CRUD endpoints
Real-time push New SSE endpoint or SignalR hub
Grafana expansion Depends on platform gap P5 metrics being implemented first

All other items can be built against existing API endpoints.


Success criteria

When complete, the dashboard should support:

Bedside Clinicians (Nurses, Physicians)

  • All four scoring trends visible over time (NEWS2, SOFA, GCS, qSOFA) with clinical context.
  • Medication administration times overlaid on vital trend charts.
  • Patient allergies and demographics visible at all times.
  • Chronological encounter timeline for case review and handoff.
  • Vital sign entry directly in the dashboard.
  • GCS entry form already exists — vitals entry extends this pattern.

Charge Nurses & Supervisors

  • Ward table shows SOFA, GCS, data staleness, and attending physician.
  • Sort and search/filter to find patients by any clinical criterion.
  • Hospital-wide sepsis bundle compliance board with countdown timers.
  • Audible/visual notification for new critical alerts.
  • Printable handoff report for shift change.

Administrators & Compliance

  • Threshold management without API tools.
  • User creation and role management.
  • Gateway health monitoring for IT operations.
  • Audit log and PHI access log browsing.
  • Reconciliation findings (data quality) dashboard.

All Users

  • Role-appropriate navigation (no admin clutter for bedside users).
  • Dark mode that works for charts too (night shift, ICU).
  • Keyboard shortcuts for power users.
  • Mobile-responsive patient detail for tablet use.
  • WCAG AA accessibility compliance.