Showcases 1: Claims on a Synthea Population
Requires Pro (FlatFile) — a Free-tier version of the same idea is Showcases 3
Problem statement
You’re testing a claims pipeline — adjudication, pricing, an 837 ingest, a reserving model. It needs two things that pull in opposite directions: members who look like a real population, and far more claims than any real population would hand you.
Real member data solves the first and fails the second, because it’s PHI and it isn’t leaving its controlled environment to sit in your CI. Synthea solves the first beautifully — it’s MITRE’s open-source patient generator, with disease modules built by clinicians and calibrated against CDC and NIH statistics — but it can’t effectively solve the second.
Synthea does export claims. What it can’t do is separate claim volume from population size. Every claim exists because Synthea simulated the encounter that produced it, so the only way to get more claims is to simulate more patient lifetimes. We measured 45 patients producing 4,687 claims; a million claims means simulating on the order of ten thousand-plus people, which took about three hours at the rate we measured.
That’s not a criticism — Synthea is modeling decades of medical history per person, which is a far harder computation than emitting a single patient row. It’s just a different job from the one you need here.
So use Synthea for the half it’s extraordinary at, and SpiceGrinder for the half it isn’t built for.
The model
Generate a population with Synthea once, then project patients.csv down to the handful of columns a claim line actually needs — member id, birthdate, gender, state — and commit that file. It’s now an ordinary repo artifact: reviewable, diffable, and fixed, so nothing downstream depends on re-running Synthea.
FlatFile samples a row from that pool. The important property is that it draws a whole row atomically: the member id, birthdate, gender, and state on any given claim line always belong to the same person. There’s no join to get wrong and no consistency pass to remember afterward.
The claim economics are the same Poisson and Lognormal pairing from Claims 1 — count and amount — and an Append joins the member to the claim, exactly as Claims 2 did with the bundled person model. The only thing that changes here is where the member comes from.
<?xml version="1.0" encoding="UTF-8"?>
<dataset seed="42">
<root node="ClaimLine"/>
<nodes>
<Append name="ClaimLine">
<input name="Member"/>
<input name="ServiceUnits"/>
<input name="BilledAmount"/>
</Append>
<FlatFile name="Member" file="member-pool.csv" mode="unweighted" skipHeader="true"/>
<Poisson name="ServiceUnits" lambda="2.5"/>
<Lognormal name="BilledAmount" mu="6.4" sigma="0.9"/>
</nodes>
</dataset>
Run it (samples/pro/health/claims-from-synthea.xml, from that directory so the relative pool path resolves) and each row is a real Synthea member with claim economics you chose:
4aaa0001-3832-cc52-e2f3-47aad08f4284,2006-01-17,M,Massachusetts,4,269.411696423658
6f45f2e9-a617-570a-8867-dec1260088bb,2013-06-07,M,Massachusetts,2,393.7695537208169
ba419d35-0dfe-8af7-347c-eebf02485a56,1925-08-30,F,Massachusetts,7,1178.44857884471
f32a088b-988b-d7d5-008b-89ea4ed1fc48,2006-07-27,M,Massachusetts,1,207.4297691008044
46976cf7-b0bf-be20-39a5-9f425a52886d,2007-08-25,F,Massachusetts,1,1510.9624542853426
Members recur across claim lines the way they do in a real book of business, because the pool is sampled randomly.
Why this shape works
Generate at build time, read at generate time. Synthea runs once and produces a file you commit. It’s tempting to wire a generator directly to a live data source instead, but that costs you reproducibility — ServiceCall against a live endpoint isn’t deterministic unless the service is. A committed pool is a fixed input, so the same model and seed produce the same bytes forever. If you don’t need that determinism, then the “live” source would work just fine.
Project the pool before you use it. patients.csv has 28 columns; a claim line needs four. Carrying 24 unused dimensions is needlessly inefficient, and several of Synthea’s columns won’t survive the trip anyway — ZIP and FIPS are numeric-looking strings with leading zeros, and 01109 arrives as 1109.0 from CSV regardless of how you quote it. Filter out what you don’t need, and zero-pad downstream anything you do.
Volume and population are now separate knobs. The same 40-member pool produced a million claim lines in 1.6 seconds. Want a heavier book? Change lambda. Want a catastrophe year? Change mu. Want more distinct members? Regenerate the pool. None of those three decisions forces the other two, which is the whole point of splitting the problem this way.
As always, lambda="2.5" and mu="6.4" sigma="0.9" are examples. They’re yours to set — that’s the lever that SpiceGrinder gives you and that Synthea can’t.