Skip to main content
Synced from the repo — do not edit here

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 in Authorization header (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):

TierWhat's includedPrice
Clearinghouse pipeTransmit 837P, route 999/277CA/835. No denial/AR work.per-claim (cents), NOT a %
Tech-enabled / AI-assisted RCM ← our targetPipe + 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 hand4–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.pybuild_837p_claim(...) pure mapper (auto-balances, ABK principal dx, units→serviceUnitCount, EIN as employerId, 9-digit ZIP pad, modifiers).
  • stedi_scrubber.pyscrub_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 FY2025 generated/icd10cm_codes.json + _descriptions.json + _nonbillable.json) & CPT/HCPCS (clinic catalog + curated). _extract_code strips scribe "M54.5 - desc [VERIFY]"M54.5.
  • charge_intel.py — per-code {medicare, suggested (2× Medicare), priorCharge, learnedAllowed, patientShare}. Pluggable: full MPFS drops into generated/medicare_mpfs.json.
  • insurance_readiness.py — shared readiness validator (#20).
  • functions_scribe.pysign_note, finalize_note_codes (note codes → billingCodes → superbill trigger), plus codeValidation on scribe notes (#26).
  • functions_superbill.pygenerate_superbill_from_note trigger (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.tsxClaimReviewModal (superbill OR manual entry, code search w/ validity badges, CPT units via allowDuplicates, link appointment/note, reuse previous claim, ChargeGaps inline 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). In firestore.indexes.json fieldOverrides.

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 uses STEDI_API_KEY_TEST (mock members: Aetna 60054, member AETNA12345, 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 (default 8005551212).
  • Seeded demo claims: demo_paid_claim, demo_rejected_claim under 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 networkGET /payers, /payers/search, CSV; immutable Stedi payer IDs + aliases (integrate against these).
  • Events/webhookstransaction.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:
    1. Underpayment / variance detection — 835 line vs expected contracted rate; 3–5% net-revenue leak. (#39)
    2. Patient cost estimate / GFE — Medicare + learned-allowed + 271 → out-of-pocket; point-of-service collection + No Surprises Act. (#40)
    3. Learned denial prediction — model on our own 277CA/835 outcomes over the deterministic scrubber; multi-tenant data moat. (#41)
    4. 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):

  1. #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.providerId on settings/reimbursement), per-payer enrollments under stediEnrollment.enrollments.{stediPayerId}. Callable stedi_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 gained contactEmail (required for enrollment).
  2. #15 claims worklist + AI denial triage#31 correction/resubmit#23/#27 pre-submission review + AI appeals (+ MCP #35).
  3. #14 collections metering → Stripe (once automation is live).
  4. Fast-follows on same data: #39 underpayment, #40 patient estimates.
  5. 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.