Triaging Claim Denials, and Designing for the Moment It's Wrong
Healthcare revenue-cycle teams drown in denials. A payer rejects a claim, an EOB comes back with a terse reason code and a line of free text, and a human has to read it, figure out the real root cause, and decide what happens next — appeal it, fix the coding, re-verify eligibility, or write it off. It is high-volume, repetitive judgment that maps almost perfectly onto an LLM — if you can do it reliably enough to sit inside a clinic's billing operations, which means retries, audit, and never confidently doing the wrong thing on a patient's claim.
What we built
We built DenialDesk, a focused slice of that workflow on the same stack a healthcare-ops platform would actually use: Django with Celery and Redis over Postgres on the backend, Next.js with React and TypeScript on the front. A claim arrives at a webhook, gets persisted and acknowledged immediately, and a background Celery job calls an LLM to classify the denial into structured JSON — a reason class (auth/precert, eligibility, coding error, missing info, timely filing, non-covered, or other), a root cause, whether it is appealable, a suggested next action, and a confidence score. The result lands on a claims worklist, and every state change is recorded on an append-only audit trail you can replay.
The hard part: the failure modes, not the happy path
Anyone can wrap a model in a prompt. The work that matters here is everything around it.
- Retries. LLM APIs return 429 and 503 under load. The classify job retries transient failures with exponential backoff and, only after it has genuinely exhausted them, marks the claim
failed— a clean terminal state, not a crashed worker. The same instinct drives the 503/429 retry-and-model-fallback in our production streaming work at demo.levelbrook.com/relay. - The abstain path. A confidently wrong denial classification is worse than no answer — it could route a claim to the wrong appeal or the wrong patient contact. So the model returns a required
confidence, and below a threshold the claim parks inneeds_reviewand triggers no automated action. Designing for "I don't know" first is the line between a production tool and a demo. - Audit and PHI-free logging. Every transition — received, queued, classified or needs-review or failed, retried — is an immutable
claim_eventsrow, so the claim's history is derived, never mutated. Structured logs carry a job id, claim id, and status, and deliberately no patient text. That is the baseline posture you want before anything touches real PHI.
The model call is the part that takes an afternoon. The retries, the abstain gate, and the immutable audit are the part that takes the project — and the part that decides whether a billing team can trust it.
Architecture
- Webhook ingest. Validates the payload, idempotent-upserts the claim on
(payer_id, claim_id)so a re-delivered webhook is a no-op, returns a fast202, and enqueues the classify job. It acks immediately and does the work asynchronously. - Worker. A Celery task forces the Anthropic Messages API to a JSON schema via a tool, so the output is structured and validated before it is trusted; it retries transient errors and writes an event per transition.
- Data model. A
claimstable, tenant-keyed by payer, and an append-onlyclaim_eventstable. The worklist and the per-claim timeline are both reads over that. - LLM. Runs entirely server-side in the worker — the API key never reaches the browser.
What this example is really about
DenialDesk runs on fake claims only, is single-tenant, and is not a HIPAA-certified system — and we say so. But the seams that make such a system trustworthy — idempotency, retries, abstain, immutable audit, PHI-free logs — are the real ones, built deliberately, on a stack that mirrors theirs end to end. The lesson generalizes past healthcare: when a model's output drives a consequential action, the engineering worth paying for is the machinery that decides when not to act on it.