Skip to main content

Tertiary waterfall and auto-resume

Outcome

A tertiary claim auto-generates when the secondary 835 posts. Pending waterfalls (PENDED primary that later pays) resume on the next 835. Manual force-runs are documented for coverage-change edge cases.

Prerequisites

How the auto-trigger works

processERA collects every matched claim that transitioned to PAID, DENIED, or PARTIAL inside its transaction. After the transaction commits:

PathWhen
CascadeFor each PAID or DENIED claim. Primary 835 → generates secondary. Secondary 835 → generates tertiary. Ancestor exclusion guarantees the original primary never resurfaces as a tertiary candidate.
ResumeAlways. No-op when nothing pending; flips PENDING → RESUMED/CANCELLED otherwise.

PARTIAL is treated as non-terminal for waterfall purposes.

Diagnose a silently-skipped tertiary

If a secondary 835 posted but no tertiary appeared:

  1. Confirm the secondary transitioned to PAID (not PARTIAL):

    SELECT status_current
    FROM billing.claim
    WHERE claim_id = '<secondary-id>';
  2. Confirm a higher-priority coverage exists and is active on the service date:

    SELECT policy_id, payer_id, priority_order, is_active,
    effective_from, effective_to
    FROM member.coverage_policy
    WHERE member_id = '<member-id>'
    AND is_active = true
    ORDER BY priority_order;
  3. Confirm the tertiary payer isn't already an ancestor (would indicate a duplicate or misrouted claim):

    WITH RECURSIVE chain AS (
    SELECT claim_id, parent_claim_id, payer_id
    FROM billing.claim WHERE claim_id = '<secondary-id>'
    UNION ALL
    SELECT c.claim_id, c.parent_claim_id, c.payer_id
    FROM billing.claim c
    JOIN chain ON c.claim_id = chain.parent_claim_id
    )
    SELECT payer_id FROM chain;
  4. Check ERAProcessingResult.cobWaterfallErrors logs from the latest ERA run — a thrown CONFLICT means the tertiary already exists; check billing.claim_relationship.

  5. If the guard fired, see Medicaid last resort suppression audit.

Force-run the waterfall after a coverage change

If ops updates a member's coverage after the primary 835 has already posted — e.g., the tertiary Medicaid coverage was missed at original submission and added later — the auto-trigger won't re-fire on its own. Force the waterfall:

import { processSecondaryClaimWaterfallDetailed } from '@rcm/core/.../secondary-claim-service';

await processSecondaryClaimWaterfallDetailed(
db,
{
claimId: '<current-claim-id>',
paidAmountCents: <paid>,
remittanceId: '<remit-id-or-null>',
},
{ userId: '<ops-user>', type: 'USER' },
);

Pass the current claim id (the one whose 835 should have driven the next step). The driver re-reads coverages and emits the next claim or a SUPPRESSED outcome with a reason.

Validation

CheckExpected
New tertiary billing.claim rowCreated after secondary 835
claim_relationship rowType=SECONDARY linking secondary→tertiary
cobWaterfallErrors on ERA resultEmpty
Pending rowFlipped to RESUMED or CANCELLED after resume

Known limits

  • Chain depth. PAYER_SEQUENCE_MAP only maps priority 1..3 to P/S/T. Quaternary payers aren't supported.
  • Ancestor walk depth cap. collectAncestorPayerIds stops at 8 hops as a cycle guard. The unique (parent, child, type) index on billing.claim_relationship prevents real cycles; the cap is defence-in-depth.
  • In-transaction scope. The auto-trigger runs after the ERA transaction commits. A waterfall failure leaves the posted 835 intact and surfaces the error on cobWaterfallErrors.

Cross-references

Next

3.6 — Commercial EOB posting