Applying Per-Unit Conservation Surcharges in Python
A conservation surcharge is a deceptively simple line item: a few cents per kilowatt-hour or per thousand gallons (kgal) that a utility levies on consumption above a policy threshold, designed to discourage waste during a drought or a peak-demand season. The arithmetic looks trivial until three questions collide — does the surcharge apply to the whole volume or only the units above the threshold, what happens exactly at the threshold, and does it get taxed? Each answer changes the ratepayer’s total, and getting any of them wrong produces a bill that no ordinance authorizes. This page sits under surcharge and fee application logic, part of the broader automated rate calculation and rule engines subsystem, and shows how to compute a per-unit conservation surcharge deterministically with decimal.Decimal, layer it cleanly on the base commodity charge, and apply it in the correct order relative to utility tax.
Minimal Prerequisites
- Python 3.11+ — for the
X | Noneunion syntax and modern typing used below; no third-party runtime dependency is required for the core calculation. decimal.Decimalfor every quantity — usage volume, threshold, per-unit rate, and money all stay inDecimal. A per-unit rate like0.0032per gallon multiplied by a large volume is exactly the kind of value where binaryfloatdrift crosses a cent.- A single, explicit rounding boundary — the surcharge is quantized to the cent once, with
ROUND_HALF_UP, and never re-rounded. - Pydantic v2 (
pydantic>=2.6) is assumed upstream for validating the surcharge definition, but the function here takes already-typedDecimalinputs so it can be unit-tested in isolation.
Data assumptions: the base commodity charge for the account has already been rated (this page decorates it, it does not recompute it), metered usage is a clean non-negative Decimal in the same unit as the threshold (kWh or kgal), and the per-unit surcharge rate and threshold come from an authoritative, version-controlled rule record with a governing PUC reference. The one design decision you must fix before writing a line of code is whether the surcharge is marginal — applied only to units above the threshold — or whole-volume — applied to every unit once any threshold is crossed. Marginal is by far the more common and more defensible conservation design, so it is the default here, with the whole-volume variant shown explicitly as an edge case.
Annotated Implementation
The whole computation is one pure function. It takes usage, a threshold, a per-unit surcharge rate, and the already-rated base cost, and returns a small breakdown so the caller can post each component to the ledger separately. Keeping it pure — no I/O, no clock, no global state — is what makes it deterministically replayable during an audit.
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
CENTS = Decimal("0.01")
def _money(value: Decimal) -> Decimal:
# Quantize to the cent exactly once, at the boundary, with an explicit mode.
return value.quantize(CENTS, rounding=ROUND_HALF_UP)
@dataclass(frozen=True)
class SurchargeResult:
billable_units: Decimal # units the surcharge actually applied to
surcharge: Decimal # the conservation surcharge, quantized to cents
subtotal: Decimal # base commodity charge + surcharge, pre-tax
tax: Decimal # utility tax, computed on the subtotal
total: Decimal # amount due
def apply_conservation_surcharge(
usage: Decimal, # metered consumption, kWh or kgal
threshold: Decimal, # units below this are exempt from the surcharge
per_unit_rate: Decimal, # surcharge charged per unit above the threshold
base_cost: Decimal, # base commodity charge, already rated (Decimal)
tax_rate: Decimal, # utility tax as a fraction, e.g. Decimal("0.045")
) -> SurchargeResult:
"""Apply a marginal per-unit conservation surcharge, then tax the subtotal."""
if usage < 0 or threshold < 0 or per_unit_rate < 0:
raise ValueError("usage, threshold, and rate must be non-negative")
# Marginal design: only the slice of usage ABOVE the threshold is surcharged.
# max(...) clamps at zero so usage at or below the threshold yields no charge.
billable_units = max(Decimal("0"), usage - threshold)
# Full-precision multiply first; quantize the surcharge ONCE, here.
surcharge = _money(billable_units * per_unit_rate)
# The surcharge is layered on the base commodity charge to form the subtotal.
subtotal = _money(base_cost + surcharge)
# Tax is applied to the subtotal LAST, so it captures the surcharge too.
tax = _money(subtotal * tax_rate)
total = _money(subtotal + tax)
return SurchargeResult(
billable_units=billable_units,
surcharge=surcharge,
subtotal=subtotal,
tax=tax,
total=total,
)
Two ordering decisions carry all the billing weight. First, billable_units is computed as usage - threshold clamped at zero, so a customer who stayed under the conservation threshold pays nothing extra and the account exactly at the threshold pays nothing extra either — the surcharge begins on the first unit past the line, not at it. Second, the surcharge is folded into the subtotal before tax is computed, so the utility tax lands on the base charge plus the surcharge together. Reversing that order — taxing the base, then adding an untaxed surcharge — understates the tax the ordinance requires and is one of the most common quiet errors in a conservation-fee rollout. The single-quantization discipline mirrors the marginal-slice approach used across surcharge and fee application logic: multiply at full precision, round once at the money boundary.
Edge Cases and Billing Gotchas
Conservation surcharges break in specific, threshold-shaped ways. These are the boundary conditions a naive implementation charges wrong.
- Usage exactly at the threshold. With
max(Decimal("0"), usage - threshold), an account that lands precisely on the threshold has zero billable units and pays no surcharge — the fee begins on the first unit above the line. Decide this deliberately and document it against the ordinance; some conservation programs instead specify the surcharge begins at the threshold, in which case the comparison must change. Because everything isDecimal, a usage ofDecimal("500.00")against aDecimal("500")threshold compares exactly, with none of the499.9999drift afloatwould introduce. - Marginal vs. whole-volume surcharge. The function surcharges only the slice above the threshold. Some drought ordinances instead surcharge every unit once any usage crosses the threshold. That is a different rule:
billable_units = usage if usage > threshold else Decimal("0"). Whole-volume is punitive by design and far more sensitive to a reading error near the threshold, so confirm which the ordinance mandates before shipping — the two produce very different bills for a customer just over the line. - Tiered conservation surcharge. Some programs escalate the per-unit rate in bands — for example a lower rate for the first block above the threshold and a steeper rate beyond it. That is the marginal-slice model applied to the surcharge itself; reuse the band-slicing approach from step-rate versus block-rate structure design rather than a single
per_unit_rate, and quantize only after summing every band’s contribution. - Rounding order relative to tax. Quantize the surcharge once, fold it into the subtotal, then tax the subtotal. Taxing the base charge and the surcharge separately and summing invites a double-rounding discrepancy of a cent; taxing before the surcharge is added under-collects tax the statute requires. The order in the code — surcharge, subtotal, then tax — is the auditable one.
- Partial-month conservation windows. A drought surcharge that begins mid-cycle applies only to usage inside the effective window, which requires point-in-time rule resolution rather than a flat per-cycle threshold; the seasonal-window mechanics live in surcharge and fee application logic, and time-based fee proration is covered separately under proration and billing period calculation.
Verification Snippet
Test the boundaries that matter: below, exactly at, and above the threshold, plus the tax-ordering invariant. Every expected figure is a literal Decimal so the assertions catch drift the moment it appears.
from decimal import Decimal
def test_conservation_surcharge_boundaries() -> None:
threshold = Decimal("500") # kgal
rate = Decimal("0.0032") # surcharge per gallon above threshold
base = Decimal("42.00") # already-rated base commodity charge
tax_rate = Decimal("0.045") # 4.5% utility tax
# Below threshold: no surcharge, tax on base only.
under = apply_conservation_surcharge(Decimal("450"), threshold, rate, base, tax_rate)
assert under.billable_units == Decimal("0")
assert under.surcharge == Decimal("0.00")
assert under.total == Decimal("43.89") # 42.00 + (42.00 * 0.045)
# Exactly at threshold: still no surcharge (fee starts on the next unit).
at = apply_conservation_surcharge(Decimal("500"), threshold, rate, base, tax_rate)
assert at.surcharge == Decimal("0.00")
# Above threshold: surcharge on the marginal 300 units, taxed with the base.
over = apply_conservation_surcharge(Decimal("800"), threshold, rate, base, tax_rate)
assert over.billable_units == Decimal("300")
assert over.surcharge == Decimal("0.96") # 300 * 0.0032 = 0.96
assert over.subtotal == Decimal("42.96") # base + surcharge
assert over.tax == Decimal("1.93") # 42.96 * 0.045, half-up
assert over.total == Decimal("44.89")
# Ordering invariant: tax is computed on the surcharge-inclusive subtotal.
assert over.tax == (over.subtotal * tax_rate).quantize(Decimal("0.01"))
Running these against fixtures drawn from real reconciled bills — not only synthetic numbers — is what surfaces the off-by-a-cent tax cases and the “just over the threshold” disputes before a cycle posts. An account whose stored total no longer matches a fresh recomputation points to a rule change that was applied in place instead of versioned.
FAQ
Related Topics
- Surcharge & Fee Application Logic — the parent guide covering rule schemas, seasonal windows, and audit trails this surcharge plugs into.
- Automated Rate Calculation & Rule Engines — the end-to-end rating subsystem this fee layer decorates.
- Step-Rate vs Block-Rate Structure Design — the marginal-slice band model to reuse for a tiered conservation surcharge.
- Proration & Billing Period Calculation — how to handle a surcharge that runs only part of a billing cycle.
- Seasonal Rate Mapping & Calendar Logic — resolving the drought or peak season a conservation surcharge is tied to.
Up: Surcharge & Fee Application Logic · Automated Rate Calculation & Rule Engines