No SOFA trend chart or organ-system timeline No GCS trend chart or component history No qSOFA history view
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
- Create
SofaHistory.vuechart component showing SOFA total score over time (line chart, matchingNews2History.vuepattern). - Add optional stacked area or grouped bar overlay showing per-organ contributions (respiratory, coagulation, liver, cardiovascular, CNS, renal).
- Fetch
GET /encounters/{id}/sofa/historyinPatientDetail.vue(already cursor-paginated). - Add a
fetchSofaHistory(encounterId, { limit })function toclinical.js. - Place below
SofaScorePanelin the patient detail layout. - 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
- Backend: Add
GET /api/v1/encounters/{encounterId}/gcs/historyendpoint toGcsControllerwith cursor pagination (mirror NEWS2/SOFA pattern). QueryGcsScorestable ordered byCalculatedAt DESC. - Create
GcsHistory.vuechart component showing total GCS and optionally eye/verbal/motor component lines. - Add
fetchGcsHistory(encounterId, { limit })toclinical.js. - Render below or alongside
ScoresPanelinPatientDetail.vue. - 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
- Backend: Add
GET /api/v1/encounters/{encounterId}/qsofa/historywith cursor pagination. Store qSOFA evaluations in the database (currently Redis-only with 30-min TTL) or derive from observation history. - Create
QsofaHistory.vuetimeline or chart showing criteria count over time with markers for when alerts fired. - Add
fetchQsofaHistory(encounterId, { limit })toclinical.js. - Render in
PatientDetail.vueclinical 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
- Add vertical annotation lines or markers on
VitalTrendChart.vuefor medication administration timestamps. - Filter medications to the same time window as the observation data.
- Show medication name, dose, and route on hover/tooltip.
- 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
- Create
EncounterTimeline.vuecomponent rendering a vertical chronological timeline. - Each event type gets a distinct icon/color: observation (blue), alert (red/amber), order (purple), medication (green), status change (gray).
- Fetch
GET /encounters/{id}/timeline— addfetchTimeline(encounterId)toencounters.js. - Add as a tab or expandable section in
PatientDetail.vue. - 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
- Create
PatientBanner.vue— a persistent header strip showing: name, MRN, DOB/age, gender, allergies (highlighted), blood type, room/bed, department, attending physician, admission reason. - Use standard clinical banner pattern: red background for known allergies, "NKDA" (no known drug allergies) if allergies field is empty.
- Replace the simple name display in
PatientDetail.vueheader withPatientBanner. - 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
- Backend: Extend
WardEncounterSummaryrecord to include:int? SofaScore,int? SofaDelta,int? GcsScore,string? GcsClassification,DateTimeOffset? LastObservationAt,string? AttendingPhysician,DateTimeOffset OpenedAt. - Update
EncounterService.ListAsyncto join/subquery for SOFA, GCS, and latest observation timestamp. - Frontend: Add columns to
WardTable.vueand data toPatientRow.vue/PatientCard.vue. - Add "stale" visual indicator (amber/red text or icon) when
LastObservationAtis > 2 hours old for active inpatients. - 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
- Create
SepsisBoardView.vue— new route at/sepsis. - 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.
- Sort by urgency: overdue first, then by time remaining ascending.
- Add severity coloring: green (> 30 min remaining), amber (< 30 min), red (overdue or non-compliant).
- Click-through to
PatientDetailfor the encounter. - Add
fetchSepsisBundles({ status, page, pageSize })to a newapi/sepsis.jsmodule. - 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
- Create
DepartmentOverviewView.vue— new route at/departmentsor modify/wardto show summary cards above the patient list. - Show per-department cards with: patient count, acuity distribution (pie/bar), active bundle count, unacknowledged alert count, average NEWS2.
- Click a department card to filter the ward table to that department.
- Consume
GET /analytics/alerts/summaryand derive patient counts fromGET /encounters?department=X&status=ACTIVE. - Optionally add
GET /api/v1/encounters/summarybackend 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
- Add sortable column headers to
WardTable.vuewith visual sort indicators (arrow up/down). - Extend
ward.jsstore to supportsortFieldandsortDirectionstate. - Compute sorted list based on selected sort field.
- Default to NEWS2 risk (preserve current behavior).
- Persist sort preference to
localStoragevia 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
- Add a search input to the ward toolbar (debounced, 300ms).
- Client-side filter for name/MRN matching (data already loaded).
- Add filter chips or toggles: "Has alerts", "Sepsis active", "Critical (NEWS2 ≥7)".
- Optionally integrate
GET /analytics/patientsfor 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
- Track
lastSeenAlertIdsin the alert store. - On each poll, detect new critical alerts by comparing IDs.
- Play an audible tone (configurable, with mute toggle) for new CRITICAL alerts.
- Show a browser notification (requires permission request) with patient name and alert type.
- Flash the browser tab title:
"⚠ CRITICAL ALERT — VigilCare". - Add a visual banner at the top of the page that persists until dismissed.
- 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
- Add "Export Handoff Report" button to the ward toolbar.
- 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).
- Use
@media printCSS for clean print layout. - 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
- Backend: Add
GET /api/v1/encounters/{id}/discharge-summaryendpoint that returns a pre-signed MinIO URL or streams the PDF. - Frontend: Add "Discharge Summary" section to
PatientDetail.vue(visible only for DISCHARGED encounters). - Embed PDF viewer or download link.
- 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
- Replace free-text clinician ID with the authenticated user's identity from the JWT token.
- Display the user's name and role in the acknowledgment confirmation modal.
- Include role in the acknowledgment note automatically.
- Update
AlertCard.vueto 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
- Create
VitalsEntryForm.vue— a structured form with fields for all standard vital signs. - Pre-populate observation codes: HEART_RATE, RESP_RATE, SYSTOLIC_BP, DIASTOLIC_BP, SPO2, TEMP_C, AVPU.
- Add plausibility validation client-side (e.g., HR 20-300, temp 30-45°C) matching backend
PlausibilityValidator. - Submit as batch observation via
POST /encounters/{id}/observations. - Add to
PatientDetail.vueas 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
- Create
ThresholdManagementView.vue— new route at/admin/thresholds. - Display a table of all thresholds: observation code, warning low/high, critical low/high, unit.
- Inline editing or modal edit form with validation.
- Create/delete threshold support.
- Add to sidebar under an "Admin" section (visible only to Admin role).
- 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
- Backend first: Add
UsersControllerwithGET /api/v1/users(list),POST(create),PATCH /{id}(update role, deactivate), protected byUsersAdminpermission. - Create
UserManagementView.vue— new route at/admin/users. - Table of users: username, role, email, last login, active status.
- Create/edit form: username, password (create only), role selection, active toggle.
- 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
- Create
GatewayStatusView.vue— new route at/admin/gateways. - Display sites as collapsible sections, each containing its gateways.
- Per gateway: status badge (green/amber/red), last heartbeat time, buffer depth, department, last sync time.
- Auto-refresh every 30 seconds.
- Highlight gateways with buffer depth > 0 (data waiting to sync) or heartbeat older than 2 minutes.
- 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
- Create
AuditLogView.vue— new route at/admin/audit. - Filterable table: date range, user, entity type, action type.
- Show before/after state for modifications (already stored in
ClinicalAuditLog.OldValues/NewValues). - Add PHI access log tab with patient-specific filtering.
- Restrict to
AuditReadpermission. - Consume
GET /audit-logsandGET /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
- Create
ReconciliationView.vue— new route at/admin/reconciliation. - Three sections (matching check types): unacknowledged alerts, pending orders, stale observations.
- Each item links to the relevant patient or encounter.
- Show resolved vs. unresolved toggle.
- 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
- Read the user's role from the JWT token (already decoded in
auth.jsstore). - Add
meta.requiredRoleormeta.requiredPermissionto route definitions. - Filter sidebar navigation items by user role.
- Add
v-ifguards on role-specific components. - 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
- Create a
useChartTheme.jscomposable that returns Chart.js options based on current dark mode state. - Dynamically set
scales.x.ticks.color,scales.y.ticks.color,scales.x.grid.color,scales.y.grid.color,plugins.legend.labels.colorbased on dark/light mode. - Apply to all chart components:
VitalChart.vue,VitalTrendChart.vue,News2History.vue, and any new chart components (SofaHistory, GcsHistory). - React to dark mode toggle via
watchon 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
- Add keyboard event listeners in
AlertCenter.vueandPatientDetail.vue. - Implement navigation:
J(next alert),K(previous alert),Enter(select). - Implement actions:
A(acknowledge selected),R(resolve selected),Esc(cancel modal). - Show keyboard shortcut hints in the UI (small tooltip or
?help overlay). - 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
- Audit all
PatientDetail.vuesub-components at 320px, 768px, and 1024px viewports. - Stack panels vertically on mobile (scores → vitals → alerts → orders).
- Make charts full-width on mobile with touch-friendly tooltips.
- Collapse less-critical sections (replay controls, feedback) behind expandable accordions on mobile.
- 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
- Add
aria-labelto all interactive elements and chart containers. - Add severity icons alongside color badges (e.g., exclamation triangle for critical, warning triangle for warning).
- Ensure color contrast meets WCAG AA (4.5:1 for text, 3:1 for UI components).
- Add focus management: trap focus in modals, return focus on close.
- Add skip links and landmark roles (
role="main",role="navigation"). - 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
- Add Consumer Lag panel:
kafka_consumer_lagby consumer group. - Add Outbox Depth panel:
outbox_pending_eventsover time. - Add Gateway Status panel:
ward_gateway_statusper gateway (fromWardGatewayMetricsCollector). - Add Sepsis Bundle Compliance panel:
sepsis_bundle_compliance_totalby status. - Add Scoring Latency panels: processing time per scoring service (requires P5 metrics from platform gap analysis).
- Add API Latency panel:
http_request_duration_secondsp50/p95/p99 (requires platform gap P5-01 fix). - 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:
- Add
GET /api/v1/alerts/streamSSE endpoint that pushes new alert events. - Backend consumes from Kafka
alert.generatedtopic and writes to SSE connections. - Frontend replaces alert polling with
EventSourceconnection. - Fall back to polling if SSE connection drops.
Phase 2 — SignalR for full real-time:
- Add SignalR hub for dashboard events: new observations, score updates, alert state changes.
- Backend bridges Kafka consumers → SignalR groups (by encounter, by department).
- 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.