medOS ultra

OR Feature Suite

Room Status / Turnover / CSSD / Incident / Block Utilization shipped as one integrated OR suite with canonical record linkage.

10 min read diagramsUpdated 2026-05-15docs/architecture/or-feature-suite-20260515.md

Five OR features shipped as one integrated suite over a 30-iteration loop on 2026-05-14 → 2026-05-15. This doc is the single reference for the data model, the linkage to the canonical OR record, the cross-feature triggers, and the operator runbook.

Why these five together

These features had been requested as separate items:

Room Status Board · Turnover/Cleaning Workflow · CSSD Integration · Incident/Near-Miss Reporting · Surgeon Block Utilization Dashboard

They were built as a suite — not as five independent miniapps — because they share infrastructure (rooms, OR record, procedure_request_id, encounter_id) and they cross-fire (a CSSD recall produces an incident draft; a turnover gates a procedure_start). Shipping them in isolation would have produced 5 disconnected surfaces; shipping them together produced one coherent perioperative QM layer.

Canonical OR record

The canonical OR record in this system is the MongoDB OperatingRoomRequest entity, mirrored into Supabase as or_state_cache. One row per OR request, carrying:

or_state_cache (
  id              uuid PRIMARY KEY,
  mongo_ref       text UNIQUE,         -- OperatingRoomRequest._id
  encounter_id    uuid NOT NULL,       -- joins to encounter_journey_cache
  patient_id      uuid,
  or_room_id      uuid,                -- legacy uuid (often per-env)
  or_room_code    text,                -- 'OR-1' etc — durable join key (NEW)
  surgeon_id      text,
  anesthesiologist_id text,
  status          text,
  scheduled_at    timestamptz,
  procedure_started_at  timestamptz,
  procedure_finished_at timestamptz,
  ...
)

Every new feature table threads back to this row via or_state_id + encounter_id + patient_id so the 5 features share the same patient/case context. The linkage is bi-directional and lazy:

                   forward auto-link (20260515a)
                ┌───────────────────────────────────┐
                │                                   ▼
or_incident_drafts ◀── procedure_request_id ── or_state_cache.mongo_ref
or_turnover_instances                              │
cssd_tray_case_usage                               │
                                                   ▼
                       reverse fan-out (20260515f)
                       on or_state_cache write:
                       propagate or_state_id +
                       encounter_id + or_room_code
                       down to procedure_day_queue
                       + 3 child tables

This closes the projector-vs-orchestrator temporal race: the procedure-day-queue projector emits queue rows from procedureRequestEvent events that arrive BEFORE the orchestrator finishes writing the OR record. Without the reverse trigger those rows stayed un-linked.

Migration order

20260514g_or_room_phase_board.sql           Foundation: or_rooms + runtime state + v_or_room_status_board
20260514h_or_turnover_workflow.sql          Templates + instances + open/complete/signoff RPCs
20260514i_cssd_tray_tracking.sql            Trays + autoclave + lots + recall RPC + views
20260514j_or_incident_autodraft.sql         Drafts + trigger on or_intraop_counts mismatch
20260514k_surgeon_block_utilization.sql     Allocations + 3 utilization views
20260515a_or_record_linkage.sql             Connects all 5 to or_state_cache (forward + auto-link triggers)
20260515b_or_turnover_procedure_start_gate.sql  Wires turnover→blocked_actions[]
20260515c_or_feature_suite_demo_seed.sql    One cohesive demo case threading through everything
20260515d_cssd_recall_to_incident.sql       Recall fans out incident drafts per affected case
20260515e_or_room_warning_chips.sql         v_or_room_warnings — per-room aggregated warning counts
20260515f_or_state_cache_reverse_link.sql   Reverse fan-out on or_state_cache write

Apply in order. Each is idempotent.

Tables introduced

Table Rows seeded Purpose
or_rooms 8 (OR-1…6, CYSTO-1, MINOR-1) Physical room registry. code is durable; legacy_room_uuid bridges to existing or_state_cache.or_room_id.
or_room_runtime_state 8 Per-room current phase + active/next procedure refs
or_room_phase_audit_log 0 Append-only phase transition trail
or_turnover_templates 3 (default / contaminated / cardiac) Versioned checklist library
or_turnover_template_items 23 Line items per template, bilingual TH/EN, categorized
or_turnover_instances 1 demo (OR-3) Per-case execution row
or_turnover_instance_items ~10 Snapshot of template items, statuses
cssd_trays 6 (Major Lap / Minor / Cardiac / Ortho / OB-GYN / ENT) Tray master inventory
cssd_tray_instruments 8 (Minor tray itemized) For count audit
cssd_autoclave_cycles 4 demo (3 pass, 1 fail) Sterilizer cycle log
cssd_tray_lots ~6 Per-cycle tray instance, status: available/in_use/recalled/expired
cssd_tray_case_usage 1 demo Tray ↔ case linkage
cssd_recall_events 0 Recall audit log + counters
or_incident_drafts 1 demo (count_discrepancy) Auto-spawned near-miss drafts
or_incident_attachments 0 Photos / x-rays / count sheets
surgeon_block_allocations ~250 (8 surgeons × 5 weekdays × 38 days) Block reservations
surgeon_block_utilization_settings 1 (default facility) Target / underuse / overrun thresholds

Views

View Drives
v_or_room_status_board Kiosk status display (room phase + active procedure + next-up + encounter_id)
v_or_room_warnings Status-board chip overlay (open incidents / cssd recalls / turnover blocking)
v_or_room_ready_state Read-side gate signal (procedure-start can refuse based on this)
v_cssd_affected_cases CSSD recall workflow — every affected case with patient HN/name/procedure/encounter_id
v_cssd_pre_case_check Pre-case tray verification UI
v_or_open_incidents OR Incident Inbox listing
v_or_linkage_health Ops sanity check — linked vs unlinked row counts per child table
v_surgeon_block_utilization Per-block live calc
v_surgeon_block_utilization_daily Per-surgeon per-day rollup
v_surgeon_block_utilization_monthly Per-surgeon per-month rollup

RPCs

RPC Purpose Idempotent
or_room_phase_transition(room_id, to_phase, procedure_id?, actor?, reason?) Atomic phase change + audit + ready-flag toggle yes
or_turnover_open(room_id, prior_proc?, next_proc?, wound_class?, surgical_type?) Spawn instance via best-match template lookup yes (returns existing open instance)
or_turnover_complete_item(item_id, actor?, status='done', notes?) Tick / skip / block an item yes
or_turnover_signoff(instance_id, actor, role, secondary_actor?, secondary_role?) Attests ready-state; refuses if mandatory items remain no (state-changing)
or_turnover_set_next_case(instance_id, next_proc_id) Re-target the gate to a different next case yes
cssd_record_autoclave_cycle(cycle_code, …, tray_codes[]) Records cycle + auto-spawns lots + auto-fail on BI/CI/BD yes
cssd_link_tray_to_case(lot_number, procedure_id, room_id, actor) Pre-case tray verification + link no
cssd_recall_cycle(cycle_id, reason, detail, actor) Marks lot recalled + counts affected cases + fires fan-out trigger no
`or_incident_resolve(incident_id, ‘dismiss’ ‘escalate’, actor, reason?)` Closes a draft as false alarm or escalates for review
or_incident_sign(incident_id, actor, role, external_id?) Signs & submits the draft (in-app or external) no
link_or_record(case_id) Resolves (or_state_id, encounter_id, patient_id, or_room_code, or_room_id) for a procedure_request_id / UUID yes (read-only)
surgeon_block_recompute() Placeholder for future materialized view refresh yes

Triggers

Trigger When What
trg_or_count_to_incident After insert/update on or_intraop_counts where total_mismatch > 0 Auto-spawns / updates a count_discrepancy incident draft. Also auto-dismisses if mismatch returns to 0 before human action.
trg_or_turnover_item_count After insert/update/delete on or_turnover_instance_items Recomputes items_total/items_completed/items_mandatory_remaining on the parent instance.
trg_or_turnover_sync_gate After insert/update on or_turnover_instances.status or items_mandatory_remaining Appends/removes 'fire:procedure_start' from the next case’s procedure_day_queue.blocked_actions[].
trg_or_incident_drafts_autolink / trg_or_turnover_instances_autolink / trg_cssd_tray_case_usage_autolink Before insert/update on child tables Fill or_state_id / encounter_id / patient_id from or_state_cache if missing.
trg_or_state_cache_propagate_link (BEFORE) Before insert/update on or_state_cache.or_room_id or or_room_code Backfill or_room_code from or_rooms.legacy_room_uuid if caller didn’t supply it.
trg_or_state_cache_fanout_link (AFTER) After insert/update on or_state_cache Propagate trio + or_room_code down into matching procedure_day_queue + 3 child tables.
trg_cssd_recall_fanout_incidents After insert/update on cssd_recall_events where lots_affected > 0 Walks v_cssd_affected_cases and upserts one pending_review incident draft per affected (case, tray_lot).

Cross-feature interactions

              count mismatch on intraop counts
                          │
                          ▼
                or_incident_drafts (draft)
                          ▲
                          │ also fans out per case
              cssd_recall_events                Status Board shows
              (BI/CI/BD failure)               warning chips per room:
                          │                       - !N (open incidents)
                          ▼                       - CSSD N (recalled lots)
              cssd_tray_lots → recalled           - TURNOVER N (mand left)
                          │
                          ▼
        v_cssd_affected_cases ──▶ patient follow-up

              turnover instance open
                          │
                          ▼
       blocked_actions[] += 'fire:procedure_start'
                          │
                          ▼
       record_procedure_event refuses the start
                          │
                          ▼
               signoff clears the gate

Frontend surfaces

Route Sandbox key Drives from
/operating-room/status-board OrRoomStatusBoard v_or_room_status_board + v_or_room_warnings
/operating-room/turnover?roomCode=OR-3 OrTurnoverChecklist or_turnover_instances + items + template (realtime)
/operating-room/cssd OrCssdConsole cssd_trays + cssd_autoclave_cycles + cssd_recall_events + v_cssd_affected_cases
/operating-room/incidents OrIncidentInbox v_or_open_incidents + per-case or_incident_drafts
/operating-room/block-utilization OrBlockUtilization v_surgeon_block_utilization + daily / monthly views
/operating-room (landing) Live count chips on the 5 tiles
Card Click target
Status board card (with active case) /encounter/<active_encounter_id>
Status board card (turnover/cleaning, no active case) /operating-room/turnover?roomCode=<code>
CSSD recall row (when linked) ↗ /encounter/<encounter_id>
Incident card patient strip ↗ /encounter/<encounter_id>
Block-util leaderboard row (drill-down to be added)

Demo seed entry point

After applying migrations, the seed in 20260515c_or_feature_suite_demo_seed.sql provisions one cohesive demo case (DEMO-OR-SUITE-001) threaded through:

  • or_state_cache row → encounter 11111111-2222-3333-4444-555555550001
  • procedure_day_queue keyed on the same procedure_request_id
  • or_room_runtime_state for OR-1 set to active w/ this case
  • or_turnover_instances for OR-3 with next_procedure_request_id = DEMO-OR-SUITE-001 (visualises the gate)
  • cssd_tray_case_usage linking a passed lot of Major Lap to the case
  • or_intraop_counts with total_mismatch = 1 (auto-spawns the incident draft via trigger)
  • surgeon_block_allocations row for today’s morning OR-1 block (Dr Tanaka)

UAT viewers can hit any of the 5 routes and see the SAME patient + case.

Operator runbook

Initial deploy (per region):

# 1. Apply 11 migrations in order from infrastructure/medbase/migrations/
#    in the Supabase SQL editor (or psql -f). Each is idempotent.

# 2. The cohesive demo seed runs automatically with 20260515c.

# 3. Verify linkage:
SELECT * FROM v_or_linkage_health;
# All rows should have linked_rows = total_rows after the demo seed runs.

# 4. Smoke test:
#    - Open /operating-room (landing) — 5 NEW tiles + live count chips
#    - Open /operating-room/status-board — 8 room cards, OR-1 in INCISION
#      with active case chip linking to encounter
#    - Open /operating-room/turnover?roomCode=OR-3 — turnover in progress
#    - Open /operating-room/cssd — 6 trays, 4 cycles (one in FAIL state)
#    - Click "Recall" on the FAIL row → confirm → switch to Recalls tab
#      → see the demo case listed under affected cases + verify it also
#      appears in /operating-room/incidents
#    - Open /operating-room/block-utilization — Dr Tanaka shows some
#      utilization for today; OPEN blocks panel renders separately

Linkage health monitoring:

-- Run periodically. unlinked_rows should be near 0 except for legacy
-- cases without a corresponding or_state_cache row.
SELECT * FROM v_or_linkage_health;

Manual gate clearance (if a turnover instance is stuck):

-- 1. Identify the open instance
SELECT id, or_room_id, items_mandatory_remaining, status
  FROM or_turnover_instances
 WHERE status IN ('pending','in_progress');

-- 2a. Force sign-off (after manual ready-state verification)
SELECT or_turnover_signoff('<instance_id>', 'manual:supervisor', 'in_charge', NULL, NULL);

-- 2b. Or directly clear the gate (last resort, leaves audit gap)
SELECT _or_turnover_clear_gate('<next_procedure_request_id>');

Known limitations (post-demo follow-ups)

  • Backend integration: the record_procedure_event RPC reads blocked_actions[] but the NestJS gateway hasn’t been wired to surface the gate reason to the OR worklist UI as a user-readable string yet. Today the frontend disables the button and shows a generic tooltip.
  • CSSD pre-case verification widget: planned for a separate iteration. Today the link is one-way (tray → case at the moment of use); a barcode-scan widget in the OR sign-in flow would be the natural place.
  • Block-utilization drill-down: per-surgeon detail page (case list inside a block) is not yet wired from the leaderboard.
  • Open-block reallocation: identifying surgeons who repeatedly under-use blocks doesn’t yet trigger an admin workflow to reallocate them. The insights row surfaces the candidates; reassignment is manual.
  • Incident → external system submission: or_incident_sign accepts an external_id field but the bridge to the administration incidentReport NestJS service isn’t built. Today drafts stop at status=‘signed’.

File index

infrastructure/medbase/migrations/
  20260514g_or_room_phase_board.sql
  20260514h_or_turnover_workflow.sql
  20260514i_cssd_tray_tracking.sql
  20260514j_or_incident_autodraft.sql
  20260514k_surgeon_block_utilization.sql
  20260515a_or_record_linkage.sql
  20260515b_or_turnover_procedure_start_gate.sql
  20260515c_or_feature_suite_demo_seed.sql
  20260515d_cssd_recall_to_incident.sql
  20260515e_or_room_warning_chips.sql
  20260515f_or_state_cache_reverse_link.sql

web/sandbox/targets/
  OrRoomStatusBoardTarget.tsx
  OrTurnoverChecklistTarget.tsx
  OrCssdConsoleTarget.tsx
  OrIncidentInboxTarget.tsx
  OrBlockUtilizationTarget.tsx

web/src/containers/operating-room/
  status-board/index.tsx
  turnover/index.tsx
  cssd/index.tsx
  incidents/index.tsx
  block-utilization/index.tsx
  intraop-count/index.tsx       (modified — embeds incident banner)

web/src/containers/operating-room-landing/
  page.tsx                      (modified — 5 NEW tiles + live count chips)

web/src/routes/OperatingRoomRoutes.tsx  (modified — 5 new lazy routes)

Authors

Built across 30 iterations on 2026-05-14 → 2026-05-15 by Claude Opus 4.7 (1M context) in collaboration with the operator.

Ask Anything