Skip to content
back to selected work

Bidirectional 837

C#.NET 10TypeScriptReactEF CoreAzure App ServiceBicepVitest

ASC X12 837 translator · governed both ways

LIVE

Since I've been working in insurance, I always thought it would be fun to try to make my own 837 translator. This one was built entirely from free and open-source material: providers come from the public NPI registry, and medical codes and prices from published CMS fee schedules. Patient names are synthetic, so no real person appears anywhere in it.

An 837 is the electronic claim a provider sends a payer. This goes both ways: it generates synthetic bills and serialises them to ASC X12 837 Professional, and it reads 837 files back into the same schema. The requirement joining those two halves is that a file read in and written back out is the file that went in, byte for byte.

At the very bottom of this is just a simple format translation, and translation between formats is a classic problem in computer science. The domain is what makes it interesting to get wrong: a parser that drops a trailing zero produces a file that is still well-formed, still passes every downstream check, and is off by a cent. Nothing tells you. That is why the round-trip is the requirement rather than a nice property — it is the only assertion that catches a translation which lost something and stayed plausible.

I built it against a governance document written by a different model, and treated it as a binding contract rather than a suggestion — partly to see whether I could stand working that way. Where I had to break its letter, the departure is an entry in a decision register with the reason, and a test that fails if the code and the register drift apart. Thirty-two decisions and twenty-three findings later, that register is the thing I would keep if I had to throw the rest away.

▶ play walkthrough
~40s through the live app, and it is one round trip rather than a tour. Ten bills are generated, the table is scrolled across all nine governed columns, and the batch leaves as an 837 archive. That same archive is then uploaded on the other tab and rebuilt into the schema it came from — so the file going in is the one you just watched come out, which is the only reason the verdict at the end means anything.
~/the-same-ten-claims

The same ten bills, twice — taken out of one session through the application's own two buttons. Open the CSV to read them, then open an interchange from the archive and find the same claim on its CLM segment. The archive is what a clearinghouse would receive.

Example data. Real providers and real published charges, invented patients, and no bill in either file describes anything that happened.

~/the-data

Nothing in it is invented that could have been real. I downloaded the 1.1 GB NPPES public file to avoid making a network call per claim, and distilled 3,120 providers out of it, each with a check-digit-valid NPI. 980 procedure codes, every one priced by a published CMS fee schedule — which is the ordering that matters: a code enters the catalogue because a schedule prices it, so there is no list of codes to discover holes in later. CPT is excluded throughout, because it is AMA copyright, and a test fails the build if a five-digit code ever reaches the catalogue.