medOS ultra

NHSO Revenue Engine Master Plan

10-phase plan for the NHSO billing layers: charge masters, TMT seeding, F9 auto-tagging, 16-file generator, revenue optimization.

29 min read diagramsUpdated 2026-05-22docs/architecture/nhso-revenue-engine-master-plan.md

Goal: Build the missing charge-mapping, auto-tagging, 16-file generation, and revenue optimization layers so medOS can (a) generate NHSO-compliant claim files from first principles, (b) auto-tag every encounter for maximum reimbursement, and © surface missed-revenue opportunities to coders and finance staff.

Table of Contents


Current State Audit

What’s Built (✅)

Component Location Status
RCM BRE (rule engine) medbase/functions/rcm-rule-engine/ ✅ Production — evaluates 15+ rule types per th-nhso-uc.json
Coding validation engine medbase/functions/coding-rules-engine/ ✅ Production — DRG v5, CC/MCC capture monitoring
e-Claim connector medbase/functions/eclaim-connector/ ✅ Production — submit, REP import, govt sync, statement import
RCM persistence tables medbase/migrations/010_rcm_tables.sql ✅ Production — alert, batch, rep_response, correction, denial queue
Terminology cache medbase/migrations/012_terminology_cache.sql ✅ Production — supports TMT/TMLT/ICD/LOINC/SNOMED/ATC/NHSO_CATEGORY
Terminology server medbase/functions/terminology-server/ ✅ Production — cache lookup, $translate, ValueSet expansion
FP&A warehouse medbase/migrations/090-092 ✅ Production — fact_encounter, fact_charge, dim_department, dim_diagnosis, dim_procedure
Gold dim_scheme medbase/migrations/013_gold_layer.sql ✅ Production — NHSO_UC base rate 8350 THB/adjRW
Revenue settlement UI rcm-kit/financial/components/revenue-settlement/ ✅ 30+ components — dashboard, batch, REP, correction, denial panels
NestJS service proxies services/financial/src/.../rcmValidation/, eclaimConnector/ ✅ Thin proxy to Deno edge functions
Airflow DAG infrastructure/airflow/dags/medos_eclaim_submission.py ✅ Daily batch orchestration

What’s Missing (❌)

Gap Impact Phase
No ADP master table — internal charges can’t map to NHSO ADP Type + Code Can’t generate ADP file or tag claims with project codes 0, 1
No ChrgItem mapping — local charge categories don’t map to NHSO 19-category system Can’t generate CHA/CHT files 0
TMT/TMLT seeds emptyterminology_cache table exists but has zero TMT/TMLT rows Drug/lab lines missing national codes on claims 1
No auto-tagging engine — HOSxP’s F9-equivalent doesn’t exist Every claim requires manual code entry = missed money 2
No 16-file generatorth-nhso-eclaim.json declares nhso_16files format but no code generates them Can’t submit to hospitals still using file-based e-Claim 3
No HIPData integration — no authen code verification or real-time scheme check Authen code field exists but is never validated 4
No revenue suggestions — coding engine validates but doesn’t suggest better codes Leave 5-15% adjRW on the table 5
No per-charge reconciliation — statement totals tracked, not per-line approved/denied Can’t investigate underpayments at charge level 6
Appeal/write-off UI incomplete — tables exist, no frontend workflow D/P cases go to queue but staff can’t act on them 7
No fee schedule engine — NHSO’s 24-item prevention/promotion list not prompted Eligible services not tagged → not billed 8
No revenue-at-risk dashboard — no aggregate view of money being left on the table Finance can’t prioritize where to focus 9

Architecture Decisions

AD-1: Config-driven JSON, not hardcoded TypeScript

All country-specific logic lives in JSON files loaded at runtime by Deno edge functions. This is the established pattern (th-nhso-uc.json, th-nhso-eclaim.json, th-nhso-coding.json). New engines follow the same pattern — zero country logic in TypeScript.

AD-2: Supabase tables for editable masters, JSON for immutable rules

  • Charge item mappings → Supabase tables (nhso_adp_master, nhso_chrgitem_map, etc.) because finance staff need to edit them in-app.
  • Structural rules (late penalty tiers, diagnostic pathways, quota limits) → JSON country packs because they change only at fiscal year boundaries.

AD-3: Auto-tagger is a Deno edge function

The auto-tagger runs as medbase/functions/nhso-auto-tagger/ — same hosting model as BRE and e-Claim connector. NestJS proxies through the financial service. Frontend calls at two points:

  1. Pre-billing — when a cashier opens the billing screen, auto-tagger suggests tags.
  2. Pre-submission — when a batch is assembled, auto-tagger validates all encounters have required tags.

AD-4: 16-file generator extends the existing eclaim-connector

The generator lives in medbase/functions/eclaim-connector/generators/nhso-16files.ts — called by the existing submitClaims() when exportFormat === 'nhso_16files'. No new edge function needed.

AD-5: Revenue optimization is advisory, never auto-mutating

The optimization engine suggests — it never auto-changes a code. All suggestions go to rcm_ai_suggestion_log (existing table) with action_type: 'coding_suggestion'. Coders accept/reject in the UI.


Phase 0 — Charge Item Master Tables

Goal: Create the foundational mapping tables that everything else depends on.

Migration: 100_nhso_charge_masters.sql

┌──────────────────────────────────────────────────────────────────────────┐
│ nhso_adp_master                                                        │
│ ─────────────────────                                                   │
│ id (serial PK)                                                         │
│ adp_type        smallint  NOT NULL  -- 3=service, 4=PP, 5=project_code │
│ adp_code        text      NOT NULL  -- 'WALKIN', 'UCEP24', '36598'     │
│ adp_name_th     text      NOT NULL                                     │
│ adp_name_en     text                                                   │
│ scheme_codes    text[]    DEFAULT '{NHSO_UC}'  -- which schemes use it  │
│ unit_price      numeric(12,2)  -- 0 for project codes, fee for services│
│ claim_track     text      -- 'op_anywhere', 'ucep', 'er_ext', etc.     │
│ effective_from  date      NOT NULL                                     │
│ effective_to    date                                                   │
│ is_active       boolean   DEFAULT true                                 │
│ UNIQUE(adp_type, adp_code)                                             │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│ nhso_chrgitem_map                                                      │
│ ─────────────────────                                                   │
│ id (serial PK)                                                         │
│ local_charge_category  text  NOT NULL  -- medOS charge_category enum    │
│ nhso_category_code     text  NOT NULL  -- NHSO ChrgItem code (1-19)    │
│ nhso_category_name_th  text  NOT NULL                                  │
│ nhso_category_name_en  text                                            │
│ charge_category_detail text  -- sub-category for ORF file              │
│ UNIQUE(local_charge_category, nhso_category_code)                      │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│ nhso_fee_schedule                                                      │
│ ─────────────────────                                                   │
│ id (serial PK)                                                         │
│ fee_code        text      NOT NULL UNIQUE                              │
│ fee_name_th     text      NOT NULL                                     │
│ fee_name_en     text                                                   │
│ unit_price      numeric(12,2) NOT NULL                                 │
│ service_type    text      -- 'prevention', 'promotion', 'screening'    │
│ eligible_departments text[]  -- dept codes where this auto-triggers    │
│ auto_trigger_conditions jsonb  -- age/gender/frequency rules           │
│ fiscal_year     text      NOT NULL DEFAULT '2568'                      │
│ effective_from  date      NOT NULL                                     │
│ effective_to    date                                                   │
│ is_active       boolean   DEFAULT true                                 │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│ nhso_project_code_rule                                                 │
│ ─────────────────────                                                   │
│ id (serial PK)                                                         │
│ project_code    text      NOT NULL  -- ADP Code (e.g. 'WALKIN')        │
│ adp_type        smallint  NOT NULL DEFAULT 5                           │
│ rule_name_th    text      NOT NULL                                     │
│ rule_name_en    text                                                   │
│ trigger_conditions jsonb  NOT NULL                                     │
│   -- {                                                                 │
│   --   "encounter_class": ["AMB"],                                     │
│   --   "scheme_code": ["NHSO_UC"],                                     │
│   --   "visit_type": ["walk_in"],                                      │
│   --   "hospmain_match": false,                                        │
│   --   "department_types": ["er"],                                     │
│   --   "time_window": { "start": "16:30", "end": "08:29" },           │
│   --   "icd_prefixes": ["Z51.5"],                                      │
│   --   "min_activity_codes": 1                                         │
│   -- }                                                                 │
│ priority        smallint  NOT NULL DEFAULT 100  -- lower = higher pri  │
│ auto_inject     boolean   DEFAULT true   -- inject into billing line   │
│ requires_confirmation boolean DEFAULT false                            │
│ effective_from  date      NOT NULL                                     │
│ effective_to    date                                                   │
│ is_active       boolean   DEFAULT true                                 │
│ UNIQUE(project_code, effective_from)                                   │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│ nhso_ucef_marker                                                       │
│ ─────────────────────                                                   │
│ id (serial PK)                                                         │
│ charge_item_code  text    NOT NULL  -- internal product/item code      │
│ ucef_eligible     boolean NOT NULL DEFAULT false                       │
│ ucef_code         text    -- UCEP-specific override code if different  │
│ notes             text                                                 │
│ UNIQUE(charge_item_code)                                               │
└──────────────────────────────────────────────────────────────────────────┘

Admin UI: /admin/nhso-masters

Four tabs in the existing admin-kit:

  1. ADP Master — CRUD for nhso_adp_master. Import from CSV (HOSxP nhso_adp_type + nhso_adp_code tables).
  2. Charge Category Map — CRUD for nhso_chrgitem_map. Maps medOS 14-category → NHSO 19-category.
  3. Fee Schedule — CRUD for nhso_fee_schedule. 24-item NHSO PP fee list per fiscal year.
  4. Project Code Rules — CRUD for nhso_project_code_rule. Visual rule builder for auto-injection conditions.

Seed Data

Market pack infrastructure/market-packs/medos-thailand/:

  • seed-nhso-adp-master.sql — ~200 rows (3 ADP types × ~60 codes each)
  • seed-nhso-chrgitem-map.sql — 19 rows mapping medOS categories → NHSO categories
  • seed-nhso-fee-schedule.sql — 24 rows (FY2568 prevention/promotion items)
  • seed-nhso-project-code-rules.sql — ~15 rules (WALKIN, UCEP24, ER-EXT, ER-QUAL, OP-ANYWHERE variants, CANCER, HOMEWARD, IMC, PALLIATIVE, etc.)

Dependencies

  • None — this is the foundation layer.

Deliverables

  • [ ] Migration 100_nhso_charge_masters.sql
  • [ ] Seed files in market-packs/medos-thailand/
  • [ ] Admin UI at /admin/nhso-masters (4 tabs)
  • [ ] RLS policies + realtime subscriptions

Phase 1 — Terminology Seeding (TMT / TMLT / ADP)

Goal: Populate terminology_cache with Thai national code systems so every drug and lab line carries the required national code.

TMT (Thai Medicines Terminology)

Source: GPO TMT database (~30,000 codes) or NLEMOnline. Target: terminology_cache rows with system = 'TMT'.

// Example terminology_cache row for TMT
{
  "system": "TMT",
  "code": "1000088",
  "display": "Metformin 500mg tablet",
  "display_th": "เมทฟอร์มิน 500 มก. เม็ด",
  "properties": {
    "isActive": true,
    "inNLEM": true,
    "nlemCategory": "บัญชี ก",
    "genericName": "metformin",
    "atcCode": "A10BA02",
    "dosageForm": "tablet",
    "strength": "500 mg",
    "tradeName": "Glucophage"
  },
  "translations": {
    "ATC": "A10BA02",
    "SNOMED": "109071002"
  }
}

TMLT (Thai Medical Laboratory Terminology)

Source: ~5,000 codes from the Thai Lab Standard Terminology project. Target: terminology_cache rows with system = 'TMLT'.

{
  "system": "TMLT",
  "code": "L0001",
  "display": "Complete Blood Count (CBC)",
  "display_th": "ตรวจนับเม็ดเลือดทั้งหมด",
  "properties": {
    "isActive": true,
    "specimen": "blood",
    "method": "automated",
    "loincMapping": "57021-8"
  },
  "translations": {
    "LOINC": "57021-8"
  }
}

NHSO_CATEGORY (ChrgItem)

Source: NHSO 19-category billing classification. Target: terminology_cache rows with system = 'NHSO_CATEGORY'.

terminology_concept_map entries:

  • LOCAL_DRUG-to-TMT — maps internal drug product codes → TMT codes
  • LOCAL_LAB-to-TMLT — maps internal lab item codes → TMLT codes
  • LOCAL_CHARGE-to-NHSO_CATEGORY — maps charge items → NHSO ChrgItem codes
  • TMT-to-ATC — TMT → ATC cross-reference

Implementation

  1. Seed script: infrastructure/market-packs/medos-thailand/seed-terminology-tmt.ts — TypeScript script that loads the TMT CSV and bulk-upserts into terminology_cache.
  2. Seed script: infrastructure/market-packs/medos-thailand/seed-terminology-tmlt.ts — same pattern for TMLT.
  3. Seed script: infrastructure/market-packs/medos-thailand/seed-nhso-categories.sql — 19 rows.
  4. Concept map admin UI — extend the existing terminology server admin to show terminology_concept_map rows and allow manual review/correction.
  5. Drug master integration — when a drug product is created/edited in the medication setup UI, the TMT code field auto-suggests from terminology_cache WHERE system='TMT' with fuzzy search on display_th.
  6. Lab master integration — when a lab item is created/edited, TMLT auto-suggest from terminology_cache WHERE system='TMLT'.

Dependencies

  • Phase 0 (for NHSO_CATEGORY seeding consistency)
  • terminology_cache table (already exists)
  • terminology_concept_map table (already exists)

Deliverables

  • [ ] TMT seed script + CSV source data
  • [ ] TMLT seed script + CSV source data
  • [ ] NHSO category seed SQL
  • [ ] Concept map seed SQL (LOCAL_DRUG→TMT, LOCAL_LAB→TMLT)
  • [ ] Drug master TMT auto-suggest integration
  • [ ] Lab master TMLT auto-suggest integration

Phase 2 — Auto-Tagging Engine

Goal: Replicate HOSxP’s F9 auto-injection behavior — when a billing screen opens (or a batch is assembled), auto-tag every encounter with the correct ADP codes, project codes, and fee schedule items.

Edge Function: medbase/functions/nhso-auto-tagger/

Architecture:

┌─────────────────────────────────────────────────────────────┐
│ nhso-auto-tagger (Deno Edge Function)                      │
│                                                             │
│  Input:  EncounterBillingContext (encounter + charges)      │
│  Output: AutoTagResult (suggested tags + injections)        │
│                                                             │
│  1. Load encounter context (scheme, class, dept, time)      │
│  2. Load all active project-code rules from Supabase        │
│  3. Evaluate each rule's trigger_conditions against context │
│  4. For matched rules: generate billing line injections      │
│  5. Load fee-schedule items eligible for this encounter     │
│  6. Cross-check existing charges for missing national codes │
│  7. Return: { projectCodes[], feeScheduleItems[],           │
│              missingTmtCodes[], missingTmltCodes[],         │
│              adpMappings[], warnings[] }                    │
└─────────────────────────────────────────────────────────────┘

Key Types:

interface EncounterBillingContext {
  encounterId: string;
  encounterClass: 'AMB' | 'IMP' | 'EMER';
  schemeCode: string;
  patientCitizenId?: string;
  hospmain?: string;        // patient's registered primary hospital
  hospsub?: string;         // patient's registered sub-unit
  treatingHcode: string;    // this hospital's HCODE
  departmentCode: string;
  departmentType: string;
  serviceTime: string;      // ISO datetime — for time-window rules
  visitType?: string;       // walk_in, appointment, referral, emergency
  admissionDate?: string;
  dischargeDate?: string;
  diagnoses: Array<{ code: string; type: 'principal' | 'secondary' }>;
  procedures: Array<{ code: string; name?: string }>;
  charges: Array<{
    chargeItemCode: string;
    chargeCategory: string;
    quantity: number;
    unitPrice: number;
    tmtCode?: string;       // may already be mapped
    tmltCode?: string;      // may already be mapped
    adpType?: number;       // may already be tagged
    adpCode?: string;       // may already be tagged
  }>;
  referralInfo?: {
    fromHcode: string;
    referralType: string;
    sameProvince: boolean;
  };
  ipdInfo?: {
    ipdType?: string;
    accidentAeType?: string;
  };
}

interface AutoTagResult {
  encounterId: string;
  schemeCode: string;
  
  // Project codes to inject as zero-priced billing lines
  projectCodes: Array<{
    adpType: number;
    adpCode: string;
    nameTh: string;
    nameEn: string;
    autoInject: boolean;
    requiresConfirmation: boolean;
    matchedRule: string;    // rule ID for audit
  }>;
  
  // Fee schedule items the encounter is eligible for
  feeScheduleItems: Array<{
    feeCode: string;
    nameTh: string;
    unitPrice: number;
    alreadyCharged: boolean;  // true if a matching charge line already exists
  }>;
  
  // Charges missing national codes
  missingTmtCodes: Array<{
    chargeItemCode: string;
    chargeDescription: string;
    suggestedTmt?: string;  // from concept map if available
    confidence?: number;
  }>;
  missingTmltCodes: Array<{
    chargeItemCode: string;
    chargeDescription: string;
    suggestedTmlt?: string;
    confidence?: number;
  }>;
  
  // ADP type/code mappings for charges
  adpMappings: Array<{
    chargeItemCode: string;
    suggestedAdpType: number;
    suggestedAdpCode: string;
    source: 'rule' | 'category_map' | 'manual';
  }>;
  
  // Claim track classification
  claimTrack: {
    track: 'in_network' | 'op_anywhere_same_province' | 'op_anywhere_cross_province' | 
           'ucep' | 'ucep24' | 'er_ext' | 'er_quality' | 'referral' | 'standard';
    autoDetected: boolean;
    hospmainMatch: boolean;  // treatingHcode === hospmain?
    provinceMatch: boolean;
  };
  
  warnings: string[];
  evaluatedAt: string;
}

Project Code Rule Evaluation Logic

For each active rule in nhso_project_code_rule (sorted by priority):
  1. Check encounter_class matches
  2. Check scheme_code matches
  3. Check visit_type matches (if specified)
  4. Check hospmain_match:
     - false: treatingHcode !== patient's hospmain (cross-facility)
     - true:  treatingHcode === patient's hospmain (home facility)
  5. Check department_types (if specified)
  6. Check time_window (if specified — for ER off-hours rules)
  7. Check icd_prefixes (if specified — for palliative, cancer, etc.)
  8. Check min_activity_codes (if specified — for palliative: 5 required)
  9. If ALL conditions match → add to projectCodes[]

Claim Track Auto-Detection

Given: treatingHcode, hospmain, hospsub, encounterClass, visitType, referralInfo

if encounterClass === 'EMER' && visitType === 'emergency':
  if first24Hours: claimTrack = 'ucep24'
  else: claimTrack = 'ucep'
elif treatingHcode === hospmain:
  if isOffHours(serviceTime) && departmentType === 'er':
    claimTrack = 'er_ext' or 'er_quality'
  else:
    claimTrack = 'standard'  (home facility, normal hours)
elif sameProvince(treatingHcode, hospmain):
  claimTrack = 'op_anywhere_same_province'
else:
  claimTrack = 'op_anywhere_cross_province'

NestJS Proxy

services/financial/src/api/financial/modules/nhsoAutoTagger/:

  • nhsoAutoTagger.service.ts — thin proxy to Deno edge function
  • nhsoAutoTagger.controller.mixin.ts — REST endpoint POST /api/v2/financial/auto-tag

Frontend Integration Points

  1. Cashier billing screen — when opening a billing encounter, call auto-tagger. Display suggested project codes as checkboxes (pre-checked if autoInject: true). Display fee schedule items as a “Did you bill these?” checklist. Highlight charges missing TMT/TMLT.

  2. Batch creation — in BatchCreationPanel.tsx, run auto-tagger on all encounters in the batch. Show summary: “42 encounters tagged, 3 need manual review, 5 missing TMT codes.”

  3. Pre-submission validation — the existing BRE (rcm-rule-engine) gains a new evaluator that checks auto-tagger results and blocks encounters missing required tags.

Dependencies

  • Phase 0 (nhso_project_code_rule, nhso_fee_schedule, nhso_chrgitem_map tables)
  • Phase 1 (terminology_cache populated with TMT/TMLT for code suggestions)

Deliverables

  • [ ] Edge function medbase/functions/nhso-auto-tagger/ (index.ts, types.ts)
  • [ ] NestJS proxy services/financial/.../nhsoAutoTagger/
  • [ ] Frontend auto-tag panel in cashier billing screen
  • [ ] Batch-level auto-tag summary in BatchCreationPanel.tsx
  • [ ] New BRE evaluator: autoTagCompleteness

Phase 3 — 16-File Generator

Goal: Generate the NHSO standard 16 flat files (ADP, ADT, CHA, CHT, DIA, DRG, INS, IPD, IRF, LVD, ODX, OOP, OPD, ORF, PAT, PRO) from encounter + charge data.

Generator Module: medbase/functions/eclaim-connector/generators/nhso-16files.ts

Architecture:

eclaim-connector/index.ts
  └─ submitClaims(req, config)
       └─ if req.exportFormat === 'nhso_16files':
            └─ generate16Files(encounters, config)
                 ├─ generatePAT(encounters)   → PAT.txt
                 ├─ generateOPD(encounters)   → OPD.txt (AMB encounters)
                 ├─ generateIPD(encounters)   → IPD.txt (IMP encounters)
                 ├─ generateDIA(encounters)   → DIA.txt (all diagnoses)
                 ├─ generatePRO(encounters)   → PRO.txt (all procedures)
                 ├─ generateDRG(encounters)   → DRG.txt (DRG codes)
                 ├─ generateCHA(encounters)   → CHA.txt (charge totals)
                 ├─ generateCHT(encounters)   → CHT.txt (charge details)
                 ├─ generateADP(encounters)   → ADP.txt (additional payment)
                 ├─ generateINS(encounters)   → INS.txt (insurance/scheme)
                 ├─ generateADT(encounters)   → ADT.txt (admit/discharge/transfer)
                 ├─ generateIRF(encounters)   → IRF.txt (IP referral)
                 ├─ generateORF(encounters)   → ORF.txt (OP referral)
                 ├─ generateODX(encounters)   → ODX.txt (OP diagnosis detail)
                 ├─ generateOOP(encounters)   → OOP.txt (OP procedure detail)
                 └─ generateLVD(encounters)   → LVD.txt (leave day)
                 
                 → zip all → return as base64 or upload to Supabase storage

File Format Spec

Each file is pipe-delimited (|), one row per encounter (or per line item for detail files). Character encoding: UTF-8 (with optional TIS-620 export for legacy systems via exportEncoding in connector config).

Column definitions stored in: medbase/functions/eclaim-connector/generators/nhso-16files-schema.json

This schema file defines for each of the 16 files:

  • Column names
  • Data types
  • Max lengths
  • Required/optional
  • Source mapping (which field from Encounter16FileContext populates it)
  • Default values

Example — ADP file columns:

AN|DATEOPD|TYPE|CODE|QTY|RATE|TOTAL|PERSON_ID|SEQ|CAGCODE|DOSE|CA_TYPE|SERIALNO|TOTCOPAY

Extended Encounter Context

The 16-file generator needs more data than the current ClaimEncounterPayload. New type:

interface Encounter16FileContext extends ClaimEncounterPayload {
  // PAT file
  citizenId: string;
  patientTitle: string;
  firstName: string;
  lastName: string;
  dateOfBirth: string;
  gender: 'M' | 'F';
  marriageStatus: string;
  nationality: string;
  address: { addrpart: string; moopart: string; tmbpart: string; amppart: string; chwpart: string };
  
  // INS file
  inscl: string;       // insurance type code
  subtype: string;     // sub-insurance type
  hospmain: string;    // main registered hospital
  hospsub: string;     // sub-registered hospital
  
  // Charge details (for CHA/CHT/ADP files)
  chargeLines: Array<{
    chargeDate: string;
    chargeCategory: string;         // medOS category
    nhsoCategoryCode: string;       // mapped NHSO ChrgItem code
    chargeCode: string;
    description: string;
    quantity: number;
    unitPrice: number;
    totalAmount: number;
    adpType?: number;
    adpCode?: string;
    tmtCode?: string;
    tmltCode?: string;
    personId?: string;              // CID of person charge is for
  }>;
  
  // ADT file (IP only)
  admissionType?: string;
  admissionWeight?: number;
  dischargeStatus?: string;
  dischargeType?: string;
  
  // Leave days (LVD file)
  leaveDays?: Array<{ leaveDate: string; returnDate: string; hours: number }>;
  
  // Referral (IRF/ORF files)
  referralFrom?: string;
  referralTo?: string;
  referralReason?: string;
  referralDate?: string;
  
  // Auto-tagger results (from Phase 2)
  autoTagResult?: AutoTagResult;
}

Data Assembly Edge Function

Before generating files, the connector needs to assemble Encounter16FileContext from multiple sources. New function assemble16FileContext():

1. Load encounter from encounter_journey_cache (Supabase read model)
2. Load patient from MongoDB via NestJS proxy (PAT fields)
3. Load charges from fpa_fact_charge (charge details)
4. Load auto-tag results from nhso-auto-tagger (project codes, ADP mappings)
5. Load terminology mappings from terminology_cache (TMT/TMLT codes)
6. Load scheme info from gold_dim_scheme (INSCL code)
7. Merge into Encounter16FileContext

Export Artifact Storage

Generated ZIP files are uploaded to Supabase Storage bucket eclaim-exports/ with path: {hospitalCode}/{schemeCode}/{batchNumber}/{timestamp}.zip.

Dependencies

  • Phase 0 (nhso_chrgitem_map for CHA/CHT file generation)
  • Phase 1 (TMT/TMLT for drug/lab lines in ADP file)
  • Phase 2 (auto-tagger for ADP code population)
  • Existing: eclaim-connector, fpa_fact_charge, encounter_journey_cache

Deliverables

  • [ ] Generator module generators/nhso-16files.ts (~1500 LOC)
  • [ ] Schema definition generators/nhso-16files-schema.json
  • [ ] Context assembler generators/assemble-16file-context.ts
  • [ ] Supabase Storage bucket for exports
  • [ ] UI: “Export 16-File” button on ClaimBatchPanel.tsx
  • [ ] UI: File preview/download dialog
  • [ ] E2E test with sample data

Phase 4 — HIPData / Authen Code Integration

Goal: Verify patient insurance eligibility and retrieve authentication codes from NHSO’s HIPData system in real-time.

Edge Function: medbase/functions/hipdata-connector/

┌─────────────────────────────────────────────────────────────┐
│ hipdata-connector (Deno Edge Function)                     │
│                                                             │
│ Actions:                                                    │
│   verifyEligibility — CID → scheme + hospmain + hospsub    │
│   checkAuthenCode   — CID + visit → authen code status     │
│   fetchAuthenCode   — pull authen code from NHSO system    │
│   batchVerify       — bulk eligibility check for batch     │
│                                                             │
│ Config: connector-packs/th-nhso-hipdata.json               │
│   - Endpoint URLs, auth (X.509 certificate or API key)     │
│   - Response mapping                                        │
│   - Timeout and retry settings                              │
└─────────────────────────────────────────────────────────────┘

Persistence

New table (extend migration):

CREATE TABLE IF NOT EXISTS nhso_eligibility_cache (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  citizen_id      TEXT NOT NULL,
  scheme_code     TEXT,
  inscl_code      TEXT,
  hospmain        TEXT,
  hospsub         TEXT,
  effective_date  DATE,
  expire_date     DATE,
  authen_code     TEXT,
  authen_date     TIMESTAMPTZ,
  authen_method   TEXT,  -- 'id_card', 'mobile_app', 'kiosk', 'fingerprint'
  verified_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  raw_response    JSONB DEFAULT '{}',
  UNIQUE(citizen_id, scheme_code)
);

Frontend Integration

  1. Patient registration — when CID is entered, auto-verify eligibility. Show scheme + hospmain in the scheme picker.
  2. Encounter creation — verify eligibility is current. Warn if expired.
  3. Billing screen — “Check Authen Code” button that calls fetchAuthenCode. Display status badge: verified (green) / missing (red) / expired (yellow).
  4. Batch submission — block encounters without valid authen codes.

Dependencies

  • External: NHSO HIPData API access (X.509 certificate or API key from hospital registration)
  • Phase 0 (scheme_code consistency)

Deliverables

  • [ ] Edge function medbase/functions/hipdata-connector/ (index.ts, types.ts)
  • [ ] Connector config connector-packs/th-nhso-hipdata.json
  • [ ] Persistence table nhso_eligibility_cache
  • [ ] NestJS proxy services/financial/.../hipdataConnector/
  • [ ] Patient registration eligibility auto-check
  • [ ] Billing screen authen code verification
  • [ ] BRE evaluator: authenCodePresence (BLOCK if missing)

Phase 5 — Revenue Optimization Engine

Goal: Proactively suggest coding improvements that increase DRG weight / adjRW without upcoding.

Edge Function: medbase/functions/revenue-optimizer/

Input:  EncounterCodingContext (diagnoses, procedures, charges, DRG, demographics)
Output: RevenueSuggestion[] (each with estimated RW impact)

Evaluators:
  1. CC/MCC Gap Detector
     - Check if any documented conditions qualify as CC/MCC but are NOT coded
     - Cross-reference clinical notes (if available) with ICD-10 CC/MCC lists
     - "Documentation says 'acute kidney injury' but no N17.x code → +0.4 adjRW"

  2. Specificity Upgrader
     - Detect .9 (unspecified) codes that could be more specific
     - "E11.9 (Type 2 DM unspecified) → E11.65 (with hyperglycemia) if BG > 250"
     - Estimate adjRW delta between current and suggested DRG

  3. Procedure Completeness
     - Check if performed procedures are missing from the coded list
     - Cross-reference OR log, medication admin, lab orders
     - "Chest tube insertion documented but no 34.04 coded"

  4. SDx Capture (Secondary Diagnoses)
     - Identify conditions present on admission that affect DRG grouping
     - Chronic conditions (HTN, DM, CKD) that are undertreated in coding
     - "Patient on metformin → E11.x should be coded as SDx"

  5. ADP Opportunity Detector
     - Check for services rendered that qualify for ADP but aren't tagged
     - "Lab COVID test done but no ADP Type 15 / Code 36598 tagged"
     - "Palliative visit but missing ADP code 30001"

  6. Claim Track Optimizer
     - Verify the encounter is on the optimal claim track
     - "Currently tagged as standard but patient is from Province X → OP Anywhere"

Country Pack: revenue-optimizer/country-packs/th-nhso-uc.json

{
  "countryCode": "TH",
  "schemeCode": "NHSO_UC",
  "optimizers": {
    "ccMccGap": {
      "enabled": true,
      "ccListSource": "terminology_cache",   // query WHERE system='ICD10' AND properties->>'isCc' = 'true'
      "mccListSource": "terminology_cache",
      "minConfidence": 0.7,
      "requireDocumentation": true
    },
    "specificityUpgrade": {
      "enabled": true,
      "unspecifiedPatterns": ["\\.9$", "\\.0$"],
      "drgGrouper": "THAI_DRG_V5",
      "baseRate": 8350,
      "minRwDelta": 0.05                     // only suggest if delta > 0.05 adjRW
    },
    "adpOpportunity": {
      "enabled": true,
      "adpMasterTable": "nhso_adp_master",   // query for eligible codes
      "feeScheduleTable": "nhso_fee_schedule"
    },
    "claimTrackOptimizer": {
      "enabled": true
    }
  },
  "messages": {
    "REV_CC_GAP": {
      "local": "ผู้ป่วยมีภาวะ {condition} แต่ยังไม่ลงรหัส {suggestedCode} — อาจเพิ่ม RW ได้ ~{rwDelta}",
      "en": "Patient has {condition} but {suggestedCode} not coded — potential +{rwDelta} adjRW"
    },
    "REV_SPECIFICITY": {
      "local": "รหัส {currentCode} เป็นรหัสไม่เจาะจง — พิจารณาใช้ {suggestedCode} เพื่อเพิ่ม RW {rwDelta}",
      "en": "Code {currentCode} is unspecified — consider {suggestedCode} for +{rwDelta} adjRW"
    },
    "REV_ADP_MISSED": {
      "local": "บริการ {serviceName} ยังไม่ติด ADP Type {adpType} Code {adpCode} — มูลค่า {amount} บาท",
      "en": "Service {serviceName} missing ADP Type {adpType} Code {adpCode} — worth {amount} THB"
    }
  }
}

Suggestion Persistence

Uses existing rcm_ai_suggestion_log table with action_type: 'coding_suggestion':

{
  "action_type": "coding_suggestion",
  "encounter_id": "ENC-001",
  "request_context": {
    "optimizer": "ccMccGap",
    "currentCodes": ["K80.20"],
    "suggestedCode": "K80.21",
    "estimatedRwDelta": 0.4,
    "estimatedRevenueDelta": 3340  // 0.4 * 8350
  },
  "response_text": "ผู้ป่วยมีภาวะ obstruction แต่ยังไม่ลงรหัส K80.21",
  "confidence": 0.85,
  "user_accepted": null  // pending coder review
}

Frontend: Revenue Suggestion Panel

New panel in rcm-kit: RevenueSuggestionPanel.tsx

  • Shows per-encounter suggestions grouped by type (CC/MCC, Specificity, ADP, etc.)
  • Each suggestion shows: current code → suggested code, estimated revenue impact, confidence
  • Accept/Reject buttons that update user_accepted in rcm_ai_suggestion_log
  • Accept auto-updates the encounter’s coding (via BRE re-evaluation)

Dependencies

  • Phase 0 (nhso_adp_master for ADP opportunity detection)
  • Phase 1 (terminology_cache for CC/MCC lists, code lookups)
  • Phase 2 (auto-tagger for claim track optimization)
  • Existing: rcm_ai_suggestion_log, coding_worklist, fpa_dim_diagnosis

Deliverables

  • [ ] Edge function medbase/functions/revenue-optimizer/ (index.ts, types.ts, evaluators.ts)
  • [ ] Country pack revenue-optimizer/country-packs/th-nhso-uc.json
  • [ ] NestJS proxy services/financial/.../revenueOptimizer/
  • [ ] Frontend RevenueSuggestionPanel.tsx
  • [ ] Coding worklist integration (suggestions appear inline)
  • [ ] Acceptance workflow (accept → re-code → re-evaluate BRE)
  • [ ] Monthly aggregate: “Revenue Captured vs Revenue Suggested” report

Phase 6 — Charge-Level Reconciliation

Goal: Match each charge line to its government-approved/denied status so finance can investigate underpayments at the item level.

Migration: 101_charge_reconciliation.sql

CREATE TABLE IF NOT EXISTS rcm_charge_reconciliation (
  id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  encounter_id        TEXT NOT NULL,
  statement_line_id   UUID REFERENCES eclaim_statement_line(id),
  charge_line_id      BIGINT REFERENCES fpa_fact_charge(id),
  
  -- Submitted values
  submitted_charge_code    TEXT,
  submitted_nhso_category  TEXT,
  submitted_adp_type       SMALLINT,
  submitted_adp_code       TEXT,
  submitted_amount         NUMERIC(14,2),
  
  -- Government response (per line)
  approved_amount          NUMERIC(14,2),
  denied_amount            NUMERIC(14,2),
  adjustment_reason        TEXT,
  adjustment_code          TEXT,      -- NHSO denial reason code
  
  -- Status
  recon_status        TEXT DEFAULT 'pending' CHECK (recon_status IN (
    'pending',          -- not yet reconciled
    'matched',          -- amounts match
    'underpaid',        -- approved < submitted
    'denied',           -- fully denied
    'overpaid',         -- approved > submitted (rare)
    'unmatched'         -- can't find matching government line
  )),
  
  variance_amount     NUMERIC(14,2) GENERATED ALWAYS AS (
    COALESCE(submitted_amount, 0) - COALESCE(approved_amount, 0)
  ) STORED,
  
  -- Investigation
  investigated_by     TEXT,
  investigation_note  TEXT,
  investigated_at     TIMESTAMPTZ,
  
  created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Reconciliation Engine

New action in eclaim-connector: action: 'reconcileCharges'

1. Load fpa_fact_charge rows for the encounter
2. Load eclaim_statement_line for the encounter
3. Match charge lines to statement lines by:
   a. Exact match: charge_code + amount
   b. Category match: nhso_category + amount (±5%)
   c. Fuzzy match: charge_date + category (for aggregated lines)
4. Compute variance per line
5. Insert into rcm_charge_reconciliation
6. Flag 'underpaid' and 'denied' lines for investigation

Frontend: Reconciliation Detail Dialog

Extend ReconciliationLedgerDetailDialog.tsx:

  • Per-line table: charge item | submitted amount | approved amount | variance | status
  • Color coding: green (matched), yellow (underpaid), red (denied), gray (unmatched)
  • “Investigate” button per line → opens note dialog, assigns to user
  • Aggregate summary: total submitted vs total approved vs total variance

Dependencies

  • Phase 3 (charges must be submitted with per-line detail for government to respond per-line)
  • Existing: fpa_fact_charge, eclaim_statement_line

Deliverables

  • [ ] Migration 101_charge_reconciliation.sql
  • [ ] Reconciliation engine in eclaim-connector
  • [ ] Enhanced ReconciliationLedgerDetailDialog.tsx
  • [ ] Variance investigation workflow
  • [ ] Automated reconciliation trigger on statement import

Phase 7 — Appeal & Write-Off Workflow UI

Goal: Build the missing frontend for the existing rcm_denial_queue tables so finance staff can action D-cases and P-cases.

Components (in rcm-kit)

7a. Denial Triage Panel

  • Worklist of open D/P cases from rcm_denial_queue
  • Sortable by: variance amount, priority, deadline, denial type
  • Quick actions: Assign, Start Review, Escalate
  • Filters: full_denial vs partial_denial, scheme, date range

7b. Appeal Preparation Dialog

  • Pre-fills encounter context (diagnoses, procedures, charges, original REP message)
  • AI-assisted appeal letter generation (uses coding-ai-assistant edge function)
  • Supporting document attachment
  • Appeal strategy selector: full_appeal / partial_appeal / dispute_claim
  • Review/approval workflow: preparer → reviewer → submit

7c. Appeal Batch Submission

  • Groups appeal-prepared cases into a batch
  • Submits via eclaim-connector with submission_type: 'appeal'
  • Creates new rcm_claim_batch row linked to appeal
  • Updates rcm_denial_queue.appeal_batch_id

7d. Write-Off Approval Workflow

  • Write-off request form: reason (uncollectable/disputed/expired/policy/other), amount, justification
  • Approval chain: requester → department head → finance director
  • On approval: mark workflow_status = 'written_off'
  • GL posting stub: insert into fpa_fact_charge with charge_category: 'write_off' (actual GL integration is Phase 10+)

7e. Denial Analytics Dashboard

  • Denial rate by scheme, department, diagnosis, month
  • Top 10 denial reasons
  • Average time to resolution
  • Appeal success rate
  • Write-off totals by category

Dependencies

  • Existing: rcm_denial_queue table (020_rcm_denial_queue.sql)
  • Existing: coding-ai-assistant edge function (for appeal letter generation)
  • Phase 6 (charge-level detail for appeal evidence)

Deliverables

  • [ ] DenialTriagePanel.tsx
  • [ ] AppealPreparationDialog.tsx
  • [ ] AppealBatchPanel.tsx
  • [ ] WriteOffApprovalDialog.tsx
  • [ ] DenialAnalyticsDashboard.tsx
  • [ ] Denial queue status indicators on DashboardOverviewPanel.tsx

Phase 8 — Fee Schedule & Project Code Engine

Goal: Auto-prompt eligible prevention/promotion services at point of care, and auto-inject project codes per department configuration.

Fee Schedule Trigger

At departments flagged with show_nhso_fee_schedule = true:

  1. When an encounter is opened for billing, load nhso_fee_schedule items matching the department
  2. Check auto_trigger_conditions against patient demographics (age, gender, last service date)
  3. Display eligible items as a checklist: “These services are eligible for this patient today”
  4. Pre-check items not yet billed; gray out items already billed

Department-Level Project Code Configuration

New admin screen at /admin/department-nhso-config:

  • Per department: toggle show_nhso_fee_schedule, select default project codes, set time-window rules
  • Stored in department_nhso_config table (or extend existing department master)
  • This replaces HOSxP’s kskdepartment.show_nhso_fee_schedule flag

Frontend: Fee Schedule Prompt

New component FeeSchedulePrompt.tsx:

  • Renders as a banner/card at top of cashier billing screen
  • Shows eligible items with unit prices
  • “Add to Bill” button per item
  • “Add All” button for convenience
  • Dismissable per visit (stored in encounter metadata)

Dependencies

  • Phase 0 (nhso_fee_schedule table)
  • Phase 2 (auto-tagger provides fee schedule eligibility data)

Deliverables

  • [ ] FeeSchedulePrompt.tsx component
  • [ ] Department NHSO config admin page
  • [ ] Auto-trigger evaluation logic in auto-tagger
  • [ ] Bilingual fee schedule display (Thai + English)

Phase 9 — Revenue-at-Risk Dashboard

Goal: Single-screen executive view showing how much money is being left on the table and where to focus.

Dashboard Sections

9a. Revenue Leakage Waterfall

Gross Charges → Contractual Adj → Discounts → Net Revenue → Collections → Write-offs
                                                               ↑
                                                 show gap here (revenue at risk)

9b. Coding Opportunity Score

  • Per-month aggregate: sum of all estimatedRevenueDelta from revenue optimizer suggestions
  • Split by: accepted vs pending vs rejected
  • Trend line: “Are we capturing more over time?”

9c. Submission Timeliness

  • Distribution of days-to-submission across encounters
  • Late penalty exposure: sum of estimated late penalties per tier
  • Projected savings if all submitted by day 30

9d. Denial Prevention Score

  • Predicted denial rate (from BRE blockers resolved vs ignored)
  • Actual denial rate (from REP responses)
  • Delta: “Denial rate dropped from 12% to 7% since BRE activation”

9e. Auto-Tag Coverage

  • % of encounters auto-tagged vs manually tagged vs untagged
  • Revenue from auto-tagged project codes
  • Fee schedule capture rate: eligible items billed / eligible items available

9f. Per-Scheme Profitability

  • Revenue, cost, margin by scheme (NHSO_UC, SSS, CSMBS, etc.)
  • DRG case mix index by scheme
  • Average adjRW by department

Data Sources

All existing — no new tables needed:

  • fpa_fact_encounter (revenue, cost, margin, DRG, claim status)
  • fpa_fact_charge (charge-level detail)
  • rcm_financial_alert (BRE alert counts)
  • rcm_rep_response (denial rates)
  • rcm_denial_queue (appeal/write-off status)
  • rcm_ai_suggestion_log (optimization suggestions)
  • eclaim_government_sync (submission status)
  • eclaim_statement (payment status)
  • gold_dim_scheme (scheme metadata)

Frontend: RevenueAtRiskDashboard.tsx

New top-level panel in the revenue settlement workspace. Uses:

  • Recharts for waterfall, trend lines, distributions
  • MUI DataGrid Pro for detail drill-down
  • Real-time subscription for live KPI updates

Dependencies

  • Phase 5 (revenue optimizer data for coding opportunity score)
  • Phase 6 (reconciliation data for variance analysis)
  • Existing: all FP&A tables

Deliverables

  • [ ] RevenueAtRiskDashboard.tsx (main component)
  • [ ] 6 dashboard section components
  • [ ] SQL materialized views for KPI aggregation (extend 092_fpa_aggregates.sql)
  • [ ] Cron-refreshed materialized views (register in cron_jobs)
  • [ ] Executive PDF export (via report-template-engine)

Dependency Graph

Phase 0: Charge Masters ──────────────────────────────────────────────
    │                                                                 │
    ▼                                                                 │
Phase 1: TMT/TMLT Seeding ──────────┐                               │
    │                                │                               │
    ▼                                ▼                               │
Phase 2: Auto-Tagging Engine ───── Phase 8: Fee Schedule Engine      │
    │                                │                               │
    ▼                                │                               │
Phase 3: 16-File Generator ─────────┘                               │
    │                                                                 │
    ├──► Phase 4: HIPData Integration (parallel, independent)        │
    │                                                                 │
    ▼                                                                 │
Phase 5: Revenue Optimizer ───────────────────────────────────────────┤
    │                                                                 │
    ▼                                                                 │
Phase 6: Charge Reconciliation ───────────────────────────────────────┤
    │                                                                 │
    ▼                                                                 │
Phase 7: Appeal/Write-Off UI ─────────────────────────────────────────┘
    │
    ▼
Phase 9: Revenue Dashboard (aggregates everything)

Parallelizable:

  • Phase 4 can run in parallel with Phases 2-3
  • Phase 7 can start after Phase 0 (tables exist already), but is enriched by Phase 6
  • Phase 8 can start with Phase 2 (shared auto-tagger)

Estimated Scope

Phase New LOC (est.) New Files New Tables New Edge Functions
0 ~500 SQL + ~800 UI 8 5 0
1 ~400 seed + ~300 UI 6 0 (uses existing) 0
2 ~1200 engine + ~600 UI 8 0 1 (nhso-auto-tagger)
3 ~1500 generator + ~200 UI 5 0 0 (extends eclaim-connector)
4 ~800 connector + ~400 UI 6 1 1 (hipdata-connector)
5 ~1000 engine + ~500 UI 7 0 (uses existing) 1 (revenue-optimizer)
6 ~300 SQL + ~600 UI 4 1 0 (extends eclaim-connector)
7 ~2000 UI 6 0 (uses existing) 0
8 ~600 UI + ~200 config 4 1 (dept config) 0 (extends auto-tagger)
9 ~1500 UI + ~300 SQL 8 0 (uses MVs) 0
Total ~12,700 62 8 3

Priority Recommendation

Must-have for production (Phases 0→2→3): Without charge masters + auto-tagging + 16-file generation, medOS can’t generate compliant NHSO claims from first principles. Everything else is optimization.

High-value quick wins (Phases 5, 7): Revenue optimizer and appeal UI have the most direct revenue impact — they surface money being missed and let staff act on denials.

Nice-to-have (Phases 4, 8, 9): HIPData integration, fee schedule prompting, and the executive dashboard are polish that improve workflow efficiency but aren’t blocking claim generation.


Market Pack Authoring Checklist

When deploying to a new Thai hospital, the minimum data capture from their existing system (HOSxP or other):

  1. ☐ Scheme master (with HIPData verification code)
  2. ☐ Charge category master → nhso_chrgitem_map rows
  3. ☐ Drug master (with TMT codes) → terminology_cache + terminology_concept_map
  4. ☐ Lab master (with TMLT codes) → terminology_cache + terminology_concept_map
  5. ☐ Service master (with ADP Type + Code) → nhso_adp_master
  6. ☐ Department flags (show_nhso_fee_schedule) → department NHSO config
  7. ☐ Project code services → nhso_project_code_rule
  8. ☐ ICD-9/10 with HOMEWARD/IMC overload rows → terminology_cache
  9. nondrugitems.ucef_code markers → nhso_ucef_marker
  10. ☐ Fee schedule items → nhso_fee_schedule
Ask Anything