Contingency Drawdown Tracking: Modeling Utilization Against a Bond Drawdown Schedule

Almost every bonded production carries a contingency line — commonly around ten percent of the direct cost budget — and the completion guarantor treats that line as its own margin of safety, not the producer’s discretionary reserve. Draws against it are authorized against a schedule, phased across the shooting and post windows, and every dollar released narrows the cushion the guarantor is relying on to avoid a cutback or a takeover. The failure mode is rarely a single catastrophic overspend; it is a slow, unlogged erosion where department draws are approved ad hoc, recorded in a spreadsheet that nobody reconciles, and the remaining contingency is discovered to be gone only when a covenant test fails. This page specifies a deterministic engine that tracks approved draws against the authorized drawdown schedule, maintains a Decimal-exact utilization accumulator per cost category, computes remaining contingency and the covenant ratios a guarantor watches, and emits a tamper-evident audit entry for every draw — one subsystem of the broader Completion Bond Reporting & Guarantor Analytics reference architecture.

Prerequisites and Expected Inputs

The implementation targets Python 3.11+, both for standard-library zoneinfo and for modern union-type syntax. It uses a deliberately small stack: Pydantic v2 for boundary schema validation via model_validate and field_validator, as documented in the Pydantic v2 reference; the standard-library decimal, hashlib, and zoneinfo modules for currency-exact arithmetic, deterministic payload hashing, and timezone-aware timestamps; and, at production scale, polars or SQLAlchemy to stream draw records and persist an append-only ledger. Money is never a float — as the Python decimal module documentation sets out, only fixed-point arithmetic yields the deterministic rounding a bond cost statement demands, and a fractional-cent drift compounded across a season of draws is a discrepancy the guarantor will make you reconcile line by line.

The engine assumes its inputs arrive already normalized. A draw does not originate here: authorization memos, purchase-order commitments, and cost-report deltas are cleaned upstream by the Cost Ingestion & Data Parsing Workflows subsystem, and each draw is keyed against the taxonomy defined in Cost Code Standardization so it maps to exactly one budget category. The two inputs this engine consumes are a DrawdownSchedule — the authorized contingency allowance per category, phased by schedule period — and a stream of ContingencyDraw records, each a validated, authorized release against a category. Its output is an immutable DrawLedgerEntry carrying the resolved utilization, the remaining contingency, the covenant ratio in force, and a SHA-256 fingerprint of the draw that produced it. Reconciling a period’s draws against that schedule is developed in depth in Tracking Contingency Utilization Against a Drawdown Schedule, and the forward-looking view — spotting the categories that will exhaust their allowance before the guarantor acts — in Flagging Over-Budget Categories Before a Bond Cutback.

Architecture: A Draw Is an Authorized Debit Against a Phased Allowance

The common design error is to model contingency as a single running balance. A single balance hides the two facts a guarantor actually asks about: which category is consuming the cushion, and whether it is consuming it faster than the schedule allowed. The reference architecture therefore treats a draw as an authorized debit routed by category into a per-category accumulator, checked against both the category’s scheduled allowance for the period and the covenant ratio computed over the whole contingency, before it is stamped into an append-only ledger. A draw that is unauthorized, duplicated, or would exceed a hard covenant ceiling is diverted to quarantine rather than silently applied.

The diagram below shows how a validated draw is routed by category into its utilization accumulator, evaluated against the scheduled allowance and the covenant thresholds, and either committed to the ledger or diverted; any draw the engine cannot authorize is quarantined rather than guessed.

Contingency drawdown tracking architecture A validated contingency draw enters an authorization gate. An authorized, non-duplicate draw is routed by cost category into a per-category utilization accumulator, which updates remaining contingency and computes the covenant ratio. A threshold check then classifies the run as within schedule, a warning band, or a covenant breach, and every outcome is committed to a SHA-256 stamped, append-only draw ledger. A draw that is unauthorized, a duplicate by payload hash, or over the hard covenant ceiling follows a dashed path to a quarantine store and a guarantor alert instead of being applied. Validated draw authorized · category Authorized & not duplicate? Per-category accumulator utilization · remaining Covenant ratio check within · warning · breach Draw ledger append-only SHA-256 stamped Quarantine & alert unauthorized · duplicate over hard ceiling an unauthorized or duplicate draw is quarantined, never applied

Two properties make this defensible rather than merely functional. First, application is total: every draw either commits to the ledger with a covenant classification or is quarantined with a reason code, so a release the schedule never authorized cannot silently drain the cushion. Second, the accumulator is deterministic — the same ordered stream of draws always yields the same remaining contingency and the same ratio — which is what lets a production accountant replay a disputed period exactly and hand the guarantor a number it can recompute. When a draw is authorized but its category allowance for the period is exhausted, the engine does not reject it outright; it commits it and raises the category into the warning band, the early signal developed in Flagging Over-Budget Categories Before a Bond Cutback.

Core Implementation

The reference engine models the drawdown schedule and each draw as frozen Pydantic v2 objects validated at the boundary, applies authorized draws into a per-category Decimal accumulator, and writes a hashed provenance entry for every committed draw. Monetary fields are Decimal; audit timestamps are timezone-aware; duplicate draws are rejected by payload hash before they touch the accumulator.

import hashlib
import json
import logging
from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
from typing import Any
from zoneinfo import ZoneInfo

from pydantic import BaseModel, ConfigDict, Field, field_validator

logger = logging.getLogger("contingency_drawdown")

CENTS = Decimal("0.01")
# Covenant bands as utilization ratios of total contingency.
WARNING_RATIO = Decimal("0.75")
BREACH_RATIO = Decimal("0.90")


def _to_decimal(v: Any) -> Decimal:
    # Reject floats outright; parse strings/ints exactly.
    if isinstance(v, float):
        raise ValueError("monetary fields must be str/Decimal, never float")
    return Decimal(str(v))


class DrawdownSchedule(BaseModel):
    """Authorized contingency allowance per category, phased by period."""
    model_config = ConfigDict(frozen=True)

    total_contingency: Decimal = Field(gt=Decimal("0"))
    # {category: {period: authorized_allowance}}
    category_allowance: dict[str, dict[str, Decimal]]

    @field_validator("total_contingency", mode="before")
    @classmethod
    def _dec_total(cls, v: Any) -> Decimal:
        return _to_decimal(v)

    @field_validator("category_allowance", mode="before")
    @classmethod
    def _dec_allowance(cls, v: Any) -> Any:
        return {
            cat: {per: _to_decimal(amt) for per, amt in periods.items()}
            for cat, periods in v.items()
        }

    def allowance_for(self, category: str, period: str) -> Decimal:
        return self.category_allowance.get(category, {}).get(period, Decimal("0"))


class ContingencyDraw(BaseModel):
    """One authorized release against the contingency for one category."""
    model_config = ConfigDict(frozen=True)

    draw_id: str
    category: str
    period: str                    # schedule period, e.g. "2026-W29"
    amount: Decimal = Field(gt=Decimal("0"))
    authorization_ref: str         # guarantor / producer approval reference
    authorized: bool
    requested_at: str              # ISO 8601, timezone-aware

    @field_validator("amount", mode="before")
    @classmethod
    def _dec_amount(cls, v: Any) -> Decimal:
        return _to_decimal(v)


class DrawLedgerEntry(BaseModel):
    model_config = ConfigDict(frozen=True)

    draw_id: str
    category: str
    period: str
    amount: Decimal
    category_utilization: Decimal   # cumulative drawn for this category
    remaining_contingency: Decimal  # across the whole contingency
    covenant_ratio: Decimal         # total drawn / total contingency
    covenant_band: str              # within | warning | breach
    schedule_status: str            # on_schedule | over_period_allowance
    payload_hash: str
    committed_at: str
    operator_id: str = "SYS_AUTO"


class DrawdownTracker:
    def __init__(self, schedule: DrawdownSchedule) -> None:
        self.schedule = schedule
        self.ledger: list[DrawLedgerEntry] = []
        self.quarantine: list[dict] = []
        self._category_util: dict[str, Decimal] = {}
        self._period_drawn: dict[tuple[str, str], Decimal] = {}
        self._seen_hashes: set[str] = set()

    def _hash(self, draw: ContingencyDraw) -> str:
        payload = json.dumps(
            draw.model_dump(mode="json"), sort_keys=True, separators=(",", ":")
        ).encode("utf-8")
        return hashlib.sha256(payload).hexdigest()

    @property
    def total_drawn(self) -> Decimal:
        return sum(self._category_util.values(), Decimal("0"))

    def apply(self, draw: ContingencyDraw) -> DrawLedgerEntry | None:
        payload_hash = self._hash(draw)

        if not draw.authorized:
            self._quarantine(draw, payload_hash, "unauthorized draw")
            return None
        if payload_hash in self._seen_hashes:
            self._quarantine(draw, payload_hash, "duplicate draw (hash seen)")
            return None

        projected_total = self.total_drawn + draw.amount
        projected_ratio = (projected_total / self.schedule.total_contingency)
        # A draw that would push utilization past the hard breach ceiling is
        # held for the guarantor rather than silently exhausting the bond.
        if projected_ratio > BREACH_RATIO:
            self._quarantine(
                draw, payload_hash,
                f"exceeds covenant ceiling: ratio {projected_ratio:.4f}",
            )
            return None

        # Commit: update accumulators only after every gate has passed.
        self._seen_hashes.add(payload_hash)
        self._category_util[draw.category] = (
            self._category_util.get(draw.category, Decimal("0")) + draw.amount
        )
        key = (draw.category, draw.period)
        self._period_drawn[key] = self._period_drawn.get(key, Decimal("0")) + draw.amount

        allowance = self.schedule.allowance_for(draw.category, draw.period)
        schedule_status = (
            "over_period_allowance"
            if self._period_drawn[key] > allowance
            else "on_schedule"
        )
        remaining = self.schedule.total_contingency - projected_total
        ratio = projected_ratio.quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
        band = (
            "breach" if ratio >= BREACH_RATIO
            else "warning" if ratio >= WARNING_RATIO
            else "within"
        )

        entry = DrawLedgerEntry(
            draw_id=draw.draw_id,
            category=draw.category,
            period=draw.period,
            amount=draw.amount.quantize(CENTS, rounding=ROUND_HALF_UP),
            category_utilization=self._category_util[draw.category].quantize(CENTS),
            remaining_contingency=remaining.quantize(CENTS),
            covenant_ratio=ratio,
            covenant_band=band,
            schedule_status=schedule_status,
            payload_hash=payload_hash,
            committed_at=datetime.now(tz=ZoneInfo("UTC")).isoformat(),
        )
        self.ledger.append(entry)
        logger.info(
            "draw=%s category=%s band=%s remaining=%s hash=%s",
            entry.draw_id, entry.category, entry.covenant_band,
            entry.remaining_contingency, entry.payload_hash,
        )
        return entry

    def _quarantine(self, draw: ContingencyDraw, payload_hash: str, reason: str) -> None:
        self.quarantine.append({
            "payload": draw.model_dump(mode="json"),
            "payload_hash": payload_hash,
            "reason_code": reason,
            "quarantined_at": datetime.now(tz=ZoneInfo("UTC")).isoformat(),
            "requires_manual_override": True,
        })
        logger.warning("Quarantined draw %s: %s", draw.draw_id, reason)

The frozen=True config makes both the schedule and each committed ledger entry immutable once validated, so no downstream caller can restate an authorized amount after it has been hashed and applied. Because serialization is canonical — sorted keys, no incidental whitespace — the SHA-256 fingerprint is stable and doubles as the deduplication key: replaying the same draw produces the same hash, which the tracker recognizes and quarantines rather than double-debiting the cushion. That fingerprint is the spine of the audit story, and it is what lets a guarantor’s examiner reconcile the drawdown ledger against the cost-to-complete projections and the variance report without re-keying a single figure.

Bond and Covenant Specifics

The contingency line is where the completion guarantor’s economics and the producer’s flexibility collide, and the tracker has to respect the guarantor’s view precisely because the guarantor holds the more consequential remedy.

  • The schedule is an authorization, not a forecast. The drawdown schedule the guarantor approves phases the contingency across the production timeline; a draw is legitimate only against the allowance for its category and period. Drawing the whole contingency in the first third of the shoot, even for authorized costs, signals a cost problem the guarantor reads as elevated risk regardless of the arithmetic balance still showing green.
  • Utilization ratio drives covenant tests. Most bond agreements express a covenant as a ceiling on the ratio of drawn contingency to total contingency, tested at defined reporting points. The engine computes that ratio on every draw and classifies it into a within, warning, or breach band so the exposure is visible continuously rather than only at the test date.
  • Over-utilization invites a cutback or takeover. When contingency erosion outpaces the schedule and the cost-to-complete no longer fits the remaining budget, the guarantor’s contractual remedies escalate: a cutback of discretionary spend, a demand for additional funds from the financiers, and — at the extreme — a takeover of the production. Every remedy begins with the guarantor’s own reading of contingency utilization, which is exactly the number this engine is built to make defensible.
  • Category detail is what a cutback negotiation needs. A takeover conversation is fought category by category, not on a single total. Maintaining a per-category accumulator, rather than one balance, is what lets a producer show the guarantor that erosion is concentrated in one department with a known cause rather than spread across the whole picture.

Two realities shape all four. First, remaining contingency and the covenant ratio must be derived from the same immutable draw stream that feeds the ledger, never from a re-keyed summary, so the report and the ledger can never disagree. Second, contingency tracking is inseparable from the forward view: a healthy ratio today with a cost-to-complete that already exceeds the remaining budget is a breach waiting for the next reporting period, which is why the accumulator here is designed to be read alongside the cost-to-complete projections rather than in isolation.

Error Handling and Quarantine

Not every draw should resolve to a ledger entry. The rule mirrors the boundary discipline of the ingestion layer: the accumulator handles authorized utilization; quarantine handles draws that must never touch the cushion. A draw whose authorized flag is false, whose payload hash has already been applied, whose amount is non-positive, or which would push utilization past the hard covenant ceiling is not a candidate for the accumulator — it is quarantined.

Every quarantine event serializes the original payload verbatim, attaches the SHA-256 hash of that payload, records a machine-readable reason_code, and pushes the record to a reconciliation queue for human triage. Crucially, the failing draw never mutates a single accumulator — it enters a separate exception store, so the drawdown ledger stays clean while the discrepancy is preserved for review. Duplicate suppression is the load-bearing case here: because a retried batch, a double-submitted memo, or an at-least-once message queue all reintroduce the same draw, deduplicating on the payload hash before applying is what keeps the same authorized release from draining the contingency twice. That write-once, hash-chained discipline is the same one specified for the cost ledger in Audit Logging & Immutability, which is why the hashing belongs in one shared module rather than reimplemented per report.

Commit versus quarantine for an incoming contingency draw An incoming contingency draw hits a gate that checks authorization, duplicate status by payload hash, and the hard covenant ceiling. On the commit side, an authorized, non-duplicate, in-ceiling draw updates the per-category accumulator and appends to the SHA-256 stamped, append-only draw ledger. On the quarantine side, a draw that is unauthorized, a duplicate hash, non-positive, or over the covenant ceiling is diverted to a separate exception store that keeps the payload verbatim with its SHA-256 hash, a reason_code, and a manual-override flag. A barred, dashed link shows the quarantined draw never touches the accumulator. COMMIT QUARANTINE Incoming contingency draw Authorized, unique, in ceiling? yes no Update per-category accumulator Classify covenant band within · warning · breach Draw ledger append-only · SHA-256 stamped Draw-validity failure • unauthorized (authorized = false) • duplicate payload hash • non-positive amount • over hard covenant ceiling Exception store (quarantine) payload verbatim · SHA-256 hash reason_code · requires_manual_override never touches accumulator

Verification

A drawdown tracker is only trustworthy if you can prove, after the fact, that remaining contingency and every covenant band followed deterministically from the authorized draws. Verification checks three artifacts, not one.

First, the ledger entry. Every committed draw must produce exactly one DrawLedgerEntry whose payload_hash matches an independent re-hash of the source draw, whose remaining_contingency equals total contingency minus the cumulative drawn total at that point, whose covenant_band follows from the covenant_ratio, and whose monetary fields are Decimal. Re-applying the same ordered stream must yield identical remaining balances and identical bands — the determinism that lets you replay a disputed period.

Second, the audit log fields. Each committed draw must log, at minimum: draw_id, category, period, amount, category_utilization, remaining_contingency, covenant_ratio, covenant_band, schedule_status, payload_hash, a UTC committed_at, and an operator_id (SYS_AUTO for automated runs, a real ID for manual overrides). A completion-bond examiner reads this sequence as the provenance of the remaining balance, so no field is optional.

Third, the reconciliation shape. A period report should surface, per category: cumulative utilization, the scheduled allowance, the over-allowance flag, and the covenant band — plus the count and reason codes of every quarantined draw awaiting triage. A minimal harness confirms the core invariants:

def verify(tracker: DrawdownTracker) -> None:
    running = Decimal("0")
    for entry in tracker.ledger:
        running += entry.amount
        expected_remaining = (
            tracker.schedule.total_contingency - running
        ).quantize(CENTS)
        assert entry.remaining_contingency == expected_remaining, "balance drift"
        assert isinstance(entry.amount, Decimal), "money must be Decimal"
        assert entry.covenant_band in {"within", "warning", "breach"}, "bad band"
        assert entry.operator_id, "every entry needs an accountable operator id"
    # No committed draw may share a payload hash with another.
    hashes = [e.payload_hash for e in tracker.ledger]
    assert len(hashes) == len(set(hashes)), "duplicate draw committed"

Engineered with per-category accumulation, Decimal-exact arithmetic, hash-based deduplication, and a covenant classification computed on every draw, a contingency drawdown tracker turns the most closely watched line on a bonded picture into a controlled, auditable workflow: the cushion cannot silently disappear, the guarantor’s covenant test is reproducible to the cent, and every draw remains traceable to a signed authorization long after wrap.

Up: Completion Bond Reporting & Guarantor Analytics