IPD Admission & Discharge Pipeline
Workflow architecture for the inpatient admission and discharge pipeline.
Status: ACTIVE Date: 2026-04-08 Scope: Two linked manifest workflows for inpatient encounters: admission request queue + 7-phase discharge pipeline (doctor → nurse → ปิดบัญชี → coder → validation → ส่งเบิก → ออกใบเรียกเก็บ).
1. Why Two Workflows, Not One
IPD is fundamentally different from OPD/ER:
| Aspect | OPD/ER | IPD |
|---|---|---|
| Duration | Minutes/hours | Days/weeks |
| Entities | 1 (Encounter) | 3 (Encounter + Admission + Bed) |
| Discharge | 1 action | 7-phase pipeline crossing 5 departments |
| View pattern | Tab-based worklist (queue) | Ward bed board (spatial) + discharge pipeline (queue) |
What fits manifest: The admission request queue and the discharge pipeline — both are sequential, multi-step, tab-based workflows.
What does NOT fit manifest: The ward bed board (spatial UI, not a queue) and nurse daily tasks (task dashboard, not sequential). These stay in adt-kit.
2. The Three-Entity Model
┌─────────────────────────────────────────────────────────────────────┐
│ ENTITY 1: Encounter (encounterClass: IMP) │
│ PLANNED → ARRIVED → IN_PROGRESS → FINISHED → DISCHARGED │
│ │
│ Key fields: │
│ status: EncounterStatus │
│ coderStatus: CoderStatus (post-discharge coding) │
│ participants[]: EncounterParticipant[] (ADM/CON/ATND) │
│ dischargedAt, dischargedBy │
├─────────────────────────────────────────────────────────────────────┤
│ ENTITY 2: Admission (linked via encounterIpd → Encounter._id) │
│ REQUEST → PENDING → ADMITTED → DISCHARGED │
│ │
│ Key fields: │
│ admissionStatus: AdmissionStatus │
│ approvalStatus: ApprovalStatus (pending/reserved/receiveOrders) │
│ transferStatus: TransferStatus (pending/success) │
│ encounterID: Ref<Encounter> (source OPD/ER encounter) │
│ encounterIpd: Ref<Encounter> (created IPD encounter) │
│ bedNumber: Ref<Bed> │
│ ward: Ref<SubClinic> │
│ dischargeDisposition, dischargePatientStatus │
├─────────────────────────────────────────────────────────────────────┤
│ ENTITY 3: Bed (linked via admission.bedNumber ↔ bed.admissionID) │
│ SELECT_BED → REGIS_BED → HAVE_PATIENT → EMPTY_BED/WAIT_CLEAN │
│ │
│ Key fields: │
│ status: BedStatus │
│ admissionID: Ref<Admission> (back-reference) │
│ ward: Ref<SubClinic> │
│ room: Ref<Location> │
└─────────────────────────────────────────────────────────────────────┘
Entity Linkages
Admission.encounterIpd → Encounter._id (forward)
Admission.bedNumber → Bed._id (forward)
Bed.admissionID → Admission._id (back-reference)
Admission.encounterID → Source OPD/ER Encounter._id (origin)
3. Workflow 1: Admission Request Pipeline
State Machine
ipd-pending-bed → ipd-bed-reserved → ipd-ready-to-admit → ipd-admitted
↓ ↓
ipd-cancelled ipd-cancelled
Mapping to Legacy approvalStatus
| Workflow Node | Admission.approvalStatus | Legacy Tab |
|---|---|---|
ipd-pending-bed |
pending |
Tab 0: รอจองเตียง |
ipd-bed-reserved |
reserved |
Tab 1: จองเตียงสำเร็จ |
ipd-ready-to-admit |
receiveOrders / receivePendingAdmit |
Tab 2: รอแอดมิท |
ipd-admitted |
admitted (admissionStatus) |
Admitted |
ipd-cancelled |
cancel / cancelRequest (admissionStatus) |
Tab 3: ยกเลิก |
Actions per Node
| Node | Action | API Endpoint |
|---|---|---|
ipd-pending-bed |
Reserve Bed | PUT /v2/administration/admissions/:id |
ipd-pending-bed |
Cancel | PUT /v2/administration/admissions/:id (cancelRequest) |
ipd-bed-reserved |
Assign Bed | PUT /v2/administration/admissions/:id/acknowledgeadmission |
ipd-ready-to-admit |
Admit Patient | PUT /v2/administration/admissions/:id (admitted) |
What Happens at Admission
When admissionStatus changes to admitted (in admission.service.ts:updateAdmission):
- New IPD Encounter created via
encounterService.createEncounter()withencounterClass: IMP - Admission linked to new encounter via
encounterIpd - Bed status updated to
PENDING - Vital signs copied from source OPD/ER encounter to new IPD encounter
- AN (Admission Number) generated via
generateAssignmentNumber()
Existing Frontend (LIVE)
| Route | Component | Status |
|---|---|---|
/admission/list-patient-request-admission |
PatientListRequestAdmission.tsx |
LIVE (REST + Socket.IO) |
/admission/dashboard |
Admission Dashboard | LIVE |
/admission/wardview |
Ward Bed Board | LIVE |
/in-patient/list-patient-request-admission |
Nurse IPD variant | LIVE |
All 4 tabs are already working on GET /v2/administration/admissions/listAdmissionPatient with Socket.IO realtime updates.
4. Workflow 2: Discharge Pipeline
State Machine
┌── Discharge Phase ──┐ ┌── Coder Phase ──────────────────────┐ ┌── Billing ──────────┐
ipd-in-treatment → ipd-doctor-discharged → ipd-nurse-discharged → ipd-account-closed → ipd-coder-in-progress
↓ ↓
ipd-coder-await-reconsider ipd-coder-pending-validation
↓ ↓ ↓
(back to coder) ipd-coder-validated ipd-coder-validation-failed
↓ ↓
ipd-claim-submitted (back to coder)
↓
ipd-invoiced
The 7 Phases Explained
Phase 1: Doctor Discharge
- API:
PUT /encounters/:id/discharge - What happens:
encounter.status = FINISHED,coderStatus = AWAIT_REGISTER, each participant’scompletedTreatmentmarked - For IMP with multiple participants: Same multi-doctor gate as EMER — all ADM+CON must complete
Phase 2: Nurse Discharge
- API:
PUT /encounters/:id/dischargeEncounterNurse - What happens:
encounter.status = DISCHARGED, records note, cause, zone - Nurse adds: Discharge note, discharge cause, zone assignment
Phase 3: Account Close (ปิดบัญชี)
- API:
PUT /admissions/:id/dischargeAdmission - What happens:
admission.admissionStatus = DISCHARGEDbed.status = EMPTY_BEDorWAIT_CLEANbed.admissionIDclearedDischargeDispositionrecorded (HOME/HOSPICE/SNF/EXPIRED/etc.)DischargePatientStatusrecorded (FullRecovery/Improved/Deceased/etc.)
- Known issue: No transaction wrapping 3 entity updates (race condition risk)
Phase 4: Medical Coder
- API:
PUT /encounters/:id/updateCoderStatus - What happens: Coder assigns ICD-10 diagnosis codes + ICD-9 procedure codes + DRG
- CoderStatus flow:
AWAIT_REGISTER → IN_PROGRESS → PENDING_VALIDATION - Frontend:
/medical-coder?mainTab=ipd&encounterId=:id
Phase 5: Validation
- What happens: 13 validation rules (C1-C13) run against codes
- Pass:
coderStatus = REGISTED(coded & validated) - Fail:
coderStatus = VALIDATION_FAILED→ back to coder - Reconsider:
coderStatus = AWAIT_RECONSIDER→ back to coder
Phase 6: Claim Submission (ส่งเบิก)
- API:
POST /v2/financial/claims/:encounterId/submit - What happens: Generate claim from coded encounter, submit to payer
Phase 7: Invoice (ออกใบเรียกเก็บ)
- API:
POST /v2/financial/invoices/:encounterId/generate - What happens: Revenue recorded, invoice generated
Selector Logic (How Rows Route to Nodes)
The discharge pipeline selectors use compound conditions because the same encounter.status value (e.g., finished) can mean different things depending on coderStatus and admission.admissionStatus:
| Node | Primary Field | Secondary Field |
|---|---|---|
ipd-in-treatment |
encounter.status == in-progress |
encounter.encounterClass == IMP |
ipd-doctor-discharged |
encounter.status == finished |
coderStatus == Await Register |
ipd-nurse-discharged |
encounter.status == discharged |
admission.admissionStatus != discharged |
ipd-account-closed |
admission.admissionStatus == discharged |
coderStatus in [Await Register, Acknowledged] |
ipd-coder-* |
coderStatus == <value> |
— |
ipd-claim-submitted |
claimStatus == submitted |
— |
ipd-invoiced |
claimStatus == invoiced |
— |
5. Manifest JSON Files
| File | Location | Purpose |
|---|---|---|
admission-request-workflow.json |
medbase/functions/ |
Backend authoritative (admission request) |
discharge-pipeline-workflow.json |
medbase/functions/ |
Backend authoritative (discharge pipeline) |
| Same files | packages/medical-kit/src/medical-worklist/defaults/ |
Frontend fallback copies |
6. Status Enums Reference
AdmissionStatus
REQUEST, PENDING, ADMITTED, DISCHARGED, CANCELLED, CANCELREQUEST, APPOINTMENT, RECEIVEPENDINGADMIT
ApprovalStatus
PENDING, RESERVED, RECEIVEORDERS, CANCEL, RECEIVEPENDINGADMIT
BedStatus
EMPTY_BED, HAVE_PATIENT, HAVE_RESERVATION, REGIS_BED, PENDING, WAIT_CLEAN, CLOSED, SELECT_BED
CoderStatus
AWAIT_REGISTER, AWAIT_RECONSIDER, ACKNOWLEDGED, REGISTED, IN_PROGRESS, PENDING_VALIDATION, VALIDATION_FAILED
DischargeDisposition
HOME, ALTERNATIVE_HOME, OTHER_HEALTHCARE_FACILITY, HOSPICE, LONG_TERM_CARE,
LEFT_AGAINST_ADVICE, EXPIRED, PSYCHIATRIC_HOSPITAL, REHABILITATION, SKILLED_NURSING_FACILITY, OTHER
DischargePatientStatus
FullRecovery, Improved, FollowUpRequired, ConditionStable, ConditionWorsened, Deceased
7. What Stays Outside Manifest (adt-kit)
| Feature | Why Not Manifest | Where It Lives |
|---|---|---|
| Ward bed board | Spatial UI, not a queue | adt-kit/admission/wardview/ |
| Nurse IPD daily tasks | Task dashboard, not sequential | adt-kit/in-patient/ + nurse-ipd-dashboard/ |
| Vital signs monitoring | Real-time charting | adt-kit/in-patient/vital-sign-view/ |
| Medication cart/rounds | Task-based, per-bed | adt-kit + pharmacy-ipd |
| Progress notes | Per-encounter SOAP | ipd-progress-note/ (Supabase direct) |
8. Orchestrator Projection Requirements
For the manifest selectors to work, the encounter-orchestrator must project these fields into encounter_journey_cache:
For Admission Request Workflow
"clinical_context.admission_context": {
"admissionStatus": "...",
"approvalStatus": "...",
"transferStatus": "...",
"wardName": "...",
"bedName": "...",
"roomName": "...",
"bedStatus": "...",
"visitType": "...",
"priority": "...",
"createdAt": "..."
}
For Discharge Pipeline Workflow
"clinical_context.encounter_context": {
"coderStatus": "...",
"coderName": "...",
"dischargedAt": "...",
"nurseDischargedAt": "...",
"drgCode": "...",
"relativeWeight": "...",
"icd10Count": "...",
"icd9Count": "...",
"claimStatus": "..."
},
"clinical_context.admission_context": {
"admissionStatus": "...",
"dischargeDisposition": "...",
"dischargePatientStatus": "...",
"dischargeDate": "..."
}
If the orchestrator doesn’t yet project admission_context or coderStatus, these must be added to the buildClinicalContext() function.
9. Payload Builders Required
| Builder ID | Workflow | Payload |
|---|---|---|
cancelAdmissionRequest |
admission-request | { admissionStatus: 'cancelRequest', reason: <input> } |
admitPatient |
admission-request | { admissionStatus: 'admitted', bedNumber: <selected>, ward: <selected> } |
doctorDischargeIPD |
discharge-pipeline | { lastStatus: 'discharged' } |
startCoding |
discharge-pipeline | { coderStatus: 'In Progress' } |
submitClaim |
discharge-pipeline | { encounterId: <id> } |
10. Rollout Checklist
- [x] Add IPD admission nodes to
MASTER_WORKFLOW_NODES(5 nodes) - [x] Add IPD discharge nodes to
MASTER_WORKFLOW_NODES(11 nodes) - [x] Add
admission-request-defaulttoWORKFLOW_REGISTRY - [x] Add
discharge-pipeline-defaulttoWORKFLOW_REGISTRY - [x] Add
admissionRequest+dischargeRequesttoDEFAULT_INITIAL_STATES - [x] Create
admission-request-workflow.json(backend + frontend) - [x] Create
discharge-pipeline-workflow.json(backend + frontend) - [ ] Ensure encounter-orchestrator projects
admission_contextintoencounter_journey_cache - [ ] Ensure encounter-orchestrator projects
coderStatus,drgCode,claimStatusinto cache - [ ] Register IPD payload builders in
workflowActionHandler.ts - [ ] Insert workflow JSONs into Supabase
workflow_templatestable - [ ] Wire admission-request manifest into admission centre UI (replace REST + Socket.IO with manifest)
- [ ] Wire discharge-pipeline manifest into nurse IPD discharge view
- [ ] Wire coder pipeline tabs into medical-coder page
- [ ] Test full flow: admission request → admit → treat → doctor discharge → nurse discharge → ปิดบัญชี → code → validate → claim → invoice