How to Automate Lowest Unit Rate Calculations in Python
The lowest unit rate is deceptively simple to state and treacherous to compute: during the 45 days before a primary and the 60 days before a general election, a legally qualified candidate cannot be charged more per spot than the lowest rate the station charged any advertiser for the same class of time in that window. The trap is the word “lowest” — it is not a number fixed at booking, it falls every time a commercial advertiser buys the same class cheaper, and when it falls beneath a rate a candidate already paid, the station owes a rebate. This guide solves one exact task: gather every spot for a candidate in a given class and daypart within the window, compute the live lowest unit rate, detect the rebate obligation when a later, lower rate appears, and emit the adjustment. It is the calculation that sits under Automating FCC Political File Compliance, itself a subsystem of Spot Scheduling Validation & Rule Engines. Getting it wrong is not cosmetic: an overcharge that survives to an FCC complaint under 47 CFR §73.1942 is a direct liability, and an undetected rebate is money the station must refund with interest once an audit finds it.
The core requirement is determinism. The same set of orders and rate observations, replayed tomorrow, must yield the same lowest unit rate and the same rebate — or the political file cannot be reconciled against billing. That rules out any calculation that depends on arrival order or wall-clock time, in favour of a pure function over the class’s rate history, keyed on a class of time that has already passed through billing code normalization so a candidate’s buy compares against the right commercial spots.
Prerequisites
- Python 3.11+ — required for the
X | Noneunion syntax and timezone-awaredatetime.now(timezone.utc)used throughout. - Pydantic v2 — pin exactly (
pydantic==2.9.2); the models below rely on v2field_validator/model_validatorsemantics and will not run under v1. - Integer money, never floats — all rates are held in whole cents (
int); binary floating point silently mis-rounds a rebate and fails a financial audit. No third-party money library is required. - Read access to the class rate history — a
SELECT-only view of every rate charged to every advertiser per class during the window, resolved through the canonical traffic field data dictionary soclass_of_timemeans the same thing here as in the scheduler. - A stable class taxonomy — preemptible and fixed-position classes must be distinguishable, because they are different products and must never be pooled into one lowest-unit-rate comparison.
Step-by-Step Implementation
The calculation runs as a pure function over a class’s rate history: filter observations to the class and window, take the minimum as the lowest unit rate, compare each candidate charge against it, and emit a rebate adjustment for any charge that now exceeds it. Nothing mutates a filed disposition; a rebate is a new record that references the original.
Figure — Lowest unit rate computation: the minimum in-class, in-window rate becomes the lowest unit rate, and any candidate charge above it produces a rebate adjustment for the difference.
Step 1 — Model the rate observation and the candidate charge
Goal: fix the typed models that carry a rate through the calculation, and emit audit lines in the traffic-ops timestamp | level | module | spot_id shape. Every rate is an integer count of cents so a rebate never suffers a floating-point rounding error.
from __future__ import annotations
import logging
from datetime import datetime, timezone
from enum import Enum
from pydantic import BaseModel, Field, field_validator
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger("traffic.political.lur")
class ElectionKind(str, Enum):
PRIMARY = "primary" # LUR window opens 45 days before
GENERAL = "general" # LUR window opens 60 days before
class RateObservation(BaseModel):
"""A single rate charged to any advertiser for one class of time."""
advertiser_id: str
class_id: str # Preemptible and fixed classes are distinct
rate_cents: int = Field(gt=0)
charged_at: datetime
@field_validator("charged_at")
@classmethod
def _utc_aware(cls, v: datetime) -> datetime:
# A naive local timestamp can misplace the window edge across DST.
if v.tzinfo is None:
raise ValueError("charged_at must be timezone-aware (UTC)")
return v.astimezone(timezone.utc)
class CandidateCharge(BaseModel):
"""What a candidate's committee was actually billed for a spot."""
order_id: str
spot_id: str
class_id: str
charged_cents: int = Field(gt=0)
aired_at: datetime
@field_validator("aired_at")
@classmethod
def _utc_aware(cls, v: datetime) -> datetime:
if v.tzinfo is None:
raise ValueError("aired_at must be timezone-aware (UTC)")
return v.astimezone(timezone.utc)
Step 2 — Resolve the pre-election window boundary
Goal: turn an election date and kind into the concrete window during which the lowest unit rate applies — 45 days for a primary, 60 for a general — so an observation is only ever compared inside the correct interval.
from datetime import timedelta
# Federal LUR windows: 45 days before a primary, 60 before a general.
_WINDOW_DAYS: dict[ElectionKind, int] = {
ElectionKind.PRIMARY: 45,
ElectionKind.GENERAL: 60,
}
def window_opens_at(election_at: datetime, kind: ElectionKind) -> datetime:
# The window opens N days before the election and runs through election day.
if election_at.tzinfo is None:
raise ValueError("election_at must be timezone-aware (UTC)")
opens = election_at.astimezone(timezone.utc) - timedelta(days=_WINDOW_DAYS[kind])
logger.info("LUR window for %s opens %s", kind.value, opens.isoformat())
return opens
def in_window(moment: datetime, election_at: datetime, kind: ElectionKind) -> bool:
opens = window_opens_at(election_at, kind)
return opens <= moment.astimezone(timezone.utc) <= election_at.astimezone(timezone.utc)
A representative log line reads 2026-09-20T11:04:02+00:00 | INFO | traffic.political.lur | LUR window for general opens 2026-09-05T00:00:00+00:00.
Step 3 — Compute the lowest unit rate for a class
Goal: take the minimum rate charged to any advertiser for the exact class within the window. Filtering on class_id is what keeps a preemptible political spot from being priced against a fixed-position commercial buy.
def compute_lur(
observations: list[RateObservation],
class_id: str,
election_at: datetime,
kind: ElectionKind,
) -> int | None:
# Keep only same-class rates that fall inside the pre-election window.
in_class = [
obs.rate_cents
for obs in observations
if obs.class_id == class_id
and in_window(obs.charged_at, election_at, kind)
]
if not in_class:
# No comparable commercial sale in the window: the LUR rule cannot bind.
logger.warning("no in-window observations for class_id=%s", class_id)
return None
lur = min(in_class)
logger.info("class_id=%s lur_cents=%d over %d observations",
class_id, lur, len(in_class))
return lur
Step 4 — Detect the rebate obligation and emit an adjustment
Goal: compare each candidate charge against the live lowest unit rate and, when a later, lower rate has pushed the charge above it, emit a rebate adjustment for the exact difference. The adjustment references the original spot; it never rewrites the filed disposition.
class RebateAdjustment(BaseModel):
order_id: str
spot_id: str
class_id: str
charged_cents: int
lur_cents: int
rebate_cents: int = Field(ge=0)
computed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
def detect_rebates(
charges: list[CandidateCharge],
observations: list[RateObservation],
election_at: datetime,
kind: ElectionKind,
) -> list[RebateAdjustment]:
adjustments: list[RebateAdjustment] = []
# Cache the LUR per class so a shared class is computed once per run.
lur_cache: dict[str, int | None] = {}
for charge in charges:
if charge.class_id not in lur_cache:
lur_cache[charge.class_id] = compute_lur(
observations, charge.class_id, election_at, kind
)
lur = lur_cache[charge.class_id]
if lur is None:
continue # No comparable sale: nothing to rebate against.
if charge.charged_cents > lur:
rebate = charge.charged_cents - lur
logger.warning(
"%s | order=%s rebate_due charged=%d lur=%d rebate=%d",
charge.spot_id, charge.order_id,
charge.charged_cents, lur, rebate,
)
adjustments.append(RebateAdjustment(
order_id=charge.order_id, spot_id=charge.spot_id,
class_id=charge.class_id, charged_cents=charge.charged_cents,
lur_cents=lur, rebate_cents=rebate,
))
else:
logger.info("%s | order=%s compliant charged=%d lur=%d",
charge.spot_id, charge.order_id, charge.charged_cents, lur)
return adjustments
When a general-election candidate paid 42000 cents for a prime rotator and a commercial advertiser later bought the same class at 39000, the run emits 2026-10-11T09:15:44+00:00 | WARNING | traffic.political.lur | SP-2026-1004-0112 | order=PO-2026-0742 rebate_due charged=42000 lur=39000 rebate=3000 and returns a single RebateAdjustment of 3000 cents.
Verification & Testing
Correct behaviour rests on two properties: determinism (the same observations always yield the same lowest unit rate) and exact integer rebates (a refund is the precise difference, never a rounded float). Both are assertable against fixture data drawn from a real window.
election = datetime(2026, 11, 3, 0, 0, tzinfo=timezone.utc)
# Two commercial sales in the same class and window; the min is the LUR.
obs = [
RateObservation(advertiser_id="ADV-1", class_id="PRIME-ROT-P",
rate_cents=42000, charged_at=datetime(2026, 9, 20, tzinfo=timezone.utc)),
RateObservation(advertiser_id="ADV-2", class_id="PRIME-ROT-P",
rate_cents=39000, charged_at=datetime(2026, 10, 8, tzinfo=timezone.utc)),
# Out-of-window sale must be ignored even though it is cheaper.
RateObservation(advertiser_id="ADV-3", class_id="PRIME-ROT-P",
rate_cents=30000, charged_at=datetime(2026, 7, 1, tzinfo=timezone.utc)),
]
lur = compute_lur(obs, "PRIME-ROT-P", election, ElectionKind.GENERAL)
assert lur == 39000 # min of in-window sales, not the July 30000
# A candidate charged 42000 is owed exactly the 3000 difference.
charges = [CandidateCharge(order_id="PO-2026-0742", spot_id="SP-1",
class_id="PRIME-ROT-P", charged_cents=42000,
aired_at=datetime(2026, 10, 4, tzinfo=timezone.utc))]
rebates = detect_rebates(charges, obs, election, ElectionKind.GENERAL)
assert len(rebates) == 1
assert rebates[0].rebate_cents == 3000 # exact integer cents, no float drift
Because compute_lur and detect_rebates are pure functions of their inputs, replaying an archived set of observations during an audit reproduces byte-identical rebates. Run the suite in CI against a golden fixture of observations and expected rebates so a change to the window logic that would silently under-refund a candidate fails the build instead of shipping. The rebate rows then flow into generating political file audit exports as adjustments to the filed dispositions.
Edge Cases & Failure Handling
- No comparable commercial sale in the window. If no advertiser bought the class inside the window,
compute_lurreturnsNoneand the candidate charge is left as-is rather than rebated to zero. The lowest-unit-rate rule compares against actual sales; inventing a phantom lowest rate would over-refund. Log the class and reconcile whether the political spot should have been booked in a class with real commercial demand. - Preemptible-versus-fixed class mismatch. A rebate computed by pooling preemptible and fixed rates into one
class_idrefunds against the wrong product and can both over- and under-pay. Keep the classes distinct at the source through billing code normalization; the filter in Step 3 only protects you if the taxonomy upstream is clean. - A cheaper rate lands after the spot already aired. This is the defining case: a commercial sale at a lower rate on October 8th retroactively lowers the lowest unit rate for a candidate spot that aired October 4th. Because
detect_rebatesrecomputes the lowest unit rate over the full window on every run, the later sale surfaces the rebate on the next pass. Schedule the calculation to re-run on every rate-card change during the window, and treat the emitted adjustment as an amendment to the already-filed disposition confirmed by as-run reconciliation.
FAQ
Why hold every rate in integer cents instead of a decimal or float?
Because a rebate must be exact to the penny and reproducible across machines. Binary floating point cannot represent most decimal money values precisely, so a subtraction like 420.00 minus 390.00 can drift by a fraction of a cent and fail a financial audit. Integer cents make every rebate an exact difference. If you must display dollars, format at the boundary only, and keep the canonical field constraints enforcing the integer type upstream so a float never enters the calculation.
Does the lowest unit rate apply to issue ads and PAC buys too?
No. The lowest unit rate under §73.1942 is a benefit of candidate time bought by a legally qualified candidate’s authorized committee during the pre-election window. Issue ads and independent-PAC buys must still be disclosed in the political file, but they pay the station’s comparable commercial rate, not the lowest unit rate. The classification that decides which rule attaches is made in the parent FCC political file compliance engine before this calculation runs, so detect_rebates only ever sees candidate charges.
How often should I re-run the calculation during a window?
On every rate-card change for any class a candidate is buying, because that is the only event that can lower the lowest unit rate and create a new rebate. A nightly sweep is a safe floor, but an event-driven re-run on each commercial sale catches the obligation sooner and shrinks the interest a late refund accrues. The trade-off between sweep frequency and load is the same threshold-tuning problem covered in Tuning Thresholds for Scheduling Accuracy.
What if a spot was preempted before it aired — does it still count?
A preempted political spot that never aired is not a charge to rebate against; it is a make-good obligation. The recovery spot inherits the original order’s rate and re-enters this calculation once it airs, so the lowest-unit-rate check runs against the airing that actually happened. The routing of that recovery is handled by make-good routing for preemptions, which preserves the political order’s identity so billing and the political file stay joined.
Related
- Automating FCC Political File Compliance — the parent engine that classifies orders and calls this lowest-unit-rate calculation as its rate gate.
- Generating FCC Political File Audit Exports — how the rebate adjustments produced here are filed as amendments in the immutable audit bundle.
- Standardizing Billing Codes Across Traffic Systems — the class-of-time normalization that must run upstream so candidate and commercial spots compare against the same class.