Defining grocery's five hardest metrics.
Fill rate, shrink, on-shelf availability, promotional lift and waste. Not hard to calculate. Hard to agree on, which is a different problem and the one that puts two numbers for the same week in two decks. Formula, grain, additivity and the edge cases that cause the disagreement, for each.
Version 1.0, updated 20 September 2026. The YAML carries the same definitions this page does, generated from the same source.
Every one of these is contested inside a single company.
Not between vendors. Between the supply chain team and the commercial team, in the same building, using the same source system, reporting different numbers for the same week and both being defensible.
The disagreements are always in the same three places. What is in the denominator, which is where fill rate and on-shelf availability go wrong. What basis the money is on, cost or retail, which is where shrink and waste go wrong. And what the counterfactual is, which is the whole of promotional lift. None of that is exotic, and none of it is written down anywhere in most grocery estates, which is why reconstructing it is the step that eats a warehouse project.
It matters more with an agent than it did with people. A human who is handed an ambiguous metric asks someone. An agent picks the first plausible table, computes something, and reports it with exactly the confidence of a right answer. Definitions stop being documentation and start being the control surface.
Fill rate
Of what we ordered, how much actually turned up?
| Formula | units_delivered / units_ordered |
| Grain | One purchase order line, per ship-to location, per requested delivery date. |
| Additivity | Non-additive. Re-aggregate from the numerator and denominator at every level. Never average a fill rate across stores. |
| Window | Rolling 7 and 28 days, plus fiscal period to date. |
Computed as a ratio of two sums over the window, not as an average of per-line ratios. Averaging the ratios gives a one-unit line the same weight as a forty-case line, which is how a fill rate reads 96% while the shelf is empty.
What it needs
| Input | Source | The thing that goes wrong |
|---|---|---|
| units_ordered | Purchase order line | The confirmed quantity, not the originally requested quantity, unless you are deliberately measuring supplier confirmation behaviour as well. |
| units_delivered | Goods receipt line | Received and accepted at the door. Product refused on arrival is not delivered. |
| uom | Item master | Cases, eaches or weight. Both sides of the ratio must be in the same one. |
Edge cases
| Over-delivery | Cap the line at 100% before summing, or a supplier that ships 130% of one line offsets a complete cut on another and the estate reads as fully served. |
| Cancelled lines | Decide once whether a cancellation is excluded from the denominator or counted as zero delivered. Excluding it measures the supplier's performance against what they agreed. Counting it as zero measures what the store actually got. Both are defensible and they differ by several points. |
| Substitutions | A substituted item satisfies the shelf but not the order line. Count it in a separate substitution rate and leave fill rate measuring the ordered article, otherwise the number stops being able to detect a supplier who never ships what was ordered. |
| Split deliveries | One PO line answered by three receipts over four days is one line. Roll the receipts up to the line before dividing, and hold the on-time question in a separate OTIF measure. |
| Unit of measure drift | Ordering in cases and receiving in eaches is the single most common cause of a fill rate above 100%. Convert through the item master case pack at read time and fail the row rather than the conversion. |
| Short-dated stock refused at the door | Physically arrived, commercially not delivered. It belongs in the numerator only if your commercial team says it does, and that decision belongs in the definition rather than in a filter someone added to one report. |
In Ward. Ward holds one fill rate definition with the cancellation and substitution rules declared, so a supply chain analyst and an agent resolve the same number.
Shrink
How much inventory did we lose, and did we ever find out why?
| Formula | shrink_cost / net_sales_at_cost |
| Grain | Item by store by fiscal period, bounded by the two physical counts that close the period. |
| Additivity | Non-additive. Sum the loss and the sales separately, then divide. |
| Window | Fiscal period. Anything shorter is a model, not a measurement. |
Expressed at cost on both sides. Mixing a numerator at cost with a denominator at retail is the most common reason two shrink numbers differ inside the same company, and the gap is roughly your gross margin.
What it needs
| Input | Source | The thing that goes wrong |
|---|---|---|
| counted_units | Physical count | The closing count. Shrink is only observable between two of these. |
| book_units | Perpetual inventory | Opening count plus receipts plus transfers in, less sales, less transfers out, less recorded waste. |
| known_loss_cost | Waste and damage log | Recorded and reason-coded. This is the part you can act on. |
| unit_cost | Item master or receipt | State whether it is standard, last or weighted average cost. The three disagree on fresh. |
Edge cases
| Known versus unknown | Known shrink is reason-coded at the point of loss. Unknown shrink is the count variance left over. Report them separately. A total that moves tells you nothing about whether to call loss prevention or fix a receiving process. |
| Daily shrink does not exist | Between counts there is no measurement, only an allocation. If you publish a daily shrink figure, publish the allocation method beside it and label it as an estimate. |
| Markdowns are not shrink | Product sold at a reduced price was sold. It belongs in margin erosion. Rolling markdown into shrink hides the difference between a pricing decision and a loss. |
| Mispostings look like shrink twice | A transfer booked out of store A and never booked into store B reads as shrink at A and negative shrink at B. Reconcile transfers before the period closes, and treat negative shrink as a data quality alert rather than a good result. |
| Weight items | Loose produce and service counters lose weight to trim and evaporation. That loss is real, predictable and not theft. Carry an expected-yield allowance per item or the fresh departments will always look like they are being robbed. |
| Negative on-hand | A negative book quantity means the count and the movements have already diverged. Clamp it for reporting and raise it as an exception, because a negative feeding the formula silently reduces measured shrink. |
In Ward. Ward keeps known and unknown shrink as two metrics with one shared cost basis, so the total cannot be quoted without the split that makes it actionable.
On-shelf availability
Could the shopper standing in the aisle actually buy it?
| Formula | observations_available / observations_measured |
| Grain | Item by store by day, and by daypart wherever you replenish more than once a day. |
| Additivity | Non-additive. Weight by sales or by ranged item count, and say which. The two answers differ by several points and the unweighted one flatters you. |
| Window | Daily, reported as a rolling 7 day mean. |
This is not system in-stock. System in-stock asks whether the book quantity is above zero. OSA asks whether the product was on the shelf, facing the shopper, at the moment they looked.
What it needs
| Input | Source | The thing that goes wrong |
|---|---|---|
| ranged_flag | Assortment or planogram | Whether the item is supposed to be in this store at all. This is the denominator control and it is the one most often missing. |
| observation | Shelf audit, shelf image, or zero-sales inference | State the method. An inferred OSA and an audited OSA are different metrics with the same name. |
| book_on_hand | Perpetual inventory | Used to separate a real out of stock from phantom inventory. |
| expected_rate_of_sale | Forecast | Needed before zero sales can be read as evidence of anything. |
Edge cases
| Items that were never ranged | The single largest source of a wrong OSA. An item not ranged to a store is not unavailable there, it is absent by design. Leave it out of the denominator or every small format store reads as failing. |
| Phantom inventory | Book quantity above zero with an empty shelf. It is invisible to any in-stock report built on the perpetual inventory, and it is the majority of the gap between in-stock and OSA in most estates. |
| Slow sellers | Zero sales on an item that sells four units a week is a normal Tuesday. Infer an out of stock only when the observed gap is improbable against that item's own rate of sale, not against a category average. |
| Discontinued and seasonal lines | An item being delisted or out of season should leave the denominator on its effective date, not when someone remembers to update the report. |
| Promotional displays | Product available on an end cap while the home shelf is empty is available to the shopper and unavailable to the replenishment system. Decide which question the metric answers and hold it. |
| Store closed or trading restricted | Exclude non-trading hours and closure days entirely. Counting them as unavailable turns a bank holiday into a supply chain failure. |
In Ward. Ward declares the ranged-item denominator and the observation method on the metric itself, so an OSA quoted in a supplier meeting can be traced to the rule that produced it.
Promotional lift
How much of what sold on promotion would not have sold anyway?
| Formula | (promo_units - baseline_units) / baseline_units |
| Grain | Item by store by promotion instance. |
| Additivity | Non-additive. Sum incremental units and baseline units to the level you want, then divide. Averaging store-level lifts overweights the smallest stores. |
| Window | The promotion period, plus a declared pre-period and post-period for baseline and dip. |
Every number in this formula except promo_units is estimated. Lift is a modelled metric wearing the clothes of a measured one, and the baseline method is the metric. Publish the method beside the number or the number cannot be argued with.
What it needs
| Input | Source | The thing that goes wrong |
|---|---|---|
| promo_units | POS sales | Units sold in the promotion window at the promotion price. |
| baseline_units | Modelled | The counterfactual. Pre-period mean, matched control stores, or a forecast. Name which. |
| promo_calendar | Promotion master | Mechanic, price point, start and end, and which stores actually executed it. |
| osa_during | On-shelf availability | A promotion that sold out on day three did not have low lift, it had no stock. Carry OSA alongside or the two failures are indistinguishable. |
Edge cases
| Pantry loading | Shoppers buying four weeks of supply produce a large lift and a post-promotion dip. Measure the dip window or you are reporting a sales transfer as growth. |
| Cannibalisation | The promoted line grows and its two nearest substitutes fall. Category-level incrementality is the honest number, and it is usually a fraction of the item-level one. |
| Stockout during the promotion | Truncates measured lift and makes a successful mechanic look weak. Either exclude store-days with an out of stock or report lift with an availability caveat attached. |
| Overlapping mechanics | An item on a multibuy and in a leaflet and on a gondola end in the same week has one blended lift, not three. Attribute at the instance level and refuse to split it three ways. |
| New lines have no baseline | An item with under a full baseline window of history has no counterfactual. Report units and distribution instead of a lift percentage, rather than inventing a denominator. |
| Non-executed promotions | Stores that never put the display up are in the promotion master and not in the promotion. Compliance is a separate measure and it belongs in the denominator decision. |
In Ward. Ward stores the baseline method as part of the metric definition, so a lift number arriving from an agent carries the counterfactual it was computed against.
Waste
What did we throw away, and could we have sold it instead?
| Formula | waste_cost / cost_of_goods_sold |
| Grain | Item by store by day, by reason code. |
| Additivity | Non-additive as a rate. The underlying waste cost and waste units are additive and should be the figures that travel. |
| Window | Daily, rolled to fiscal week. Fresh waste is a daily operational signal and a weekly commercial one. |
Waste is the reason-coded, recorded subset of shrink. Reporting both without saying so double counts the loss in every summary that adds them together.
What it needs
| Input | Source | The thing that goes wrong |
|---|---|---|
| waste_units | Waste scan or dump log | Scanned at the point of disposal. Unscanned waste lands in unknown shrink instead, which is why the two metrics have to be read together. |
| reason_code | Waste scan | Date expiry, damage, quality, recall, own use. Without it waste is a number you can report and cannot reduce. |
| unit_cost | Item master or receipt | Same cost basis as shrink, or the two will never reconcile. |
| cogs | Finance | The denominator. Using sales at retail instead inflates nothing consistently and understates fresh. |
Edge cases
| Markdown is avoided waste | Product marked down and sold did not become waste. Counting the markdown as waste punishes the behaviour you want. Track markdown cost and waste cost side by side as total date-code cost. |
| Donations and staff use | Product that left the building without being sold. Reason-code it separately. It is a cost, it is not spoilage, and treating it as spoilage hides a genuine social programme inside a failure metric. |
| Back-door and unscanned waste | Anything binned without a scan becomes unknown shrink. A store whose waste rate falls while its unknown shrink rises has changed its scanning behaviour, not its waste. |
| Weight loss on fresh | Trim, evaporation and service counter yield are not waste. Carry them as a yield allowance, otherwise every butchery counter runs a permanent double-digit waste rate. |
| Recalls | A recall is a single large event with a recoverable cost and usually a vendor credit. Leave it in the log and out of the trend, flagged, or one week rewrites the year. |
| Waste recorded at retail | Some dump logs capture retail price. Convert to cost before it reaches the metric, and store the basis on the row so the conversion is checkable later. |
In Ward. Ward defines waste as a reason-coded subset of shrink with one shared cost basis, so the two can be reported on the same page without adding up to more than was lost.
The same five definitions, as YAML.
Every field on this page, in a file a parser can open. Generated from the same source as the page above, so the two cannot drift.
Take it into your own semantic layer, your own catalog, or a review meeting. It is definitions rather than an implementation: the column names are illustrative and you will map them onto your own item master, purchase order and POS tables. The useful part is the grain, the additivity and the edge cases, because those are the decisions, and the mapping is the easy half.
An agent resolving one of these reaches it through a metric tool rather than by writing its own SQL, and the tool returns the definition version it used alongside the rows. The governed MCP server reference is the tools manifest and the permission model behind that.
Where these definitions are used.
A semantic layer an agent cannot route around
Why one definition has to be the only path to a number once the caller is an agent rather than a person.
Read the case →The audit log, with the schema
Which definition a number resolved through, at which version, is a field in the record. Here is the whole record.
Read the schema →The 48-hour test protocol
Ten metrics is step two of the protocol. These five are a reasonable half of them for a grocery estate.
Run the protocol →Ward for grocery retail
The vertical page: what Ward connects to in a grocery estate and what it computes once it is there.
See the vertical →One definition, resolved the same way by every caller.
Ward AI drafts them from the reports you already publish. You approve the diff.
Read-only to start · your LLM keys · SOC 2 Type II underway · or book a call directly
Find out what your data has been hiding.
Tell us about your operation. We’ll show you the problems Ward catches, and the ones your current tools miss.