Calculating Days Supply for Variable Dosing

Days supply arithmetic is trivial when the daily dose is constant — dispensed quantity divided by tablets-per-day — and treacherous the moment the dose is not. Tapering steroid schedules, PRN “one to two tablets as needed” ranges, metered-dose inhalers billed by actuation, and insulin dispensed in pens all break the single-divisor assumption, and each one produces a 405-D5 Days Supply value that the engine must derive rather than trust. This page settles one calculation decision: how to compute days supply when 442-E7 Quantity Dispensed over daily dose is not a single number, so that the derived 405-D5 is correct to the day. Get it wrong and the refill-too-soon check downstream misfires — an overstated days supply locks a member out of a legitimate refill, an understated one opens a stockpiling gap that surfaces as reject code 79 Refill Too Soon at the wrong time. It is the derivation half of the Quantity Limit & Days Supply Validation gate, where the parent workflow compares the number this page produces against the plan’s max_days_supply.

The Calculation Decision by Dosing Pattern

The right approach is chosen by the shape of the dose, not by the drug. A constant SIG divides once; a stepped schedule must be walked; a range must collapse to a defensible single rate; a device must be read from its labeled capacity. Each pattern also fixes a rounding rule, because the direction you round decides whether the member refills too early or waits too long. The convention below floors the derived days supply to whole days so a fractional day never silently extends coverage — with the metered-dose inhaler as the deliberate exception the parent gate calls out, where truncation would understate a 28-day device.

Dosing pattern Quantity / SIG signal Days-supply calculation Rounding Failure if wrong
Constant fixed dose × fixed interval 442-E7 ÷ (dose × admins/day) ROUND_FLOOR to whole days early 79 or member lockout
Titration / taper stepped schedule (mg × days) walk schedule, sum step days until quantity is exhausted ROUND_FLOOR at exhaustion premature 79 on the next fill
PRN / range dose “1–2 tabs, max N/day” 442-E7 ÷ maximum daily dose ROUND_FLOOR understated supply → stockpiling leakage
Packaged inhaler labeled actuations actuations ÷ (puffs × admins/day) ROUND_HALF_UP to whole days 28-day device tripping AG/79
Insulin pen / vial units per mL × quantity total units ÷ units per day ROUND_FLOOR member lockout on a valid refill

Two rules make the table defensible. First, for a range dose the maximum stated daily dose governs — “one to two tablets every four to six hours, up to eight a day” derives days supply from eight per day, the conservative rate that will not grant early-refill coverage the prescriber never authorized. Second, a derived days supply that exceeds the plan max_days_supply does not silently pass: it routes to the same disposition as a 442-E7 quantity overage, either a hard AG Days Supply Limitation or, on an override-eligible maintenance drug, prior-auth 75. A quantity that exceeds the plan maximum still rejects 76 Plan Limitations Exceeded — the two checks are independent, as spelled out in Handling Reject Code 76: Plan Limitations Exceeded.

Days-supply derivation for variable dosing feeding the refill-too-soon check A claim carrying 442-E7 Quantity Dispensed and a SIG enters. The engine classifies the dosing pattern as constant, titration, PRN range, or packaged device, then computes daily consumption with the pattern-specific method in Decimal precision. It divides the dispensed quantity by daily consumption and floors the result to whole days to produce the 405-D5 Days Supply. That days-supply value sets the next-eligible refill date as the fill date plus a threshold percentage of the days supply. A refill submitted before the eligible date rejects with NCPDP code 79 Refill Too Soon; a refill on or after the eligible date is accepted. Claim in 442-E7 quantity · SIG Classify dosing pattern constant · titration · PRN · device Compute daily consumption pattern-specific · Decimal exact Days supply = floor(qty ÷ daily) 405-D5 · whole days Next-eligible refill date fill + threshold% × 405-D5 Refill before eligible date? Reject — Refill Too Soon reject code 79 Days supply accepted proceed to QL / copay yes no

Figure: The derived 405-D5 Days Supply sets the next-eligible refill date — a refill submitted before that date is the 79 Refill Too Soon reject, so an error in the variable-dosing calculation surfaces as a wrongly-timed counter rejection.

Step-by-Step Implementation

The calculator below dispatches on the dosing pattern, keeps every intermediate in decimal.Decimal, and rounds exactly once at the final step. It reads 442-E7 Quantity Dispensed and derives 405-D5 Days Supply; it never touches or logs the 302-C2 Cardholder ID.

1. Hold every quantity as Decimal, initialized from a string. A float daily consumption of 0.1 + 0.2 drifts, and a titration walk accumulates that drift into a whole-day error. See the decimal module documentation for context management.

2. Compute daily consumption per pattern. Constant and PRN reduce to a single rate; titration walks a schedule; a device reads labeled capacity. The PRN path uses the maximum daily dose.

3. Divide and floor to whole days — once. Rounding mid-calculation is the classic defect; carry full precision through the division and quantize only at the end, ROUND_FLOOR for the default patterns and ROUND_HALF_UP for the labeled inhaler.

4. Return a structured, PHI-safe result carrying the derived days supply and the method, so the parent gate and the refill-too-soon check consume the number without re-deriving it.

python
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal, ROUND_FLOOR, ROUND_HALF_UP
from enum import Enum
from typing import Optional


class DosePattern(str, Enum):
    CONSTANT = "CONSTANT"
    TITRATION = "TITRATION"
    PRN_RANGE = "PRN_RANGE"
    PACKAGED_DEVICE = "PACKAGED_DEVICE"


@dataclass(frozen=True)
class TaperStep:
    dose_units: Decimal        # units consumed per day at this step
    duration_days: int         # scheduled length of the step


@dataclass(frozen=True)
class DaysSupply:
    days: int                  # derived 405-D5 Days Supply
    pattern: DosePattern
    daily_consumption: Decimal
    method: str


def _floor_days(exact: Decimal) -> int:
    # Floor to whole days: a fractional day never extends refill coverage.
    return int(exact.quantize(Decimal("1"), rounding=ROUND_FLOOR))


def constant_days_supply(qty_442e7: Decimal, dose_per_admin: Decimal,
                         admins_per_day: Decimal) -> DaysSupply:
    daily = dose_per_admin * admins_per_day
    if daily <= 0:
        raise ValueError("non-positive daily consumption")
    days = _floor_days(qty_442e7 / daily)          # 442-E7 ÷ daily dose
    return DaysSupply(days, DosePattern.CONSTANT, daily, "qty/daily")


def prn_days_supply(qty_442e7: Decimal, max_daily_dose: Decimal) -> DaysSupply:
    # Range dose: the MAXIMUM daily dose governs — the conservative rate.
    if max_daily_dose <= 0:
        raise ValueError("non-positive max daily dose")
    days = _floor_days(qty_442e7 / max_daily_dose)
    return DaysSupply(days, DosePattern.PRN_RANGE, max_daily_dose, "qty/max_daily")


def titration_days_supply(qty_442e7: Decimal, schedule: list[TaperStep]) -> DaysSupply:
    # Walk the stepped schedule, consuming the dispensed quantity step by step.
    remaining = qty_442e7
    days = 0
    for step in schedule:
        step_total = step.dose_units * Decimal(step.duration_days)
        if remaining >= step_total:
            remaining -= step_total
            days += step.duration_days
        else:
            # Partial final step: whole days the remainder covers at this dose.
            days += _floor_days(remaining / step.dose_units)
            remaining = Decimal("0")
            break
    # Effective daily rate = quantity consumed over the days it covered.
    daily = (qty_442e7 - remaining) / Decimal(days) if days else Decimal("0")
    return DaysSupply(days, DosePattern.TITRATION, daily, "schedule_walk")


def device_days_supply(labeled_actuations: Decimal, puffs_per_admin: Decimal,
                       admins_per_day: Decimal) -> DaysSupply:
    # Metered-dose inhaler: ROUND_HALF_UP so a 28-day device is not truncated.
    daily = puffs_per_admin * admins_per_day
    if daily <= 0:
        raise ValueError("non-positive daily actuations")
    exact = labeled_actuations / daily
    days = int(exact.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
    return DaysSupply(days, DosePattern.PACKAGED_DEVICE, daily, "actuations/daily")

Every function is a pure function of its inputs, so the same claim replays to the same 405-D5 under a payer audit. The titration walk is the subtle one: it consumes the dispensed quantity against each scheduled step and, on the step where the quantity runs out, credits only the whole days the remainder covers — which is why a taper dispensed a few tablets short still derives an honest, shorter days supply instead of a rounded-up one.

Verification and Testing Pattern

The properties to pin: the constant path floors rather than rounds up, PRN uses the maximum dose, the titration walk handles a partial final step, and the inhaler is not truncated to a short supply.

python
import pytest
from decimal import Decimal


def test_constant_floors_partial_day():
    # 90 tablets, 1 tab three times daily -> exactly 30 days.
    ds = constant_days_supply(Decimal("90"), Decimal("1"), Decimal("3"))
    assert ds.days == 30
    # 100 tablets at 3/day -> 33.33 days, floored to 33 (never 34).
    assert constant_days_supply(Decimal("100"), Decimal("1"), Decimal("3")).days == 33


def test_prn_uses_maximum_daily_dose():
    # 60 tablets, "1-2 q6h, max 8/day" -> derive from 8, the conservative rate.
    ds = prn_days_supply(Decimal("60"), Decimal("8"))
    assert ds.days == 7                       # 60/8 = 7.5 -> floor 7
    assert ds.daily_consumption == Decimal("8")


def test_titration_walks_schedule_with_partial_final_step():
    # Prednisone taper: 40mg x2d, 30mg x2d, 20mg x2d, dispensed as 5mg tablets.
    # Quantities in 5mg-tablet units per day: 8, 6, 4.
    schedule = [
        TaperStep(Decimal("8"), 2),
        TaperStep(Decimal("6"), 2),
        TaperStep(Decimal("4"), 2),
    ]
    total = Decimal("8") * 2 + Decimal("6") * 2 + Decimal("4") * 2  # 36 tablets
    assert titration_days_supply(total, schedule).days == 6
    # Dispensed 4 tablets short: last 20mg step covers one fewer day.
    assert titration_days_supply(total - Decimal("4"), schedule).days == 5


def test_inhaler_not_truncated():
    # 200 actuations, 2 puffs twice daily = 4/day -> 50 days.
    assert device_days_supply(Decimal("200"), Decimal("2"), Decimal("2")).days == 50
    # 200 actuations, 2 puffs three times daily -> 33.33 -> HALF_UP to 33.
    assert device_days_supply(Decimal("200"), Decimal("2"), Decimal("3")).days == 33


def test_zero_daily_consumption_raises():
    with pytest.raises(ValueError):
        constant_days_supply(Decimal("30"), Decimal("0"), Decimal("1"))

The titration partial-step test is the load-bearing one: it proves a short-dispensed taper derives a shorter, honest days supply rather than the rounded-up figure a naive quantity / peak_dose would report — the difference between a member’s next fill being eligible on time and hitting a 79 reject at the counter.

Gotchas and PHI Guardrails

  • Rounding mid-calculation. The single most common days-supply defect is rounding each intermediate. Carry full Decimal precision through the division and titration walk, and quantize exactly once at the end — otherwise a 33.4-day supply can round to 34 and push the next-eligible date a day late.
  • Using the minimum of a range. Deriving a PRN days supply from “one tablet” when the SIG allows two doubles the apparent supply and hands the member a stockpiling window. The maximum stated daily dose is the correct, plan-protective divisor.
  • Truncating a metered device. Flooring a 200-actuation inhaler at three-times-daily gives 33 days honestly, but flooring one that divides to 27.6 days would understate a labeled 28-day device and trip an early 79. Use ROUND_HALF_UP for labeled devices, the exception the parent gate documents.
  • Trusting the submitted 405-D5. A pharmacy-submitted days supply can disagree with the dispensed quantity and SIG. The derived value governs; a material mismatch is annotated for audit, never silently accepted — the boundary this feeds is calibrated in Rule Engine Threshold Tuning & Optimization.
  • Insulin unit conversion. A pen billed in mL must be converted through the concentration (units per mL) before dividing by daily units; comparing mL to a units-per-day rate is the same category error as an unreconciled 600-28 Unit of Measure and produces a nonsensical days supply.
  • PHI in the derivation log. Days-supply math needs the 442-E7 quantity and the SIG string, never the member identity. Log the tokenized member reference, the derived 405-D5, and the method — never the raw claim bytes, the 302-C2 Cardholder ID, or the 310-CA Patient Name. A SIG line can itself carry identifying free text, so log the parsed rate, not the raw SIG.

← Back to Quantity Limit & Days Supply Validation