Handling Reject Code 76: Plan Limitations Exceeded

Reject code 76 means the drug is covered and the member is eligible — the only thing wrong is the amount. It fires when a claim asks for more than the plan allows on one of three axes: too many units in 442-E7 Quantity Dispensed, too many days in 405-D5 Days Supply, or too many dollars against an accumulator cap. Because the product is payable, 76 is the reject a pharmacist can most often fix at the counter by reducing the quantity or the day supply and resubmitting, which makes precise limit arithmetic the whole game: emit 76 a unit too early and you block a legitimate fill; emit it a unit too late and you leak plan dollars on every claim. This guide, part of the NCPDP Reject Code Reference, specifies the trigger conditions for 76 and the Python handler that evaluates 442-E7 and 405-D5 against the plan limits.

The Exact Decision

76 is evaluated only after eligibility and coverage pass — there is no point checking a quantity limit on a drug the plan will not cover. The decision is a comparison: does the requested quantity, days supply, or dollar amount exceed the plan’s configured limit for this GPI? A limit set is drawn from the versioned formulary snapshot and typically carries a maximum quantity per fill, a maximum days supply per fill, and sometimes a rolling dollar cap tracked in an accumulator. If any axis is exceeded, the claim rejects 76; the handler records which axis tripped so the pharmacy message and the Quantity Limit & Days Supply Validation analytics can distinguish a quantity breach from a days-supply breach.

The disambiguation that trips teams up is 76 versus 79 and 70. The table draws the lines.

Symptom on the claim Correct code Why not 76
442-E7 quantity above the per-fill max 76 Plan Limitations Exceeded Canonical 76 quantity case
405-D5 days supply above the per-fill max 76 Plan Limitations Exceeded Canonical 76 days-supply case
Rolling dollar accumulator cap exceeded 76 Plan Limitations Exceeded Canonical 76 dollar case
Refill requested before earliest-refill date 79 Refill Too Soon A timing limit, not a per-fill amount limit
GPI is excluded from the formulary 70 Product/Service Not Covered Not coverable at any quantity
GPI covered but needs prior authorization 75 Prior Authorization Required Gated on PA, not on amount

The operating rule: 76 is about how much on a covered drug, 79 is about how soon, 70 is about whether at all, and 75 is about whether gated. The handler must never let a refill-timing problem masquerade as 76, because the pharmacy remedies them differently — a 76 is fixed by dispensing less, a 79 by waiting.

Step-by-Step Implementation

The handler receives the parsed claim quantities and the plan limit set for the GPI, then checks each axis in a fixed order so the reported breach is deterministic.

1. Carry money as decimal.Decimal. Any accumulator or dollar cap is decimal.Decimal, quantized explicitly — never float. A cent of float drift compounds across an accumulator and produces off-by-one reject decisions.

2. Compare 442-E7 Quantity Dispensed against the quantity max. A quantity above the per-fill limit is a 76 on the quantity axis.

3. Compare 405-D5 Days Supply against the days-supply max. A days supply above the limit is a 76 on the days_supply axis.

4. Compare the projected accumulator against the dollar cap. If this fill would push the rolling accumulator past the cap, that is a 76 on the dollar axis.

5. Emit 76 with the tripped axis, or pass. Set 501-F1 Header Response Status to R, attach 511-FB = "76", tag the axis, and log PHI-safely.

python
import json
import logging
import enum
from decimal import Decimal, ROUND_HALF_UP
from typing import Optional
from pydantic import BaseModel, ConfigDict

logging.basicConfig(format="%(message)s", level=logging.INFO)
logger = logging.getLogger("reject_76")

CENTS = Decimal("0.01")


class LimitAxis(str, enum.Enum):
    NONE = "none"
    QUANTITY = "quantity"
    DAYS_SUPPLY = "days_supply"
    DOLLAR = "dollar"


class PlanLimit(BaseModel):
    model_config = ConfigDict(frozen=True, extra="forbid")
    max_quantity: Decimal          # per-fill 442-E7 cap
    max_days_supply: int           # per-fill 405-D5 cap
    dollar_cap: Optional[Decimal] = None   # rolling accumulator cap


class Reject76Input(BaseModel):
    model_config = ConfigDict(frozen=True, extra="forbid")
    claim_ref: str                 # tokenized 402-D2, non-PHI
    quantity_442e7: Decimal        # 442-E7 Quantity Dispensed (Decimal)
    days_supply_405d5: int         # 405-D5 Days Supply
    accrued_dollars: Decimal       # accumulator to date (Decimal)
    fill_dollars: Decimal          # this fill's plan cost (Decimal)


class Reject76Result(BaseModel):
    status: str                    # "PASS" or "REJECT"
    reject_511fb: Optional[str] = None
    axis: LimitAxis = LimitAxis.NONE


def handle_reject_76(inp: Reject76Input, limit: PlanLimit) -> Reject76Result:
    axis = LimitAxis.NONE

    # 2. Quantity axis: 442-E7 vs per-fill max.
    if inp.quantity_442e7 > limit.max_quantity:
        axis = LimitAxis.QUANTITY
    # 3. Days-supply axis: 405-D5 vs per-fill max.
    elif inp.days_supply_405d5 > limit.max_days_supply:
        axis = LimitAxis.DAYS_SUPPLY
    # 4. Dollar axis: projected accumulator vs cap (Decimal math only).
    elif limit.dollar_cap is not None:
        projected = (inp.accrued_dollars + inp.fill_dollars).quantize(
            CENTS, rounding=ROUND_HALF_UP)
        if projected > limit.dollar_cap:
            axis = LimitAxis.DOLLAR

    if axis is LimitAxis.NONE:
        result = Reject76Result(status="PASS")
    else:
        result = Reject76Result(status="REJECT", reject_511fb="76", axis=axis)

    # PHI GUARDRAIL: tokenized ref + 511-FB + axis only. Never 302-C2
    # Cardholder ID, 310-CA Patient Name, or raw claim bytes.
    logger.info(json.dumps({
        "event": "reject_76_eval",
        "claim_ref": inp.claim_ref,
        "reject_511fb": result.reject_511fb,
        "axis": result.axis.value,
    }))
    return result

The axis order — quantity, then days supply, then dollars — is deterministic so a claim that breaches two limits always reports the same primary axis, which keeps replayed adjudications identical. The dollar comparison is the one place money enters: it uses decimal.Decimal end to end and quantizes to cents with ROUND_HALF_UP before comparing, because a float accumulator would drift a claim across the cap boundary non-deterministically. That discipline is the same one applied across the money paths in Rule Engine Threshold Tuning & Optimization.

Reject 76 plan-limit axis checks A covered claim is checked against three plan-limit axes in order. First the 442-E7 quantity is compared to the per-fill maximum; if exceeded it rejects 76 on the quantity axis. Otherwise the 405-D5 days supply is compared to its maximum; if exceeded it rejects 76 on the days-supply axis. Otherwise the projected dollar accumulator is compared to the cap; if exceeded it rejects 76 on the dollar axis. If all three pass, the claim passes to the next rule. Covered claim eligibility + coverage ok 442-E7 qty > max? 405-D5 days > max? $ accrual > cap? Reject 76 quantity axis Reject 76 days-supply axis Reject 76 dollar axis Pass next rule over over over ok ok all ok

Figure: The three plan-limit axes are checked in a fixed order — quantity, days supply, then the decimal.Decimal dollar accumulator — and the first breach reports the primary 76 axis.

Verification and Testing Pattern

Test each axis, the pass case, and the deterministic axis ordering when two limits breach at once.

python
import pytest

LIMIT = PlanLimit(max_quantity=Decimal("90"), max_days_supply=30,
                  dollar_cap=Decimal("500.00"))


def _inp(qty="30", days=30, accrued="0.00", fill="50.00"):
    return Reject76Input(claim_ref="tok-76", quantity_442e7=Decimal(qty),
                        days_supply_405d5=days, accrued_dollars=Decimal(accrued),
                        fill_dollars=Decimal(fill))


def test_within_all_limits_passes():
    assert handle_reject_76(_inp(), LIMIT).status == "PASS"


def test_quantity_over_rejects_76():
    r = handle_reject_76(_inp(qty="120"), LIMIT)
    assert r.reject_511fb == "76" and r.axis is LimitAxis.QUANTITY


def test_days_supply_over_rejects_76():
    r = handle_reject_76(_inp(days=90), LIMIT)
    assert r.axis is LimitAxis.DAYS_SUPPLY


def test_dollar_cap_over_rejects_76():
    r = handle_reject_76(_inp(accrued="480.00", fill="50.00"), LIMIT)
    assert r.axis is LimitAxis.DOLLAR


def test_quantity_wins_when_two_axes_breach():
    # Quantity is checked first, so it is the reported axis.
    r = handle_reject_76(_inp(qty="120", days=90), LIMIT)
    assert r.axis is LimitAxis.QUANTITY

test_quantity_wins_when_two_axes_breach pins the deterministic ordering that keeps a replayed claim reporting the same axis, and test_dollar_cap_over_rejects_76 exercises the decimal.Decimal accumulator path where float drift would otherwise put the verdict in doubt.

Gotchas and PHI Guardrails

  • Never use float for the accumulator. A dollar cap compared against a float sum drifts across the boundary unpredictably; use decimal.Decimal and quantize to cents with ROUND_HALF_UP before every comparison.
  • 76 is not 79. A quantity or days-supply breach is 76; a refill requested before the earliest-refill date is 79 Refill Too Soon. They read similarly at the counter but the remedy differs — dispense less versus wait — so keep the timing check in its own handler.
  • Fixed axis order. Check quantity, then days supply, then dollars, always in that order. Reordering makes a two-axis breach report different codes on replay and breaks audit reproducibility.
  • Per-fill versus rolling limits. Quantity and days-supply caps are per-fill; the dollar cap is a rolling accumulator that must be read from a versioned, member-scoped snapshot. Do not compare a per-fill quantity against a rolling total.
  • 405-D5 is an integer count of days. Treat days supply as an int; a fractional or float days supply is a data defect that belongs to schema validation, not to the limit comparison. For variable dosing that produces a non-integer, resolve it upstream in Calculating Days Supply for Variable Dosing.
  • Log the axis, not the amounts in context. Log the tokenized 402-D2 reference, the 511-FB code, and the axis; never log the accumulator alongside 302-C2 Cardholder ID, 310-CA Patient Name, or raw claim bytes.

For the decimal-arithmetic rationale behind the accumulator math, see Python’s decimal module documentation.

← Back to NCPDP Reject Code Reference