How it is built
Architecture overview
Revenue cycle management (RCM) is the system that turns care a hospital or clinic delivered into money received. A visit produces charges (what was done), clinicians' notes become codes (diagnoses and procedures in standard code sets), the codes and charges are assembled into a claim and checked against the payer's rules before it is sent, the payer answers with an acknowledgement and later an explanation of payment, the payment is posted against the claim, denials are worked or appealed, and whatever is left becomes a patient bill. The exchange formats are fixed by the HIPAA transaction standards (X12 837 for the claim, 835 for the remittance advice) (CMS: electronic billing). The one idea that makes it work is that a claim is a long-running case, not a message: it has a state, a deadline, and money attached at every step, so the system is built as a state machine with an exact ledger underneath, rules to prevent errors before they cost a denial, and models to decide where human attention pays most.
Parts:
- Charge capture and coding — clinical documents and orders become charges and codes — OCR text recognition, Named entity recognition, Text classification, Transformer block, Terminology and code systems (
terminology-code-systems, no page yet) - Claim build and scrubbing — assemble the claim, check it against payer and code rules — Rete rules engine, Policy engines, x12-edi-messaging (no page yet), Business-day calendars (no page yet)
- Submission and acknowledgement — send, track, retry, park failures — Idempotency keys, Exponential backoff with jitter, Dead-letter queue, Order lifecycle state machine (no page yet)
- Claim work-flow — the long-running case with waits, deadlines and appeals — Durable workflow execution
- Payment posting and ledger — remittances applied exactly, balances proven — Double-entry ledger, Money representation, Priority waterfall allocation (
priority-waterfall-allocation, no page yet), Reconciliation - Denial and payment prediction — which claims will be denied, which accounts will pay — Gradient-boosted trees, Linear and logistic regression, Calibration
- Patient identity and interfaces — who is this, and how the clinical systems talk to us — Record linkage (no page yet), HL7 v2 message parsing (no page yet), FHIR resource model (no page yet), Row-level security
- Platform and assurance — Process supervision, Graceful draining, Primary-backup replication, Point-in-time recovery, Mutual TLS, Deterministic simulation testing, Coverage-guided fuzzing, HDR histogram (no page yet)
registration / eligibility ─► encounter (EHR: HL7 v2 / FHIR) ─► charges + documents
│
OCR + NLP + classifier ──► suggested codes ──► coder review
│
▼
claim builder ──► scrubber (rules, payer edits) ──► ready to send
│
claim state machine / durable workflow ◄── acks (999, 277CA) ◄── clearinghouse ◄── 837
│ ▲ │
│ └──── denial prediction, work queues, appeals ▼ payer
▼ remittance 835 + payment
ledger (double entry) ◄── posting (waterfall) ◄────────────────────────┘
│
▼ reconcile (835 vs bank deposits) ─► patient statement ─► collections
Two clocks matter. The front-end clock (minutes: eligibility, coding while the chart is fresh, scrubbing before the patient leaves) is where errors are cheapest to fix. The back-end clock (days to months: payer adjudication, appeals, patient payment) is where timeliness limits and cash flow are decided.
How the parts fit together
Charge capture and coding
A claim needs codes: diagnoses (ICD-10-CM, with ICD-10-PCS for inpatient procedures), procedures (CPT and HCPCS),
and for inpatient stays a grouping into a diagnosis-related group. The code sets are published and versioned, and
the right code depends on documentation, so coders read the chart and assign them. Computer-assisted coding
shortens that read. Scanned documents (referrals, outside records) enter through OCR text recognition;
Named entity recognition finds the clinically meaningful spans (a condition, a drug, a
procedure, laterality); and Text classification assigns codes to a document as a multi-label
problem. Published research frames it exactly so: CAML uses convolution and label-wise attention over clinical notes
to predict codes (Mullenbach et al. 2018), and PLM-ICD applies pretrained
transformers (Huang et al. 2022); both are trained on the public MIMIC data
(PhysioNet). The Transformer block is the
modern encoder for long notes, with the cost that notes exceed typical input lengths and must be chunked. The
contract with the rest of the system: the model suggests codes with evidence spans and a confidence, a coder
accepts or edits, and only accepted codes reach the claim. Mapping between local and standard codes needs
Terminology and code systems (terminology-code-systems, no page yet): versioned code sets with effective dates,
since a code valid on the date of service may not exist today.
Claim build and scrubbing
The claim builder assembles the 837: professional claims for physician services, institutional for hospitals. The structure is loops of billing provider, subscriber and patient, claim, and service lines, and the formats are X12 transactions (x12-edi-messaging (no page yet); see the X12 transaction set list). Before sending, the scrubber applies edits so errors are caught while the encounter is fresh: missing fields, invalid code or modifier combinations, mutually exclusive procedures (CMS publishes National Correct Coding Initiative edits: NCCI), authorization on file, and payer-specific rules. The edits are many, change every quarter, and differ per payer, which is the shape a Rete rules engine is built for: facts (claim, lines, payer, dates) go in and fired rules come out, with an explanation of each. Where the question is "who may do what, under which condition" (who may override an edit, who may write off a balance), a Policy engine separates the decision from the code. Deadlines are counted in business days and payer calendars, with holidays and weekends: Business-day calendars (no page yet). Timely-filing limits and appeal windows depend on them, and a miscounted day is lost revenue.
Submission and acknowledgement
Claims go through a clearinghouse to the payer. Each hop acknowledges: a syntax acknowledgement (X12 999) says the file was readable, and a claim acknowledgement (277CA) says each claim was accepted or rejected by the clearinghouse or payer (X12 list). The submission part has three jobs. Do not send twice: an Idempotency key per claim submission (claim id plus version) lets a retry after a timeout be recognised, so a lost acknowledgement does not become a duplicate claim. Retry wisely: transport failures retry with Exponential backoff with jitter, so a payer outage does not turn into a storm when it recovers. Park what cannot proceed: a file that fails repeatedly goes to a Dead-letter queue with the error, to be fixed and replayed by a person; the claim is not lost and does not block the queue. The claim's own states (built, scrubbed, submitted, accepted, in review, paid, denied, appealed, closed) form an Order lifecycle state machine (no page yet), with only allowed transitions and a recorded reason for each.
Claim work-flow
Between submission and closure, a claim waits: for the payer (days to weeks), for a missing medical record, for an appeal deadline, for the patient. Durable workflow execution holds that waiting state with timers: check status after 30 days, escalate when overdue, start an appeal when a denial arrives, route to a human queue with a due date. A restart of the system or a deploy does not lose any case, and every step is retried safely because the individual steps are idempotent.
Payment posting and ledger
The 835 remittance advice explains payment per claim and per line: amount paid, and adjustments with a group code
(contractual obligation, patient responsibility, other) and a reason code. The same remittance has to be tied to
the money: the 835 carries a trace number that matches the electronic funds transfer, so reconciliation can show that
every dollar deposited maps to remittances and every remittance to a deposit (reassociation of payment and remittance is covered by the
CAQH CORE operating rules). The posting logic follows three blocks.
Money representation stores amounts as exact integer minor units with a currency, never as
floating point, and rounds in one place. A Double-entry ledger records every change in
balance as balanced entries (charge: receivable up, revenue up; payment: cash up, receivable down; contractual
adjustment: allowance up, receivable down), so any balance can be explained and the books always sum to zero.
Priority waterfall allocation (priority-waterfall-allocation, no page yet) decides how a payment is divided across
service lines and between payer and patient responsibility in a fixed order (for example, line by line in the
payer's stated order, then secondary insurance, then the patient), with leftover cents assigned deterministically.
Then Reconciliation closes the loop: expected versus received, deposits versus postings, with
exceptions (short payments, unmatched deposits, takebacks) as work items.
Denial and payment prediction
Not all claims are equal; models decide where to look. Denial prediction estimates, per claim, the chance the payer will deny it, so the scrubber or a biller can fix it before sending. The data is tabular and mixed (payer, code, modifiers, provider, place of service, authorization status, history): Gradient-boosted trees are the strong default, and Linear and logistic regression is the baseline and the choice where each factor must be explainable to a payer-relations team. The prediction is only useful if it can be used as a probability: Calibration maps the raw score so that "30 %" means about 30 % of such claims are denied, and then the threshold is chosen by cost (a claim is held for review when the expected loss exceeds the cost of a review), not by a fixed score. The same machinery supports likelihood-to-pay for patient balances, which drives statement timing and payment plans.
Patient identity and interfaces
Charges are posted to the right person and encounter. Registration systems create duplicates (name spellings, moves, newborns); Record linkage (no page yet) finds candidate matches, and a wrong link puts another person's balance on a statement, so uncertain links go to a human. The clinical systems send events: admit, transfer and charge posting arrive as HL7 v2 message parsing (no page yet) messages (ADT and DFT types are the usual ones), and newer integrations use the FHIR resource model (no page yet), which has Claim and ExplanationOfBenefit resources (Claim, ExplanationOfBenefit). Many hospitals and practices share one platform, so Row-level security is applied in the database, not just in the application: one tenant's claims are never visible to another's.
Platform and assurance
Money processes cannot lose data or double-act. Process supervision and Graceful draining keep workers alive across failures and deploys; Primary-backup replication and Point-in-time recovery protect the ledger and claim store and allow undoing a mistaken bulk posting; Mutual TLS secures the links to clearinghouses and hospitals. The code is checked by Deterministic simulation testing (timeouts between "sent" and "acknowledged", duplicate messages, crashes mid-posting) and Coverage-guided fuzzing of the X12 and HL7 parsers. An HDR histogram (no page yet) tracks processing latency per stage, such as scrub time and posting time, where long tails show up as delayed cash.
Data and control flow
One claim from charge to cash:
- The EHR sends the encounter and charges (HL7 v2 or FHIR); patient identity is resolved and the account opened.
- Documents are processed (OCR, entity extraction, code classification); the coder sees suggested codes with evidence and accepts or edits; the codes are stored with the code-set version.
- The builder creates the 837; the scrubber's rules fire; denial risk is scored; the claim is fixed, held or released.
- The claim is submitted with an idempotency key; acknowledgements (999, 277CA) arrive and move its state, rejects go to a work queue and failures to the dead-letter queue.
- The workflow waits for adjudication with a timer; the remittance (835) arrives, is parsed, and posted: payment, contractual adjustments, patient responsibility, each as ledger entries in one transaction.
- Denials create work items with reason codes and an appeal deadline (counted in business days); the rest of the balance moves to the patient statement. Cash is reconciled against bank deposits. Time goes to the waits (payer, patient), not to the processing; the front-end steps take seconds to minutes against weeks of payer time.
One remittance (835) posting: parse and validate, find each claim by its id (idempotent: a duplicate file posts nothing new), apply each line's payment and adjustments to the ledger in one transaction, allocate by the waterfall, flag mismatches (paid more or less than expected, unknown claim), and update the claim states.
Scaling
- Coding labour: the first bottleneck is people. Levers: model suggestions with confidence so that high-confidence charts are only audited, and low-confidence ones go to coders; work queues by specialty.
- Denial rework: the second. Levers: prevention (scrubber rules fed by denial analysis), prediction to focus effort, automated resubmission for fixable errors.
- File throughput: thousands of claims and remittances per day per hospital. Levers: batch by payer, partition by tenant, idempotent steps so that workers scale out and retries are safe.
- Ledger write rate: posting is many small transactions. Levers: one database transaction per remittance, partition by account, balances as derived views.
- Payer variance: each payer behaves differently. Levers: per-payer adapters and rules, per-payer rate limits and backoff, latency and denial dashboards per payer.
- Model drift: payer behaviour changes. Levers: scheduled retraining, calibration checks against recent outcomes, and a human fallback.
Failure modes
| Failure | Symptom | Cause | Fix |
|---|---|---|---|
| Duplicate claim | payer denies as duplicate, or pays twice | retry after a lost acknowledgement without an idempotency key | idempotency key per submission, claim versions for corrected claims |
| Wrong code accepted | denials for medical necessity, audit risk | an auto-suggested code accepted without review | evidence span shown, confidence threshold, sampling audits of auto-accepted codes |
| Outdated code set | rejects for invalid code | a code used outside its effective dates | versioned code sets by date of service, scrubber edit on validity |
| Missed filing deadline | denial that cannot be appealed | deadline miscounted (holidays, weekends) or the case stalled | business-day calendar per payer, workflow timers, aged-claim reports |
| Payment applied wrongly | patient billed for an insurer's share, balances do not sum | allocation order wrong, rounding lost cents | waterfall rules in one place, integer money, ledger balance checks |
| Unreconciled deposits | cash in the bank with no matching remittance | missing 835, wrong trace number | reconcile deposit to remittance by trace number, exceptions queue |
| Uncalibrated denial model | too many holds or too few catches | scores read as probabilities without calibration, drift | calibrate on recent data, choose the threshold by cost, monitor |
| Wrong patient link | statement or payment on the wrong person | a loose match in registration | high-threshold linking, review queue, undo with history |
| Poison file | queue blocked by a malformed remittance | parser error on one file | dead-letter queue, replay after a fix, fuzzed parsers |
| Tenant data exposure | one hospital sees another's claims | application-only filtering | row-level security in the database |
Real implementations
| name | what it is | repo | licence | notes |
|---|---|---|---|---|
| OpenEMR | open EHR with billing that produces X12 claim files | openemr/openemr | GPL-3.0 | the open system closest to a full small-practice RCM |
| pyx12 | Python X12 validator and translator, including HIPAA transaction sets | azoner/pyx12 | see repo | for parsing and validating 837 and 835 files |
| HAPI FHIR | Java FHIR implementation with Claim and ExplanationOfBenefit resources | hapifhir/hapi-fhir | Apache-2.0 | for the FHIR side of the interface |
| Medplum | open FHIR-based healthcare platform | medplum/medplum | Apache-2.0 | a platform, not a billing system |
| medspaCy | clinical NLP built on spaCy | medspacy/medspacy | MIT | a building block for entity extraction from notes |
| CMS NCCI edits | published code-pair edits used by scrubbers | cms.gov | public | quarterly updated reference data |
| CAQH CORE operating rules | rules for transaction timing and content | caqh.org | public rules | e.g. for acknowledgements and remittances |
| Epic Resolute, Waystar, R1 RCM, Change Healthcare (Optum) | provider and vendor revenue-cycle platforms and clearinghouses | epic.com, waystar.com, optum.com | proprietary | named in the goal; no public source |
The alternative: for a small practice, the billing module of an EHR plus a clearinghouse portal is enough, and the prediction and workflow parts are left out. For pharmacy the claim goes in real time as an NCPDP transaction rather than as an 837 file, see the pharmacy management system page. For private-pay care (no payers) the whole claim and remittance side disappears, leaving statements, the ledger and collections.
Sources
- CMS, HIPAA electronic billing and transaction standards: cms.gov.
- X12 transaction set list (837, 835, 277, 999): x12.org.
- CMS, National Correct Coding Initiative edits: cms.gov; CMS, ICD-10: cms.gov.
- CAQH CORE operating rules: caqh.org.
- J. Mullenbach et al., Explainable Prediction of Medical Codes from Clinical Text, NAACL 2018: arXiv:1802.05695.
- C.-W. Huang, S.-C. Tsai, Y.-N. Chen, PLM-ICD: Automatic ICD Coding with Pretrained Language Models, 2022: arXiv:2207.05289.
- MIMIC-III clinical database: physionet.org.
- HL7 FHIR: Claim, ExplanationOfBenefit.