A pipeline you can read is a dataset you can trust.
Every number on this site was produced by code that anyone can read, run and check. That is not a footnote to the project — it is the design. Healthcare data that is technically public arrives scattered, inconsistent and unchecked; the pipeline's job is to turn that published record into something a person can question, and to leave a trail at every step so the questioning never has to stop at "trust us". The tools to do this used to require an institution. They now fit in a public repository that runs on a laptop, and that shift — the collapsing cost of holding a system's numbers up to the light — is the bet the whole project is built on.
This page is the map: the architecture end to end, the data flow of each analysis stage by stage, and the structure of every table the site serves. The documentation is the manual — the equations, the column-level dictionaries, and the step-by-step guide to reproducing every figure from the open-source repository.
From the published record to a database anyone can question
The system is two analyses sharing one discipline. Each starts from documents the law already requires to exist, collects them with code that records exactly what it fetched, computes every rate from the publisher's own counts, checks the arithmetic, and publishes the results three ways at once — as files, as pages, and as an API. Nothing flows in the other direction: the site can only display what the pipeline produced, so a wrong number on a page is reproducible all the way back to the bytes a payer served.
The whole system, left to right. Data moves one way: from documents the law requires, through open-source code, into artifacts, into one database, onto this site. Navy boxes are code that runs; white boxes are files it writes; the orange-edged box is the database the site reads.
Why it is shaped this way
The design has one governing idea: at no point should a reader have to take a number on faith. So collection records provenance (the exact URL, and for the pricing sources the SHA-256 of the bytes), computation happens in short Python files a non-programmer can read in an afternoon, and the database is loaded from versioned dumps rather than edited by hand. Every stage writes files the next stage reads — there is no hidden state, which means any stage can be re-run alone and its output compared against what is published.
The stack is deliberately boring: standard-library Python, MySQL, PHP on shared hosting, no build step, no external requests. Boring is a feature. A pipeline that needs a cluster to run is a pipeline most people cannot check; this one asks for a laptop and about an hour.
The numbers it carries today
Analysis 01 holds 658 prior authorization filings from 124 organizations across 48 states, with 497 coverage gaps recorded and classified rather than dropped. Analysis 02 holds the three CMS Medicare utilization files, a sample of 3,109 hospitals' mandated price-transparency files, county-level medical debt and health outcomes, and a code dictionary of the tens of thousands of strings the price files claim are codes.
Both analyses publish their misses next to their findings. A dataset that hides what it missed is asking to be trusted rather than checked, and being checkable is the product.
Analysis 01: from 658 scattered filings to one checked table
CMS-0057-F obliges payers to publish prior authorization metrics but sets no format, requires no counts and checks no arithmetic — so each filing is a small act of interpretation, and the pipeline's job is to make every interpretation visible and reversible. The flow below runs in five commands. Each stage reads files, writes files, and prints what it did.
The filings flow. Solid navy stages are the five Python files; every white box is a file on disk that can be inspected between stages. Note the two side-products of merging — conflicts and failures — which most pipelines discard and this one publishes.
Twenty-four collector segments overlap on purpose: several crawlers were pointed at overlapping universes of payers, so a document missed by one pass surfaces in another rather than disappearing silently. The price of overlap is duplication, so merging dedupes in two passes — first on numeric identity (same source URL, same approved and denied counts: two collectors reading the same row produce the same numbers), then on name identity (same URL, contract ID and normalized plan name), which catches filings that published no counts at all. When two independent reads of the same document disagree, the disagreement is not resolved by keeping the prettier row — it is written to merge_conflicts.json and resolved against the source document, because a disagreement between readers is itself evidence about how hard the document is to read.
Every record lands in one flat schema, and this is where the project's central rule is enforced: rates are computed from counts, never transcribed. If a payer published 1,000 standard requests and 214 denials, its denial rate is 21.4% by division, whatever its PDF says. The percentage the payer printed is kept in its own column — std_denial_rate_printed — precisely so the two can disagree in public. A filing that published only percentages keeps them, flagged as unverifiable; its count columns stay null, because "the payer did not say" is a fact, and zero would be a lie.
The part that exists nowhere else. CMS mandates publication but checks no arithmetic, so the 10 consistency rules do what an auditor would: approved plus denied must account for the stated total, the printed rate must match the computed one, overturned appeals cannot exceed appeals filed, expedited decisions should not take longer than standard ones, and rates at the extremes get flagged before anyone quotes them. A rule that fires produces a finding — the rule name, a severity, and the exact numbers that tripped it — never a correction. The pipeline does not fix payers' arithmetic; it reports it. The documentation states each rule as an equation with its thresholds.
Build stamps every filing with its quality flags and writes the two principal artifacts: the metrics CSV and the findings CSV. Gaps does the same honest bookkeeping for the misses: every document the collectors could not read is classified — in code, reproducibly — into blocked (the document exists and resisted machines: robots.txt, HTTP 403, bot walls, scans), missing (a search that came up empty, a finding about the crawl rather than the payer), or not a gap (duplicates, plans with no CY2025 filing to find). The distinction matters because only the first group supports the claim that a filing is being withheld — and conflating the three would have overstated that claim by a factor of five.
The same rows go out in every form someone might actually use: a standalone HTML report that opens without a server, and a formatted two-sheet workbook — Metrics and Validation — for the spreadsheet-first reader. Then tools/ in the web repository turns the CSVs into the versioned SQL dumps this site imports. Nothing is retyped anywhere in that chain; the number in a payer's PDF and the number in this page's HTML are connected by code alone.
Seven files, one command line
The whole filings pipeline is seven short Python files in the public repository, standard library except for the workbook step. Derived files are never committed — they rebuild from the segments in data/, which is the point: if you do not trust an output, delete it and watch it come back.
The full walkthrough — what each command prints, what to check, and how to load the results into your own database — is in the documentation.
Analysis 02: five sources, one manifest, every byte accounted for
The pricing analysis applies the same discipline to a harder collection problem. Medicare publishes clean central files; hospitals publish their mandated price files wherever and however they like, exactly as payers do with prior authorization filings. So the pipeline splits into fetchers that record provenance for every download, parsers per source family, a code dictionary that decides what is even a billing code, and the same validate-and-publish tail as analysis 01.
The prices flow. Everything downstream of fetch.py can be re-run offline against the recorded downloads, and every figure the site shows can be traced through manifest.json to a SHA-256 of the publisher's own bytes.
fetch.py
Every source the analysis reads is declared in one file — the pinned URL for each Medicare file, the CHR and Urban Institute releases, the Census relationship file, and the discovery rule for hospital price files. Fetching writes a manifest entry per download: the exact URL, the fetch time, the byte count and the SHA-256. That last field is the quiet backbone of the whole analysis — anyone can download the same file tomorrow, hash it, and know with certainty whether they are looking at what the pipeline read.
mrf.py
counties.py
Three parser families, one per source shape. The Medicare files are large, clean CSVs — every hospital by every MS-DRG and APC, every state by every HCPCS code — where the only judgment is remembering that CMS suppresses rows under 11 patients, so a missing row means "not published", never "none performed". The hospital price files are the opposite: each is discovered through the cms-hpt.txt pointer the rule requires at the hospital's web root, streamed rather than loaded, and read for every code-like string it carries. Files that cannot be found or parsed are recorded with a reason and classified exactly as the prior authorization gaps are. The county parsers join health outcomes, medical debt and demographics onto one county spine via the Census ZCTA-to-county file.
basket.py
Most strings a hospital's price file calls a code are not codes — chargemaster numbers and fragments that happen to pass a regex. The dictionary gives every string a status by set membership, never by judgment: official if it appears in a CMS list, hospitals only if well-formed and independently listed by two or more sampled hospitals, unverified otherwise — and unverified strings are priced nowhere on this site, because a price for a non-code is not a price. Descriptions are copied from a named source or left blank; the pipeline never writes one, and CPT text outside the Medicare file is AMA copyright and stays blank rather than guessed.
validate.py
Enrichment computes what the sources imply but do not say — charge-to-payment ratios per row, state medians for the comparison basket, system-level sums weighted by discharges — always from figures in the same row or the same declared join, never across an undeclared boundary. Validation then runs the price rules: a cash price above list, a negotiated rate outside the file's own stated bounds, a price file more than a year old, and a hospital whose published list price sits far from the average charge it reported to Medicare for the same admission. As in analysis 01, every miss is a finding with the numbers attached.
The one stage that is not standard library (numpy, pandas, scikit-learn) fits gradient-boosted trees and a ridge regression for three measured county outcomes — medical debt in collections, premature mortality, years of life lost — from prices, insurance, income, age, race, rurality, access and behaviour. Cross-validated, fixed seed, permutation importance, partial dependence. Nothing in it is causal and every figure it produces says so; the Analysis page renders the module's output and nothing else.
Twenty tables, and why each one exists
The database mirrors the pipeline's honesty rules in its shape. Count columns are nullable because "not published" must stay distinct from zero. Findings live in their own table rather than as flags, because they are the product. Every pricing table traces to a row in price_source carrying the URL and SHA-256 of the file it came from. The full column-by-column dictionary is in the documentation; this is the shape.
The shape of the database. Navy-headed tables are the spines — one row per filing, one per hospital; everything else hangs off them or stands alone as reference. "1 ─ n" reads "one row here owns many rows there".
Reading analysis 01's shape
One table per claim the dataset makes. filing is the record — one row per plan-level disclosure, its counts nullable because a payer that printed no counts gets nulls, not zeros. finding is the audit — one row per rule violation, cascading with its filing, holding the exact numbers that tripped the rule so the finding can be re-derived. coverage_gap is the confession — every document the collectors could not read, classified by why. Most datasets ship the first table; the honesty lives in the other two.
Reading analysis 02's shape
Two spines and a dictionary. Geography hangs on county_profile — debt, outcomes and demographics per county — and institutions hang on price_hospital, which joins each hospital to its county, its system, its Medicare rows and, where sampled, its own published price file with every coded charge. price_code arbitrates what counts as a billing code at all. The model_* tables carry the county models' output with their caveats stored alongside, so no page can quote a model without its warning label. Every column of every table is defined in the data dictionary.
What this is, and what it is not
The rule
CMS-0057-F requires Medicare Advantage organizations, Medicaid and CHIP fee-for-service programs and managed care plans, and federally-facilitated exchange QHP issuers to publish annual prior authorization metrics. The first deadline was 31 March 2026, covering calendar year 2025.
There is no central repository, no machine-readable requirement, and the CMS template is optional. Each payer posts a document wherever it likes, in whatever shape it likes. That is why this dataset has to exist.
Collection
658 filings from 124 organizations across 48 states, each traced to the payer's or state agency's own document. Collectors overlapped deliberately, so a document missed by one pass surfaced in another rather than disappearing. Deduplication runs on numeric identity first, then name identity; where two independent reads disagreed, the disagreement was itself recorded and resolved against the source.
Computed versus transcribed
Counts, turnaround values and plan identifiers are transcribed from the source. Every rate is computed from those counts — no rate is estimated or carried over from a payer's own summary. Where a payer printed its own percentage, that figure is stored separately so the two can be compared rather than conflated.
A blank is a blank. 125 filings published percentages with no counts; their count columns are null, not zero. "This payer denied nothing" and "this payer published nothing" are different claims, and only one of them is interesting.
Known limits
Coverage is broad, not complete. 497 gaps are logged, and classified in the pipeline into three groups: 126 documents that exist and could not be read, 304 searches that came up empty, and 67 that are not gaps at all. Only the first shows a filing being withheld from machines. Humana publishes no readable filing; Cigna publishes exactly one. Where a turnaround unit could not be established, the value is null rather than guessed.
Take the data
Where each price came from
The pricing analysis uses the same pipeline discipline with different sources. Each is fetched by pipeline/prices/ in the open-source repository — standard library Python — and every downloaded byte is recorded with its URL, fetch time and SHA-256.
Every file the pricing analysis reads
the files read, as fetched — not our copy of themAs fetched by the pipeline: the exact URL, when, the size and the SHA-256 of the bytes. The 3,109 sampled hospital price files are listed on each hospital's page with the same fields.
- MUP_INP_RY26_P03_V10_DY24_PrvSvc.CSV · 38.0M bytes · fetched 2026-08-21 · sha256 2ab6da15be4c… · publisher's page · public domain (US federal)
- MUP_OUT_RY26_P04_V10_DY24_Prov_Svc.csv · 28.1M bytes · fetched 2026-08-21 · sha256 f293918edbf6… · publisher's page · public domain (US federal)
- MUP_PHY_R26_P05_V10_D24_Geo.csv · 42.1M bytes · fetched 2026-08-21 · sha256 c26956788333… · publisher's page · public domain (US federal)
- july-2025-alpha-numeric-hcpcs-file.zip · 2.4M bytes · fetched 2026-08-22 · sha256 91a9e95e0681… · publisher's page · public domain (US federal); HCPCS Level II is CMS-maintained
- MS-DRG definitions — IPPS final rule Table 5 (FY2025) · CMS · FY2025fy-2025-ipps-final-rule-table-5.zip · 146K bytes · fetched 2026-08-22 · sha256 d5e0a0bd2314… · publisher's page · public domain (US federal)
- County Health Rankings 2025 — national analytic data · University of Wisconsin Population Health Institute · 2025 release (measures 2019–2023)analytic_data2025_v3.csv · 13.1M bytes · fetched 2026-08-21 · sha256 5a129b856489… · publisher's page · free for non-commercial use with attribution; see countyhealthrankings.org
- Debt in America 2025 — county-level medical debt · Urban Institute · August 2025 credit-bureau panelDebt%20in%20America%20County-Level%20Medical%20Debt.xlsx · 396K bytes · fetched 2026-08-21 · sha256 4b2ec3982817… · publisher's page · ODC-BY
- Debt in America 2025 — state-level medical debt · Urban Institute · August 2025 credit-bureau panelDebt%20in%20America%20State-Level%20Medical%20Debt.xlsx · 10K bytes · fetched 2026-08-21 · sha256 343a63cba9ba… · publisher's page · ODC-BY
- Debt in America 2025 — national medical debt · Urban Institute · August 2025 credit-bureau panelDebt%20in%20America%20National-Level%20Medical%20Debt.xlsx · 3K bytes · fetched 2026-08-21 · sha256 c4e5f02de5a9… · publisher's page · ODC-BY
- CDC PLACES — county health measures, 2025 release · CDC · 2025 release (BRFSS 2022–2023 model-based estimates)rows.csv · 4.8M bytes · fetched 2026-08-21 · sha256 a47cad3a852c… · publisher's page · public domain (US federal)
- ACS 5-year 2023 — county age and poverty · US Census Bureau · 2019–2023
- chsp-hospital-linkage-2023.csv · 1.5M bytes · fetched 2026-08-21 · sha256 a86146f10c8d… · publisher's page · public; cite AHRQ Compendium of U.S. Health Systems
- 2020 ZCTA to County relationship file · US Census Bureau · 2020tab20_zcta520_county20_natl.txt · 6.8M bytes · fetched 2026-08-21 · sha256 3ed41278d637… · publisher's page · public domain (US federal)
Every figure on this page is computed from these files by pipeline/prices/; the same rows are in the API.
Consistency rules for prices live in pipeline/prices/validate.py: a cash price above the list price, a negotiated rate outside the file's own stated minimum and maximum, a percentage rate with no dollar estimate, a file more than a year old, and — across two of the hospital's own publications — a list price far from the average charge it submitted to Medicare for the same admission. The Costs page and the CSV read the same findings table.