medOS ultra

RCM Pipeline Unified Contract

8-stage RCM lifecycle, canonical types, shared mock encounter pool, dual data-source mapping, export matrix, 11 invariants.

22 min read diagramsUpdated 2026-05-28docs/architecture/rcm-pipeline-unified-contract.md

Master reference for the end-to-end Revenue Cycle Management pipeline. Every surface — Cashier, Medical Coder, Revenue Collection, FPA Export — imports shared types from a single source of truth to guarantee type-level alignment.

Status: Active — updated 2026-05-27 Related docs:


1. Pipeline Overview

┌─────────┐    ┌──────────┐    ┌────────────┐    ┌──────────┐    ┌───────────┐    ┌──────────────┐    ┌─────────┐
│ CHECKOUT │───▶│  CODING  │───▶│ VALIDATION │───▶│  CLAIM   │───▶│  INVOICE  │───▶│  REP RESULT  │───▶│ SETTLED │
│ (Cashier)│    │  (Coder) │    │  (Rules)   │    │  BATCH   │    │ (Export)  │    │ (Reconcile)  │    │  (AR)   │
└─────────┘    └──────────┘    └────────────┘    └──────────┘    └───────────┘    └──────────────┘    └─────────┘
     │               │               │                │               │                │                  │
     ▼               ▼               ▼                ▼               ▼                ▼                  ▼
  Cashier UI    Medical Coder   Coder + Rules     Revenue        Revenue           Revenue            Revenue
  (billing)     Landing +       Engine (edge      Collection     Collection        Settlement          Settlement
                Worklist        function)         DataGrid       Export Wizard     Command Center      AR Close

7 Pipeline Stages

# Stage Thai Owner Surface Entry Condition Exit Condition
0 CHECKOUT ชำระเงิน Cashier Patient visit complete Payment confirmed OR billing finalized
1 CODING ลงรหัส Medical Coder OPD: paid/settled; IPD: discharged All ICD assigned, coding finalized
2 VALIDATION ตรวจสอบ Medical Coder + Rules Coding complete No BLOCK alerts (WARN OK)
3 CLAIM_BATCH จัดชุด Revenue Collection Validation passed Batch confirmed, encounters grouped
4 INVOICE ใบแจ้งหนี้ Revenue Collection Export Batch created Invoice exported to payor
5 REP_RESULT ผล REP Revenue Settlement Invoice submitted REP response parsed (D-case/P-case/Paid)
6 AR_SETTLEMENT ตั้งลูกหนี้ Revenue Settlement REP received Full payment / write-off / appeal closed
7 SETTLED ปิดบัญชี Revenue Settlement AR resolved

Stage Colors (consistent across all UI surfaces)

CHECKOUT:      #64748b (slate)
CODING:        #8b5cf6 (purple)
VALIDATION:    #f97316 (orange)
CLAIM_BATCH:   #17b8e0 (teal)
INVOICE:       #185FA5 (blue)
REP_RESULT:    #0F6E56 (green-dark)
AR_SETTLEMENT: #dc2626 (red)
SETTLED:       #22c55e (green)

2. Canonical Type Definitions

Source of Truth File

web/src/containers/medical-coder/components/RevenuePipelineContract.ts

All surfaces MUST import pipeline types from this file. Do NOT redefine PipelineStage, CodingStatus, CodingOutput, or EncounterPipelineState elsewhere.

2.1 Pipeline Stage

export const PIPELINE_STAGES = [
  'CODING', 'VALIDATION', 'CLAIM_BATCH', 'INVOICE',
  'REP_RESULT', 'AR_SETTLEMENT', 'SETTLED',
] as const;
export type PipelineStage = (typeof PIPELINE_STAGES)[number];

2.2 Coding Status (7 states)

           ┌──────────────────────────────────────┐
           │              AWAITING                 │  ← Entry: encounter queued
           └──────────┬───────────────────────────┘
                      │ coder picks up
                      ▼
           ┌──────────────────────────────────────┐
           │            IN_PROGRESS                │
           └──────────┬──────────┬────────────────┘
                      │          │ request review
                      │          ▼
                      │  ┌──────────────────┐
                      │  │ REVIEW_REQUESTED  │──▶ REVIEW_COMPLETED ──▶ back to IN_PROGRESS
                      │  └──────────────────┘
                      │ submit
                      ▼
           ┌──────────────────────────────────────┐
           │         PENDING_VALIDATION            │
           └──────────┬──────────┬────────────────┘
                      │          │ rules fire BLOCKs
                      │          ▼
                      │  ┌──────────────────┐
                      │  │ VALIDATION_FAILED │──▶ coder fixes ──▶ PENDING_VALIDATION
                      │  └──────────────────┘
                      │ all clear
                      ▼
           ┌──────────────────────────────────────┐
           │             COMPLETED                 │  ← Exit: ready for claim batch
           └──────────────────────────────────────┘
export type CodingStatus =
  | 'AWAITING' | 'IN_PROGRESS' | 'COMPLETED'
  | 'REVIEW_REQUESTED' | 'REVIEW_COMPLETED'
  | 'VALIDATION_FAILED' | 'PENDING_VALIDATION';

2.3 Claim Status (6 states — Revenue Collection surface)

  Prepare ──▶ Pending ──▶ Verified ──▶ Exported
                                          │
                                    ┌─────┴─────┐
                                    ▼           ▼
                                Mismatch     Flagged
export type ClaimStatus =
  | 'Prepare' | 'Pending' | 'Verified'
  | 'Exported' | 'Mismatch' | 'Flagged';

2.4 Payment Status (4 states)

export type PaymentStatus = 'Draft' | 'Pending' | 'Paid' | 'Settled';

2.5 CodingOutput — What the Coder Produces

export interface CodingOutput {
  encounterId: string;
  encounterNo: string;          // VN for OPD, AN for IPD
  patientHn: string;
  patientName: string;
  encounterClass: 'AMB' | 'IMP';
  schemeCode: string;
  schemeName: string;

  codingStatus: CodingStatus;
  validationStatus: 'PASSED' | 'WARNED' | 'BLOCKED' | null;

  principalDiagnosisCode: string;   // ICD-10 PDx
  principalDiagnosisDesc: string;
  secondaryDiagnosisCodes: string[];
  procedureCodes: string[];         // ICD-9-CM

  drgCode: string;
  drgDescription: string;
  rw: number;                       // Relative Weight
  adjRw: number;                    // Adjusted RW
  estimatedPayment: number;         // ฿ estimated

  qualityScore: number;             // 0–100
  blockCount: number;
  warnCount: number;

  coderId: string;
  coderName: string;
  codedAt: string;                  // ISO 8601
  finalizedAt: string | null;
}

2.6 ReceivableRow — Revenue Collection Grid Row

export interface ReceivableRow {
  id: string;                       // UUID (Supabase) or ObjectId (MongoDB)
  _id: string;                      // Always present for dual-source compat
  encounterClass: 'OPD' | 'IPD' | 'ER';
  hn: string;
  patientName: string;
  patientTitle: string;
  vn: string;
  an?: string;

  schemeName: string;
  schemeCode: string;
  schemeColor: string;
  debtorGroup: string;

  totalAmount: number;              // ฿ total charge
  claimAmount: number;              // ฿ claimable portion
  reimAmount: number;               // ฿ reimbursement expected
  nonReimAmount: number;            // ฿ non-reimbursable
  paidAmount: number;               // ฿ received
  coverage: number;                 // ฿ coverage ceiling

  visitDate: string;
  dischargeDate?: string;
  diagnosis: string;
  clinicName: string;
  doctorName: string;
  hospitalName: string;

  claimStatus: ClaimStatus;
  anomalies: AnomalyFlag[];
  verifiedBy?: string;
  verifiedAt?: string;

  // Raw refs for drill-through
  patient: any;
  encounter: any;
  payor: any;
  payorPlan: any;
}

2.7 AnomalyFlag — Quality Flags on Receivables

export interface AnomalyFlag {
  type: 'same-day-duplicate' | 'non-reim-spike' | 'missing-icd'
      | 'high-value' | 'mismatch' | 'expired-scheme';
  severity: 'warning' | 'error';
  message: string;
}

2.8 EncounterPipelineState — Per-Encounter Journey Tracker

export interface EncounterPipelineState {
  encounterId: string;
  encounterNo: string;
  patientHn: string;
  patientName: string;
  encounterClass: 'AMB' | 'IMP';
  schemeCode: string;

  currentStage: PipelineStage;
  stageHistory: { stage: PipelineStage; enteredAt: string; exitedAt: string | null }[];

  coding: CodingOutput | null;
  claimBatchId: string | null;
  invoiceDocNo: string | null;
  repCode: string | null;
  arItemId: string | null;

  totalCharge: number;
  claimedAmount: number;
  approvedAmount: number;
  paidAmount: number;
  variance: number;

  hasAlert: boolean;
  alertCount: number;
}

3. Data Source Architecture

Dual Data Source (MongoDB + Supabase)

┌───────────────────────────────────────────────────────────────┐
│                     WRITE PATH (Backend)                       │
│  NestJS Services ──▶ MongoDB (canonical truth)                 │
│    financial.SalesOrder, financial.Invoice, clinical.Encounter  │
└──────────────────────────────┬────────────────────────────────┘
                               │ Moleculer events
                               ▼
┌───────────────────────────────────────────────────────────────┐
│                     EVENT BUS (NATS)                           │
│  financial.salesOrder.paid, clinical.encounter.discharged      │
└──────────────────────────────┬────────────────────────────────┘
                               │ Edge functions subscribe
                               ▼
┌───────────────────────────────────────────────────────────────┐
│                   READ MODEL (Supabase)                        │
│  encounter_journey_cache ← orchestrator                        │
│  coding_worklist ← fn_auto_populate_coding_worklist trigger    │
│  department_queues ← queue placement triggers                  │
│  billing_queue ← trg_sync_billing_queue trigger                │
└──────────────────────────────┬────────────────────────────────┘
                               │ Realtime subscriptions
                               ▼
┌───────────────────────────────────────────────────────────────┐
│                    FRONTEND SURFACES                           │
│  Medical Coder ← coding_worklist + encounter_journey_cache     │
│  Revenue Collection ← receivables (derived from journey cache) │
│  FPA Dashboard ← aggregated views                              │
└───────────────────────────────────────────────────────────────┘

Field Name Mapping (Critical for Dual-Source)

Concept MongoDB/REST Supabase SharedMockEncounter
Record ID _id (ObjectId) id (UUID) id + encounterId
Encounter ref encounter encounter_id encounterId
Patient ref patient patient_id patientId
Patient HN patient.hn patient_hn hn
Patient name patient.firstName + lastName patient_name patientFullName
Visit number encounter.vn encounter_vn vn
Coding status codingStatus coding_status codingStatus
Claim status claimStatus claim_status claimStatus
Created at createdAt created_at (derived)

Live Read Path + Mock Fallback (Revenue Collection grid)

The Receivables / Revenue Collection grid reads through one hook with a graceful fallback:

useReceivablesUnified  (web/packages/rcm-kit/src/financial/hooks/useReceivablesUnified.ts)
  ├─ live:  SELECT * FROM v_receivables_unified   (Supabase, realtime, HTTP 200)
  │          → maps snake_case view cols → ReceivableRow + 4 derived cols
  │            (currentStage, daysInAr, paymentModel, denial*)
  └─ mock:  shared-encounter-pool  (when no client / view 404 / view empty)
  • The hook is client-agnostic (rcm-kit stays Supabase-free): the app/sandbox passes its own Supabase client; rcm-kit only needs .from().select() + .channel().
  • When live rows are present, the sandbox’s deriveStageFromClaimStatus / deriveArDays / derivePaymentModel prefer the view’s current_stage / days_in_ar / payment_model and only fall back to the hash-mocks when Supabase is unreachable. See §6.4.

When Supabase read models return empty (no data deployed), surfaces fall back to the Shared Mock Encounter Pool — a deterministic dataset generated from a seeded PRNG.

web/packages/rcm-kit/src/financial/mock-data/shared-encounter-pool.ts

The pool generates 80 encounters with consistent IDs across all consumers. Same encounter ID appears in Medical Coder worklist AND Revenue Collection grid.


4. Surface-to-Surface Data Flow

4.1 Cashier → Medical Coder

Trigger: OPD payment confirmed OR IPD discharge finalized

Cashier (financial service)
  └─▶ SalesOrder.status = 'Paid'
       └─▶ hospital_events: financial.salesOrder.paid
            └─▶ encounter-orchestrator edge fn
                 └─▶ encounter_journey_cache.financial_summary = { status: 'Paid' }
                      └─▶ fn_auto_populate_coding_worklist trigger
                           └─▶ coding_worklist row inserted (coding_status = 'AWAITING')
                                └─▶ Medical Coder realtime subscription fires

Key contract: coding_worklist row must carry:

  • encounter_id, patient_id, encounter_class (from journey cache)
  • scheme_code, clinic_id, clinic_name (from clinical_context JSONB)
  • patient_hn, patient_name (denormalized for display)
  • discharge_date (from admission_context or financial_summary timestamp)
  • coding_status = 'AWAITING', priority (URGENT for EMER, ROUTINE otherwise)

4.2 Medical Coder → Revenue Collection

Trigger: Coder finalizes all codes, validation passes

Medical Coder (coding service)
  └─▶ coding_worklist.coding_status = 'COMPLETED'
       └─▶ coding_worklist.validation_status = 'PASSED' or 'WARNED'
            └─▶ CodingOutput produced:
                 { encounterId, pdxCode, drgCode, rw, adjRw, estimatedPayment, qualityScore }
                      └─▶ Revenue Collection reads encounter as ReceivableRow
                           claimStatus = 'Prepare' (ready for batching)

Key contract: Revenue Collection needs from coding:

  • diagnosis (PDx display text) for anomaly detection
  • estimatedPayment flows into reimAmount
  • qualityScore / blockCount / warnCount inform anomaly flags
  • claimStatus starts at Prepare when coding completes

4.3 Revenue Collection → FPA Export

Trigger: User selects encounters, opens Export Wizard

Revenue Collection DataGrid
  └─▶ User selects rows (checkboxes) or "Select All"
       └─▶ "Export…" button opens ExportWizardDialog
            └─▶ Step 1: Scope (date range, include mode, hospital)
            └─▶ Step 2: Scheme selection (UC/SSS/CSMBS/LGO/MVA per OPD/IPD)
            └─▶ Step 3: Preflight validation
                 ├─ Missing ICD → error (blocks export)
                 ├─ Same-day duplicate → warning
                 ├─ High-value claim → warning
                 └─ Non-reim spike → warning
            └─▶ Step 4: Format selection
                 ├─ UC → NHSO 16 flat files
                 ├─ SSS → SSDATA format
                 ├─ CSMBS → Eclaim 2021 XML
                 └─ Custom → CSV/Excel
            └─▶ Step 5: Generate + download
                 └─▶ ClaimInvoice created (status = 'Exported')
                      └─▶ ReceivableRow.claimStatus = 'Exported'

Key contract: ExportWizardDialog receives:

interface ExportWizardDialogProps {
  open: boolean;
  onClose: () => void;
  rows: ReceivableRow[];           // Full dataset (for scheme grouping)
  selectedIds: string[];           // Pre-selected row IDs (from DataGrid)
  onExportSubmit: (scope: ExportScope, format: string) => Promise<void>;
}

4.4 FPA Export → Revenue Reports

Trigger: Aggregation over claim invoices

ClaimInvoice records (exported + settled)
  └─▶ FPA Data Warehouse views (materialized)
       ├─▶ Monthly revenue by department
       ├─▶ Payor mix analysis
       ├─▶ AR aging buckets (0-30, 31-60, 61-90, 91-120, 121-180, 180+)
       ├─▶ Claim settlement timelines
       └─▶ Daily/weekly/monthly trends
            └─▶ Revenue Reports page
                 └─▶ KpiSummary, DepartmentRevenue, PayorMixEntry, ArAgingBucket

5. Shared Mock Encounter Pool Contract

Location

web/packages/rcm-kit/src/financial/mock-data/shared-encounter-pool.ts

Design Principles

  1. Deterministic — seeded PRNG (mulberry32(20260527)), same seed = same data
  2. Single pool — 80 encounters, each carrying fields for ALL consumers
  3. Transform functions — each surface gets its own projection of the same data
  4. SingletongetSharedMockPool() returns memoized array

SharedMockEncounter Interface

interface SharedMockEncounter {
  // ── Identity ──
  id: string;                    // UUID-like deterministic ID
  encounterId: string;           // Same as id
  patientId: string;             // Deterministic patient ref

  // ── Patient demographics ──
  hn: string;                    // HN 0000001–0000080
  patientTitle: string;          // นาย/นาง/น.ส./ด.ช./ด.ญ.
  patientFirstName: string;      // Thai first name
  patientLastName: string;       // Thai last name
  patientFullName: string;       // title + first + last
  patientSex: 'M' | 'F';
  patientDob: string;            // ISO date
  patientIdCard: string;         // 13-digit Thai ID

  // ── Visit info ──
  vn: string;                    // VN000000001–VN000000080
  an: string | undefined;        // AN only for IPD
  encounterClass: 'OPD' | 'IPD';
  visitDate: string;             // ISO date
  dischargeDate: string | undefined;
  los: number;                   // Length of stay (IPD only)
  clinic: { _id: string; code: string; name: string };
  doctorName: string;

  // ── Coding ──
  codingStatus: CodingStatus;
  coderName: string;
  codingStartedAt: string | undefined;
  codingCompletedAt: string | undefined;
  agingDays: number;
  icd10: string[];               // ICD-10 codes assigned
  icd9: string[];                // ICD-9-CM procedure codes
  pdxCode: string;               // Principal diagnosis
  pdxDisplay: string;
  drg: { code: string; name: string; rw: number };
  qualityScore: number;
  validationStatus: 'PASSED' | 'WARNED' | 'BLOCKED' | null;
  blockCount: number;
  warnCount: number;

  // ── Financial ──
  paymentStatus: PaymentStatus;
  payor: { _id: string; code: string; name: string };
  payorPlan: { _id: string; code: string; name: string; payorId: string };
  totalAmount: number;           // ฿ total charge
  claimAmount: number;           // ฿ claimable
  paidAmount: number;
  coverage: number;
  estimatedPayment: number;
  debtorGroup: string;

  // ── Revenue Collection ──
  claimStatus: ClaimStatus;
  mainHospital: { _id: string; code: string; name: string };
}

Transform Functions

Function Consumer Output Type
getSharedMockPool() All SharedMockEncounter[] (80 rows, singleton)
toCodingWorklistRows(pool?) Medical Coder worklist TCodingWorklistRow[]
toCodingDashboardSummary(encounterClass?) Medical Coder KPIs TCodingDashboardSummary[]
toRevenueCollectionRows(pool?) Revenue Collection grid (legacy row shape)[]
getMockCodingCounts(encounterClass?) Medical Coder + Revenue Collection banner Record
getClaimReadyEncounters() Export Wizard SharedMockEncounter[] (COMPLETED only)
SCHEME_COLORS All surfaces Record<string, string>

Coding Status Distribution (weighted)

Status Weight ~Count (of 80)
AWAITING 25% ~20
IN_PROGRESS 15% ~12
COMPLETED 30% ~24
REVIEW_REQUESTED 5% ~4
VALIDATION_FAILED 8% ~6
PENDING_VALIDATION 17% ~14

Scheme Distribution

UC:   #e65100 (deep orange)    ~25% of encounters
SSS:  #1565c0 (blue)           ~20%
CSMBS:#2e7d32 (green)          ~15%
LGO:  #9c27b0 (purple)         ~8%
MVA:  #d32f2f (red)            ~7%
INS:  #00838f (teal)           ~10%
SELF: #455a64 (grey)           ~8%
AIA:  #f9a825 (amber)          ~7%

6. Supabase Read Model Tables

6.1 coding_worklist (migration 042)

Auto-populated by fn_auto_populate_coding_worklist trigger on encounter_journey_cache.

Column Type Source
id UUID PK
encounter_id TEXT journey cache
patient_id TEXT journey cache
encounter_class TEXT AMB/IMP/EMER
scheme_code TEXT clinical_context.payor.code
clinic_id TEXT clinical_context.clinic._id
clinic_name TEXT clinical_context.clinic.name
patient_hn TEXT denormalized
patient_name TEXT denormalized
discharge_date TIMESTAMPTZ admission or financial timestamp
coding_status TEXT starts AWAITING
coder_id TEXT assigned coder
coder_name TEXT assigned coder name
coding_started_at TIMESTAMPTZ when coder picks up
coding_completed_at TIMESTAMPTZ when finalized
pdx_code TEXT ICD-10 principal
pdx_display TEXT PDx display text
drg_estimate TEXT DRG code
rw_estimate NUMERIC Relative Weight
adjrw_estimate NUMERIC Adjusted RW
estimated_payment NUMERIC ฿ estimated
validation_status TEXT PASSED/WARNED/BLOCKED/null
block_count INT validation block count
warn_count INT validation warn count
info_count INT validation info count
quality_score NUMERIC 0–100
aging_days INT days since discharge
priority TEXT ROUTINE/URGENT/STAT

6.2 coding_connectors (migration 041)

Pluggable routing registry for coding capabilities.

Column Type Purpose
id UUID PK
module TEXT always ‘medical-coder’
capability TEXT validate/estimate-drg/quality-score/suggest-icd/…
provider_type TEXT edge_function/dify_workflow/n8n_webhook/external_api
endpoint_url TEXT webhook/API URL
priority INT dispatch order (1 = primary)
is_active BOOLEAN enabled/disabled
scheme_filter TEXT[] optional scheme whitelist
encounter_class_filter TEXT[] optional OPD/IPD whitelist

RPC: fn_resolve_coding_connectors(capability, hospital, scheme, enc_class)

6.3 encounter_journey_cache (migration 003+)

Central read model for patient journey. RCM-relevant JSONB columns:

JSONB Column RCM-Relevant Fields
financial_summary status, totalAmount, claimAmount, paidAmount, updatedAt
clinical_context clinic, payor, payorPlan, doctor
admission_context dischargeDate, admissionDate, los
coding_summary codingStatus, pdxCode, drgCode, rw, qualityScore

6.4 v_receivables_unified (migration 20260528s) — the canonical grid read model

Applied to demo Supabase hynsmfrevlsegbmjnoiy on 2026-05-28. One row per billed encounter; the single query the Receivables grid runs. It is a VIEW (always live), not a table — current_stage is derived, never stored.

v_receivables_unified  ◀── encounter_journey_cache (financial_summary, encounter_header, …)
                       ◀── coding_worklist           (CODING / VALIDATION)
                       ◀── claim_batch_line + claim_batch (CLAIM_BATCH / INVOICE / AR / SETTLED)
                       ◀── remittance                (REP_RESULT, reconciliation)
                       ◀── encounter_payment_binding + contract (payment_model, scheme)
                       ◀── denial_code_master        (denial reason, appealability)

Derived columns added on top of ReceivableRow: current_stage (rcm_pipeline_stage enum — 7 stages, see note below), days_in_ar, payment_model, denial_code / denial_reason / denial_is_appealable, plus claim_batch_id / remittance_id for nav.

Stage derivation walks downstream→upstream so the most-advanced state wins (remittance RECONCILED → SETTLED; batch SETTLED/PARTIALLY_SETTLED → AR_SETTLEMENT; remittance present → REP_RESULT; batch SUBMITTED/ACK/PROCESSING/ADJUDICATED → INVOICE; batch line exists → CLAIM_BATCH; coding_worklist validation_status set → VALIDATION; else CODING).

7-stage resolution (closes contradiction #2 in billing-rcm-unified-master §7.2): the shipped enum rcm_pipeline_stage is CODING → VALIDATION → CLAIM_BATCH → INVOICE → REP_RESULT → AR_SETTLEMENT → SETTLED. CHECKOUT is NOT a stage — it is the entry condition into CODING. Frontend PIPELINE_STAGES (7) and this DB enum (7) now agree.

Prerequisite migrations (apply in order; all idempotent): 20260528f (contract + encounter_payment_binding) → 20260528p (claim_batch / claim_batch_line / remittance / denial_code_master) → 055 (adds patient_display_name to journey cache) → 20260528s.

Companion summary views (same migration): v_receivables_pipeline_distribution (KPI mini-bar), v_receivables_ar_buckets (4 aging buckets), v_receivables_denial_by_scheme. Audit table: rcm_stage_transitions (RLS-enabled) — reserved for the future rcm.pipeline.advance write command (not yet built).

Known data-mapping gaps (cosmetic, not wiring): amounts read financial_summary → total_amount/claim_amount/reim_amount; this demo’s financial_summary uses total/netPay (billing-queue trigger keys), so ฿ show 0 until a key-mapping pass. Patient name sourced from patient_display_name; blank where encounter_header lacks a name key.

Read by the useReceivablesUnified hook (§3). Canonical SQL: infrastructure/medbase/migrations/20260528s_receivables_unified_view.sql.


7. Cross-Surface Navigation

From To Route Trigger
Medical Coder Landing Revenue Collection /revenue-collection HubCard “Revenue Collection”
Medical Coder Worklist Encounter Workspace /medical-coder/encounter/:id Row action button (OpenInNew icon)
Encounter Workspace Revenue Settlement CC /revenue-settlement-command-center?stage=VALIDATION&from=coder “Finalize” button (seeds mock pipeline + navigates)
Revenue Settlement CC Stage-specific tab /revenue-settlement?tab=N PipelineStageIndicator stage click
Revenue Collection Medical Coder Landing /medical-coder Chip “Medical Coder” in banner
Revenue Collection Revenue Reports /revenue-reports Chip “Reports” in banner
Revenue Collection FPA Dashboard /fpa-dashboard Chip “FP&A” in banner
Revenue Settlement Revenue Collection /revenue-collection Back nav

Shared Banner Contract

Revenue Collection displays a shared-data banner:

SHARED MOCK DATA — {total} encounters linked to Medical Coder
({completed} coded, {awaiting + in_progress} pending)
                                                [Medical Coder →]

Medical Coder displays in Quick Jumps:

Revenue Collection — {completed} claim-ready encounters

8. Export Format Matrix

Scheme Format File Structure Generator
UC (บัตรทอง) NHSO 16-file ADP/ADT/CHA/CHT/DIA/DRG/INS/IPD/IRF/LVD/ODX/OOP/OPD/ORF/PAT/PRO nhso-16-file-generator (planned)
SSS (ประกันสังคม) SSDATA Single CSV per batch ssdata-exporter (planned)
CSMBS (ข้าราชการ) Eclaim 2021 XML per invoice eclaim-xml-builder (planned)
LGO (อปท.) NHSO 16-file variant Same as UC with LGO headers Shares UC generator
MVA (พ.ร.บ.) Custom PDF + CSV Per-accident claim form mva-claim-builder (planned)
INS / AIA (ประกันเอกชน) Fax claim form PDF per claim insurance-claim-pdf (planned)
SELF (จ่ายเอง) Receipt only No export needed

9. Anomaly Detection Rules

Applied in Revenue Collection when building ReceivableRow.anomalies:

Rule Type Severity Condition
Same-day duplicate same-day-duplicate warning Same HN + same visitDate, different encounter
Missing ICD missing-icd error diagnosis empty or ‘-’
High-value claim high-value warning claimAmount > ฿30,000
Non-reim spike non-reim-spike warning nonReimAmount > ฿2,000
Scheme-claim mismatch mismatch error Claim exceeds scheme ceiling (planned)
Expired scheme expired-scheme error Payor plan expired (planned)

10. Implementation Checklist

Currently Wired (2026-05-27)

  • [x] RevenuePipelineContract.ts — canonical types (7 stages, CodingOutput, EncounterPipelineState)
  • [x] shared-encounter-pool.ts — 80 deterministic mock encounters
  • [x] Medical Coder landing mock fallback (when Supabase empty)
  • [x] Revenue Collection using shared pool data
  • [x] Cross-navigation: Medical Coder ↔ Revenue Collection ↔ Reports ↔ FP&A
  • [x] ReceivablesDataGrid with anomaly detection + Pipeline stage column
  • [x] ExportWizardDialog (mock export flow)
  • [x] coding_worklist Supabase trigger (migration 042)
  • [x] coding_connectors registry (migration 041)
  • [x] PipelineStageIndicator component — visual 7-stage stepper with click-to-navigate
  • [x] CoderWorkstation — split-pane encounter workspace (discharge summary + coding ladder + DRG preview)
  • [x] CdiQueryCard — CDI query drawer for clinician reviews
  • [x] Encounter page data extraction (ICD-10/ICD-9 from live encounter → CoderWorkstation props)
  • [x] Revenue Settlement Command Center — PipelineStageIndicator in header, auto-pipeline seeding from coder
  • [x] Revenue Collection — pipeline tracker banner with stage indicator + Reports/FP&A nav chips
  • [x] Worklist → Workspace flow — row action navigates to /medical-coder/encounter/:id
  • [x] Finalize → Settlement flow — seeds mockPipelineStore + navigates to command center
  • [x] Sandbox targets: CodingWorklist (worklist→workspace), CoderWorkstation, RevenueCollection
  • [x] v_receivables_unified view (migration 20260528s) applied to demo Supabase 2026-05-28 — derives current_stage from real downstream tables (see §6.4); prereqs 20260528f + 20260528p + 055 applied
  • [x] useReceivablesUnified hook (rcm-kit) — live view read + realtime + mock fallback; ReceivablesGridTarget reads it (badge flips live/mock), grid live at 91 rows
  • [x] Fixed 6 latent bugs in 20260528s (4 column refs, denial join, ORDER-BY alias) — the view had never been run before

Backend Wiring Needed

  • [ ] Deploy coding_worklist + coding_connectors migrations to PH demo Supabase
  • [ ] Wire fn_auto_populate_coding_worklist trigger (currently defined but tables may not exist)
  • [ ] Backend service: coding.service Moleculer actions (getCodingWorklist, updateCodingStatus, submitValidation)
  • [ ] NATS events: coding.encounter.assigned, coding.encounter.completed, coding.validation.passed
  • [ ] Edge function: encounter-orchestrator handler for coding_summary projection

Frontend Wiring Needed

  • [x] Revenue Collection: switch from mock to Supabase when data available (keep mock fallback) — done via useReceivablesUnified + v_receivables_unified (2026-05-28)
  • [ ] Medical Coder worklist: switch listAllActiveEncounter REST to Supabase coding_worklist realtime
  • [x] Pipeline stage indicator on Revenue Collection rows (Pipeline column in ReceivablesDataGrid)
  • [x] Pipeline stage indicator on Revenue Collection page (tracker banner above grid)
  • [x] Pipeline stage indicator on Revenue Settlement Command Center header
  • [ ] Revenue Reports: wire to FPA data warehouse views (currently mock data)
  • [ ] FPA Dashboard: connect to materialized views

Export Pipeline Needed

  • [ ] NHSO 16-file generator (Phase 4 of NHSO master plan)
  • [ ] SSDATA format exporter
  • [ ] Eclaim 2021 XML builder
  • [ ] Pre-export validation engine (not just anomaly flags)
  • [ ] Batch management UI (group encounters into submission batches)

11. Backend Service Architecture

NestJS Modules (Financial Service)

Module Location Purpose
rcmValidation services/financial/.../modules/rcmValidation/ Thin proxy to Deno BRE edge function; validates encounter financial context
claimInvoice services/financial/.../modules/claimInvoice/ Claim batching, export generation, invoice CRUD
settlement services/financial/.../modules/settlement/ AR settlement workflow
reconciliation services/financial/.../modules/reconciliation/ Payment reconciliation
eclaimConnector services/financial/.../modules/eclaimConnector/ E-claim submission to NHSO
nhsoAutoTagger services/financial/.../modules/nhsoAutoTagger/ Auto-tagging engine wrapper
revenueCollection services/financial/.../modules/revenueCollection/ Revenue grid & dashboard
orCaseCosting services/financial/.../modules/orCaseCosting/ OR case-level cost tracking

Coder Management (Administration Service)

Module Location Purpose
coder services/administration/.../modules/coder/ Coder entity CRUD, status transitions, concurrent-coder soft-lock

Edge Functions (Deno)

Function Location Purpose
coding-rules-engine infrastructure/medbase/functions/coding-rules-engine/ Validation dispatcher with country packs (TH_NHSO, CSMBS, commercial, generic)
rcm-rule-engine infrastructure/medbase/functions/rcm-rule-engine/ Business rules engine (late penalties, quotas, surcharges)
eclaim-connector infrastructure/medbase/functions/eclaim-connector/ E-claim submission, REP response parsing
coding-ai-assistant infrastructure/medbase/functions/coding-ai-assistant/ Dify workflow integration for AI suggestions

Backend ↔ Frontend Enum Alignment

The backend CoderStatus enum uses Thai-legacy string values (matching the older API). The frontend CodingStatus uses uppercase constants. Both represent the same 7 states:

Backend (CoderStatus) Frontend (CodingStatus) Thai Label
'Await Register' 'AWAITING' รอลงรหัส
'In Progress' 'IN_PROGRESS' กำลังลงรหัส
'Await Reconsider' 'REVIEW_REQUESTED' รอแพทย์ทบทวน
'Acknowledged' 'REVIEW_COMPLETED' แพทย์ทบทวนแล้ว
'Pending Validation' 'PENDING_VALIDATION' รอตรวจสอบ
'Validation Failed' 'VALIDATION_FAILED' ตรวจสอบไม่ผ่าน
'Registed' 'COMPLETED' ให้รหัสแล้ว

Important: The backend uses 'Registed' (not ‘Registered’) — this is a legacy typo preserved for backward compatibility. The Supabase read model coding_worklist uses the frontend-style uppercase constants. The fn_auto_populate_coding_worklist trigger maps backend values to frontend constants.

Backend Pipeline Stages

The backend does not export an explicit PIPELINE_STAGES constant. Instead, stage transitions are event-driven via NATS:

financial.salesOrder.paid         → triggers CODING stage (coding_worklist insertion)
coding.encounter.completed        → triggers VALIDATION (coding-rules-engine edge fn)
coding.validation.passed          → encounter eligible for CLAIM_BATCH
claimInvoice.batch.confirmed      → INVOICE stage
eclaim.submission.complete        → REP_RESULT stage
eclaim.rep.response.received      → AR_SETTLEMENT stage
settlement.ar.resolved            → SETTLED stage

The canonical PipelineStage type lives in the frontend (RevenuePipelineContract.ts) and is the single source of truth. Backend services don’t need the enum because they operate on individual entity status transitions (SalesOrder.status, Coder.status, ClaimInvoice.status) rather than a unified pipeline state.


12. Invariants

  1. One encounter = one pipeline journey — an encounter cannot be in two stages simultaneously
  2. Stage ordering is strict — CODING → VALIDATION → CLAIM_BATCH → INVOICE → REP_RESULT → AR_SETTLEMENT → SETTLED (no skipping)
  3. BLOCK validation = hard stop — encounters with validationStatus = 'BLOCKED' cannot advance past VALIDATION
  4. Deterministic mock data — same seed produces identical encounters; changing the seed breaks cross-surface alignment
  5. Dual data source safety — always support both _id and id, encounter and encounter_id (see §3)
  6. Mock fallback — when Supabase returns zero rows, surfaces MUST fall back to shared mock pool (never show empty state)
  7. Scheme colors are canonical — all surfaces import SCHEME_COLORS from shared pool, never redefine
  8. Pipeline types are canonical — all surfaces import from RevenuePipelineContract.ts, never redefine
Ask Anything