Guides
ICU
The first of the four specialty boards — critical-care patient chart, vitals, and orders.
Two parts:
- Part A — Business level. What ICU staff are responsible for, what the screen looks like when they sign in, and how the day flows.
- Part B — Technical level. What the routes, components, RBAC, and APIs do under the hood.
This guide complements docs/WORKFLOWS.md (cross-department flows) and docs/ADMISSIONS_OFFICER_GUIDE.md (reception side).
Part A — Business level
A.1 What an ICU doctor / nurse owns
| Responsibility | Detail |
|---|---|
| ICU board | The single screen they sign in to. One row per ICU patient, colour-coded by severity (critical / serious / stable). |
| Patient chart | A popup that opens when they click a patient name. Holds vitals, allergies, conditions, nursing assessments, current medications, lab/imaging orders, results, care plans, consents. |
| Recording observations | They can add new vitals, allergies, conditions, and nursing assessments directly from the chart popup — no page hop. |
| Placing orders | The + Order button on each row opens Quick Orders (Prescription / Lab / Imaging). The order is routed to Pharmacy / Laboratory / Radiology, but those receiving departments are invisible to the ICU clinician. |
| Transfer to ward | When the patient stabilises, the row’s Transfer to Ward button moves them out of ICU into a regular wing. |
| Discharge | When the patient is going home from ICU directly (rare), the Discharge button finalises the encounter. |
A.2 What they cannot do
The ICU shell is intentionally narrow — it’s a kiosk, not a full clinical workstation:
- No sidebar, no global navigation, no dashboard.
- No access to lab, pharmacy, radiology, billing, HR, or admissions screens.
- No access to other wards (Emergency, Maternity, Operations).
- No access to patients who are not in ICU.
If they try to type a forbidden URL into the address bar, the system responds with the standard Forbidden page.
A.3 What the screen looks like
When an ICU doctor or nurse logs in:
┌─────────────────────────────────────────────────────────────────────────────┐
│ M Mando · ICU [ Intensive Care ] 🔔 Dr. Layla H ┄ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Intensive Care Unit + ICU Admission │
│ 3 patients in ICU • 1 critical │
│ │
│ ┌─ Critical (1) ─────────────────────────────────────────────────────┐ │
│ │ John Smith Dr. Karim N • Room 401 [critical] │ │
│ │ Ventilator: BiPAP O2 Flow: 6 L/min Vitals: 92/58, 110, 39°C │ │
│ │ ──────────────────────────────────────── [+ Order] [Transfer] │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Top bar: brand → single static tab “Intensive Care” → notification bell → user pill → Sign out.
The bell still fires for admission alerts, deposit-collected callbacks, lab/imaging acknowledgements that target the doctor.
A.4 The day, end to end
- Sign in. The system bounces them straight to
/icu. There is no dashboard intermediate step. - Read the board. A coloured strip on each card says critical / serious / stable. The header counts all three.
- Click the patient name. A popup opens with ten clinical category cards (Vitals, Allergies, Conditions, Nursing Assess., Medications, Lab Orders, Imaging Orders, Results, Care Plans, Consents). Each card shows a count.
- Click a category. The popup swaps content to that category’s list. If the category is editable (Vitals / Allergies / Conditions / Nursing Assess.), an + Add button appears top-right of the list. Read-only categories (Medications, Orders, Results, Care Plans, Consents) just show the existing items.
- Add a vital (or allergy / condition / nursing score). The form opens inline below the list header. Quick chips at the top of the vitals form (BP, HR, Temp, SpO2, RR, Wt, Pain) prefill the LOINC code, display name, and unit so the user only has to type the value.
- Save. The list refreshes; the count badge on the category card updates.
- Place an order. Back on the board, click
+ Orderon the row. Three tabs: Prescription, Lab, Imaging. Submit. Pharmacy / Lab / Radiology gets the alert; the ICU clinician sees nothing about who’s working on it until results show up under Results. - Transfer or discharge when ready.
A.5 Notifications
Inbound (the bell flashes):
- New ICU admission landed on the wing — fired by the Reception/Admissions Officer at admit time.
- A stat lab result was finalised on one of their patients (planned — see WORKFLOWS.md §14).
- A prior-auth or eligibility check came back relevant to one of their patients.
Outbound (they create them implicitly):
- Placing a Lab order notifies Laboratory.
- Placing an Imaging order notifies Radiology.
- A new prescription enters the MAR / pharmacy queue.
A.6 Common pitfalls
- “I don’t see a sidebar.” Correct — the ICU role is kiosk-style. Click the patient name on the board to drill in.
- “I tried to open the lab page and got Forbidden.” Correct — ICU staff can place orders but never browse lab/pharmacy/radiology. Use
+ Orderon the patient row instead. - “The patient I’m looking for isn’t in the list.” They aren’t admitted to ICU. Reception admits patients into ICU using admission_type =
icu— until that happens, the patient won’t appear on this board. - “Why is the count badge 0 on Allergies but the patient clearly has allergies?” The patient may have allergies recorded against a prior chart that wasn’t migrated. Click the card → + Add to record the current set.
Part B — Technical level
B.1 Identity & ward shell
A user is treated as an “ICU ward user” when all of these are true:
users.roleisdoctorornurse.users.department(case-insensitive, trimmed) is one of:icu,critical care,intensive care,cardiology,neurology.
This list is the same as WARD_NAV in frontend/src/services/role-permissions.js. The frontend check lives in AppRoot.isICUWardUser.
When isICUWardUser(user) returns true, app-root.js:
- Sets the post-login home to
#/icu(skips/dashboard). - Bounces any
#/dashboardhit back to#/icu. - Renders
renderICUWardShell()instead of the sidebar shell — top bar with brand, a single static “Intensive Care” tab, the notification bell, the user pill, and Sign out. No<sidebar-nav>, no<patient-search>, no breadcrumb. - The
simple-contentslot just hosts<icu-page>directly. Hash navigation still works for the forbidden-page fallback.
B.2 Seed data
Four ward-doctor logins are created idempotently on every boot by ensureWardDoctors. For ICU specifically:
- email:
icu.doctor@hospital.com - password:
admin123 - name: Dr. Layla Haddad
- department:
ICU - role:
doctor
Idempotency:
users.emailisUNIQUE→ INSERT usesON CONFLICT (email) DO NOTHING.doctorsrow is added viaINSERT … WHERE NOT EXISTS (SELECT 1 FROM doctors WHERE email = ?).
Nurses are seeded the same way once a user record with role='nurse' and department='ICU' is created (no nurse seed today — add via /hr or /users).
B.3 RBAC
Backend (path-based middleware): the doctor and nurse role permission tables in backend/internal/middleware/roles.go explicitly omit lab, pharmacy, pharmacy-dispense, radiology, general-inventory. They allow prescriptions, service-requests, diagnostic-reports, imaging-studies, observations, allergies, conditions, nursing-assessments, care-plans, consents, plus the ward routes (icu, emergency, maternity, operations) and the utility routes (oncall, beds, wings:GET).
Frontend: getNavigationForUser(user) in frontend/src/services/role-permissions.js narrows frontDesk to ['icu'] for any doctor/nurse whose department resolves to ICU. Other wards are not in their nav.
B.4 ICU board
frontend/src/pages/icu-page.js is unchanged in shape:
loadData()callsGET /api/icu(cases),GET /api/admissions?status=admitted,GET /api/wings— in parallel.cases[]is the source of truth for the row list._isWardUsergetter (new) hides the Quick Navigation chip-bar for doctors and nurses, since those chips routed to pages outside their permission set.- Patient name click now calls
openChart(c)→<icu-patient-chart>.show(patient_id, patient_name, admission_id)instead of the older<patient-encounter-popup>. The popup is still imported and stays available, but the default click target is the chart.
B.5 ICU patient chart popup
frontend/src/components/icu-patient-chart.js is the new kiosk-friendly drill-down. Two views inside one panel:
Grid view (default after show() is called):
- Header:
name • MRN • gender • DOB. “All categories” button hidden. - Info bar: gender, DOB, blood type, phone, admission #.
- Body: 10 category cards in a CSS-grid. Each card has icon + label + hint + count badge.
- Counts are loaded by
loadCounts()— fires nine list endpoints in parallel and bins service-requests into lab/imaging bycategorysubstring.
Detail view (after a card click):
- Header gains an “All categories” button that calls
backToGrid()(which also re-fetches counts). - Body shows
<h3>+ count pill + (if editable)+ Addbutton + the list + (ifshowAdd) the inline add form. - Each list item is rendered by
renderItem(catId, x)— one switch arm per category, with severity / criticality / priority badges where they apply.
B.6 Categories — list and create endpoints
| Card | List API | Add API | Editable |
|---|---|---|---|
| Vitals | GET /api/observations/patient/:id/vitals | POST /api/observations category: vital-signs | yes |
| Allergies | GET /api/allergies/patient/:id | POST /api/allergies | yes |
| Conditions | GET /api/conditions/patient/:id | POST /api/conditions | yes |
| Nursing Assess. | GET /api/nursing-assessments/patient/:id | POST /api/nursing-assessments | yes |
| Medications | GET /api/prescriptions?patient_id= | (use Quick Orders → Prescription) | no |
| Lab Orders | GET /api/service-requests?patient_id= filter category ~ “lab” | (use Quick Orders → Lab) | no |
| Imaging Orders | same, filter category ~ “rad|imag” | (use Quick Orders → Imaging) | no |
| Results | GET /api/diagnostic-reports?patient_id= | n/a | no |
| Care Plans | GET /api/care-plans?patient_id= | (use the care-plan workflow) | no |
| Consents | GET /api/consents?patient_id= | (use the consents workflow) | no |
For each editable category the chart’s submitAdd(e) method assembles the FHIR-shaped payload and POSTs it. On success it re-fetches the active category and re-runs loadCounts() so the badge on the card matches.
B.7 Vitals quick chips
The vitals add form has seven preset chips above the inputs. Clicking one calls applyVitalPreset(preset), which fills code, display, and unit for that vital (LOINC codes):
| Chip | code | display | unit |
|---|---|---|---|
| BP | 85354-9 | Blood Pressure | mmHg |
| HR | 8867-4 | Heart Rate | bpm |
| Temp | 8310-5 | Body Temperature | °C |
| SpO2 | 59408-5 | Oxygen Saturation | % |
| RR | 9279-1 | Respiratory Rate | /min |
| Wt | 29463-7 | Body Weight | kg |
| Pain | 38208-5 | Pain Severity | 0-10 |
The user only types the value, then Save. BP values can be entered as 120/80 — submitAdd parses via parseFloat and falls back to value_string when not numeric.
B.8 Quick Orders (placed from the board, not the chart)
The + Order button on each ICU row opens frontend/src/components/quick-orders.js. Submitting routes to:
| Tab | Endpoint | Receiving alert |
|---|---|---|
| Prescription | POST /api/prescriptions source=icu | Pharmacy queue |
| Lab | POST /api/service-requests category=laboratory | Laboratory department |
| Imaging | POST /api/service-requests category=radiology | Radiology department |
The receiving department’s notification is fired in backend/internal/handlers/service-requests.go by notifyOrderRouted(...) immediately after the row is inserted. ICU staff never see the receiving department’s worklist.
B.9 Build / verification
- Backend:
go build ./...— clean. - Frontend:
npx vite build— 1.34 MB / 205 kB gzip in ~3s.
B.10 Known follow-ups (not done yet)
- Per-patient row-level filter on reads — RBAC today is path-based; an ICU doctor with the URL of a non-ICU patient could fetch that patient’s data via
/api/patients/:id. Tighten by joining on the active encounter’s department. - Critical-result back-alert when a stat lab is finalised — currently the doctor sees it only if they re-open the chart. Plan: notify the ordering doctor on diagnostic-report finalisation.
- “All my orders” widget — a single feed showing every order this doctor has placed across all their patients.
- Apply the same kiosk shell + chart popup pattern to Emergency / Maternity / Operations once the ICU rollout settles.
Last updated: 2026-04-26