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.
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.
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.
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
Decimalprecision 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. UseROUND_HALF_UPfor 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-28Unit of Measure and produces a nonsensical days supply. - PHI in the derivation log. Days-supply math needs the
442-E7quantity and the SIG string, never the member identity. Log the tokenized member reference, the derived405-D5, and the method — never the raw claim bytes, the302-C2Cardholder ID, or the310-CAPatient Name. A SIG line can itself carry identifying free text, so log the parsed rate, not the raw SIG.
Related
- Quantity Limit & Days Supply Validation — the parent gate that compares this derived days supply against the plan maximum.
- Handling Reject Code 76: Plan Limitations Exceeded — the sibling quantity-overage reject that runs independently of the days-supply check.
- Rule Engine Threshold Tuning & Optimization — how the refill-too-soon threshold percentage that consumes this days supply is calibrated.
- Step Therapy & Prior Auth Trigger Rules — where an override-eligible days-supply overage routes for clinical justification (
75). - NDC-to-GPI Crosswalk Automation — resolves the
407-D7NDC to the GPI that keys the plan’s days-supply limit.
← Back to Quantity Limit & Days Supply Validation