medOS ultra

Admission-to-Ward Unified Contract

Entity contracts for admission/bed/IPD/journey cache, status state machines, event payloads, orchestrator side-effects, 8 invariants.

18 min read diagramsUpdated 2026-05-18docs/architecture/admission-to-ward-unified-contract.md

Unified view of entities, events, status progressions, and data flow from admission request through the IPD nurse dashboard and OPD/IPD/ER list views.

1. Entity Contracts

1.1 Admission (MongoDB — write truth)

Field Type Source
_id ObjectId MongoDB auto
encounterId ObjectId FK → Encounter
patientID ObjectId FK → Patient
admissionStatus AdmissionStatus See §2.1
approvalStatus ApprovalStatus See §2.2
transferStatus TransferStatus See §2.3
wardId / ward ObjectId FK → SubClinic (ward)
wardName string Denormalized
bedName / bedLabel string Bed display name
roomName string Room assignment
admitDateTime Date When patient admitted
dischargeDate Date When discharged
dischargeDisposition string Discharge destination
visitType AdmissionType See §2.4
priority string ROUTINE / URGENT / STAT
chiefComplaint string Free text
esiLevel number Emergency severity index
isBedReserved ‘Y’ / ‘N’ Bed reservation flag
createdAt Date Record creation

Service: services/administration/.../admission/admission.service.ts Controller: services/administration/.../admission/admission.controller.mixin.ts REST: POST /admissions (create), PUT /admissions/:_id (update)

1.2 Bed (Supabase — write truth for occupancy)

Column Type Notes
id uuid PK
ward_id text Ward identifier
room_id text Room within ward
name text Display label (e.g. “A-101”)
code text Alternate code
bed_type text “standard”, “icu”, “ccu”
status text See §2.5 — CHECK constraint
encounter_id text Current encounter (soft ref)
admission_id text Current admission (soft ref)
patient_id text Current patient (soft ref)
hn_snapshot text Patient HN (denormalized)
an_snapshot text Admission number (denormalized)
patient_name_snapshot text Patient name (denormalized)
active boolean Default true
gender text male / female / other
age_group text child / elder / other

Schema: infrastructure/medbase/migrations/20260518g_bed_table.sql Triggers: Auto-update updated_at, auto-log to bed_status_log

1.3 IPD Admission (Supabase — denormalized read model)

Column Type Notes
id uuid PK
admission_number text AN (unique per admission)
status text See §2.6 — CHECK constraint
patient_id text MongoDB patient ObjectId
patient_hn text Health number
patient_name text Display name
ward_id text FK concept → ward
ward_name text Denormalized
bed_id uuid FK → bed.id
bed_number text Denormalized bed label
room_name text Room assignment
attending_doctor_id text Doctor ObjectId
attending_doctor_name text Denormalized
department text Clinical department
admission_date timestamptz Admission time
discharge_date timestamptz Discharge time
los_days integer Length of stay
diagnosis text Primary diagnosis
has_pending_lab boolean Order status flags
has_pending_imaging boolean
has_pending_medication boolean
has_pending_procedure boolean

Schema: infrastructure/medbase/migrations/20260518k_ipd_ward_bed_tables.sql

1.4 Encounter Journey Cache (Supabase — read model)

Admission data is stored in two places within this table:

Hoisted columns (for query efficiency):

Column Source Added in
encounter_class 'IMP' for admissions migration 001
ward_id admCtx.wardId migration 055
bed_label admCtx.bedName migration 055
admit_at admCtx.admitDateTime migration 055
current_an encounterCtx.an migration 055
attending_doctor_id admCtx.attendingDoctorId migration 055
attending_doctor_name admCtx.attendingDoctorName migration 055
working_dx encounterCtx.workingDx migration 055

Nested JSONB (clinical_context.admission_context):

{
  "admissionStatus": "admitted",
  "approvalStatus": "reserved",
  "wardId": "ward-female-general",
  "wardName": "สามัญหญิง / Female General",
  "bedName": "A-101",
  "roomName": "Room 1",
  "admitDateTime": "2025-05-18T10:00:00Z",
  "chiefComplaint": "Chest pain",
  "priority": "ROUTINE",
  "updatedAt": "2025-05-18T10:00:00Z"
}

2. Status Enums & Progressions

2.1 AdmissionStatus (MongoDB — backend canonical)

                          ┌─────────────┐
                          │   request    │  Doctor creates admission request
                          └──────┬──────┘
                                 │
                    ┌────────────┼────────────┐
                    ▼            ▼             ▼
             ┌──────────┐ ┌──────────┐ ┌───────────────┐
             │ pending   │ │cancelled │ │ cancelRequest │
             └─────┬────┘ └──────────┘ └───────────────┘
                   │
          ┌────────┼──────────┐
          ▼        ▼          ▼
   ┌────────────┐ ┌───────────────────────┐
   │appointment │ │receivePendingAdmit    │
   └─────┬──────┘ └───────────┬───────────┘
         │                    │
         └────────┬───────────┘
                  ▼
           ┌──────────┐
           │ admitted  │  Patient on ward
           └─────┬────┘
                 │
                 ▼
          ┌────────────┐
          │ discharged │  Terminal
          └────────────┘

Enum values: request, pending, appointment, admitted, receivePendingAdmit, discharged, cancelled, cancelRequest

Source: packages/platform-api-schema/src/administration/admission/entity/AdmissionStatus.ts

2.2 ApprovalStatus (MongoDB — backend)

pending → reserved → receiveOrders → receivePendingAdmit
                                   └→ cancel

Values: pending, reserved, receiveOrders, receivePendingAdmit, cancel

2.3 TransferStatus (MongoDB — backend)

pending → success

2.4 AdmissionType (MongoDB — backend)

ACCIDENT, ELECTIVE, EMERGENCY, LABOUR_AND_DELIVERY, NEWBORN, ROUTINE, URGENT

2.5 Bed Status (Supabase — bed table)

                    ┌───────────┐
           ┌───────►│  vacant   │◄──────────┐
           │        └─────┬─────┘           │
           │              │                 │
    ┌──────┴──────┐  ┌────▼─────┐   ┌───────┴──────┐
    │  cleaning   │  │ reserved │   │out_of_service│
    └─────────────┘  └────┬─────┘   └──────────────┘
           ▲              │
           │         ┌────▼─────┐
           │         │ admitted │
           │         └────┬─────┘
           │              │
           │    ┌─────────▼──────────┐
           └────│ discharge_pending  │
                └────────────────────┘

CHECK constraint values: vacant, reserved, admitted, discharge_pending, cleaning, out_of_service

Transitions logged to bed_status_log:

Transition From → To Trigger
reserve vacant → reserved Bed reserved for incoming patient
cancel_reservation reserved → vacant Reservation cancelled
admit reserved/vacant → admitted Patient placed in bed
transfer_in vacant → admitted Patient transferred from another bed
transfer_out admitted → cleaning/vacant Patient moved to another bed
discharge_planned admitted → discharge_pending Discharge order written
discharge_completed discharge_pending → cleaning/vacant Patient leaves
clean_done cleaning → vacant Housekeeping complete
close any → out_of_service Bed closed for maintenance
reopen out_of_service → vacant Bed reopened

2.6 IPD Admission Status (Supabase — ipd_admissions)

planned → in-progress → pending-discharge → discharged
       → reserved    →                   → cancelled

CHECK constraint values: planned, in-progress, reserved, pending-discharge, discharged, cancelled

2.7 UI Bed Status (WardViewPanel)

Supabase Status UI Status Thai Label Color
vacant empty เตียงว่าง #9e9e9e (grey)
reserved reserved มีการจอง #2e7d32 (green)
admitted occupied มีคนใช้ #1565c0 (blue)
discharge_pending pending-discharge รอจำหน่าย #f9a825 (amber)
cleaning empty เตียงว่าง #9e9e9e (grey)
out_of_service empty เตียงว่าง #9e9e9e (grey)

Mapping: web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/useWardBeds.ts:35-42

2.8 Frontend Admission Status Groups

Group Statuses Use
Active pending, reserved, receiveOrders Pre-admission queue
In Care admitted, transferred Active inpatients
Terminal discharged, cancelled, cancelRequest Completed/cancelled

Source: web/packages/adt-kit/src/admission/admissionStatusConfig.ts


3. Events

3.1 Hospital Events (MongoDB → Supabase)

Legacy Event Type Normalized Form When Emitted
ADMISSION_CREATED manifest.admission.updated POST /admissions — new admission request
ADMISSION_UPDATED manifest.admission.updated PUT /admissions/:_id — status/bed/ward change

Source: infrastructure/medbase/functions/_shared/event-contract.ts:41-42, 167, 217-218

Both legacy types normalize to the same handler since the orchestrator processes the payload identically (upsert semantics).

3.2 Event Payload (hospital_events.payload)

{
  // Identity
  "admissionId": "ObjectId",
  "encounterId": "ObjectId",   // via hospital_events.encounter_id
  "patientId": "ObjectId",     // via hospital_events.patient_id

  // Status fields (any subset present on update)
  "admissionStatus": "pending|reserved|admitted|discharged|cancelled|cancelRequest",
  "approvalStatus": "pending|reserved|receiveOrders|cancel|receivePendingAdmit",
  "transferStatus": "pending|success",

  // Location
  "wardId": "ObjectId or text",
  "wardName": "สามัญหญิง / Female General",
  "bedName": "A-101",
  "roomName": "Room 1",

  // Clinical
  "visitType": "ELECTIVE|EMERGENCY|ROUTINE|...",
  "priority": "ROUTINE|URGENT|STAT",
  "chiefComplaint": "Chest pain",
  "esiLevel": 3,
  "admitDateTime": "2025-05-18T10:00:00Z",
  "isBedReserved": "Y|N",

  // Patient (sometimes nested)
  "hn": "6800001",
  "an": "AN2025001",
  "fullName": "สมหญิง ใจดี / Somying Jaidee",
  "patientName": "สมหญิง ใจดี",

  // Nested alternative (backend may send either flat or nested)
  "admission": { /* same fields as above */ },
  "patient": { "hn": "...", "fullName": "..." }
}

3.3 Orchestrator Handler Chain

hospital_events INSERT trigger
    → encounter-orchestrator Deno Edge Function
    → normalizeEventType()
    → switch(normalizedType):

    case 'manifest.admission.updated':
        → handleAdmissionUpdated(evt)
            1. buildAdmissionContext(payload)     // extract ward/bed/status
            2. updateCache(encounter_journey_cache) // merge admission_context
            3. buildHoistedColumnUpdates()         // ward_id, bed_label, admit_at
            4. encounter_class = 'IMP'             // mark as inpatient
            5. syncBedAndAdmission()               // side-effect:
                a. bed.status → mapped from admissionStatus
                b. bed.patient snapshots → hn, an, name
                c. ipd_admissions → upsert by AN

3.4 Side Effects per Admission Status Change

Admission Status Bed Effect IPD Admission Effect Queue Effect
request / pending dept_type=admission, status=WAITING
reserved (approved) bed → reserved, patient snapshots set ipd_admissions upsert, status=reserved status=ACTIVE
admitted bed → admitted status=in-progress dept_type=ipd, status=ACTIVE
discharged bed → vacant, snapshots cleared status=discharged, discharge_date set status=COMPLETED
cancelled bed → vacant, snapshots cleared status=cancelled status=CANCELLED

Cascading side-effect: Any bed status change fires trg_bed_recompute_ward_summary which auto-recomputes the ipd_ward_beds summary row (total/available/occupied/pending_discharge) for the affected ward. The IPD dashboard sidebar reads these counts.


4. Read Model Architecture

Three Supabase read models are updated atomically by the encounter-orchestrator:

┌──────────────────────────────────────────────────────────────────┐
│                    encounter-orchestrator                         │
│                    (Deno Edge Function)                           │
│                                                                  │
│  handleAdmissionUpdated(evt)                                     │
│    │                                                             │
│    ├──► encounter_journey_cache  ◄── IPD Command Center          │
│    │      .clinical_context          Ward-Round Roster            │
│    │      .admission_context         OPD/ER Worklist              │
│    │      + hoisted: ward_id,                                    │
│    │        bed_label, admit_at                                  │
│    │                                                             │
│    ├──► bed                      ◄── WardViewPanel (DnD grid)    │
│    │      .status                    Admission WardBedVisualizer  │
│    │      .hn/an/patient snapshots   Bed Management Admin         │
│    │                                                             │
│    └──► ipd_admissions           ◄── IPD Nurse Dashboard table   │
│           .ward_id, .bed_number      IPD Discharge Cockpit        │
│           .status, .los_days         Ward Shift Summary            │
│                                                                  │
│  handleOrderCreated(evt)                                         │
│    │                                                             │
│    └──► department_queues        ◄── QueueManagementPanel        │
│           .dept_type=admission/ipd   WorkflowBasedTabs            │
│           .status, .priority         TaskWorklistPanel (header)   │
└──────────────────────────────────────────────────────────────────┘

4.1 encounter_journey_cache

Purpose: Per-encounter manifest with full clinical journey context. Updated by: Every orchestrator handler (admission, orders, tickets, vitals, etc.) Realtime: Supabase postgres_changes subscription by encounter_id

Admission-specific queries:

-- All active inpatients
SELECT * FROM encounter_journey_cache
WHERE encounter_class = 'IMP' AND current_physical_location != 'DISCHARGED';

-- Inpatients in a specific ward
SELECT * FROM encounter_journey_cache
WHERE ward_id = 'ward-female-general' AND encounter_class = 'IMP';

4.2 bed

Purpose: Real-time bed occupancy for ward floor plans. Updated by: syncBedAndAdmission() (orchestrator) + transfer_bed_atomic() RPC (direct) Realtime: postgres_changes on bed table, filtered by ward_id

Key query patterns:

-- All beds in a ward
SELECT * FROM bed WHERE ward_id = 'ward-female-general' AND active = true ORDER BY name;

-- Ward bed summary (via ipd_ward_beds view)
SELECT ward_id, ward_name, total_beds, available_beds, occupied_beds
FROM ipd_ward_beds;

4.3 ipd_admissions

Purpose: Denormalized active-admission rows for IPD nurse dashboard table. Updated by: syncBedAndAdmission() (orchestrator) Realtime: postgres_changes on ipd_admissions, triggers notify_ipd_dashboard_update()

4.4 department_queues

Purpose: Per-department operational queues (admission, pharmacy, lab, billing, etc.) Updated by: upsertQueueRow() / updateQueueStatus() (orchestrator) Realtime: postgres_changes on department_queues, filtered by dept_type + location_id


5. Consuming Views

5.1 IPD Nurse Dashboard (Ward View)

Route: /ipd/dashboard or sandbox ?target=LegacyIpdDashboard

┌─────────────────────────────────────────────────────────┐
│ Ward Selector ▼   [🔄 Refresh]  [⚙ Settings]           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────┐  ┌──────────────────────────────────────┐ │
│  │ Profile  │  │  Tabbed Patient Table                │ │
│  │ Sidebar  │  │  ┌───┬───┬───┬───┬───┬───┐          │ │
│  │          │  │  │All│Pnd│Rsv│Dis│Cls│Fod│          │ │
│  │ BedCount │  │  └───┴───┴───┴───┴───┴───┘          │ │
│  │ Cards    │  │  [Patient rows from ipd_admissions   │ │
│  │          │  │   + legacy API fallback]              │ │
│  │ Shift    │  │                                      │ │
│  │ Summary  │  │  [🛏 แผนภาพ ห้องเตียง] ← opens →    │ │
│  │ Cards    │  │                                      │ │
│  └──────────┘  └──────────────────────────────────────┘ │
│                                                         │
│  ┌──────────────────────────────────────────────────────┤
│  │ WardViewPanel (Drawer/Modal)                         │
│  │  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐  │
│  │  │A-101│ │A-102│ │A-103│ │A-104│ │A-105│ │A-106│  │
│  │  │ 🔵  │ │ 🔵  │ │ ⚪  │ │ 🟡  │ │ ⚪  │ │ 🟢  │  │
│  │  │admit│ │admit│ │empty│ │pend │ │empty│ │resrv│  │
│  │  └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘  │
│  │  🔵=occupied  ⚪=empty  🟢=reserved  🟡=pending-dc  │
│  │  [DnD: drag patient card to empty bed → transfer]   │
│  │  [Presence: colored avatars show other nurses]       │
│  └──────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────┘

Data sources:

Component Primary Source Fallback
Ward selector ipdWardService.listWards() (Supabase ipd_ward_beds) + fetchWards() (MongoDB) mockWards
Patient table ipdWardService.listAdmissions() (Supabase ipd_admissions) listAllAdmissions() (MongoDB) → mockAdmissions
Bed status cards ipdWardService.getWardBedSummary() (Supabase ipd_ward_beds) listBedApi() (MongoDB) → mockBedStatusData
Shift summary ipdWardService.getWardShiftSummary() (Supabase ipd_shift_summaries) mockCardData
Bed grid (WardView) useWardBeds() (Supabase bed table + realtime) WARD_VIEW_MOCK_BEDS
Bed transfers bedStatusApi.transferAtomic() (Supabase transfer_bed_atomic RPC)
Collaborative presence useWardBedPresence() (Supabase Realtime Presence channels) No-op when auth unavailable

Key files:

  • web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/MainTab.tsx
  • web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/WardViewPanel.tsx
  • web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/useWardBeds.ts
  • web/packages/medical-kit/src/ipd-system/hooks/useIpdDashboard.ts

5.2 Admission Request (OPD/ER → IPD)

Route: Patient profile → Admission tab → Request for Admission

┌─────────────────────────────────────────────────────────┐
│ RequestForAdmin (admission-request miniapp)              │
│                                                         │
│  Patient: สมหญิง ใจดี (HN: 6800001)                     │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Admission Type: ○ Elective  ● Emergency  ○ Routine │ │
│  │ Priority:       ● Routine   ○ Urgent    ○ STAT     │ │
│  │ Chief Complaint: [________________________]         │ │
│  │ Ward:           [▼ Select Ward          ]          │ │
│  │ Bed:            [▼ Select Bed           ]          │ │
│  │ Attending Dr:   [▼ Select Doctor        ]          │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ WardBedVisualizer (inline bed picker)               │ │
│  │  [Visual grid of beds per ward]                     │ │
│  │  Colors: 🔵=have_patient ⚪=empty ⚫=closed         │ │
│  │  Click empty bed → auto-fill ward + bed fields      │ │
│  │  Shows other users' cursors (presence)              │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  [💾 Save Request]  → POST /admissions                  │
│                     → emits ADMISSION_CREATED            │
│                     → orchestrator syncs all read models │
└─────────────────────────────────────────────────────────┘

Data sources:

Component Source
Ward list listSubClinicApi() (MongoDB)
Bed list listBedApi() (MongoDB)
Bed visualization WardBedVisualizer (MongoDB bed data)
Bed presence useWardBedPresence() (Supabase Realtime)

Key files:

  • web/packages/miniapps/admission-request/RequestForAdmin.tsx
  • web/packages/miniapps/admission-request/WardBedVisualizer.tsx
  • web/packages/miniapps/admission-request/useWardBedPresence.ts

5.3 IPD Command Center (per-patient)

Route: Click patient row → IPD Command Center steps

Step 1: Admit    → Step1AdmitPanel (AN, ward, bed, doctor, admit date)
Step 2: Assess   → Nursing assessment, vitals entry
Step 3: Care     → Orders (medication, lab, imaging, procedure)
Step 4: Procedure → Surgical/procedural workflow
Step 5: Discharge → Discharge planning, clearances
Step 6: Closeout → Billing reconciliation, coding

Data source: encounter_journey_cache.clinical_context.admission_context via useCommandCenter()

Key file: web/packages/medical-kit/src/ipd-system/command-center/useCommandCenter.ts

5.4 OPD/IPD/ER Queue List Views

Route: /ipd-admission-request, queue panels, header task badges

These views consume department_queues with dept_type filtering:

View dept_type filter Component
Admission request queue admission ConfigDrivenWorkstation
IPD pharmacy queue ipd_pharmacy_verified QueueManagementPanel
IPD active list ipd WorkflowBasedTabs
Header task badge role-mapped TaskWorklistPanel
Generic queue panel any QueueManagementPanel

Realtime: All subscribe to department_queues via Supabase postgres_changes, filtered by location_id or dept_type.


6. End-to-End Flow

6.1 Admission Request → IPD Nurse Dashboard

 ┌──────────┐     ┌──────────────┐     ┌─────────────────┐
 │  OPD/ER  │     │   Backend    │     │   Supabase      │
 │  Doctor  │     │   (NestJS)   │     │   (Postgres)    │
 └────┬─────┘     └──────┬───────┘     └────────┬────────┘
      │                  │                       │
      │ 1. Fill form     │                       │
      │ (ward, bed,      │                       │
      │  chief complaint)│                       │
      │                  │                       │
      │ 2. POST /admissions                      │
      ├─────────────────►│                       │
      │                  │                       │
      │                  │ 3. Create admission   │
      │                  │    in MongoDB          │
      │                  │                       │
      │                  │ 4. INSERT INTO         │
      │                  │    hospital_events     │
      │                  ├──────────────────────►│
      │                  │                       │
      │                  │         5. Webhook trigger
      │                  │         fires encounter-orchestrator
      │                  │                       │
      │                  │    6a. UPDATE encounter_journey_cache
      │                  │        admission_context + hoisted cols
      │                  │                       │
      │                  │    6b. UPDATE bed      │
      │                  │        status=reserved │
      │                  │        patient snapshots
      │                  │                       │
      │                  │    6c. UPSERT ipd_admissions
      │                  │        ward, bed, status
      │                  │                       │
      │                  │         7. postgres_changes
      │                  │         fires on bed table
      │                  │                       │
 ┌────┴─────┐     ┌──────┴───────┐     ┌────────┴────────┐
 │  OPD/ER  │     │   Backend    │     │   Supabase      │
 │  Doctor  │     │   (NestJS)   │     │   (Postgres)    │
 └──────────┘     └──────────────┘     └────────┬────────┘
                                                │
                                        8. Realtime push
                                                │
                                    ┌───────────┼───────────┐
                                    ▼           ▼           ▼
                             ┌──────────┐ ┌──────────┐ ┌──────────┐
                             │useWardBeds│ │useIpdDash│ │useCommand│
                             │(bed grid) │ │(table)   │ │Center    │
                             └─────┬────┘ └─────┬────┘ └─────┬────┘
                                   │            │            │
                                   ▼            ▼            ▼
                             WardViewPanel  Patient Table  Step1Admit
                             (bed turns     (new row       (admission
                              green=reserved appears)       context)

6.2 Bed Transfer (Nurse DnD)

Nurse drags patient from bed A-101 to empty bed A-103
    │
    ▼
bedStatusApi.transferAtomic({
  source_bed_id: 'a000...0001',
  dest_bed_id: 'a000...0003',
  encounter_id: '...',
  actor_user_id: 'nurse-001',
  reason_th: 'ย้ายจาก A-101 ไป A-103'
})
    │
    ▼
transfer_bed_atomic() RPC (single transaction):
  1. Lock both beds FOR UPDATE
  2. A-101: admitted → cleaning, clear snapshots
  3. A-103: vacant → admitted, copy snapshots
  4. Log: transfer_out (A-101), transfer_in (A-103)
    │
    ▼
postgres_changes fires on both bed rows
    │
    ▼
useWardBeds subscription receives updates
    │
    ▼
WardViewPanel re-renders:
  A-101: blue → grey (empty/cleaning)
  A-103: grey → blue (occupied)
  All connected nurses see the change instantly

6.3 Admission Approval → Ward Placement

Admission staff sees request in QueueManagementPanel
    │
    ▼
Staff approves: PUT /admissions/:_id { approvalStatus: 'reserved' }
    │
    ▼
Backend emits ADMISSION_UPDATED
    │
    ▼
Orchestrator: bed → reserved, ipd_admissions status=reserved
    │
    ▼
Nurse assigns bed: PUT /admissions/:_id { admissionStatus: 'admitted', bedName: 'A-103' }
    │
    ▼
Backend emits ADMISSION_UPDATED
    │
    ▼
Orchestrator: bed → admitted, encounter_class=IMP, ipd_admissions status=in-progress
    │
    ▼
Patient appears on IPD nurse dashboard:
  - WardViewPanel: bed A-103 turns blue with patient name
  - Patient table: new row with ward, bed, doctor, LOS
  - IPD Command Center: Step 1 (Admit) shows admission context
  - Queue: dept_type='ipd', status=ACTIVE

7. Dual Data Source Reference

Field MongoDB (REST) Supabase Resolution
Ward ID ward._id (ObjectId) bed.ward_id (text, e.g. ward-female-general) Ward selector merges both; useWardBeds uses Supabase ID
Bed ID bed._id (ObjectId) bed.id (UUID) Bed grid uses Supabase; REST uses MongoDB
Patient ID patient._id (ObjectId) ipd_admissions.patient_id (text, stores ObjectId) Cross-reference by HN when needed
Encounter ID encounter._id (ObjectId) encounter_journey_cache.encounter_id (text) String comparison works
Admission Number admission.an ipd_admissions.admission_number Unique business key

8. Key Files Index

Backend (services/)

File Purpose
services/administration/.../admission/admission.service.ts Admission CRUD (MongoDB)
services/administration/.../admission/admission.controller.mixin.ts REST endpoints + event emission
packages/platform-api-schema/src/administration/admission/entity/AdmissionStatus.ts Canonical status enum

Infrastructure (Supabase)

File Purpose
infrastructure/medbase/functions/_shared/event-contract.ts Event type definitions + normalization
infrastructure/medbase/functions/encounter-orchestrator/index.ts Read model projection (all handlers)
infrastructure/medbase/migrations/20260518g_bed_table.sql Bed table schema + triggers
infrastructure/medbase/migrations/20260518k_ipd_ward_bed_tables.sql ipd_admissions + ipd_ward_beds + seed data
infrastructure/medbase/migrations/043_inpatient_bed_status_log.sql Bed audit trail
infrastructure/medbase/migrations/052_inpatient_orchestrator_caches.sql transfer_bed_atomic RPC
infrastructure/medbase/migrations/001_phase1_hardening.sql encounter_journey_cache schema
infrastructure/medbase/migrations/003_department_queues.sql department_queues schema

Frontend (web/)

File Purpose
web/packages/medical-kit/src/ipd-system/hooks/useIpdDashboard.ts Dashboard data hook (MongoDB + Supabase merge)
web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/MainTab.tsx Dashboard orchestrator
web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/WardViewPanel.tsx DnD bed grid
web/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/useWardBeds.ts Supabase bed query + realtime
web/packages/medical-kit/src/ipd-system/command-center/useCommandCenter.ts IPD command center data
web/packages/miniapps/admission-request/RequestForAdmin.tsx Admission request form
web/packages/miniapps/admission-request/WardBedVisualizer.tsx Bed picker visualization
web/packages/miniapps/admission-request/useWardBedPresence.ts Collaborative bed presence
web/src/services/supabase/ipdWard.supabase.service.ts IPD ward Supabase service
web/src/services/medbase/bedStatus.medbase.ts Bed status API (transfer, log)
web/packages/adt-kit/src/admission/admissionStatusConfig.ts Frontend status → label/color config

9. Invariants

  1. Bed table is write-truth for occupancy — the orchestrator and transfer_bed_atomic RPC are the only writers; frontend never writes directly.
  2. MongoDB is write-truth for clinical admission — all status transitions go through PUT /admissions/:_id; Supabase tables are projections.
  3. encounter_class = ‘IMP’ — always set by orchestrator when admission events fire; never inferred from queue status.
  4. bed_status_log is append-only — UPDATE/DELETE blocked by trigger; provides complete audit trail.
  5. transfer_bed_atomic is atomic — both beds locked FOR UPDATE in a single transaction; either both flip or neither does.
  6. Ward IDs are heterogeneous — MongoDB uses ObjectIds, Supabase uses text slugs (e.g. ward-female-general); the dashboard hook merges both into one selector.
  7. Realtime propagation is eventual — postgres_changes fire within ~100ms of commit; UI should handle brief inconsistency gracefully.
  8. Patient snapshots are denormalizedhn_snapshot, an_snapshot, patient_name_snapshot on the bed row avoid joins but must be cleared on discharge/transfer-out.
Ask Anything