Specialty Clinic Entry Paths
Entry paths from specialty clinics into the procedure day-queue plus room-level addons.
Status: design + partial wiring (2026-05-18). PR adds procedurePreset + procedureAddons to SpecialtyClinicConfig for the 5 procedure-heavy clinics; admin seeding of procedure_workflow_config rows + the addon-renderer panel are PR-6 follow-ups.
See also:
docs/architecture/procedure-day-queue.md— canonical event lifecycle + Supabase-primary modeldocs/architecture/procedure-day-queue-summary-i1-i20.md— 20-iteration summary of the new procedure flowweb/src/services/procedure-workflow-config/procedureWorkflowConfig.types.ts— feature/gate typesweb/packages/miniapps/specialty-clinics/clinic-configs.ts— per-clinic config
1. The canonical entry path (universal, all 13 clinics)
Every specialty clinic enters the new procedure flow via the same nominal ProcedureRequest Mongo entity — no per-clinic forks of the write model. The differences live in:
procedurePreset— whichprocedure_workflow_configpreset seeds the clinic’ssubDeptprocedureAddons[]— clinic-specific fields rendered at the room level on top of the nominal rowpriority— STAT is universal (OrderRequestItemPriority.STAT) regardless of clinic
Write path
┌──────────────────────────────────────────────────────────────────────────────┐
│ 1. Doctor in the OPD encounter opens "ขอใบส่งทำหัตถการ" → fills │
│ ProcedureRequest dialog. │
│ │
│ Required fields (canonical ProcedureRequest entity): │
│ • patientRef, encounterRef, requester (doctor user) │
│ • code ← clinic-specific (e.g. 'EGD', 'HD-SESSION-01') │
│ • category ← ProcedureCategory ref (one per clinic) │
│ • items[] ← ProcedureRequestItem rows (procedure list) │
│ • priority ← ROUTINE | URGENT | STAT | ASAP (default ROUTINE) │
│ • inOperatingRoom ← true for GI/OR-style, false for room-level │
│ • performingDoctorRef ← proceduralist (optional, may differ from │
│ requester) │
│ • appointmentRef (optional — when scheduled) │
│ • Stat toggle in the dialog flips priority → STAT and surfaces a red │
│ chip on the day-queue row. │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 2. POST /api/v2/procedure-requests │
│ → medication service `procedureRequest.create` Moleculer action │
│ → Mongo INSERT into `procedure_request` │
│ → emits hospital_event 'PROCEDURE_REQUESTED' │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 3. Encounter-orchestrator handler `handleProcedureRequested` │
│ → reads ProcedureRequest doc + resolves `department_id` from the │
│ patient's current `subDept` (or appointment.departmentRef) │
│ → looks up `procedure_workflow_config` for that `department_id` │
│ → calls Supabase RPC `record_procedure_event` with │
│ eventType='requested' which UPSERTs the `procedure_day_queue` row: │
│ procedure_request_id, department_id, day, status='pending', │
│ patient_hn, patient_name, procedure_code, procedure_name, │
│ priority ('routine' | 'urgent' | 'stat'), │
│ feature_progress = {} ← addons start empty │
│ blocked_actions = [...] ← derived from procedure_workflow_config │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 4. Specialty clinic worklist (SpecialtyClinicLanding) reads the │
│ `procedure_day_queue` slice WHERE department_id = clinic.subDeptId │
│ AND day = today() via the existing `useProcedureDayQueue` hook. │
│ │
│ Each row renders: │
│ • Nominal block (HN, name, priority chip, current_phase) │
│ • OR-style features (Sign-In / Time-Out / etc.) per │
│ procedure_workflow_config — driven by `procedurePreset` │
│ • Clinic addon panel — driven by `clinic.procedureAddons` │
│ Each addon field reads/writes `feature_progress[<addon.key>]` │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 5. Per-event writes go through the same RPC: │
│ record_procedure_event(eventType, payload, txId) │
│ Addon writes use eventType='custom_feature_completed' with │
│ payload.featureId='clinic_addon' + the addon key/value. │
│ │
│ The frontend hook re-receives the row via Supabase realtime + re-renders │
│ — no polling, no manual refetch. │
└──────────────────────────────────────────────────────────────────────────────┘
Read path (frontend hooks)
| Hook | Returns | Used by |
|---|---|---|
useProcedureDayQueue({ deptId, day }) |
All rows for the clinic today | Specialty clinic worklist preview |
useProcedureDayQueueRow({ id }) |
Single row with realtime updates | Row-detail dialog (OR-style features + addon panel) |
useProcedureWorkflowConfig({ deptId }) |
Effective feature enabled/required map | Decides which feature tabs render |
useRecordProcedureEvent() |
Mutation that calls the RPC | Every button that fires an event |
2. STAT priority — universal across all 13 clinics
OrderRequestItemPriority enum (packages/platform-api-schema/src/medication/orderRequestItem/entity/OrderRequestItemPriority.ts) already declares:
ROUTINE | STAT | URGENT | ASAP | MEDICATIONRECONCILE | HOME_MED | HALF_HOUR | ONE_HOUR
ProcedureRequest.priority defaults to ROUTINE. The day-queue row projects this to a 3-value chip: 'routine' | 'urgent' | 'stat'.
Required UX (every clinic’s ProcedureRequest dialog):
- Stat toggle in the priority radio group — always visible
- When checked, sets
priority = OrderRequestItemPriority.STATon POST - Stat rows sort to the top of the day-queue board with a red banner
- (Future) Stat triggers SLA timers via
policy_gates— escalates after configurable minutes
3. Per-clinic procedure presets + addon fields
The 5 procedure-heavy clinics now declare procedurePreset + procedureAddons[] in clinic-configs.ts. The 8 assessment/follow-up clinics omit them (default = no procedure flow).
3.1 GI Endoscopy — procedurePreset: 'full-or' 🔬
Preset features (auto-seeded into procedure_workflow_config): sign_in, time_out, intra_op, sign_out, waiting_room, recovery, par_score, or_history, surgical_team, surgical_type
Room-level addons (9 fields):
| Phase | Key | Field | Type | Notes |
|---|---|---|---|---|
| pre | scope_id |
หมายเลขเครื่องส่องกล้อง / Scope ID | text | required |
| pre | scope_type |
ชนิดเครื่อง / Scope type | select | EGD / Colonoscope / ERCP / EUS — required |
| pre | sedation_level |
ระดับ Sedation | select | none / conscious / deep / GA — required |
| pre | bowel_prep |
คุณภาพการเตรียมลำไส้ | select | excellent / good / fair / poor |
| intra | images_captured |
จำนวนภาพที่ถ่าย | number | Captured to PACS |
| intra | video_clip |
คลิปวิดีโอ | video_capture | PACS upload |
| intra | biopsy_count |
จำนวนชิ้นเนื้อ | number | Spawns ใบสั่งตรวจชิ้นเนื้อ when ≥ 1 |
| post | findings |
ผลที่ตรวจพบ | text | required |
| post | recovery_note |
บันทึกการพักฟื้น | text |
Cross-module side-effects:
biopsy_count ≥ 1→ auto-create LabRequest (pathology pipeline)images_captured > 0→ push to PACS via integration adaptersedation_level ∈ {deep, general}→ unlock PAR score requirement
3.2 Hemodialysis — procedurePreset: 'minor-ops' 🩸
Preset features: sign_in, time_out, intra_op, sign_out, surgical_team, surgical_type
Room-level addons (15 fields) — split across pre/intra/post for the HD session sheet:
| Phase | Key | Field | Type | Unit |
|---|---|---|---|---|
| pre | machine_id |
หมายเลขเครื่อง HD | text | — |
| pre | dialyzer_type |
ชนิด Dialyzer | text | — |
| pre | access_type |
ทาง vascular access | select (AVF/AVG/CVC) | — |
| pre | pre_weight_kg |
น้ำหนักก่อน HD | number | kg |
| pre | pre_bp |
BP ก่อน HD | text | mmHg |
| pre | uf_target_ml |
UF target | number | mL |
| intra | qb_ml_min |
Blood flow Qb | number | mL/min |
| intra | qd_ml_min |
Dialysate flow Qd | number | mL/min |
| intra | heparin_dose |
ขนาด Heparin | text | — |
| intra | machine_alarms |
สัญญาณเตือนจากเครื่อง | device_reading | (realtime feed) |
| post | post_weight_kg |
น้ำหนักหลัง HD | number | kg |
| post | post_bp |
BP หลัง HD | text | mmHg |
| post | kt_v |
KT/V | number | — |
| post | urr |
URR | number | % |
| post | access_complication |
ภาวะแทรกซ้อนทาง access | text | — |
Cross-module side-effects:
machine_idtriggers realtime monitor subscription (device-monitor integration, status=‘pending’)pre_weight_kg − post_weight_kgauto-fills “UF achieved” on session sheetKT/V < 1.2raises CDS alert (under-dialysis)
3.3 Holter Monitoring — procedurePreset: 'custom' 💓
Preset features: none from catalog (fully custom). Day-queue still tracks requested → accepted → operating (fitting) → completed (return) lifecycle.
Room-level addons (10 fields) — split between fitting visit (pre) and removal visit (post):
| Phase | Key | Field | Type |
|---|---|---|---|
| pre | device_serial |
หมายเลขอุปกรณ์ Holter | text req |
| pre | lead_config |
จำนวน lead (3 / 5 / 12) | select req |
| pre | duration_hours |
ระยะเวลาบันทึก (24/48/72) | select req |
| pre | skin_prep_ok |
เตรียมผิวเรียบร้อย | boolean |
| pre | diary_issued |
มอบสมุดบันทึกแล้ว | boolean req |
| pre | return_time |
นัดถอดอุปกรณ์ | datetime req |
| post | device_returned |
รับอุปกรณ์คืน | boolean req |
| post | recording_quality |
คุณภาพการบันทึก (good/fair/poor) | select |
| post | cardiologist_id |
อายุรแพทย์หัวใจที่อ่าน | text |
| post | arrhythmia_flag |
พบ arrhythmia | boolean |
Note: Holter is a two-visit flow. The day-queue row stays open between visits with current_phase='operating' until device_returned=true.
3.4 Ambulatory Blood Pressure — procedurePreset: 'custom' 🩺
Preset features: none from catalog (fully custom).
Room-level addons (11 fields) — same two-visit shape as Holter:
| Phase | Key | Field | Type |
|---|---|---|---|
| pre | device_serial |
หมายเลขเครื่อง ABP | text req |
| pre | cuff_size |
ขนาด cuff | select req |
| pre | arm_used |
แขนที่ใช้ (left/right) | select req |
| pre | baseline_bp |
BP เริ่มต้น (mmHg) | text req |
| pre | interval_day_min |
ช่วงเวลาวัดกลางวัน (นาที) | number req |
| pre | interval_night_min |
ช่วงเวลาวัดกลางคืน (นาที) | number req |
| pre | return_time |
นัดถอดอุปกรณ์ | datetime req |
| post | device_returned |
รับอุปกรณ์คืน | boolean req |
| post | valid_readings_pct |
% การวัดที่ใช้ได้ | number (%) |
| post | mean_24h_bp |
BP เฉลี่ย 24 ชม. (mmHg) | text |
| post | dipper_pattern |
dipper / non-dipper / reverse / extreme | select |
3.5 Vaccination — procedurePreset: 'safety-only' 💉
Preset features: sign_in (= consent + identity verify), time_out (= lot/expiry/cold-chain verify), sign_out (= post-dose observation + AEFI watch)
Room-level addons (13 fields):
| Phase | Key | Field | Type | Notes |
|---|---|---|---|---|
| pre | vaccine_code |
รหัสวัคซีน | text | required |
| pre | lot_number |
Lot number | text | required |
| pre | expiry_date |
วันหมดอายุ | datetime | required (blocks procedure_start if expired) |
| pre | cold_chain_temp |
อุณหภูมิ cold chain (°C) | number | required (CDS alert outside 2–8 °C) |
| pre | dose_number |
เข็มที่ | number | required |
| pre | consent_signed |
ลงนามยินยอม | signature | required |
| intra | route |
วิถีทาง (IM/SC/ID/oral/intranasal) | select | required |
| intra | site |
ตำแหน่งฉีด (L/R deltoid / thigh) | select | required |
| intra | administered_by |
ผู้ฉีด | text | required |
| post | observation_min |
สังเกตอาการ (นาที) | number | required — gates discharge |
| post | aefi_observed |
พบ AEFI | boolean | |
| post | aefi_detail |
รายละเอียด AEFI | text | |
| post | next_dose_due |
นัดเข็มถัดไป | datetime | Triggers EPI follow-up reminder |
Cross-module side-effects:
aefi_observed=true→ auto-create AEFI report (eform-vacc-aefi)lot_numberdeducts from inventoryVaccineSKUcold_chain_temp ∉ [2, 8]raises hard gate viapolicy_gates
3a. DDD aggregate: Diagnostic Device Loan
Holter and ABP are not standalone clinics — they’re two procedure subtypes of the same domain aggregate.
Aggregate root: DeviceLoanProcedure
Bounded context: Diagnostic Device Loan
Lifecycle invariant: fit (pre) → home monitoring window → return (post) → report
Subtype discriminator: procedureSubtype: 'holter' | 'abp' | 'ett' | 'cgm' | 'fetal_monitor' | 'sleep_study'
Shared physical room: cadba3b9-40e2-4f9e-8f2d-da3ab1704b28 (Cardiac Investigation Unit) for the cardiac subtypes; other devices may live in different rooms but share the same aggregate.
Shared addons (DIAGNOSTIC_DEVICE_LOAN_ADDONS in clinic-configs.ts):
| Phase | Key | Field | Why it’s invariant |
|---|---|---|---|
| pre | device_serial |
Device serial # | Every loan needs equipment traceability |
| pre | fit_time |
Fit time | Starts the monitoring window |
| pre | return_time |
Return appointment | Closes the two-visit flow |
| post | device_returned |
Boolean | Gates the lifecycle exit (incomplete returns stay open) |
| post | recording_quality |
good / fair / poor | Drives downstream report eligibility |
Device-specific overlays (per subtype):
| Subtype | Extra addons | Notes |
|---|---|---|
holter |
lead_config (3/5/12), duration_hours (24/48/72), skin_prep_ok, diary_issued, cardiologist_id, arrhythmia_flag | Read by cardiologist |
abp |
cuff_size, arm_used, baseline_bp, daytime_interval, nighttime_interval, valid_readings_pct, mean_24h_bp, dipper_pattern | Dipper analysis is the differentiator |
ett (future) |
protocol (Bruce/Modified Bruce), max_HR_achieved, target_HR, METs, ST_changes | Stress-test fields |
cgm (future) |
sensor_type, mard_pct, time_in_range_pct | Continuous glucose monitor |
fetal_monitor (future) |
gestational_age, baseline_FHR, reactivity, contractions_pattern | Obstetric NST |
sleep_study (future) |
hookup_channels, ahi, oxygen_desaturation_index | Polysomnography |
Adding a new device:
- Declare a new
SpecialtyClinicConfigwithcategory: 'diagnostic_device', the rightsubDeptId, and a newprocedureSubtype. - Set
procedureAddons: [...DIAGNOSTIC_DEVICE_LOAN_ADDONS, ...deviceSpecific]. - Re-run
scripts/seed-specialty-clinic-procedure-config.ts— the seeder is subtype-aware via the clinic key. - No backend changes; the shared lifecycle is already wired in
procedure_day_queue.
Day-queue grouping: the room workspace at the Cardiac Investigation Unit reads procedure_day_queue WHERE department_id = '...cadba3b9...' and groups rows by payload.procedure_subtype. One queue, two tabs.
4. The 8 assessment/follow-up clinics
These omit procedurePreset (effective = basic-opd, no procedure flow):
| Clinic | Why no procedure flow |
|---|---|
| Psychiatry | Mental status exam — no procedure record needed |
| Family Medicine | Holistic care + referrals — assessment shape |
| Palliative Care (Clinic) | Care planning + ACP — no procedure |
| Elderly | CGA, ADL, falls assessment — no procedure |
| Respiratory & Thoracic | PFT/6MWT are diagnostics, owned by lab/imaging |
| Antenatal Care | Visits with handoff to L&D for delivery |
| Diabetes Registry | Annual review + screening |
| Home Isolation | Remote monitoring + escalation |
| Home Visit | Field assessment |
If a future requirement adds a procedure to any of these (e.g. Antenatal amniocentesis), the path is: add procedurePreset + procedureAddons to that clinic’s config; everything else auto-wires.
5. Implementation status (checklist)
Done in this PR
- [x]
SpecialtyClinicConfig.procedurePresetfield added (types.ts) - [x]
SpecialtyClinicConfig.procedureAddons[]field added (types.ts) - [x]
SpecialtyClinicProcedureAddonshape exported (types.ts) - [x] GI Endoscopy preset + 9 addons wired (clinic-configs.ts)
- [x] Hemodialysis preset + 15 addons wired (clinic-configs.ts)
- [x] Holter preset + 10 addons wired (clinic-configs.ts)
- [x] ABP preset + 11 addons wired (clinic-configs.ts)
- [x] Vaccination preset + 13 addons wired (clinic-configs.ts)
- [x] STAT confirmed in
OrderRequestItemPriorityenum (no change needed) - [x] Entry-path doc (this file)
Pending (PR-6 follow-ups)
- [ ]
SpecialtyClinicLanding.tsx— render addon panel fromconfig.procedureAddonson day-queue rows - [ ] Admin seeder — script that reads
SPECIALTY_CLINIC_CONFIGS, looks up real subDept UUIDs, and insertsprocedure_workflow_configrows applying the named preset - [ ] Stat toggle UX — add to every ProcedureRequest dialog (currently only some)
- [ ] STAT SLA timer —
policy_gatesrule escalating after configurable minutes - [ ] Vaccination cold-chain
policy_gateshard gate (temp outside 2–8 °C blocksfire:procedure_start) - [ ] HD KT/V CDS rule (under-dialysis alert when < 1.2)
- [ ] PACS upload integration for GI Endoscopy (still
status='pending'inspecialty_clinic_integrations) - [ ] Realtime HD machine-monitor integration (vendor + protocol still TBD)
- [ ] Holter / ABP “Pending removal” tab on day-queue board (two-visit flow)
Permanently out of scope
- Per-clinic forks of the ProcedureRequest Mongo entity — addons go in
feature_progressJSONB at the day-queue level only - Direct frontend writes to
procedure_day_queue— all writes flow throughrecord_procedure_eventRPC
6. Open questions
- subDept UUIDs — 10 of 13 clinics still carry placeholder IDs. The seeder can’t run until real UUIDs land.
- GI Endoscopy PACS API — vendor + endpoint pending (already flagged in
tor_notes) - HD machine vendor / protocol — API vs Serial (already in
specialty_clinic_integrations.notes) - Holter / ABP device vendors — affects whether
device_readingkind fields can pull data automatically vs manual entry - STAT SLA values — what’s the threshold per clinic? GI Endoscopy stat might mean 30 min; vaccination stat (rabies post-exposure) might mean 15 min
- Two-visit flow on the day-queue board — should Holter / ABP rows appear on TWO days (fitting day + removal day) or stay as ONE persistent row spanning days? Design recommends one persistent row with
current_phase='operating'between visits.