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.

Guarantor variance reporting architecture Three normalized inputs — the bonded budget baseline, the actual cost report, and the estimate-at-completion forecast — are matched by cost code in a single matcher. Matched cost codes flow to a variance engine that computes budget-to-actual and budget-to-EAC deltas in Decimal, which emits one immutable, hashed variance report. Any budget or actual line that matches nothing on the opposite side follows a dashed path to a quarantine store for manual reconciliation rather than being absorbed into a report total. Budget baseline bonded · one per code Actual cost report committed + incurred EAC forecast cost-to-complete Match by cost code total · order-invariant Variance engine budget − actual · to-EAC Variance report immutable · hashed Unmatched lines quarantine & reconcile a line matching nothing is surfaced, never absorbed

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 an OVERRUN; spend that trails while the estimate at completion still holds is TIMING, 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_ref of 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.

Matched-row resolution versus unmatched-line quarantine A budget line and an actual cost-report line meet a matching gate keyed on cost code. When both sides resolve to the same code the pair is passed to the variance engine, which computes Decimal deltas and appends an immutable, SHA-256 stamped row to the variance report. When a line matches nothing on the opposite side, or carries a negative or float amount, it is diverted to a separate exception store that keeps the cost code, a reason_code, a hash, and a manual-override flag. A barred, dashed link shows the quarantined line never enters the report total. RESOLUTION QUARANTINE Budget line + actual line keyed on cost code Codes match? both sides present yes no Variance engine (pure function) Variance report immutable · SHA-256 stamped Exception store (quarantine) • budget line with no actual • actual line with no budget • negative or float amount reason_code · payload hash requires_manual_override never enters the total

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.

Up: Completion Bond Reporting & Guarantor Analytics