medOS ultra

ER System Master Contract

Master source of truth for the Emergency Room system in medOS-ultra.

19 min read diagramsUpdated 2026-05-26docs/architecture/er-system-master-contract.md

Master source of truth for the Emergency Room system in medOS-ultra. Mirrors admission-to-ward-unified-contract.md and ipd-medication-order-master-contracts.md in scope and shape.

Read this before any ER worklist, ER request, triage, or fast-track work.


0. The Core Insight

ER is BOTH an Encounter AND a Request. This is the same dual nature as Admission (encounter+request), Blood Bank (request only, no separate encounter), and Operating Room (request only). What makes ER special is that both layers already exist in the backend — they just aren’t wired through the central order system or the Supabase read model layer yet.

Layer Entity Purpose Status
Encounter Encounter with encounterClass='EMER' The patient’s clinical episode in the ER ✅ Working
Request EmergencyMedicalService (emergency_medical_service collection) The operational ER service ticket ✅ Entity exists, ⚠️ not wired through orderRequest
Resource EmergencyRoom (emergency_room collection) Physical bay/zone (resus, trauma, fast-track) with ESI, devices, attending ✅ Entity exists
Read model department_queues with dept_type='screening' Operational queue surface ⚠️ Works, but ER mixed with OPD triage
Manifest encounter_journey_cache.encounter_class='EMER' Macro view per encounter ✅ Working

The implication: any clinical flow can spawn an ER request, same way any flow can spawn a lab order or imaging request. The patient may already be in OPD (deteriorating walk-in), IPD (code blue / RRT), in transit (EMS pre-arrival), or external (inbound referral). The ER request is the universal escalation primitive.


1. Three Worlds, Six Entry Points

                    ┌──────────────────────────────┐
                    │     EmergencyMedicalService    │   ← canonical request
                    │  (emergency_medical_service)   │     patient, encounter,
                    │                                │     severity, status,
                    │                                │     orderRequestRef ─┐
                    └──────────────┬─────────────────┘                      │
                                   │                                        │
                                   │  Created by ANY of                     │ links
                                   │  6 entry points ↓                      │ to
                                   │                                        ▼
   ┌──────────┬──────────┬────────┴────────┬──────────┬──────────┐  ┌────────────┐
   │          │          │                 │          │          │  │ orderRequest│
   ▼          ▼          ▼                 ▼          ▼          ▼  │ (billing,   │
 Direct    OPD       IPD               Inbound     EMS        Other│  events,    │
 Walk-in   Escalate  Code Blue/RRT     Referral    Pre-arrival     │  webhooks)  │
   │          │          │                 │          │          │  └────────────┘
   │          │          │                 │          │          │
   ▼          ▼          ▼                 ▼          ▼          ▼
 NEW       LINK to    LINK to           LINK to    NEW         LINK to
 EMER      existing   existing IPD      new EMER   EMER         existing
 encounter OPD enc    encounter         enc        encounter    encounter
 +request  +request   +request          +request   +request     +request

Entry point matrix

# Source Encounter ER Request Trigger Status today
1 Direct walk-in NEW EMER NEW DialogCreateVisit with emergencyDetails.emergencyPatient=true ✅ Works
2 OPD escalation LINK existing AMBEMER reclass, or NEW EMER NEW OPD doctor “ส่ง ER” menu action ❌ No UI
3 IPD Code Blue / RRT LINK existing IMP (keep IPD encounter) NEW (special type=emergency_response) IPD nurse “Code Blue” button ❌ No UI
4 Inbound referral NEW EMER NEW /referral accept → spawn EMER encounter ✅ Path exists, not wired
5 EMS pre-arrival Optional pre-registered EMER NEW (PENDING, no encounter yet) Public API POST /api/ems/notify ❌ Endpoint doesn’t exist
6 Mass casualty Multiple NEW EMER Multiple, batch-spawned Mass casualty incident dispatch ✅ Tab exists, partial

Why ER must be both layers

Why an Encounter? Why a Request?
Patient needs a clinical record (HN, AN, EN) OPD/IPD/Referral patients already HAVE an encounter — need an ER “ticket” hanging off it
Billing rolls up to one encounter Multiple ER touches per encounter (initial visit, repeat visit same day)
Diagnosis codes attach to encounter ESI level / severity is per-event, not per-encounter
Discharge disposition is encounter-level Multiple severity escalations within one encounter are valid
Per-encounter LOS / arrival timestamp Operational SLA clock (door-to-doctor) is per-request

Rule: the ER encounter is the patient’s clinical record; the ER request is the operational unit of work. One encounter, many requests possible.


2. Entity Contracts

2.1 EmergencyMedicalService (MongoDB — write truth)

Source: packages/platform-api-schema/src/administration/emergencyMedicalService/entity/EmergencyMedicalService.ts

Field Type Notes
_id UUID MongoDB
patientRef UUID FK → Patient
encounterRef UUID FK → Encounter (REQUIRED — every request has an encounter)
status EmergencyMedicalServiceStatus pending / in_progress / completed
emergencyMedicalServiceType EmergencyMedicalServiceType diagnosis / treatment / transfer / other
severityLevel EmergencyMedicalServiceSeverityLevel normal / urgent / emergency
serviceDate Date When service began
clinicRef / subClinicRef / locationRef UUID Where
responsibleProvider UUID FK → User
chiefComplaint string TH or EN
treatmentHistory string
testResults string
notes string
transferDate Date When transferred out (if disposition=transfer)
orderRequestRef UUID FK → OrderRequest (declared, not auto-populated yet)

Gaps in current model (proposed extensions in §6):

  • No esiLevel: 1..5 (currently lives on Encounter only)
  • No zone: red\|yellow\|green\|black (lives on screeningDetails)
  • No arrivalAt / triageAt / doctorAssignedAt / dispositionAt SLA timestamps
  • No disposition: discharged\|admitted\|referred\|expired\|lwbs
  • No triggerSource: walkin\|opd_escalation\|ipd_code\|referral\|ems\|mass_casualty
  • No parentEncounterRef for OPD→ER or IPD code-blue cases

2.2 EmergencyRoom (MongoDB — physical resource)

Source: packages/platform-api-schema/src/administration/emergencyRoom/entity/EmergencyRoom.ts

Field Notes
status EmergencyRoomStatus (available / occupied / cleaning)
emergencyRoomType EmergencyRoomType (resus / trauma / fast-track / observation)
roomNumber display label
patient current occupant
encounter linked encounter
esiLevel current ESI of occupant (1-5)
attendingStaff[] doctor + nurses
device[] available devices in bay
medication[] crash cart contents
lastCleaned turnover timestamp

2.3 Encounter (existing, write truth)

The Encounter entity already supports ER. Relevant fields:

Field Notes
encounterClass 'EMER' for ER (also accepts 'AMB', 'IMP', 'OBSENC', 'VR')
screeningDetails.screeningInfo.esiLevel ESI 1-5
screeningDetails.screeningInfo.zone Red/Yellow/Green/Black
screeningDetails.screeningInfo.activateFastTrack chest pain / stroke / sepsis / trauma flags
emergencyDetails.emergencyPatient boolean flag — the gate that sets encounterClass='EMER'
emergencyDetails.isMassCasualtyAccident mass casualty incident link
emergencyDetails.transporterName / transporterTelContact EMS / ambulance
consultationWorkflowState 'er-wait-screening', 'er-in-process', etc.
parentEncounter (proposed) FK to OPD encounter when ER spawned from OPD escalation

2.4 Supabase read models

encounter_journey_cache — already supports EMER. Existing columns:

Column EMER usage
encounter_class 'EMER'
clinical_context.encounter_context.esiLevel 1-5
clinical_context.encounter_context.emergencyType trauma / non-trauma / fast-track
current_physical_location ER bay / triage / resus
pending_tickets { "screening": [...], "consultation": [...], "lab": [...], ... }

Proposed new columns (migration xxxx_er_journey_columns.sql):

Column Purpose
er_arrival_at hoisted from encounter for SLA queries
er_triage_at door-to-triage
er_doctor_at door-to-doctor
er_disposition discharged | admitted | referred | expired | lwbs
er_disposition_at door-to-disposition
er_chief_complaint hoisted for column display
er_zone red / yellow / green / black

department_queues — works, but ER currently uses dept_type='screening' which mixes ER triage with OPD triage. Proposed:

Current dept_type Proposed dept_type for EMER Why
screening er_triage Isolate ER triage queue from OPD pre-screening
consultation er_consultation Isolate ER doctor queue from OPD clinic doctors
wait-result er_observation Observation bay separate from OPD wait-result

This is the single most important change — it lets the frontend filter dept_type IN ('er_triage','er_consultation','er_observation') and get a clean ER worklist.

New table: emergency_service_request_cache (Supabase mirror of EmergencyMedicalService) — same pattern as referral_cache (migration 052).

CREATE TABLE emergency_service_request_cache (
  id              UUID PRIMARY KEY,
  mongo_ref       TEXT,                          -- EmergencyMedicalService._id
  encounter_id    TEXT NOT NULL,
  patient_id      TEXT NOT NULL,
  parent_encounter_id TEXT,                      -- OPD/IPD link for escalations
  trigger_source  TEXT NOT NULL,                 -- walkin/opd_escalation/ipd_code/referral/ems/mass_casualty
  service_type    TEXT,                          -- diagnosis/treatment/transfer/other
  severity_level  TEXT,                          -- normal/urgent/emergency
  esi_level       SMALLINT CHECK (esi_level BETWEEN 1 AND 5),
  zone            TEXT,                          -- red/yellow/green/black
  status          TEXT NOT NULL,                 -- pending/in_progress/completed
  chief_complaint TEXT,
  arrival_at      TIMESTAMPTZ,
  triage_at       TIMESTAMPTZ,
  doctor_at       TIMESTAMPTZ,
  disposition     TEXT,                          -- discharged/admitted/referred/expired/lwbs
  disposition_at  TIMESTAMPTZ,
  responsible_provider_id TEXT,
  responsible_provider_name TEXT,
  emergency_room_id TEXT,                        -- EmergencyRoom._id
  emergency_room_label TEXT,                     -- "Resus-1", "Trauma-A"
  order_request_id TEXT,                         -- canonical orderRequest (billing)
  ack_request_id  TEXT,                          -- AcknowledgementRequest for SLA
  fast_track_flags JSONB,                        -- { chestPain, stroke, sepsis, trauma }
  metadata        JSONB,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_emer_request_encounter ON emergency_service_request_cache(encounter_id);
CREATE INDEX idx_emer_request_status ON emergency_service_request_cache(status);
CREATE INDEX idx_emer_request_severity ON emergency_service_request_cache(severity_level, esi_level);
CREATE INDEX idx_emer_request_trigger ON emergency_service_request_cache(trigger_source);

3. Status Enums & Progressions

3.1 EmergencyMedicalServiceStatus

  ┌─────────────┐
  │   pending    │  Request created, not yet started
  └──────┬──────┘     (e.g. EMS pre-arrival, OPD escalation queued)
         │
         ▼
  ┌─────────────┐
  │ in_progress  │  Patient in ER, treatment underway
  └──────┬──────┘     (most of ER lifecycle)
         │
         ▼
  ┌─────────────┐
  │  completed   │  Disposition reached (discharge/admit/refer/expired/lwbs)
  └─────────────┘

3.2 EmergencyMedicalServiceType

Value Use
diagnosis Diagnostic-only visit (e.g. CT, ECG, then discharge)
treatment Active treatment (IV fluids, stitching, reduction, etc.)
transfer Refer-out from ER to another hospital
other Catch-all (observation, fast-track, social admit)

3.3 EmergencyMedicalServiceSeverityLevel

Value Maps to ESI Color zone
emergency 1–2 Red
urgent 3 Yellow
normal 4–5 Green

3.4 ER Encounter Workflow States (existing)

From nurse-emergency-workflow.json:

                  er-wait-screening (initial)
                          │ ESI triage
                          ▼
                     er-screened
                          │ assign doctor
                          ▼
                    er-wait-doctor
                          │ start treatment
                          ▼
   ┌──────────────── er-in-process ────────────────┐
   │                      │                          │
   │  [send results]      │ [observe]    [discharge]│
   ▼                      ▼                          ▼
er-wait-result      er-observe-symptoms          er-finished
   │                      │                          │
   │ [return]             │ [finish observe]        ├─ discharge → er-discharged
   └────► er-in-process   └────► er-finished        ├─ admit → er-admitted
                                                     └─ refer → er-referred

4. Event Choreography (manifest events)

ER spans 3 services. All events flow through hospital_eventsencounter-orchestrator.

4.1 Events currently emitted

Event Emitted by Payload
clinical.emergency.created emergencyMedicalService.controller.mixin.ts { emergencyMedicalServiceId, encounter, patient, status, severityLevel }
manifest.encounter.seeded encounter create (any class) full encounter context
manifest.encounter.updated encounter update partial context
manifest.queue.workflow_transition workflow action handler { encounter, fromState, toState, deptType }

4.2 Events to add (proposed)

Event When Payload
manifest.emergency.created ER request created { emergencyServiceId, encounterId, patientId, triggerSource, parentEncounterId, severityLevel, esiLevel, chiefComplaint, arrivalAt }
manifest.emergency.triaged ESI assigned, screening complete { emergencyServiceId, esiLevel, zone, fastTrackFlags, triageAt }
manifest.emergency.escalated OPD/IPD → ER trigger { parentEncounterId, newEncounterId, triggerSource, severityLevel, escalatedBy }
manifest.emergency.disposed Disposition reached { emergencyServiceId, encounterId, disposition, dispositionAt }
manifest.emergency.lwbs Left without being seen { emergencyServiceId, encounterId, lastSeenAt }
manifest.emergency.boarding Admitted but no bed > 4h { emergencyServiceId, encounterId, admittedAt, boardingDuration }

4.3 Orchestrator handler additions

In infrastructure/medbase/functions/encounter-orchestrator/:

// New handler: handleEmergencyCreated
async function handleEmergencyCreated(event: HospitalEvent) {
  const { emergencyServiceId, encounterId, patientId, triggerSource,
          parentEncounterId, severityLevel, esiLevel, chiefComplaint,
          arrivalAt } = event.payload;

  // 1. Upsert emergency_service_request_cache
  await supabase.from('emergency_service_request_cache').upsert({
    id: emergencyServiceId,
    encounter_id: encounterId,
    patient_id: patientId,
    parent_encounter_id: parentEncounterId,
    trigger_source: triggerSource,
    severity_level: severityLevel,
    esi_level: esiLevel,
    chief_complaint: chiefComplaint,
    arrival_at: arrivalAt,
    status: 'pending',
  });

  // 2. Place in department_queues as dept_type='er_triage'
  await upsertQueueRow({
    encounterId, patientId,
    dept_type: 'er_triage',
    ticket_id: emergencyServiceId,
    status: 'WAITING',
    priority: severityLevel === 'emergency' ? 'STAT'
            : severityLevel === 'urgent' ? 'URGENT' : 'ROUTINE',
    metadata: { triggerSource, parentEncounterId, fastTrackFlags },
  });

  // 3. Hoist ER columns to encounter_journey_cache
  await supabase.from('encounter_journey_cache').update({
    er_arrival_at: arrivalAt,
    er_chief_complaint: chiefComplaint,
    er_disposition: null,
  }).eq('encounter_id', encounterId);

  // 4. Create AcknowledgementRequest with SLA expiry based on ESI
  const slaMinutes = { 1: 0, 2: 10, 3: 30, 4: 60, 5: 120 }[esiLevel] ?? 30;
  await createAcknowledgementRequest({
    kind: 'er_triage_sla',
    targetUserRole: 'er_triage_nurse',
    expiresAt: addMinutes(arrivalAt, slaMinutes),
    encounterId,
    metadata: { emergencyServiceId, esiLevel },
  });
}

// New handler: handleEmergencyTriaged
async function handleEmergencyTriaged(event: HospitalEvent) {
  // Move from dept_type='er_triage' to dept_type='er_consultation'
  // Update emergency_service_request_cache.triage_at + esi_level + zone
  // Update encounter_journey_cache.er_triage_at + er_zone
  // Resolve er_triage_sla ack, create er_doctor_sla ack
}

// New handler: handleEmergencyEscalated
async function handleEmergencyEscalated(event: HospitalEvent) {
  // OPD/IPD → ER promotion
  // Either reclass existing encounter (encounter_class AMB→EMER)
  // OR spawn new EMER encounter with parentEncounterRef
  // Place in er_triage queue
}

// New handler: handleEmergencyDisposed
async function handleEmergencyDisposed(event: HospitalEvent) {
  // Move dept_type='er_*' rows to completed_tickets
  // Update emergency_service_request_cache.disposition + disposition_at
  // Update encounter_journey_cache.er_disposition + er_disposition_at
  // If disposition='admitted' → cascade to handleAdmissionUpdated
  // If disposition='referred' → cascade to handleReferralCreated
}

5. Frontend Architecture

5.1 The three surfaces

Surface Route Data source Status
ER Worklist (nurse) /emergency/list-patient Legacy: REST + Socket.IO. Migrate to: Supabase department_queues + emergency_service_request_cache realtime ⚠️ Legacy
ER Doctor View /emergency/doctor Same migration path ⚠️ Legacy
ER Hub /emergency/hub Config landing, links to admin pages ✅ New

5.2 New components needed

Component Purpose Where to mount
DialogCreateEmergencyRequest Universal “ส่ง ER” dialog — spawn ER request from any context DoctorActionBar, OPD doctor menu, IPD code blue button
useERWorklistRealtime Supabase realtime subscription hook (like useCommandCenterRealtime for IPD) TableAllPatientEr replacement
useERRequest(encounterId) React Query hook for the active ER request EmergencyDoctor, ER profile panel
ERSLABadge Door-to-triage / door-to-doctor SLA indicator Queue rows
CodeBlueButton One-click IPD → ER rapid response IPD command center, ward view
EMSPreArrivalPanel Pre-arrival notification surface ER triage worklist

5.3 Universal entry point modal

DialogCreateEmergencyRequest is the universal entry. Its behavior depends on the source context:

Source Pre-fills Encounter behavior
Walk-in (Registration) Empty Creates new EMER encounter
OPD doctor Patient + current encounter Either reclasses OPD→EMER or spawns new EMER linked via parentEncounter (admin choice)
IPD nurse (code blue) Patient + IPD encounter Keeps IPD encounter, creates ER request with serviceType=treatment and severityLevel=emergency
Inbound referral From referral payload Creates new EMER encounter, links referralRequestId
EMS pre-arrival EMS payload (national ID, chief complaint) Creates ER request with status=pending and no encounter; encounter spawned on arrival
Mass casualty Incident ID Batch-create with shared incidentId

6. Migration Plan — 6 Phases

Phase 0 — Backend Wiring (preparation, no schema)

Effort: 1 day

  • [ ] Add ER request to gateway whitelist (administration.emergencyMedicalService.*)
  • [ ] On EmergencyMedicalService.create, auto-create parent orderRequest with category='emergency', populate orderRequestRef
  • [ ] Emit manifest.emergency.created event (in addition to existing clinical.emergency.created webhook)
  • [ ] Add triggerSource, parentEncounterRef, arrivalAt, triageAt, doctorAt, dispositionAt, disposition, zone, esiLevel to entity

Phase 1 — Supabase Read Model (orchestrator + tables)

Effort: 1-2 days

  • [ ] Migration: emergency_service_request_cache table (schema in §2.4)
  • [ ] Migration: hoist er_arrival_at, er_triage_at, er_doctor_at, er_disposition, er_disposition_at, er_chief_complaint, er_zone to encounter_journey_cache
  • [ ] Migration: add dept_type='er_triage', 'er_consultation', 'er_observation' values to dept_type enum or whitelist
  • [ ] Orchestrator: add 5 new handlers (handleEmergencyCreated, handleEmergencyTriaged, handleEmergencyEscalated, handleEmergencyDisposed, handleEmergencyLwbs)
  • [ ] Orchestrator: update handleQueueWorkflowTransition to emit dept_type='er_*' when encounter_class='EMER'

Phase 2 — Cron Jobs for SLA Monitoring

Effort: ½ day

  • [ ] er-triage-sla-evaluator (every 1 min): escalate WAITING rows whose ack_request expired (ESI-2 > 10min, ESI-3 > 30min, ESI-4 > 60min)
  • [ ] er-doctor-assignment-sla (every 2 min): alert if er_triage_at set but er_doctor_at null > 45 min
  • [ ] er-lwbs-detector (daily): flag encounters closed at er-wait-screening
  • [ ] er-boarding-alert (every 15 min): admitted ER patients still in ER bay > 4h
  • [ ] All registered via cron_jobs table (see cron-jobs-registry.md)

Phase 3 — Universal DialogCreateEmergencyRequest

Effort: 1-2 days

  • [ ] Build DialogCreateEmergencyRequest component in web/packages/healthops-kit/src/er-system/components/dialogs/
  • [ ] 6 modes: walk-in, opd_escalation, ipd_code, referral, ems, mass_casualty
  • [ ] Register as DIALOG_CREATE_EMERGENCY in modalRegistry
  • [ ] Wire into:
    • OPD doctor workflow JSON menu (new er-escalate action)
    • IPD command center as CodeBlueButton
    • Referral inbound accept flow (auto-spawn)
    • Public API for EMS pre-arrival
    • DoctorActionBar quick action
    • QuickOrderDock

Phase 4 — Migrate ER Worklist to Supabase Realtime

Effort: 1-2 days

  • [ ] Build useERWorklistRealtime(locationId) hook (mirror of useCommandCenterRealtime for IPD)
  • [ ] Replace TableAllPatientEr data source: REST → supabase.from('department_queues').filter(...).filter('dept_type', 'in', ['er_triage','er_consultation','er_observation'])
  • [ ] Remove Socket.IO connection (allPatientOpdScreeningSocketIOClient)
  • [ ] Wire workflow JSON actions to WorkflowBasedTabs / workflowActionHandler pipeline (so the menus we added — e-MAR, nursing assessment, scoring, referral — actually open from the ER row context)
  • [ ] Keep legacy screening dialog (DialogScreening) — it works fine; just trigger from the new realtime row

Phase 5 — Configurable Escalation Rules

Effort: 1 day

  • [ ] Extend encounter_class_rules table (already exists from claude/patient-status-emer-entry) with escalation_rules:
    • Auto-escalate AMB→EMER if EWS ≥ 7 in OPD
    • Auto-escalate IMP→EMER short-circuit when code blue triggered
    • Auto-fast-track based on chief complaint keywords
  • [ ] Admin UI at /admin/encounter-class-config already exists — extend with escalation conditions

Phase 6 — Reporting / Analytics

Effort: 1 day

  • [ ] Door-to-doctor view (v_er_door_to_doctor) — % within target by ESI
  • [ ] LWBS rate view (v_er_lwbs_rate) — daily/weekly
  • [ ] Boarding hours view (v_er_boarding_hours) — admit-to-bed delay
  • [ ] Disposition mix view (v_er_dispositions) — discharge/admit/refer rates
  • [ ] Dashboard at /emergency/analytics consuming the views

7. Cross-System Cascades

When ER request → admitted

  1. handleEmergencyDisposed fires with disposition='admitted'
  2. Cascades to handleAdmissionUpdated (existing IPD pipeline)
  3. New admission row created, bed assignment workflow starts
  4. ER queue row closed, IPD queue row opened
  5. Encounter stays the same (encounter_class may flip EMERIMP, or remain EMER with parentForAdmission)

When ER request → referred out

  1. handleEmergencyDisposed fires with disposition='referred'
  2. Cascades to handleReferralReceived (existing referral pipeline)
  3. New row in referral_request (outbound)
  4. Same encounter; er_disposition='referred'

When ER request → discharged home

  1. handleEmergencyDisposed fires with disposition='discharged'
  2. handleEncounterClosed fires
  3. Billing finalizes via orderRequestsaleOrderclaim
  4. Encounter moves to completed_tickets

When OPD doctor escalates AMB→EMER

  1. manifest.emergency.escalated fires
  2. Two options based on admin config:
    • Reclass: Same encounter, encounter_class AMB→EMER, OPD queue row closed, ER queue row opened
    • Spawn: New EMER encounter with parentEncounter, OPD encounter stays open until OPD doctor closes it, ER encounter independent
  3. ER request created linked to the (possibly new) encounter
  4. Patient appears in ER worklist with triggerSource='opd_escalation'

When IPD code blue

  1. manifest.emergency.escalated with triggerSource='ipd_code'
  2. IPD encounter unchanged (still encounter_class='IMP')
  3. ER request created with serviceType='treatment', severityLevel='emergency'
  4. Patient does NOT move from IPD bed; instead ER team comes to ward
  5. Special EmergencyRoom assignment: virtual room representing the ward bed
  6. On resolution: ER request completed, IPD encounter continues OR escalates to ICU

8. The 9 Invariants

Same convention as admission-to-ward-unified-contract.md §10.

  1. Every ER request has an encounter. encounterRef is required. No exceptions — even EMS pre-arrival spawns an encounter shell first.
  2. Encounter class is the gate. encounter_class='EMER' is the only way to appear in ER worklist queries. ER requests linked to non-EMER encounters (IPD code blue) appear in IPD worklists with an “ER active” badge, not in ER worklist.
  3. One encounter, many requests. Multiple EmergencyMedicalService rows per encounter are valid (e.g. patient returns same day, or multiple severity escalations within the visit).
  4. The request is the SLA clock. Door-to-triage and door-to-doctor measure on the request, not the encounter. Encounter LOS may include pre-ER OPD time when escalated.
  5. orderRequestRef is the billing bridge. Without it, the ER visit has no saleOrder, no claim, no revenue. Phase 0 fix.
  6. dept_type namespacing is the queue gate. screening is OPD, er_triage is ER. Frontend filters cleanly when this is enforced.
  7. The encounter-orchestrator is the only writer to read models. Frontend never writes to emergency_service_request_cache, department_queues, or encounter_journey_cache. Always through backend → events → orchestrator.
  8. Disposition is terminal. Once disposition is set on the cache, the request is completed and queues are closed. Cascading effects (admission, referral, discharge) flow from handleEmergencyDisposed.
  9. Cron-driven SLA is the safety net. No event-driven SLA — every wait threshold is enforced by a cron job that scans WAITING rows and escalates via AcknowledgementRequest, never by inline triggers.

9. Decision: Is ER an Order System or an Encounter EMR?

Both. Same answer as Admission, Operating Room, Blood Bank, Imaging.

  • The encounter EMR layer (encounter_class='EMER') is the patient’s clinical record — the same primitive as OPD/IPD.
  • The order/request layer (EmergencyMedicalServiceorderRequest) is the operational unit that:
    • Surfaces in queues
    • Drives billing
    • Has SLA timers
    • Cascades to admission / referral / discharge
    • Can be created from ANY context (walk-in, OPD, IPD, referral, EMS)

This matches the existing pattern in central-order-system.md — ER is one of the disconnected order types that needs to follow the same Blood Bank recipe. The backend entity already exists with orderRequestRef declared. Wire it through, mirror it in Supabase, expose the universal DialogCreateEmergencyRequest, and ER becomes a first-class citizen of the unified order system while keeping its full encounter semantics.


10. File References

Topic Path
Existing entity packages/platform-api-schema/src/administration/emergencyMedicalService/entity/EmergencyMedicalService.ts
Backend service services/administration/src/api/administration/modules/emergencyMedicalService/
EmergencyRoom resource packages/platform-api-schema/src/administration/emergencyRoom/entity/EmergencyRoom.ts
ER workflow JSON web/packages/medical-kit/src/medical-worklist/defaults/nurse-emergency-workflow.json
Legacy worklist (to migrate) web/packages/medical-kit/src/emergency-system/emergency-list-patient/components/all-patient-emergency/TableAllPatientEr.tsx
Routes web/src/routes/EmergencyRoutes.tsx
Encounter class rules (recently merged) web/src/services/ever-administration/encounterClassRules.service.ts
Emergency Hub (recently merged) web/src/containers/emergency-hub/page.tsx
Screening dialog (legacy, still used) web/packages/patient-roster/src/hospital-outpatient-screeninglist/components/dialog-screening/DialogScreening.tsx
Screening V2 (modern) web/packages/patient-roster/src/hospital-outpatient-patientlist/components/workflows/patient-check-in/menu-tab/screening-v2/
Central order system spec docs/architecture/central-order-system.md
Admission contract reference docs/architecture/admission-to-ward-unified-contract.md
Orchestrator master ref docs/architecture/encounter-orchestrator-triggers.md
Cron registry docs/architecture/cron-jobs-registry.md
Acknowledgement system docs/architecture/acknowledgement-system.md
Referral system master docs/architecture/referral-system-master.md (TBD — companion doc)
Ask Anything