Guarantor Variance Reporting: Clause-Referenced, Decimal-Exact Cost Variance in Python
A completion guarantor does not fund a picture on the strength of a bottom-line number. It funds against the story behind every movement in that number: which cost code drifted, by how much, why, and whether the drift is a timing artifact or a real overrun that erodes the contingency the bond sits on top of. When a production accountant hands over a variance report whose deltas cannot each be traced back to a specific budget line and a specific cost-report row, the guarantor’s analyst does the tracing by hand — and every hour of that reconstruction is an hour the reserve release is not moving. This section specifies a variance engine that removes the ambiguity: it computes budget-to-actual and budget-to-estimate-at-completion variance per cost code, attaches an explanation code and a source reference to every delta, and emits an immutable, hashed report package the guarantor reconciles line by line rather than rebuilds. It is one subsystem of the broader Completion Bond Reporting & Guarantor Analytics reference architecture.
The reader here is the production accountant preparing the weekly or period bond submission, the line producer who has to defend a category before it triggers a cutback, and the Python engineer who has to make the arithmetic reproducible when the guarantor asks, three months later, to see exactly how a figure was derived. The engine treats a variance report the way the compliance side of the platform treats a payroll run: same inputs, same outputs, every time, with a cryptographic fingerprint that lets any party recompute the result and confirm nothing was altered after submission.
Prerequisites and Expected Inputs
The implementation targets Python 3.11+ for standard-library zoneinfo and modern union-type syntax. The dependency set is deliberately small: Pydantic v2 for boundary schema validation through model_validate and field_validator, as described in the Pydantic v2 reference; the standard-library decimal, hashlib, and zoneinfo modules for currency-safe arithmetic, deterministic report hashing, and timezone-aware stamps; and, at production scale, polars or SQLAlchemy to stream cost-report rows and persist an append-only report ledger. Money is never float. As the Python decimal module documentation makes explicit, only fixed-point arithmetic gives the deterministic rounding a bond submission requires, and a fractional-cent drift compounded across a few thousand cost lines is precisely the discrepancy a guarantor will make a production redo.
The engine assumes its inputs arrive already normalized and cost-coded. Cost-report rows do not originate here: heterogeneous accounting exports are cleaned upstream by the Cost Ingestion & Data Parsing Workflows subsystem, and each budget line and actual row is keyed against the taxonomy defined in Cost Code Standardization, so a cost dollar maps to exactly one cost code, one department, and one budget line. Three inputs feed a run: the budget baseline (the bonded budget or the current approved revision, one authoritative amount per cost code), the actual cost report (committed and incurred costs to date, sourced from the same run of the accounting system), and the estimate at completion carried forward from the Cost-to-Complete Projections engine. The output is an immutable VarianceReport whose rows each carry a budget figure, an actual, a variance, a variance percentage, an explanation code, a source reference, and a SHA-256 fingerprint of the inputs that produced them.
Architecture: A Matcher, a Variance Engine, and an Immutable Report
The design error to avoid is computing variance as a single subtraction over a joined table. Variance reporting for a bond is a matching problem before it is an arithmetic problem: a budget line and an actual line only belong on the same report row if they resolve to the same cost code, and a line that matches nothing on the opposite side is not a zero variance — it is an exception that must be surfaced, not silently absorbed into a total. A production-grade engine therefore matches budget, actual, and estimate-at-completion by cost code, routes any unmatched line to quarantine, computes each delta in Decimal, and emits an immutable report stamped with the source hash so the guarantor can reconstruct not just the number but the exact inputs that produced it.
The diagram below shows the three normalized inputs matched by cost code into a single variance engine, the emitted immutable report, and the quarantine path any unmatched line follows instead of being folded into a total.
Two properties make the model defensible rather than merely functional. First, matching is total: every budget line and every actual line either joins a counterpart by cost code or is quarantined, so a line that exists on one side and not the other can never disappear into a nil variance that a guarantor’s own tie-out would later expose. Second, the variance engine is a pure function — same three inputs, same report, every time — which is what lets a production accountant replay a disputed period deterministically and hand the guarantor a byte-identical package. When the estimate-at-completion is provisional because a forecast input is still settling, that provisional figure is flagged on the row rather than presented as final, the same conservative discipline the sibling Contingency Drawdown Tracking engine applies before it signals a category is approaching a cutback threshold.
Core Implementation
The reference engine models each budget and actual line as a frozen Pydantic v2 object validated at the boundary, matches the two sides by cost code, computes budget-to-actual and budget-to-estimate-at-completion variance in Decimal, assigns an explanation code by threshold band, and emits an immutable report whose SHA-256 fingerprint covers the sorted inputs. Monetary fields are Decimal; the report timestamp is timezone-aware; an unmatched line is quarantined, never coerced into a zero.
import hashlib
import json
import logging
from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum
from typing import Any
from zoneinfo import ZoneInfo
from pydantic import BaseModel, ConfigDict, Field, field_validator
logger = logging.getLogger("guarantor_variance")
CENTS = Decimal("0.01")
PCT = Decimal("0.1") # variance percentage rounded to one decimal place
class ExplanationCode(str, Enum):
ON_BUDGET = "on_budget" # within tolerance band
TIMING = "timing" # actual trails budget, EAC holds
OVERRUN = "overrun" # actual or EAC exceeds budget
UNDERRUN = "underrun" # forecast to finish below budget
NO_BUDGET = "no_budget" # spend against an unbudgeted code
class BudgetLine(BaseModel):
"""One authoritative bonded-budget amount for a cost code."""
model_config = ConfigDict(frozen=True)
cost_code: str
description: str
budget: Decimal = Field(ge=Decimal("0"))
@field_validator("budget", mode="before")
@classmethod
def reject_float(cls, v: Any) -> Decimal:
if isinstance(v, float):
raise ValueError("budget must be str/Decimal, never float")
return Decimal(str(v))
class ActualLine(BaseModel):
"""One cost-report row: committed plus incurred cost for a code."""
model_config = ConfigDict(frozen=True)
cost_code: str
source_ref: str # cost-report row id the delta traces to
actual: Decimal = Field(ge=Decimal("0"))
estimate_at_completion: Decimal = Field(ge=Decimal("0"))
eac_is_provisional: bool = False
@field_validator("actual", "estimate_at_completion", mode="before")
@classmethod
def reject_float(cls, v: Any) -> Decimal:
if isinstance(v, float):
raise ValueError("monetary fields must be str/Decimal, never float")
return Decimal(str(v))
class VarianceRow(BaseModel):
model_config = ConfigDict(frozen=True)
cost_code: str
description: str
source_ref: str
budget: Decimal
actual: Decimal
estimate_at_completion: Decimal
variance_to_actual: Decimal # budget − actual (positive = under)
variance_to_eac: Decimal # budget − EAC
variance_pct: Decimal # to-EAC variance as % of budget
explanation: ExplanationCode
eac_is_provisional: bool
class VarianceReport(BaseModel):
model_config = ConfigDict(frozen=True)
rows: tuple[VarianceRow, ...]
total_budget: Decimal
total_eac: Decimal
total_variance_to_eac: Decimal
payload_hash: str
computed_at: str
operator_id: str = "SYS_AUTO"
class VarianceEngine:
# Tolerance band inside which a delta is reported ON_BUDGET, expressed as
# a fraction of the line budget. A versioned bond parameter, not law.
TOLERANCE = Decimal("0.02")
def __init__(self) -> None:
self.quarantine: list[dict] = []
def _quantize(self, amount: Decimal) -> Decimal:
return amount.quantize(CENTS, rounding=ROUND_HALF_UP)
def _variance_pct(self, budget: Decimal, variance: Decimal) -> Decimal:
# Guard the zero-budget divide: percentage is undefined against a nil
# base, so an unbudgeted code reports 0.0% and is flagged NO_BUDGET.
if budget == 0:
return Decimal("0.0")
return (variance / budget * Decimal("100")).quantize(
PCT, rounding=ROUND_HALF_UP
)
def _classify(self, budget: Decimal, actual: Decimal,
eac: Decimal) -> ExplanationCode:
if budget == 0:
return ExplanationCode.NO_BUDGET
band = (budget * self.TOLERANCE)
if eac > budget + band:
return ExplanationCode.OVERRUN
if eac < budget - band:
return ExplanationCode.UNDERRUN
if actual < budget - band:
# Spend trails budget but the forecast still lands on budget.
return ExplanationCode.TIMING
return ExplanationCode.ON_BUDGET
def build(self, budget_lines: list[BudgetLine],
actual_lines: list[ActualLine]) -> VarianceReport:
budgets = {b.cost_code: b for b in budget_lines}
actuals = {a.cost_code: a for a in actual_lines}
# Any code present on only one side is an exception, not a zero.
for code in budgets.keys() ^ actuals.keys():
side = "actual" if code in budgets else "budget"
self._quarantine_code(code, f"no matching {side} line")
rows: list[VarianceRow] = []
for code in sorted(budgets.keys() & actuals.keys()):
b, a = budgets[code], actuals[code]
var_actual = self._quantize(b.budget - a.actual)
var_eac = self._quantize(b.budget - a.estimate_at_completion)
rows.append(VarianceRow(
cost_code=code,
description=b.description,
source_ref=a.source_ref,
budget=b.budget,
actual=a.actual,
estimate_at_completion=a.estimate_at_completion,
variance_to_actual=var_actual,
variance_to_eac=var_eac,
variance_pct=self._variance_pct(b.budget, var_eac),
explanation=self._classify(b.budget, a.actual,
a.estimate_at_completion),
eac_is_provisional=a.eac_is_provisional,
))
total_budget = self._quantize(sum((r.budget for r in rows), Decimal("0")))
total_eac = self._quantize(
sum((r.estimate_at_completion for r in rows), Decimal("0"))
)
report_body = tuple(rows)
return VarianceReport(
rows=report_body,
total_budget=total_budget,
total_eac=total_eac,
total_variance_to_eac=self._quantize(total_budget - total_eac),
payload_hash=self._hash(report_body),
computed_at=datetime.now(tz=ZoneInfo("UTC")).isoformat(),
)
def _hash(self, rows: tuple[VarianceRow, ...]) -> str:
material = json.dumps(
[r.model_dump(mode="json") for r in rows],
sort_keys=True, separators=(",", ":"),
).encode("utf-8")
return hashlib.sha256(material).hexdigest()
def _quarantine_code(self, code: str, reason: str) -> None:
self.quarantine.append({
"cost_code": code,
"reason_code": reason,
"quarantined_at": datetime.now(tz=ZoneInfo("UTC")).isoformat(),
"requires_manual_override": True,
})
logger.warning("Quarantined cost_code=%s: %s", code, reason)
The frozen=True config makes each input line and every emitted row immutable once validated, so no downstream caller can mutate a budget or an actual after it has been hashed. Because the report body serializes canonically — sorted keys, no incidental whitespace — the SHA-256 fingerprint is stable: two runs over the same inputs produce the same hash, and any edit to a single cost line changes it. That fingerprint is the reference key the guarantor uses to confirm the package it is reconciling is the package that was submitted, and it is what lets each delta trace back through source_ref to the exact cost-report row that produced it. The end-to-end assembly of a single report — including the percentage rounding and zero-budget handling shown here — is walked through in Generating Guarantor-Facing Variance Reports in Python.
Bond-Specific Behavior: Thresholds, Explanations, and Cutback Triggers
A variance report a guarantor accepts differs from a generic budget-versus-actual export in three ways, and each is a deliberate modeling decision rather than a formatting choice.
- Tolerance bands drive the explanation, not just the color. Every row is classified against a versioned tolerance band expressed as a fraction of the line budget. A delta inside the band is
ON_BUDGET; a forecast that lands above the band is anOVERRUN; spend that trails while the estimate at completion still holds isTIMING, not a saving. The band is a bond parameter that the guarantor and the production agree on and that is stamped into the run, so a report never silently reclassifies a category because someone widened a threshold between periods. - Every delta references its source and its clause. A guarantor’s analyst reconciles a variance by opening the underlying cost, so each row carries the
source_refof the cost-report row it derives from and, in the full package, the budget clause or approved-revision reference that authorized the baseline. A delta with no traceable source is not evidence; it is an assertion, and the engine will not emit one. - The report feeds the cutback trigger, it does not decide it. A completion bond permits the guarantor to impose a cutback — a forced reduction in remaining spend — when overruns threaten the contingency. This engine surfaces the categories trending toward that line and the aggregate to-estimate-at-completion variance, but the trigger logic itself lives in Contingency Drawdown Tracking, which watches drawdown against the schedule. Keeping detection here and the trigger there means the variance report stays a factual artifact the guarantor can trust, not an advocacy document.
Two cross-cutting realities shape all three. First, the baseline is versioned: a bonded budget is revised through approved change orders, so a report must name the budget revision it measured against, and a delta computed against last period’s baseline must never be presented as if it measured the current one. Second, the estimate at completion is the volatile input — it is a forecast, and when it is provisional the row says so, so the guarantor weights a settled overrun differently from a forecast that a distributor confirmation or a wrap-cost true-up may still move. The forecasting mechanics behind that figure are specified in Cost-to-Complete Projections.
Error Handling and Quarantine
Not every line resolves to a variance. The engine mirrors the boundary discipline of the ingestion layer: matching handles code resolution; quarantine handles record integrity. A budget line with no counterpart actual, an actual with no counterpart budget, a negative amount where the schema requires a non-negative one, or any float monetary field is not a candidate for a report row — it is quarantined. A cost code that appears on the actual side with no budgeted counterpart is the single most common case, and it is exactly the one a naive left-join would drop: unbudgeted spend is the first symptom of an overrun, so surfacing it as a no matching budget line exception rather than omitting it is the difference between a report the guarantor trusts and one it re-derives.
Every quarantine event records the offending cost code, a machine-readable reason_code, a UTC timestamp, and a manual-override flag, and — in the full pipeline — the SHA-256 hash of the original payload so a production accountant can triage without re-ingesting the batch. The failing line never enters the report as a resolved variance; it enters a separate exception store, so the report body stays clean while the discrepancy is preserved for review. This is the same quarantine contract enforced across the platform in Schema Validation & Error Handling: a rejected record keeps its identity, its reason, and its hash.
Verification
A variance report is only worth submitting if you can prove, after the fact, exactly which inputs produced every row. Verification checks three artifacts, not one.
First, the report shape. Every resolved cost code must produce exactly one VarianceRow whose budget, actual, and estimate_at_completion are Decimal, whose source_ref points at a real cost-report row, whose variance_to_actual and variance_to_eac equal the recomputed differences, and whose variance_pct matches the guarded percentage. The report total must equal the sum of its row budgets and estimates to the cent — the tie-out a guarantor performs first. Re-running the engine over the same inputs must yield an identical payload_hash, the idempotency property that lets a disputed period be replayed and handed back byte-identical.
Second, the audit fields. Each report must carry, at minimum: the budget revision it measured against, the count and value of quarantined codes, a UTC computed_at, and an operator_id (SYS_AUTO for automated runs, a real identifier for a manual override). A guarantor’s analyst reads this header as the provenance of the package, so no field is optional.
Third, the explanation distribution. A period report should surface, per explanation code, the count and aggregate variance: how much is ON_BUDGET, how much is TIMING that will settle, how much is genuine OVERRUN, and how much sits behind provisional estimates still awaiting a settled input. A healthy report is dominated by on-budget and timing rows with a short, explained overrun tail; a report heavy with overruns or provisional estimates is the early signal a category needs attention before it forces a cutback. A minimal harness confirms the invariants:
def verify(report: VarianceReport, engine: VarianceEngine,
budgets: list[BudgetLine], actuals: list[ActualLine]) -> None:
replay = engine.build(budgets, actuals)
assert replay.payload_hash == report.payload_hash, "non-deterministic hash"
assert replay.total_budget == report.total_budget, "budget total drift"
recomputed = sum((r.estimate_at_completion for r in report.rows), Decimal("0"))
assert report.total_eac == recomputed.quantize(Decimal("0.01")), "EAC tie-out"
for row in report.rows:
assert isinstance(row.variance_to_eac, Decimal), "money must be Decimal"
assert row.source_ref, "every delta must trace to a source row"
Engineered with total matching, Decimal-exact arithmetic, versioned tolerance bands, and source-referenced rows, a guarantor variance report stops being a document the bond analyst rebuilds and becomes one they reconcile: every delta ties to a budget line and a cost-report row, every classification is reproducible from a stamped parameter, and the whole package recomputes from a single hash months after submission.
Related
- Completion Bond Reporting & Guarantor Analytics — the parent architecture that routes cost and forecast data through every guarantor-facing reporting engine, including this one.
- Generating Guarantor-Facing Variance Reports in Python — the end-to-end function that assembles a single report package row by row.
- Reconciling the Cost Report to the Bond Cost Statement — the two-sided reconciliation that isolates timing and classification differences before a variance is trusted.
- Cost-to-Complete Projections — the estimate-at-completion forecast this engine consumes as its third input.
- Contingency Drawdown Tracking — where the overruns this report surfaces are watched against the drawdown schedule and a cutback trigger.