medOS ultra

IPD Billing Architecture

Six-screen IPD billing: coverage authorization, charges edit, discharge planning, close-account, cashier, invoice & reconciliation.

20 min read diagramsUpdated 2026-05-27docs/architecture/ipd-billing-architecture.md

Status: design master plan (2026-05-27). Reverse-engineered from Vajira e-PHIS, corrected by direct AR-department conversations, and grounded against the actual medOS backend (entities, endpoints, and config engines verified in the schema package + services/financial).

Scope: the end-to-end inpatient (and OPD→IPD cascade) billing loop, expressed as six screens wired to real backend shapes. This doc is the source-of-truth for how the screens connect, who uses each one, what data flows between them, and — critically — what exists today vs. what must be built.

Read alongside:

  • insurance-aware-workflows.md — the orthogonal Coverage axis (insurance_context, SchemeSlot registry). Designed, 0% built.
  • menu-price-ledger.md — per-line pricing + coverage binding substrate (billable_ledger, resolve_menu_price()). Designed, migrations pending.
  • policy-gates.md / policy-gates-coverage.md — the shipped JSON-predicate action-gating engine.
  • rcm-pipeline-unified-contract.md — the 8-stage RCM lifecycle that page 06 plugs into.
  • nhso-revenue-engine-master-plan.md — the 16-file NHSO generator + appeal/write-off workflow.
  • billing-queue-auto-sync.mdtrg_sync_billing_queue (how billing-pending rows reach department_queues).
  • encounter-orchestrator-triggers.md / admission-to-ward-unified-contract.md — the read-model layer.

0. The one-paragraph version

A patient is admitted (AN issued), charges accrue per-line against coverage rows, the ward ticks “ready for billing,” and then AR performs exactly one operational action: it assigns and verifies the payer’s reference number (e.g. CSMBS GFRFT3, an NHSO Claim Code, an SSS claim number) onto the coverage record. That single act is the billable-ready event — it flips the coverage to completed, which green-lights the cashier to collect the patient residual and queues the claim for the payer. The Close-Account screen is a read-only projection of all this, not a state transition. The operational gate is the Coverage Authorization screen, not account closure.


1. The five operational truths (corrected mental model)

  1. The gate is the Coverage Authorization screen, not the Close-Account screen. AR’s real work happens on page 01. Page 04 is observational.
  2. AR’s ONE action is reference-number assignment. Get the LOG/authorization number onto the coverage record, verify it, set the close timestamp. Everything downstream follows.
  3. Each ChargeItem knows its Coverage. The ลำดับสิทธิ column is a per-line binding to a coverage row. No coverage = cash (ลำดับสิทธิ 0). Mixed-coverage encounters work via per-line routing.
  4. Top-ups (เงินมัดจำ) accumulate in parallel. During long admissions patients pay deposits at the cashier. Account.balance = Σ charges − Σ top-ups − Σ claimable.
  5. OPD→IPD is a re-binding event, not a copy event. When OPD becomes IPD same-day, a second coverage row is created and applicable charges are re-bound to it. Earlier OPD-only charges stay where they are.

2. The six screens at a glance

# Screen Primary user Mode Status in repo
01 Coverage Authorization (AR workbench — the gate) AR clerk Operational ⚠️ prototype only (sandbox/targets/CoverageRecordTarget.tsx, unwired)
02 Charges Edit (per-AN ledger + line routing) AR, Cashier Read/write ⚠️ partial (cashier close-rights + sandbox ReceivablesGridTarget.tsx)
03 Discharge Planning (ward readiness tick) Ward nurse Single-action ⚠️ partial (encounter.billClosed flag work in delivery-room-remaining-backend-work.md)
04 Close-Account Dashboard (observational) AR supervisor Read-only ✅ exists (containers/revenue-settlement/, revenue-settlement-command-center/)
05 Cashier Checkout (collect residual) Cashier Transactional ✅ exists (containers/cashier/)
06 Invoice & Reconciliation (payer claim loop) AR / RCM Batch/back-office ⚠️ entity-rich (ClaimInvoice), generator designed (nhso-revenue-engine-master-plan.md)

Page 06 is the missing page — it closes the loop the state machine ends on (INVOICE → RECONCILIATION → mark "C" + collection code → loop). The Close-Account dashboard’s → Reconciliation (NHSO Excel) button and the Cashier’s Preview invoice PDF both point at it.


3. End-to-end state machine

        PATIENT ADMITTED (AN issued)
                  │
                  ▼
  ┌─────────────────────────────────────────────┐
  │ CHARGES ACCRUE                  (page 02)     │
  │ • per-line Coverage routing (ลำดับสิทธิ)        │
  │ • OPD→IPD rebind via เปลี่ยนสิทธิ               │
  │ • Top-ups (เงินมัดจำ) paid at cashier          │
  └──────────────────┬──────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────┐
  │ WARD: discharge-ready           (page 03)     │
  │ • single ✓ "ready for billing closure"        │
  │ • signals AR → status AMBER                   │
  └──────────────────┬──────────────────────────┘
                     │ status: AMBER (awaiting AR)
                     ▼
  ┌──────────── AR WORKBENCH ────────────────────┐
  │ Coverage Authorization          (page 01)     │  ← THE GATE
  │ • assign  EpisodeOfCarePayorPlan.claimCodeBkk │
  │           = "GFRFT3"  (channel-aware)         │
  │ • verify  (WebService / portal / RPA / paper) │
  │ • set     coverDateEnd  (ปิดสิทธิ ts)          │
  │ • status  awaiting-AR → verified → closed     │
  │           ↓ billable-ready event ↓            │
  └──────────────────┬──────────────────────────┘
                     │ status: GREEN (ready to pay)
                     ▼
  ┌─────────────────────────────────────────────┐
  │ CASHIER                         (page 05)     │
  │ • balance = total − top-ups − claimable       │
  │ • collect residual · issue receipt            │
  └──────────────────┬──────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────┐
  │ CLOSE-ACCOUNT                   (page 04)     │
  │ observational dashboard (NOT a transition):   │
  │ • red flags (ค้าง / ตาย / ไม่จ่าย / ผ่อนชำระ)   │
  │ • drill into 01 for any AN                    │
  └──────────────────┬──────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────┐
  │ INVOICE & RECONCILIATION        (page 06)     │  ← MISSING PAGE
  │ • ClaimInvoice.generate → batch → submit      │
  │ • REP / NHSO Excel reconciliation             │
  │ • mark "C" + collection code → write-off/appeal│
  └─────────────────────────────────────────────┘

4. Per-screen spec + backend binding

Legend: ✅ exists · ⚠️ partial · ❌ missing. Mongo entities live in packages/platform-api-schema/src/; REST actions are exposed by services/financial.

Page 01 — Coverage Authorization (the gate)

The central screen. AR opens it for a patient whose ward has ticked “ready,” gets the reference number from the payer onto the coverage record, verifies it, and closes the coverage period.

UI element Backend binding Status
Coverage rows on this AN Encounter.payorPlans[] of type EpisodeOfCarePayorPlan (extends PayorPlan)
เลขที่หนังสือ / Reference / LOG number EpisodeOfCarePayorPlan.claimCodeBkk (รหัสอ้างสิทธิ์ bkk) + claimCodeNumber ✅ fields exist
ลำดับสิทธิ ordering EpisodeOfCarePayorPlan.priority
Coverage period / ปิดสิทธิ timestamp coverDateFrom / coverDateEnd (= close ts)
Bill-to entity (กรมบัญชีกลาง) Payor (payer org) + locationClaim
Issue date (วันที่ออกหนังสือ) — no dedicated field ❌ add field
Verification channel (WebService / Manual / Paper / RPA) — not modeled ❌ add field
Coverage approval status (awaiting-AR → verified → closed) per-coverage state machine absent; today flips live on SalesOrder.status (PENDING_RIGHTS_CLOSURE → REVOKE_RIGHTS), hardcoded in salesOrder.service.ts ❌ add state machine
Payer terms (ส่วนลดค่าห้อง 50%, ค่าห้อง/วัน, เตียงสามัญ/พิเศษ) “resolved from SchemePolicy, operator cannot override” chargemaster Product.payorPrice[]/schemePrice[] give per-scheme absolute prices (effective-dated); the % coverage rule itself (e.g. CSMBS room 50%) is not encoded anywhere ⚠️ prices exist, rule data missing
CGD WebService preview (eligibility, credit available) ClaimInvoiceEncounter.eligibilityCheckedAt/eligibilitySource; HIPData/authen-code connector ⚠️ partial / external
Auto-fill from WebService (returns GFRFT3) CGD WebService integration ❌ build adapter
AR worklist (right rail) department_queues read model (billing-closure queue) + Task ⚠️ see §8

Required endpoint (❌ missing): a coverage-scoped action PATCH /encounters/:id/coverages/:planId/authorize that sets claimCodeBkk/claimCodeNumber, the channel, coverDateEnd, and advances the coverage status — emitting the billable-ready event. Today the equivalent happens sideways through the cashier EDC close-rights flow (packages/rcm-kit/.../dialog-close-right/CloseRightsEditor.tsx), keyed on the sales order, not the AN.

Build note: promote sandbox/targets/CoverageRecordTarget.tsx (already renders coverage tiers, approval history, logNumber, open/close timestamps, ลำดับสิทธิ) into a real route keyed on AN. Drive which fields are shown/required from Payor.property flags (isRequiredClaimCode, isRequiredCoverDateFrom, isRequiredCoverDateEnd, isRequiredMainHospital) — that per-payer config exists today and is the seed of the dynamic form.

Page 02 — Charges Edit (per-AN ledger)

Per-AN view of all charge lines with per-line coverage routing. AR uses it for corrections; cashier reads it.

UI element Backend binding Status
Charge lines SalesOrder.orderRequestItems[]OrderRequestItem (unitPrice, qty, totalPrice)
Per-line ลำดับสิทธิ (coverage FK) only an indirect link via OrderRequestItem.discount.payorPlan; no clean per-line coverage FK ❌ add per-line binding
เบิกได้ (claimable) vs ค้างชำระ (outstanding) split computed circularly in salesOrder.service.ts: totalCoverage = qty × actualDiscount, totalNonCoverage = qty × (unitPrice − actualDiscount) — assumes discount = covered amount ⚠️ placeholder, needs resolver
ส่วนลด (discount) OrderRequestItemDiscount (discount, givenDiscount, actualDiscount, socialWorkDiscount, discountType)
ชำระแล้ว (top-up paid) AccountReceivable.settlementLedger[] / Receipt
เรียกเก็บ (billed) SalesOrder.coverage (aggregate) ✅ aggregate only
Coverage tabs (cash·0 / OPD·1 / IPD·2) coverage rows = EpisodeOfCarePayorPlan[]
เปลี่ยนสิทธิ (rebind lines to another coverage) ❌ endpoint missing
โอนค่ารักษา (transfer lines to another AN — OPD→IPD) SalesOrder.allocatePlan exists but not exposed ❌ endpoint missing
แบ่ง / รวมค่าใช้จ่าย (split / merge) allocatePlan partial ❌ endpoint missing
ปรับ พ.ร.บ. (route to motor insurance) ❌ (see Open Questions: พ.ร.บ. 36 caps)
Top-up sidebar (เงินมัดจำ) AccountReceivable.settlementLedger[] + Receipt

Build note: the per-line split (เบิกได้/ค้างชำระ) and per-line coverage FK are the two structural gaps. Both are solved by the billable_ledger design in menu-price-ledger.md: each ledger row carries scheme + price_source, which is exactly the per-line ลำดับสิทธิ home. The toolbar actions (เปลี่ยนสิทธิ / โอน / แบ่ง / รวม / พ.ร.บ.) should be gated by policy_gates (new trigger_actions: rebind_coverage, transfer_charge, split_charge, route_act) so override rules are live-editable, not hardcoded.

Page 03 — Discharge Planning (ward)

Read-only ledger mirror, stripped of action buttons. Single affordance: tick “ready for billing closure.”

UI element Backend binding Status
Status banner (🔴 blocked / 🟡 awaiting AR / 🟢 ready) computed from downstream Task / coverage status (see §8, §10) ⚠️ compute layer
Pre-billing checklist (dx signed, meds reconciled, ICD-10, all lines routed, follow-up, counselling) domain signals across clinical/medication/coding services ⚠️ aggregate
“Ready for billing closure” tick canonical encounter.billClosed / billClosedAt / billClosedBy flag (spec’d in delivery-room-remaining-backend-work.md) ⚠️ frontend consumes fail-soft, backend flag pending
Read-only charges summary encounter_journey_cache.financial_summary ✅ read model
Right-rail ward discharge worklist department_queues (ward scope)

Build note: the tick = Task.status: ready on a ward.discharge-readiness-check task, projected to the read model. The amber→green transition is derived: amber while the ar.reference-number-assignment task is open, green once the coverage is verified/closed.

Page 04 — Close-Account Dashboard (observational)

AR-supervisor view of the whole queue with color-coded exceptions. Not a state-transition surface.

UI element Backend binding Status
KPI counters (today’s discharges, awaiting AR, red flags, THB at risk, closed) aggregate query over read model revenue-settlement-command-center/
Worklist table (Patient / AN / Ward / Total / Top-ups / Claimable / Balance / Coverage / Flags) encounter_journey_cache.financial_summary + coverage status + department_queues ✅ exists
Flag ค้างชำระ (outstanding) AccountReceivable.status + risk (LOW/MEDIUM/HIGH)
Flag ผู้ป่วยเสียชีวิต (deceased) encounter/patient status ⚠️
Flag ไม่ได้จ่าย (walked out unpaid) AccountReceivable open balance
Flag ผ่อนชำระ (installment) AccountReceivableInstallment + AccountReceivableStage
Flag portal lookup / RPA bridge queued NHSO maternal bridge task (see §12) ❌ build
Filters (date / ward / scheme / status) query params
Drill row → page 01 navigation by AN ⚠️ wire

Build note: per the guide, do not build a separate data model — page 04 is a query view over the same Task[category=billing-closure]Account.balanceCoverage.status that drives page 01. The trg_sync_billing_queue trigger (billing-queue-auto-sync.md) already spawns billing-pending rows from encounter_journey_cache.financial_summary.

Page 05 — Cashier Checkout (transactional)

Used at the window when the patient arrives to pay; shows running balance with top-up history.

UI element Backend binding Status
Balance due (total − top-ups − claimable) AccountReceivable amounts + settlementLedger
Breakdown rows SalesOrder.total / coverage / top-ups
Payment methods (cash / card / QR / transfer / installment) settlements/createSettlement, Receipt, AccountReceivableInstallment
Claim-to-CSMBS panel (draft queued, ref, submit-by 90d, batch) ClaimInvoice draft (status DRAFT) ✅ entity
Receipt PDF printing service / invoice-package template
Partial / installment → flags on page 04 AccountReceivableInstallment + state propagation

Page 06 — Invoice & Reconciliation (THE MISSING PAGE)

Closes the payer loop. Connects directly to rcm-pipeline-unified-contract.md (stages 4–8) and nhso-revenue-engine-master-plan.md.

UI element Backend binding Status
Generate invoice / batch claims POST /claimInvoices/generate (batches SalesOrder[]ClaimInvoice)
Batch metadata (channel, profile, template, batch/run no) ClaimInvoice.submissionChannel / exportProfileCode / templateType (SSS/UC/GOV) / batchNo / runNo
Submit / export (NHSO 16-file, SSDATA, Eclaim) PATCH /claimInvoices/:id/status (export/send); 16-file generator ⚠️ designed in nhso-revenue-engine-master-plan.md
Claim totals (gross / claim / nonClaim / copay / patientPay / settled / outstanding / denied / adjusted) ClaimInvoice total fields
REP / NHSO Excel reconciliation financial/reconciliation entity + settlements ⚠️ partial
Per-encounter snapshot (approvalCode, dx, amounts) ClaimInvoiceEncounter (approvalCode = รหัสอนุมัติ, schemeCode, eligibilitySource)
Mark “C” + collection code debtCollection + AccountReceivableStage + settlement
Denial → appeal / write-off AccountReceivable.settlementDisputes[]; appeal/write-off UI ⚠️ designed (NHSO plan)
Workflow timestamps (generated/confirmed/exported/sent/settled) ClaimInvoice.*At/*By + isLocked/versionNo

5. Backend reality map (consolidated)

Capability Backend home Verdict
Coverage record with ref-number, priority, validity EpisodeOfCarePayorPlan (claimCodeBkk, claimCodeNumber, priority, coverDate*) ✅ fields exist
Per-coverage approval state machine — (status lives on SalesOrder.DocumentStatus, hardcoded) ❌ build
Ref-number provenance / verification channel ❌ build
“Assign + verify + close coverage” endpoint — (done sideways via cashier EDC close-rights) ❌ build
Per-line coverage binding (ลำดับสิทธิ FK) only indirect via OrderRequestItemDiscount.payorPlan; aggregate SalesOrder.coverage ❌ build (use billable_ledger)
Reimbursable/non-reim split rule (e.g. CSMBS 50% room, นอกบัญชี) absent from schema + country-packs ❌ author as data
Top-up (เงินมัดจำ) ledger AccountReceivable.settlementLedger[] + Receipt
Charge transfer/split/merge (โอน/แบ่ง/รวม) SalesOrder.allocatePlan (not exposed) ⚠️ wire + gate
Close-account observational dashboard containers/revenue-settlement*
Cashier collection containers/cashier/ + settlements
Claim / invoice / reconciliation ClaimInvoice + ClaimInvoiceEncounter + reconciliation ✅ entity, ⚠️ generator designed
Action gating (live-editable rules) policy_gates (shipped; scope_json.benefit_scheme_ids supported) ✅ reuse
Scheme eligibility / validation rules country-pack BRE (rcm-rule-engine) — emits BLOCK/WARN/INFO, does not compute amounts ✅ reuse for validation, ❌ for split compute
Per-line pricing + coverage substrate billable_ledger / resolve_menu_price() (menu-price-ledger.md) ⚠️ designed, migrations pending
Per-payer required-field config Payor.property.isRequired* flags ✅ exists, drives dynamic form

6. Data-model changes required

  1. EpisodeOfCarePayorPlan — add: coverageStatus enum (PENDING → AWAITING_AR → VERIFIED → CLOSED → REVOKED), referenceIssueDate, verificationChannel (webservice|manual|paper|rpa-bridge), verifiedAt/verifiedBy, verificationResult (pending|passed|failed). (The reference value already lives in claimCodeBkk/claimCodeNumber.)
  2. Per-line coverage binding — adopt billable_ledger (menu-price-ledger.md migrations 073/074) with scheme + price_source per row; or, as a bridge, add coveragePlanRef + reimbursable/nonReimbursable to OrderRequestItem to retire the circular actualDiscount split.
  3. Split-rule data — extend the country-pack rule shape with a reimbursement-split rule type + a resolveSplit BRE action returning typed split annotations (the BRE currently only validates). Add a formulary flag to Product (ในบัญชี/นอกบัญชี) — without it the CSMBS-50%-non-formulary rule cannot be expressed.
  4. Read model — ensure encounter_journey_cache.financial_summary carries coverage status + per-coverage claimable/balance so pages 03/04 render without hitting Mongo. (Projected by the orchestrator; see §8.)

7. Dynamic & configurable layering

The repo already standardizes on “rules as data + pure evaluator + admin UI + realtime.” Coverage billing slots into it across three layers — no new engine required:

Layer Question it answers Engine Status
Eligibility / validation Is this scheme valid here? required fields? preauth? frequency caps? country-pack BRE (rcm-rule-engine) ✅ for validation
Action gating May AR rebind / transfer / split this line? close rights? deposit-covers-order? policy_gates (new triggers) ✅ reuse
Per-line pricing + coverage split What does each line cost under this scheme? reim vs patient? billable_ledger + resolve_menu_price() ⚠️ ship migrations
Dynamic form Which fields does this payer require on the auth screen? Payor.property.isRequired* ✅ today

Precedence trap (call out in implementation): product-level discounts (DiscountItem/PayorPlanDiscountSetup) and the new coverage split are both rules-as-data. The resolver must define precedence (contract → scheme → payor → standard, per menu-price-ledger.md D1) so charges aren’t double-counted.


8. Read-model & event flow

Mongo write-truth (financial/admin services)
   │  emits hospital_events (ADMISSION_*, manifest.*, rx.*, billing.*)
   ▼
encounter-orchestrator (Deno)  ──────────────┐
   │  projects                                │ trg_sync_billing_queue
   ▼                                          ▼
encounter_journey_cache.financial_summary   department_queues (dept_type='billing-pending')
   │  realtime                                │  realtime
   ▼                                          ▼
Pages 03/04 (ledger summary, dashboard)     Pages 01/04 worklists (AR queue)
  • Pages 01 & 04 worklists read department_queues (the AR billing-closure queue). trg_sync_billing_queue (billing-queue-auto-sync.md) auto-spawns billing-pending rows from financial_summary.
  • The billable-ready event (coverage verified+closed on page 01) is a write to the financial service that emits a billing.coverage_closed event → orchestrator updates financial_summary.coverage_status → realtime flips page 03 to green and moves the row from “awaiting AR” to “ready to pay.”
  • Never write read-model tables from the frontend (project rule). Page 01 calls the financial REST endpoint; the read model updates via the orchestrator.

9. FHIR mapping reconciled with Mongo

The guide’s FHIR shape is the interop projection (via services/public-api transformers), not the write-truth. Write-truth is Mongo; FHIR is read/write at the boundary; Supabase is the realtime read model.

FHIR medOS write-truth (Mongo)
Coverage (identifier, payor, period, class, costToBeneficiary, verificationResult, status) EpisodeOfCarePayorPlan on Encounter.payorPlans[] + Payor
Coverage.identifier.value = GFRFT3 EpisodeOfCarePayorPlan.claimCodeBkk / claimCodeNumber
Account + ChargeItem[*].account.coverage SalesOrder + per-line binding (billable_ledger)
PaymentNotice[*] (top-ups) AccountReceivable.settlementLedger[] / Receipt
Invoice → Claim → ClaimResponse → PaymentReconciliation Invoice → ClaimInvoice → reconciliation/settlement
Task[category] (worklist driver) department_queues rows + acknowledgement tasks

10. Task-driven worklist model

Four task categories drive the queues; status transitions flip the screens:

Task category Owner Drives
ward.discharge-readiness-check Ward nurse Page 03 tick → AMBER
ar.reference-number-assignment AR clerk Page 01 gate → GREEN on completion
cashier.payment-collection Cashier Page 05
ar.invoice-creation AR / RCM Page 06

The amber/green banner on page 03 is computed from ar.reference-number-assignment task state — not a field the ward sets.


11. Role / permission matrix

Role 01 Coverage Auth 02 Charges 03 Discharge 04 Close-Acct 05 Cashier 06 Invoice/Recon
Ward nurse view view edit (tick only) own-ward view
AR clerk edit edit view view view edit
AR supervisor edit edit view edit (filters/queues) view edit
Cashier view view own-counter view edit view
Physician view view

Enforce via existing RBAC + policy_gates for action-level overrides (e.g. only AR-supervisor may require_override a late charge transfer).


12. Reference-number provenance & verification channels

Different payers, different channels, same coverage slot — so the UI must show channel-of-origin to set trust.

Payer Channel Format medOS handling
CSMBS (กรมบัญชีกลาง) WebService GFRFT3, GF… CGD WebService adapter → auto-fill + verificationResult=passed
SSS (ประกันสังคม) Manual + portal claim form ID manual entry, channel=manual
NHSO (สปสช) maternal-child Browser → copy NHSO Claim Code Playwright RPA bridge (saved creds → fetch code → write back, channel=rpa-bridge)
รัฐวิสาหกิจ Paper LOG LOG number manual, channel=paper
Private insurance Email/fax authorization manual, channel=manual

The NHSO maternal RPA bridge is the one net-new automation: on a coverage with scheme=NHSO-maternal, an agent task watches the reference-needed event, logs into the portal, retrieves the Claim Code, and writes it back — surfacing on page 04 as RPA bridge queued. (Recommender/assist pattern; AR confirms.)


13. Implementation roadmap

Build order is dictated by the gate-first principle: page 01 unblocks everything downstream.

Phase 0 — contract lock (this doc).

Phase 1 — Coverage Authorization gate (page 01).

  • Add coverageStatus state machine + channel/verification fields to EpisodeOfCarePayorPlan.
  • Add the authorize-coverage financial endpoint emitting billing.coverage_closed.
  • Promote CoverageRecordTarget.tsx → real AR route keyed on AN; drive form from Payor.property.*.
  • Project coverage status into financial_summary; wire AR worklist to department_queues.

Phase 2 — Ward tick + dashboard derive (pages 03 + 04).

  • Land the canonical encounter.billClosed flag + ward.discharge-readiness-check task.
  • Compute amber/green from ar.reference-number-assignment task; page 04 becomes a query view (no new model).

Phase 3 — Per-line routing + Charges Edit (page 02).

  • Ship billable_ledger migrations (menu-price-ledger.md 073/074) for per-line scheme/price_source.
  • Expose allocatePlan-backed เปลี่ยนสิทธิ/โอน/แบ่ง/รวม endpoints; gate via policy_gates.

Phase 4 — Split resolver (kills the circular actualDiscount).

  • Add Product formulary flag; add split-rule type + resolveSplit BRE action; encode the first rule (CSMBS 50% non-formulary) end-to-end; render เบิกได้/ค้างชำระ from real splits with a verify gesture.

Phase 5 — Cashier + Invoice/Reconciliation (pages 05 + 06).

  • Cashier already exists; wire claim-draft creation on payment.
  • Build page 06 over ClaimInvoice; land the NHSO 16-file generator + appeal/write-off (nhso-revenue-engine-master-plan.md); add REP/Excel reconciliation ingest.

Phase 6 — NHSO maternal RPA bridge + provenance polish.


14. Open questions

  • Is the ward discharge tick reversible, or one-way? (Affects billClosed undo + task lifecycle.)
  • SLA between ward tick and AR reference assignment? (Drives worklist priority + the page-04 THB at risk aging.)
  • Reference number arriving after the patient has paid and left → need a back-dated reconciliation path on page 06.
  • Exact ปรับ พ.ร.บ. algorithm (พ.ร.บ. 36 caps + co-pay routing to mandatory motor insurance).
  • Precedence between product discounts and coverage-split when both apply to a line (see §7 trap).

15. Cross-references

Doc Why
insurance-aware-workflows.md Coverage as an orthogonal workflow axis; SchemeSlot registry for per-scheme UI
menu-price-ledger.md billable_ledger + resolve_menu_price() = the per-line routing + split substrate
policy-gates.md / policy-gates-coverage.md The shipped action-gating engine the toolbar/override rules reuse
rcm-pipeline-unified-contract.md The 8-stage RCM lifecycle page 06 plugs into
nhso-revenue-engine-master-plan.md 16-file generator, appeal/write-off, fee-schedule engine
billing-queue-auto-sync.md trg_sync_billing_queue — how billing-pending rows reach department_queues
encounter-orchestrator-triggers.md The read-model projection layer (events → caches)
admission-to-ward-unified-contract.md AN/admission/bed contract upstream of billing
delivery-room-remaining-backend-work.md Canonical encounter.billClosed flag spec
Ask Anything