A revenue-cycle dashboard for a hospital-system-owned ambulatory practice, for a Quality/PI and finance committee: clean claim rate and denial rate (by claim count and by dollars), a denial-reason breakdown, a payer-mix comparison, and net collection rate trended monthly.
Synthea does not model claim adjudication, so every denial outcome, denial reason, and collection figure on this page is synthesized - see the scope panel below before citing any number.
Claims are built one-per-encounter from
encounter_class IN ('ambulatory', 'wellness') rows of a Synthea CSV export
(https://github.com/synthetichealth/synthea). Billed amount and
allowed amount come straight from the encounter's cost fields; everything about
whether a claim was denied, why, and how much was collected is this project's own synthetic model,
tunable from Edit Settings.
Data covers service dates from 2025-09 through 2026-08.
"Allowed amount" is Synthea's PAYER_COVERAGE field,
relabeled. Synthea computes it as its own internal cost-sharing figure; this report
repurposes it as a stand-in for a payer's contracted allowed amount. It is not a real
payer-contract lookup, and Synthea gives no clean split between contractual write-off and patient
responsibility.
Denial occurrence, denial reason, and collection rates are entirely
synthetic. Synthea models charge/payment/transfer flows but has no concept of a payer
denial, a CARC reason code, an appeal, or an adjudication outcome. This report does not read
Synthea's claims_transactions.csv; it assigns each claim a clean/
denied status from an author-chosen base denial rate per payer category (not sourced
from MGMA/HFMA or any real benchmark) and models collections from that. The base rates and every
other knob are on the Edit Settings page.
Self-pay / uninsured encounters are excluded from claims entirely - there is no third-party adjudication to model for them.
Clean claim rate is the share of claims accepted on first submission. The denial rate is shown both ways because a payer that denies many small claims and one that denies a few large ones are different problems.
| Reason | Denied claims | Share of denials | Denied billed $ | $ share |
|---|---|---|---|---|
| Missing/incomplete information | 183 | 26.9% | $100,740.36 | 25.5% |
| Missing prior authorization | 154 | 22.6% | $97,755.07 | 24.7% |
| Diagnosis/procedure code mismatch | 129 | 19.0% | $78,649.51 | 19.9% |
| Not deemed medically necessary | 109 | 16.0% | $70,998.95 | 18.0% |
| Timely filing limit exceeded | 105 | 15.4% | $47,042.92 | 11.9% |
Payer-mix-driven denial variance is a real PI-committee conversation - here the drivers are the author-chosen base rates, not observed payer behavior. The Medicaid row includes Dual Eligible.
| Payer category | Claims | Denial rate | Net collection rate |
|---|---|---|---|
| Medicare | 6262 | 6.1% | 92.6% |
| Medicaid | 1157 | 13.1% | 91.5% |
| Commercial | 1642 | 8.9% | 92.9% |
Net collection rate is actual dollars collected ÷ allowed amount (expected reimbursement) over the period - a truer profitability signal than gross charges. Claims with an allowed amount of $0 (full patient responsibility / deductible) are excluded from this denominator.
The model applies a small per-claim chance of a later downward adjustment (recoupment) to clean claims - fabricated variance, no claim to represent real payer recoupment patterns. Without it, net collection rate would have been 93.4%; the adjustments (319 claims, $45,984.24) drag it down 1.0 pts to the headline 92.3%.
3158 claims (34.9% of claims, 18.3% of billed dollars) have a high modeled patient-responsibility share. This is tracked alongside the denial rate, not counted in it - a patient balance is not a payer denial.
Rule-based flags only: a swing is listed when it clears a configured threshold (Edit Settings). This reports the size and direction of a change - it does not attempt to explain the cause.
| Month | Metric | Change | n |
|---|---|---|---|
| 2026-07 → 2026-08 | Denial rate | Denial rate rose +5.6 pts | 69 |
| 2026-07 → 2026-08 | Net collection rate | Net collection rate fell -4.9 pts | 69 |
| 2026-07 → 2026-08 | Claim volume | Claim volume fell -41% | 69 |
| 2026-05 → 2026-06 | Denial rate | Denial rate fell -3.9 pts | 123 |
| 2026-05 → 2026-06 | Net collection rate | Net collection rate rose +4.7 pts | 123 |
| 2026-04 → 2026-05 | Net collection rate | Net collection rate fell -8.2 pts | 113 |
| 2026-02 → 2026-03 | Denial rate | Denial rate fell -3.3 pts | 120 |
| 2026-01 → 2026-02 | Claim volume | Claim volume fell -23% | 112 |
| 2025-12 → 2026-01 | Denial rate | Denial rate rose +4.3 pts | 145 |
| 2025-10 → 2025-11 | Net collection rate | Net collection rate rose +5.3 pts | 113 |
| 2025-10 → 2025-11 | Claim volume | Claim volume fell -20% | 113 |
| 2025-09 → 2025-10 | Denial rate | Denial rate fell -3.3 pts | 142 |
claim_status = 'clean' - accepted on first submission, no denial
assigned by the synthetic model.PAYER_COVERAGE, relabeled - a single modeled figure, not a payer-contract
lookup. Synthea does not split contractual adjustment from patient responsibility.This report follows the Readmission Effectiveness Report's module pattern - its
own SQLite file (data/revcycle.db), its own config, its own Synthea loader, its own
admin area - rather than the ER Effectiveness Report's piggyback pattern. The reason: the
denial/collections layer is a stochastic, config-tunable model that needs an explicit load or
"Regenerate claim outcomes" step, and fabricated-data generation should not live inside a loader
whose contract is faithful reshaping of Synthea.
payers.csv and payer_transitions.csv (Synthea emits
both by default). No new Synthea modules.claim_status, denial reason, and all
collection fields are assigned by app/healthcare/revcycle/denial_model.py under a fixed RNG
seed, in encounter_id order so a reload reproduces the same outcomes.
payer_transitions.csv is used only as a load-time validation cross-check, never a
per-row join.Python 3, Flask, SQLite, Jinja2, Chart.js, python-dotenv, gunicorn, and pytest. The synthetic-EHR source data is built offline with a local JRE and a Synthea checkout (generation-time only).