Admission-to-Ward Unified Contract
Entity contracts for admission/bed/IPD/journey cache, status state machines, event payloads, orchestrator side-effects, 8 invariants.
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.tsxweb/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/WardViewPanel.tsxweb/packages/medical-kit/src/ipd-system/ipd-management/components/dashboard/useWardBeds.tsweb/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.tsxweb/packages/miniapps/admission-request/WardBedVisualizer.tsxweb/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
- Bed table is write-truth for occupancy — the orchestrator and
transfer_bed_atomicRPC are the only writers; frontend never writes directly. - MongoDB is write-truth for clinical admission — all status transitions go through
PUT /admissions/:_id; Supabase tables are projections. - encounter_class = ‘IMP’ — always set by orchestrator when admission events fire; never inferred from queue status.
- bed_status_log is append-only — UPDATE/DELETE blocked by trigger; provides complete audit trail.
- transfer_bed_atomic is atomic — both beds locked FOR UPDATE in a single transaction; either both flip or neither does.
- Ward IDs are heterogeneous — MongoDB uses ObjectIds, Supabase uses text slugs (e.g.
ward-female-general); the dashboard hook merges both into one selector. - Realtime propagation is eventual — postgres_changes fire within ~100ms of commit; UI should handle brief inconsistency gracefully.
- Patient snapshots are denormalized —
hn_snapshot,an_snapshot,patient_name_snapshoton the bed row avoid joins but must be cleared on discharge/transfer-out.