Feature-complete with the legacy viewer

Read 837 claims as the forms they describe.

ClaimTracker turns raw X12 837 claim data into print-accurate UB-04 and CMS-1500 forms that you can search and browse. It is a modern Django replacement for an aging internal claim viewer, built to show every code that was submitted.

Rendered as
UB-04 · INSTITUTIONALSample data
8 Patient nameSAMPLE, JANE
4 Type of bill0131
6 Statement covers period030126 030426
42 Rev cd0250
43 DescriptionPHARMACY
47 Total charges1,240.00
66–67 Principal & other diagnosis codes
J189E119I10N179Z794
Illustrative, synthetic values only
81 + 33numbered boxes rendered on the UB-04 and CMS-1500
~124legacy report fields, each ported to its original box
3 waysto find a claim: Claim ID, Localuse #, or QNXT claim #
0schema changes or writes to the source database

Features

Everything the old viewer did, plus the codes it dropped.

ClaimTracker matches the legacy tool's day-to-day workflow. It also fixes the places where that tool hid data from the people reading claims.

Print-accurate forms

UB-04 and CMS-1500 layouts were generated straight from the legacy report definitions, with the real column widths and row heights in inches. They print the same way they look on screen.

Browse by date

Pick a day on the calendar, then a file, then a pay-to provider, then a member. Each dropdown fills in from the one before it, and you can page through members with Prev and Next.

One search box

Type a Claim ID, a Localuse number or a QNXT claim number. ClaimTracker tries each one in turn, opens the match and moves the calendar and dropdowns to where that claim lives.

Adjudication status inline

A side panel shows the selected claim's QNXT claim number, status, clean date, OK-to-pay date and paid date. It loads separately, so a slow lookup never holds up the form.

Recovers dropped codes

The legacy loader silently drops some diagnosis and procedure codes. ClaimTracker detects the gap and rebuilds those codes from the original 837 file. Every DX, ECI and procedure code then appears on the form.

Fast by design

Claim queries run in parallel, and recently opened claims are cached in memory. The calendar and dropdowns come from a snapshot that is rebuilt every night, so the page appears before the claim has finished loading.

How it works

From flat EDI text to a form a person can read.

An 837 claim is stored as positional columns such as E_2300_CLM01, which nobody can read at a glance. ClaimTracker is the read side: it assembles those columns back into a claim.

837 files arrive

Providers submit institutional (837I) and professional (837P) claims as delimited X12 text.

Upstream

Loaded to SQL Server

A scheduled parser writes each segment into repository tables, one column per element position.

Upstream · to be absorbed

Reassembled

A raw-SQL service layer combines data across loops, splits a single diagnosis table by qualifier and builds the bill type.

ClaimTracker

Rendered

The result lands in the real numbered boxes: UB-04 for 837I claims and CMS-1500 for 837P claims.

ClaimTracker

Design principles

Built for healthcare data, where wrong guesses are costly.

Claims contain protected health information, and the source system is decades of accumulated behavior. ClaimTracker is careful by default.

  • Read-only, schema untouchedThe Django models mirror the existing tables as unmanaged. No migrations ever run against the source database.
  • Faithful ports, flagged quirksEach legacy query is ported one-to-one. Odd behavior is marked with a QUIRK comment and kept until someone checks it against real claims.
  • PHI-aware loggingStructured JSON logs record identifiers only, never claim contents. Errors trigger email alerts.
  • Access control readyMutual-TLS client-certificate authentication and a per-claim access audit log are built. They are switched off until certificate issuance is available.
-- QUIRK: preserved from the legacy query. -- Almost certainly meant AND, but the legacy -- screen's output depends on it. Verify on a -- known claim before "fixing". CASE WHEN E_2400_SV105 IS NOT NULL OR E_2400_SV105 != '' THEN ...

A real example from the service layer. When ClaimTracker changes how the legacy tool behaved, it does so on purpose and records why.

Roadmap

Where the project stands.

The viewer side of the legacy tool can now be retired. Next comes taking over the loader that feeds it.

Done

  • Search and browse at parity with the legacy tool
  • Print-accurate UB-04 and CMS-1500 forms
  • Full diagnosis, procedure, ECI and condition code display
  • Live QNXT adjudication status
  • Nightly browse snapshot, structured logging and alerts
  • Dark mode

Next

  • Production deployment and hosting
  • Test suite against known UB and HCFA claims
  • Turn on certificate-based access control

Planned

  • Take over 837 ingestion from the legacy loader
  • A manual re-run when a load fails
  • Fewer hand-edited files before a load succeeds

Built with

A small, boring stack.

PythonDjangoSQL Server · mssql-django SQLite read-modelWaitressCaddy usaddressPlaywright (layout checks)

Explore the code.

The repo's AGENTS.md records each decision, quirk and open question.

Open on GitHub