Canonical source: docs/claude/insurance-rcm.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.
Insurance / RCM Pipeline (Stedi)
Living reference for the insurance billing + revenue-cycle-management (RCM) pipeline. Update this as the pipeline evolves. Public brand = "Ready Practice"; AI = "Atlas". Clearinghouse = Stedi (JSON-first, "programmable clearinghouse"). Base host:
https://healthcare.us.stedi.com. Auth = raw API key inAuthorizationheader (NOT Bearer).
1. Business model & pricing
Model A (enablement). We charge a % of ERA-confirmed net insurance collections — i.e. metered on the 835 paid amount, only after the payer actually pays (not at submission, not at 277CA acceptance). Charged via the existing Stripe platform-fee path. No fund custody (payer → clinic bank direct).
Rate must match the service tier (this is the key nuance — don't sell 2.75% for a pipe):
| Tier | What's included | Price |
|---|---|---|
| Clearinghouse pipe | Transmit 837P, route 999/277CA/835. No denial/AR work. | per-claim (cents), NOT a % |
| Tech-enabled / AI-assisted RCM ← our target | Pipe + eligibility + scrubbing + tracking + AI denial triage + AR follow-up + AI-drafted appeals (clinic approves) + auto-resubmit + metering | ~2–3.5% of collections |
| Full-service RCM (staffed) | All of the above + humans working every denial/appeal by hand | 4–7% |
- The current model memo (CLAUDE.md) tiers by volume: 3.0% ≤$25k · 2.75% $25–100k · 2.5% $100–250k · 2.0% $250k+ · min $300/mo · payer fees at cost. Add a service-level dimension on top.
- Earn the % before charging it. The 2.75% is defensible only once the AI automation (denial triage, AR, appeals, resubmit) is live — that's the labor a billing company charges 5–7% for. Until then, price as clearinghouse-tier (per-claim).
- Credentialing/contracting is NOT bundled in the %. Getting a provider in-network is a separate one-time administrative process (could be a done-for-you add-on). The clinic must already be credentialed to bill.
- AI-assisted appeals are the software middle tier: Atlas drafts the appeal (reads CARC/RARC + pulls the note/superbill) → clinic approves → send. We staff nothing. Only "we file/call payers" = 4–6% (a services business, out of software scope).
The promise (positioning): "We make it super simple to submit pre-authorized / low-denial-likelihood claims; we learn from your clinic, your claims, and the market to maximize billable revenue and reduce denials; and we use AI to capture as much of those claims as possible."
2. Architecture / what's built
Backend (basis-functions/functions/src/)
functions_stedi.py— callables:stedi_check_eligibility(271, demo clinic uses TEST key),stedi_insurance_readiness,stedi_validate_codes,billing_charge_intel,set_billing_code_rate,stedi_search_payers,stedi_scan_insurance_card,stedi_submit_claim,stedi_webhook(on_request).stedi_client.py— HTTP wrapper (eligibility, claims, 271/835 parsing).StediError(status, body).stedi_claims.py—build_837p_claim(...)pure mapper (auto-balances, ABK principal dx, units→serviceUnitCount, EIN as employerId, 9-digit ZIP pad, modifiers).stedi_scrubber.py—scrub_claim()pre-flight (charges, SNIP-3 balance, principal dx, ICD-10 billability/non-billable block (#26), NCCI PTP hints, MUE caps, member/NPI/taxId).code_validator.py— validates ICD-10 (bundled CMS FY2025generated/icd10cm_codes.json+_descriptions.json+_nonbillable.json) & CPT/HCPCS (clinic catalog + curated)._extract_codestrips scribe"M54.5 - desc [VERIFY]"→M54.5.charge_intel.py— per-code {medicare, suggested (2× Medicare), priorCharge, learnedAllowed, patientShare}. Pluggable: full MPFS drops intogenerated/medicare_mpfs.json.insurance_readiness.py— shared readiness validator (#20).functions_scribe.py—sign_note,finalize_note_codes(note codes → billingCodes → superbill trigger), pluscodeValidationon scribe notes (#26).functions_superbill.py—generate_superbill_from_notetrigger (billingCodes → draft superbill).
Frontend (hybrid/basisflow-web/app/(main)/)
settings/page.tsx— Insurance & Billing tab: payers (Stedi search), Billing Codes library (inline $ rate per CPT/HCPCS), Billing Provider (NPI/taxId/taxonomy/address), Provider Credentials.clients/[id]/components/InsuranceClaimsSection.tsx—ClaimReviewModal(superbill OR manual entry, code search w/ validity badges, CPT units via allowDuplicates, link appointment/note, reuse previous claim,ChargeGapsinline rate-setter),ClaimDetailDrawer(submitted data + status timeline + remittance).clients/[id]/components/PaymentTab.tsx— renamed "Billing" tab: unified stream (payments + claims), toolbar (search left, filters, Charge/New claim/Refund/Invoice/Payment-methods-sheet).clients/[id]/ClientDetailPage.tsx— insurance capture (card scan/photo → AI prefill), eligibility verify + readiness badge.
Data (Firestore)
clinicsv2/{clinic}/settings/reimbursement— billingCodes[], billingProvider, payer mappings.clinicsv2/{clinic}/clinic_users/{uid}/claims/{id}— one claim; status advances in place. Fields: pcn, status (submitted→accepted→paid/rejected/denied), remittance{allowed,paid,patientResponsibility,charge}, allowedByCode (feeds learnedAllowed), statusHistory[], sandbox, requestClaim, errors[].clinicsv2/{clinic}/clinic_users/{uid}/superbills/{id}— draft superbills from coded notes.- Collection-group index REQUIRED on
claims.pcn+claims.transactionId(webhook lookup by PCN). Infirestore.indexes.jsonfieldOverrides.
Lifecycle (verified working): submit 837P → webhook transaction.processed events → _find_claim_ref(pcn) → 999/277CA update status + statusHistory; 835 → _parse_835 writes remittance + allowedByCode + paid/denied. allowed ≈ paid + patientResponsibility.
3. Sandbox / demo
- Demo clinic
axuk-khwf-prkr. Eligibility usesSTEDI_API_KEY_TEST(mock members: Aetna60054, memberAETNA12345, Jane Doe DOB 20040404). Claims use prod key (test key can't submit claims — 403). - Sandbox submit: demo clinic routes claims to the Stedi Test Payer (payer id
STEDI) (DEMO_SANDBOX_CLINICS) → mock 277CA + mock 835 (marks all lines paid at charge), never touches a real payer. Uses prod key + usageIndicator behavior. - Validate (usage 'T') = dry-run, does NOT persist a claim. Submit (usage 'P') persists one claim that then advances in place.
- Test NPI
1234567893(passes Luhn). Submitter phone must not start with 0/1 (default8005551212). - Seeded demo claims:
demo_paid_claim,demo_rejected_claimunder Jane Doe.
4. Stedi capability inventory (what's available — don't reinvent)
- Eligibility 270/271 — real-time + batch + PDF; 271 carries copay/coinsurance/deductible/OOP AND prior-auth-required + in/out-of-network status.
- Insurance Discovery
POST /insurance-discovery— active coverage from demographics only (13–16 eligibility checks behind scenes). Self-pay → billable. Native API. - Coordination of Benefits
POST /coordination-of-benefits— primacy/sequence from 245M-record DB. Native API. - Claims 837P/837I/837D — JSON + raw X12; CMS-1500 PDF via
/pdf-1500/{id}; SFTP batch; resubmit via Claim Frequency Code. - Claim attachments 275/PWK
POST /healthcare/claim-attachment— first-class (for appeals/records). - Claim status 276/277
POST /healthcare/claim-status— real-time. - Acks: 999 (SFTP only), 277CA (~30min post-submit; API/webhook/SFTP), 835 ERA (
GET /healthcare/reports/835, PDF). Correlate via Patient Control Number. - 835 ERA ALWAYS requires transaction enrollment. ← hard dependency for our revenue loop.
- Prior auth 278 — ⚠️ NO clean real-time healthcare-API endpoint (only raw EDI schemas). Read auth requirements from the 271 instead.
- Enhanced validation — Repairs (auto-fix formatting) + Edits (all 7 SNIP levels incl. payer-specific). Library at
edits.stedi.com. - Transaction enrollment API — providers + enrollments + tasks + documents; one-click for eligible payers; statuses DRAFT→LIVE; 24–48h typical.
- Payer network —
GET /payers,/payers/search, CSV; immutable Stedi payer IDs + aliases (integrate against these). - Events/webhooks —
transaction.processed, file delivered/failed; must ack 2xx in 5s; retries 4× @90s; idempotency required. - AI: Stedi MCP server (works with Claude Code, API key/OAuth) for eligibility perform+troubleshoot; in-portal Stedi Agent; AI-friendly docs (
/llms.txt). - Rate limits = concurrency-based (429), not RPS. Mock/test free.
5. Competitive / AI-RCM landscape (gaps to consider)
- Open source (OpenEMR, LibreHealth, Medplum, FreeMED/REMITT) covers the mechanical X12 + ERA-auto-post pipeline; none ship real AI. Greenfield = AI denial/coding/appeals.
- Highest-value adds, best fit to data we already hold:
- Underpayment / variance detection — 835 line vs expected contracted rate; 3–5% net-revenue leak. (#39)
- Patient cost estimate / GFE — Medicare + learned-allowed + 271 → out-of-pocket; point-of-service collection + No Surprises Act. (#40)
- Learned denial prediction — model on our own 277CA/835 outcomes over the deterministic scrubber; multi-tenant data moat. (#41)
- Payer contract modeling + negotiation scorecards. (#42)
- Frontier (higher cost): autonomous AR agents (portal/IVR), autonomous coding.
6. Roadmap / task pointers
Done: eligibility, scrubber (+billability), 837P submit, lifecycle 277CA/835 (#11/#13), ERA reconcile + learnedAllowed, charge intel, claim detail drawer, CPT units, reusable claim (copy previous), code validation (#26), superbill finalize, sandbox.
Build order (no-blinders):
- #12 ERA/transaction enrollment — BUILT 2026-07-10 (issue #175), pending deploy. Enrollment host is
enrollments.us.stedi.com/2024-09-01(NOT the healthcare host); one provider record per practice (stediEnrollment.providerIdonsettings/reimbursement), per-payer enrollments understediEnrollment.enrollments.{stediPayerId}. Callablestedi_era_enrollment(status/draft/submit/refresh, CLINIC_ADMIN); Settings → Insurance & Billing → ERA Enrollment tab; drafts never auto-submit. Statuses: DRAFT→STEDI_ACTION_REQUIRED→PROVIDER_ACTION_REQUIRED/PROVISIONING→LIVE (or REJECTED/CANCELED). Read-only smoke:tools/test_stedi_enrollment.py(verified: prod key reaches both list endpoints). BillingProvider gainedcontactEmail(required for enrollment). - #15 claims worklist + AI denial triage → #31 correction/resubmit → #23/#27 pre-submission review + AI appeals (+ MCP #35).
- #14 collections metering → Stripe (once automation is live).
- Fast-follows on same data: #39 underpayment, #40 patient estimates.
- Integrations: #32 Insurance Discovery, #33 COB, utilities #36 CMS-1500 PDF, #37 batch eligibility, #38 attachments 275.
Open external: #18 (ERA enrollments — see #12). #16 (prod key / no separate Stedi BAA) and #17 (% fee) RESOLVED per George 2026-07-25 — the %-of-collections fee is cleared and there is no additional BAA to sign with Stedi; do NOT re-flag these as blockers to metering/go-live.