Theoretical Vs Actual Food Cost Calculation

Supplier Price Variance Tracking

Not every food-cost overage is a kitchen problem — sometimes the kitchen executed perfectly and the supplier simply raised the price. This guide, part of the theoretical vs actual food cost calculation domain, isolates the sub-problem of measuring the supplier-price component of variance: how much of a period’s cost change is attributable to what ingredients cost, independent of how much was used. The failure this eliminates is misattribution — blaming portioning or waste for a variance that was actually a vendor price increase, which sends an operator chasing the line cooks when the real fix is a procurement conversation. It produces the supplier_price_cost component that cost variance attribution models subtract as a measured cause.

The supplier price variance pipeline Invoice prices are ingested into a temporal price ledger keyed by supplier, SKU and effective date. Each current price is indexed against a baseline-period price to produce a price delta. The delta is weighted by the quantity actually purchased to yield a supplier-price variance in cost terms. Invoices with implausible price jumps are quarantined for review rather than fed into the variance directly. implausible jump Invoice prices → price ledger Index vs baseline price delta Quantity-weight × purchased qty Price variance cost component Quarantine

Concept Definition and Data Contract

Supplier price variance is the classic price-times-quantity decomposition applied to procurement: the cost change caused by price movement, holding quantity at what was actually purchased. The input contract is a temporal price ledger — (supplier_id, ingredient_sku, unit_cost, effective_date) — plus the period’s purchased quantities in canonical units from unit conversion and canonicalization. The output contract is a supplier_price_cost per (location_id, ingredient_sku), signed, in Decimal.

The baseline is the load-bearing choice: price variance is always relative to something. A prior-period price isolates recent movement; a contracted price isolates deviation from an agreement. The pipeline supports either, but the baseline must be explicit and versioned, or the variance means nothing.

Architecture Decision Rationale

Price-quantity separation over blended averages. Isolating the price effect requires holding quantity fixed. Blending price and quantity into a single average cost hides whether a cost rose because the vendor charged more or because the kitchen used more — exactly the distinction this layer exists to draw. Weighting the price delta by actual purchased quantity gives a clean, isolated price component.

Temporal ledger over a mutable current price. A single “current price” cell cannot answer “what did this cost last quarter?” The price ledger stores every price with an effective date, so a variance can be computed as-of any period and a retroactive invoice correction mints a new dated row rather than rewriting history. This mirrors the temporal versioning in the core architecture.

Quarantine implausible jumps. A 400% overnight price spike is far more likely a unit error on the invoice (price per case booked as price per unit) than a real market move. Feeding it into the variance produces a huge false attribution; quarantining it triggers a review. The detection of slow, sub-threshold increases is worked in detecting supplier price creep.

Phase 1 — The Temporal Price Ledger

The first phase ingests invoice prices into a dated ledger and resolves the effective price as-of a date.

from __future__ import annotations

from datetime import date
from decimal import Decimal

import pandas as pd


def effective_price(ledger: pd.DataFrame, sku: str, as_of: date) -> Decimal:
    """Most recent price effective on or before as_of."""
    rows = ledger[
        (ledger["ingredient_sku"] == sku) & (ledger["effective_date"] <= as_of)
    ].sort_values("effective_date")
    if rows.empty:
        raise LookupError(f"no price for {sku} as of {as_of}")
    return Decimal(str(rows["unit_cost"].iloc[-1]))

Phase 2 — Index Against Baseline and Quarantine Jumps

Each current price is compared to its baseline; implausible deltas are quarantined before they reach the variance.

from decimal import Decimal

import pandas as pd


def index_prices(current: pd.DataFrame, baseline: pd.DataFrame,
                 jump_ceiling: Decimal = Decimal("2.0")) -> tuple[pd.DataFrame, pd.DataFrame]:
    df = current.merge(baseline, on="ingredient_sku", suffixes=("_cur", "_base"))
    df["price_delta"] = df["unit_cost_cur"] - df["unit_cost_base"]
    df["rel_change"] = df.apply(
        lambda r: (r["price_delta"] / r["unit_cost_base"])
        if r["unit_cost_base"] > 0 else Decimal("0"),
        axis=1,
    )
    suspect = df["rel_change"].abs() > jump_ceiling
    return df[~suspect].copy(), df[suspect].assign(reason="implausible_price_jump")

Phase 3 — Quantity-Weight and Reconcile

The clean price deltas are weighted by purchased quantity to produce the cost variance, exactly.

from decimal import Decimal

import pandas as pd


def weight_variance(indexed: pd.DataFrame, purchased: pd.DataFrame) -> pd.DataFrame:
    df = indexed.merge(purchased, on=["location_id", "ingredient_sku"], how="left")
    df["purchased_qty"] = df["purchased_qty"].fillna(Decimal("0"))
    df["supplier_price_cost"] = df.apply(
        lambda r: Decimal(str(r["price_delta"])) * Decimal(str(r["purchased_qty"])),
        axis=1,
    )
    return df[["location_id", "ingredient_sku", "price_delta",
               "purchased_qty", "supplier_price_cost"]]

The supplier_price_cost is signed — a negative delta (the vendor got cheaper) reduces the variance — and it is exactly the term the attribution model subtracts before inferring prep and portioning causes. Because it is quantity-weighted at actual purchased volume, it reconciles cleanly against the procurement ledger.

Production Hardening

  • Explicit, versioned baseline. Store the baseline choice (prior period or contract) with the run; a variance without a stated baseline is uninterpretable.
  • Temporal price ledger. Keep every price dated so any period is computable as-of and corrections append rather than overwrite.
  • Quarantine implausible jumps. Route price changes beyond a ceiling to review; an invoice unit error must not become a false variance.
  • Decimal weighting. Compute the price-times-quantity term in Decimal so the component reconciles against procurement to the cent.
  • Signed component. Preserve the sign so favorable price moves offset unfavorable ones in the attribution total.

Failure Modes and Troubleshooting

Symptom Likely cause Detection / fix
Variance blamed on kitchen, not vendor Price and quantity blended in one average Separate the price effect; weight the price delta by fixed quantity.
A single SKU dominates variance Invoice unit error (per-case as per-unit) Quarantine changes beyond a jump ceiling; verify the invoice unit.
Cannot compute last quarter’s variance Only a current price stored Use a temporal price ledger with effective dates.
Component does not reconcile to procurement Float weighting Compute price × quantity in Decimal.
Favorable prices vanish from the picture Price delta stored as magnitude Keep supplier_price_cost signed.

FAQ

How is the supplier-price component isolated from usage?

By holding quantity fixed. The price variance weights the change in unit price by the quantity actually purchased, so it captures only the effect of price movement, not of using more or less. Blending price and quantity into an average cost destroys this separation, which is precisely what makes it impossible to tell a vendor increase from a kitchen overuse.

Why does the baseline choice matter so much?

Because a price variance is always relative to a reference. Comparing to the prior period isolates recent movement; comparing to a contracted price isolates deviation from an agreement — a supplier billing above contract. The two answer different questions, so the baseline must be explicit and versioned with each run, or the resulting number cannot be interpreted.

What makes a price change suspect enough to quarantine?

A relative change beyond a configured ceiling — often a doubling or more overnight — is far more likely a data error than a real market move. The classic cause is a unit mismatch on the invoice, such as a per-case price booked as per-unit. Quarantining it for review keeps a single bad invoice from producing an enormous false variance that swamps the real signal.

Why keep the price component signed?

Because suppliers move prices both ways. A favorable price (the vendor got cheaper) genuinely reduces food cost and should offset an unfavorable move elsewhere in the attribution. Storing the component as a magnitude would hide those offsets and make the attribution total fail to reconcile against the measured variance.

For library specifics, see the official pandas documentation and Python decimal documentation.