medOS ultra

Specialty Clinic Entry Paths

Entry paths from specialty clinics into the procedure day-queue plus room-level addons.

13 min read diagramsUpdated 2026-05-23docs/architecture/specialty-clinic-procedure-entry-paths.md

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 model
  • docs/architecture/procedure-day-queue-summary-i1-i20.md — 20-iteration summary of the new procedure flow
  • web/src/services/procedure-workflow-config/procedureWorkflowConfig.types.ts — feature/gate types
  • web/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:

  1. procedurePreset — which procedure_workflow_config preset seeds the clinic’s subDept
  2. procedureAddons[] — clinic-specific fields rendered at the room level on top of the nominal row
  3. priority — 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.STAT on 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 adapter
  • sedation_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_id triggers realtime monitor subscription (device-monitor integration, status=‘pending’)
  • pre_weight_kg − post_weight_kg auto-fills “UF achieved” on session sheet
  • KT/V < 1.2 raises 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_number deducts from inventory Vaccine SKU
  • cold_chain_temp ∉ [2, 8] raises hard gate via policy_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:

  1. Declare a new SpecialtyClinicConfig with category: 'diagnostic_device', the right subDeptId, and a new procedureSubtype.
  2. Set procedureAddons: [...DIAGNOSTIC_DEVICE_LOAN_ADDONS, ...deviceSpecific].
  3. Re-run scripts/seed-specialty-clinic-procedure-config.ts — the seeder is subtype-aware via the clinic key.
  4. 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.procedurePreset field added (types.ts)
  • [x] SpecialtyClinicConfig.procedureAddons[] field added (types.ts)
  • [x] SpecialtyClinicProcedureAddon shape 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 OrderRequestItemPriority enum (no change needed)
  • [x] Entry-path doc (this file)

Pending (PR-6 follow-ups)

  • [ ] SpecialtyClinicLanding.tsx — render addon panel from config.procedureAddons on day-queue rows
  • [ ] Admin seeder — script that reads SPECIALTY_CLINIC_CONFIGS, looks up real subDept UUIDs, and inserts procedure_workflow_config rows applying the named preset
  • [ ] Stat toggle UX — add to every ProcedureRequest dialog (currently only some)
  • [ ] STAT SLA timer — policy_gates rule escalating after configurable minutes
  • [ ] Vaccination cold-chain policy_gates hard gate (temp outside 2–8 °C blocks fire:procedure_start)
  • [ ] HD KT/V CDS rule (under-dialysis alert when < 1.2)
  • [ ] PACS upload integration for GI Endoscopy (still status='pending' in specialty_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_progress JSONB at the day-queue level only
  • Direct frontend writes to procedure_day_queue — all writes flow through record_procedure_event RPC

6. Open questions

  1. subDept UUIDs — 10 of 13 clinics still carry placeholder IDs. The seeder can’t run until real UUIDs land.
  2. GI Endoscopy PACS API — vendor + endpoint pending (already flagged in tor_notes)
  3. HD machine vendor / protocol — API vs Serial (already in specialty_clinic_integrations.notes)
  4. Holter / ABP device vendors — affects whether device_reading kind fields can pull data automatically vs manual entry
  5. 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
  6. 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.
Ask Anything