How a variance gets found
Once the count is clean, the actual reconciliation is arithmetic. For each counted line the engine looks up what the system of record says should be there, subtracts one from the other, and works out three numbers: the gap in units, the gap as a percentage of the system quantity, and the value of the gap in pounds. Then it applies a single rule — the materiality tolerance — to decide whether this gap is worth a manager’s time or whether it just gets logged and moved past. This post walks one real line through that engine, with the numbers, and shows why every bit of it is plain code with no model anywhere near the decision.
Key takeaways
- Each counted line is joined to the system quantity from the snapshot in
stkr-inventory. - The gap is computed three ways: units, percent of the system quantity, and value at unit cost.
- One rule — the materiality tolerance — sorts every line into in agreement, minor variance, or material variance.
- Counted-not-in-system and in-system-not-counted are handled explicitly, not dropped.
- All of it is plain Python. No model is anywhere near the number or the decision.
The join
Reconciliation starts with a join. For each clean counted line from the capture — SKU, location, counted quantity — the engine looks up the matching row in stkr-inventory and reads the system quantity and the unit cost. The lookup is against the snapshot taken when the count session opened, not the live system, so a sale that rings through mid-count can’t move the target underneath the arithmetic. Counting against a moving number is how you get variances that aren’t real.
Three things can come back from the join, and each is handled on purpose:
- Counted, and in the system. The normal case — both numbers exist, and the engine computes the gap.
- Counted, but not in the system. A tin of paint on the shelf that the system has never heard of. There’s no system quantity to subtract from, so this is its own kind of variance: an unrecorded item, treated as a material gap of the full counted quantity and flagged for sign-off.
- In the system, but not counted. A SKU the snapshot expected at this location that no line came in for. If the session covered that SKU’s area, a blank is meaningful — it may be a true zero on the shelf — so it’s surfaced for the manager rather than ignored. If the SKU was simply out of this session’s scope, it’s left alone.
The three numbers
For a line that exists on both sides, the engine computes the gap three ways, because a single number lies in two different directions:
- Units.
counted − system. The raw gap. Negative is a shortage; positive is an overage. - Percent.
(counted − system) / system. The same gap relative to how much there should be. Twelve units missing from a stock of 20 is a different problem from twelve missing from a stock of 5,000. - Value.
(counted − system) × unit cost. The gap in money. This is what actually hits the books, and it’s what makes a small unit gap on an expensive item matter and a big unit gap on a cheap one not.
All three travel with the variance from here on, because the manager in Part 5 needs all three to judge it and the model in Part 4 needs them to suggest a cause.
A real line, with the numbers
Take SKU BOLT-M6-50 — M6 bolts, 50 mm, stocked loose in the fixings aisle. The system snapshot says there should be 480. The Saturday team counted 432. Unit cost is £0.35.
The engine runs the three sums: 432 − 480 = −48 units, which is −48 / 480 = −10%, worth −48 × £0.35 = −£16.80. Now the tolerance: a gap is material if it exceeds 2 units and 1% of the system quantity, or if its value exceeds £20. This gap is 48 units (well past 2) and 10% (well past 1%), so it trips the unit-and-percent test. It’s a material variance, and it goes on to Part 4 for a likely cause. Had the team counted 479 — a gap of one unit, 0.2%, 35 pence — it would have been in agreement: logged, no adjustment, no manager’s time spent. Had they counted 476 — four units, 0.8%, £1.40 — it would clear the unit test but not the percent test, so it’s a minor variance: recorded for the trend, but nothing queued.
The tolerance is the whole game
The materiality tolerance is the one knob that decides how much of a manager’s attention this system spends. Set it too tight and every count buries them in £1 variances; set it too loose and real shrinkage hides under the threshold. That’s exactly why it lives in the rules doc, not the code: a business can tune it — tighter on high-value lines, looser on bulk consumables — without a deploy. The default of “2 units and 1%, or £20” is a starting point, not a law.
The combined test — units and percent — is deliberate. A unit-only rule flags trivial gaps on huge stocks; a percent-only rule flags noise on tiny ones. Requiring both keeps the flag where it belongs: a gap that’s big enough to notice and big enough to matter relative to what should be there. The value test sits beside them as an override, so a two-unit gap on a £200 item still gets caught even though it fails the percent test.
Why there’s no model here
Nothing in this post involves Bedrock, and that’s the point. Subtraction, division, and a threshold comparison are things code does perfectly, cheaply, and identically every time. A model asked to “find the variances” would be slower, cost money per line, and — worst of all — might give a different answer on a re-run, which is intolerable for a number that feeds the books. The model earns its place in the next post, where the question stops being “how big is the gap” (arithmetic) and becomes “what most likely caused it” (judgement over messy signals). The reconciliation stays deterministic; the model only ever labels a number the code has already nailed down.
Why this shape
- The join is against the count-time snapshot, so a mid-count sale can’t fabricate a variance.
- The gap is computed three ways because units, percent, and value each tell the manager something the others don’t.
- One tolerance rule, in a doc, decides materiality — tunable without a deploy.
- Counted-not-in-system and in-system-not-counted are first-class outcomes, not dropped rows.
- It’s all plain Python. The number that feeds the books is never produced by a model.
The gap is now proven and sized. The next post answers the harder question: of the four likely causes, which one does the evidence point to — and how the system asks a model that without ever letting it touch the stock.
All posts