Skip to content

The boring part, done properly.

Public data is published, not served. The distance between a file on a government website and a query you can build on is where all the work is — and where every one of these numbers comes from.

One ingestion run

What actually survives the trip

These are the figures from the current dataset, not an illustration. The gap between the first number and the last is the product.

  1. Published by CMS116,182

    raw rows in the source file

  2. Carrying usable values63,518

    52,664 suppressed below the disclosure threshold

  3. Distinct facilities2,993

    across 49 states and DC

  4. Procedure groups68

    APC classifications with at least one usable row

55% of what the publisher ships is usable. Knowing which 55% — and being able to say why the rest went — is the difference between a dataset and a download.

Before and after

One row, both ways

Left is what the publisher emits. Right is what you query. Nothing is invented in between — names are cased, numbers are rounded to whole dollars, and blanks become explicit nulls.

SourceCMS, 30 columns
Rndrng_Prvdr_CCN        "010001"
Rndrng_Prvdr_Org_Name   "SOUTHEAST HEALTH MEDICAL CENTER"
Rndrng_Prvdr_City       "DOTHAN"
Rndrng_Prvdr_State_Abrvtn "AL"
Rndrng_Prvdr_RUCA       "2"
APC_Cd                  "5072"
APC_Desc                "Level 2 Excision/ Biopsy/ ..."
Avg_Tot_Sbmtd_Chrgs     "9637.6640957447"
Avg_Mdcr_Alowd_Amt      "1365.3702659575"
Outlier_Srvcs           ""
Vemon9 fields, typed
{
  "facility": "Southeast Health Medical Center",
  "ccn": "010001",
  "city": "Dothan",
  "state": "AL",
  "apc": "5072",
  "procedure": "Level 2 Excision/Biopsy/...",
  "submitted_charge": 9638,
  "medicare_allowed": 1365,
  "source": { "data_year": 2024 }
}

Abbreviated column names, screaming caps, float strings, and empty strings for missing values — every one of those is a decision someone has to make before the data is usable. We make them once.

Pipeline

Five stages, every dataset

The same path runs for healthcare charges and will run for the next source without modification. The pipeline is the product; no single dataset is.

  1. 1

    Collect

    Scheduled fetch from the publisher's own endpoint. Checksummed, resumable, versioned on arrival. The raw file is never modified — it is the evidence.

    116,182 rows fetched
  2. 2

    Parse

    Publisher formats vary — JSON, CSV, XML, fixed-width, and one-off dialects of each. Every source gets a parser; every parser emits the same internal shape.

    30 source columns
  3. 3

    Validate

    Row counts, type conformance, null rates, and range checks against the previous version. A dataset that fails validation does not replace the one already serving.

    52,664 rows rejected
  4. 4

    Normalise

    Names, casing, codes, and identifiers reconciled to canonical forms so one hospital is one row rather than four near-misses.

    2,993 facilities resolved
  5. 5

    Serve

    The result publishes to the API, SQL, and search surfaces at once, with its provenance attached to every response.

    63,518 rows queryable

The hard part

What actually breaks

Not hypotheticals. Every one of these came out of loading the dataset currently serving, and every one would have silently corrupted a result.

Rows that arrive empty

45% of published rows come back with every value blank — CMS suppresses cells below its beneficiary threshold. They must be dropped, not imputed, and dropping them changes what the remaining data can support.

Coverage holes that look like bugs

Maryland is absent entirely. Its hospitals are paid under a state all-payer waiver rather than Medicare's outpatient system, so they never enter the file. Coverage is 49 states and DC — a pipeline that silently reported 50 would be wrong.

Catalog entries with nothing behind them

Indexing facilities and procedures before filtering produced 133 facilities and 4 procedure groups that existed in the catalog but could never return a price. Order of operations, not data quality.

Identifiers that move

Facility names change with mergers and rebrands. The CCN does not, so it is the join key — matching on name would silently split one hospital into several.

Each of these is a day someone does not spend on their own product. That is the entire argument for the platform.

Surfaces

Four ways into the same data

One key, one schema, one set of errors. Learn the API and the rest follow.

Vemon API

Live

REST with predictable pagination, filtering, and error types. Unknown parameters return 400 rather than being ignored, so a typo fails loudly.

curl https://api.vemon.io/v1/healthcare/prices?procedure=5372 \
  -H "Authorization: Bearer vm_live_…"
Details →

Vemon Search

Live

Substring matching across facility names, cities, and procedure descriptions, ranked with exact prefixes first.

curl https://api.vemon.io/v1/search?q=tampa \
  -H "Authorization: Bearer vm_live_…"
Details →

Vemon SQL

Soon

Read-only SQL against the normalised tables, including joins across datasets once there is more than one to join.

SELECT * FROM healthcare_prices WHERE apc = '5372'
Details →

Vemon AI

Soon

Natural-language questions that return the generated query alongside the answer. If you cannot see the query, you cannot trust the answer.

curl https://api.vemon.io/v1/ai/ask -d '{"q":"widest spread?"}'
Details →

Scale

What comes next is much bigger.

Medicare outpatient data is a few hundred thousand rows. The next dataset is not.

Payer negotiated rates are published under the Transparency in Coverage rule as machine-readable files — deeply nested JSON, provider references split across separate documents, index files pointing at tens of thousands of others. A single large payer publishes on the order of tens of terabytes every month, running to billions of individual rate records.

That is the shape the ingestion path is being built toward: streaming parsers rather than whole-file loads, incremental diffs rather than full replacements, and per-payer versioning so a result from last quarter still reproduces.

None of that is running today. Today is one dataset, 116,182 source rows, refreshed by hand. Saying otherwise would be the easiest thing on this page to disprove.

Today

What is actually running

An inventory rather than a roadmap. The stages above describe the design; not all of them are automated yet.

Live datasets
1
Source rows ingested
116,182
Jurisdictions
50
Refresh
Manual

Scheduled refresh, automated validation, and dataset versioning are designed but not yet running. This page will stop saying so when they are.