IPD Billing Architecture
Six-screen IPD billing: coverage authorization, charges edit, discharge planning, close-account, cashier, invoice & reconciliation.
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.md—trg_sync_billing_queue(how billing-pending rows reachdepartment_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)
- The gate is the Coverage Authorization screen, not the Close-Account screen. AR’s real work happens on page 01. Page 04 is observational.
- 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.
- 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. - Top-ups (เงินมัดจำ) accumulate in parallel. During long admissions patients pay deposits at the cashier.
Account.balance = Σ charges − Σ top-ups − Σ claimable. - 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.balance ⨝ Coverage.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
EpisodeOfCarePayorPlan— add:coverageStatusenum (PENDING → AWAITING_AR → VERIFIED → CLOSED → REVOKED),referenceIssueDate,verificationChannel(webservice|manual|paper|rpa-bridge),verifiedAt/verifiedBy,verificationResult(pending|passed|failed). (The reference value already lives inclaimCodeBkk/claimCodeNumber.)- Per-line coverage binding — adopt
billable_ledger(menu-price-ledger.mdmigrations 073/074) withscheme+price_sourceper row; or, as a bridge, addcoveragePlanRef+reimbursable/nonReimbursabletoOrderRequestItemto retire the circularactualDiscountsplit. - Split-rule data — extend the country-pack rule shape with a reimbursement-split rule type + a
resolveSplitBRE action returning typed split annotations (the BRE currently only validates). Add a formulary flag toProduct(ในบัญชี/นอกบัญชี) — without it the CSMBS-50%-non-formulary rule cannot be expressed. - Read model — ensure
encounter_journey_cache.financial_summarycarries 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 fromfinancial_summary. - The billable-ready event (coverage verified+closed on page 01) is a write to the financial service that
emits a
billing.coverage_closedevent → orchestrator updatesfinancial_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
coverageStatusstate machine + channel/verification fields toEpisodeOfCarePayorPlan. - Add the
authorize-coveragefinancial endpoint emittingbilling.coverage_closed. - Promote
CoverageRecordTarget.tsx→ real AR route keyed on AN; drive form fromPayor.property.*. - Project coverage status into
financial_summary; wire AR worklist todepartment_queues.
Phase 2 — Ward tick + dashboard derive (pages 03 + 04).
- Land the canonical
encounter.billClosedflag +ward.discharge-readiness-checktask. - Compute amber/green from
ar.reference-number-assignmenttask; page 04 becomes a query view (no new model).
Phase 3 — Per-line routing + Charges Edit (page 02).
- Ship
billable_ledgermigrations (menu-price-ledger.md073/074) for per-linescheme/price_source. - Expose
allocatePlan-backedเปลี่ยนสิทธิ/โอน/แบ่ง/รวมendpoints; gate viapolicy_gates.
Phase 4 — Split resolver (kills the circular actualDiscount).
- Add
Productformulary flag; add split-rule type +resolveSplitBRE 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
billClosedundo + task lifecycle.) - SLA between ward tick and AR reference assignment? (Drives worklist priority + the page-04
THB at riskaging.) - 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 |