PRC-12Recommended
Compute tiered and volume prices in one tested function
Store each tiered price with its mode, graduated or volume, and its tiers in minor units. Compute every quote, usage page and invoice line through one function with table tests at each tier edge.
How to do it
The two modes give different totals for the same quantity. Graduated pricing charges each unit at the rate of the tier it falls in, and the tier totals add up. Volume pricing charges every unit at the rate of the tier the total quantity reaches. Take 7 USD for units 1 to 5 and 6.50 USD for units 6 to 10. Six units then cost 41.50 USD graduated and 39 USD volume. Volume pricing can make a larger quantity cheaper than a smaller one, so say which mode applies on the pricing page. Keep the tiers on the price in the versioned catalogue, never in code. Round once per line, in minor units, never per unit. The usage page uses the same function, as Show live usage that matches the invoice requires.
Examples
TypeScript example
type Tier = { upTo: number | null; unitMinor: number; flatMinor?: number }
export function tieredTotalMinor(quantity: number, tiers: Tier[], mode: 'graduated' | 'volume'): number {
if (mode === 'volume') {
const tier = tiers.find(t => t.upTo === null || quantity <= t.upTo)!
return quantity * tier.unitMinor + (tier.flatMinor ?? 0)
}
let total = 0
let previousUpTo = 0
for (const tier of tiers) {
const inTier = Math.min(quantity, tier.upTo ?? Infinity) - previousUpTo
if (inTier <= 0) break
total += inTier * tier.unitMinor + (tier.flatMinor ?? 0)
previousUpTo = tier.upTo ?? Infinity
}
return total
}
Python example
def tiered_total_minor(quantity: int, tiers: list[dict], mode: str) -> int:
if mode == "volume":
tier = next(t for t in tiers if t["up_to"] is None or quantity <= t["up_to"])
return quantity * tier["unit_minor"] + tier.get("flat_minor", 0)
total, previous_up_to = 0, 0
for tier in tiers:
upper = quantity if tier["up_to"] is None else min(quantity, tier["up_to"])
in_tier = upper - previous_up_to
if in_tier <= 0:
break
total += in_tier * tier["unit_minor"] + tier.get("flat_minor", 0)
previous_up_to = upper
return total
How to check
A table test prices 0, 1, 5, 6, 10, 11 and 25 units in both modes and matches totals worked out by hand. Search the code for a second function that multiplies a quantity by a tier rate. None exists.
Sources
Related rules
- PRC-1 Keep prices in a versioned catalogue in Pricing, plans and entitlements
- PRC-9 Show live usage that matches the invoice in Pricing, plans and entitlements
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/pricing/plans-and-prices/tiered-and-volume-pricing">Agent Wranglers pricing, plans and entitlements playbook</a>. In code you write, add a comment next to it with the same link.