A stock count reconciler on AWS for a few dollars a month
Every business that holds stock runs a count now and then, and every count tells the same uncomfortable story: the shelf and the system disagree. There are 432 bolts on the rack and the system swears there are 480. A tin of paint the system has never heard of is sitting in aisle three. Forty-eight units gone and nobody can say whether it walked out the door, was sold without ringing through, or was never delivered in the first place. Reconciling that by hand — line by line, working out each gap and chasing each cause — is slow, dull, and the first thing that gets skipped when the count runs late. This post walks through the design of a small system that takes a physical count, compares it against the system of record, flags every variance with a likely cause, and queues the adjustments for sign-off. It never moves stock on its own.
Key takeaways
- Two ways to submit a count: a scanner export or a filled-in sheet. Both land as clean counted lines.
- Every counted line lands in one of three states: in agreement, a minor variance that’s logged, or a material variance that’s queued.
- Each material gap is labelled with one of four likely causes: shrinkage, miscount, unrecorded sale, or receiving error.
- The variance arithmetic is plain code. The model only suggests the cause — it never moves stock.
- Designed on AWS for about $2.20/month at typical small-business volume. No adjustment posts without a manager’s sign-off.
The whole system on one page
Before any code, here’s the shape of what we’re designing. A stock count reconciler is not a reorder bot — it never buys anything. It takes a physical count, lines it up against the system of record, and tells you where the two disagree and why. The buying decision stays with you.
What you set up once (the outside)
- The system of record. Wherever your current inventory quantities already live — a stock spreadsheet, the export from your point-of-sale or warehouse system. One row per stocked line: SKU, item name, location, the system quantity, and a unit cost. You already keep this; the reconciler just needs to read it. A small sync mirrors it into a DynamoDB table,
stkr-inventory, so every comparison runs against a stable snapshot rather than a live system mid-edit. - A rules doc. One short doc holding the materiality tolerance — how big a gap has to be before it’s worth a manager’s time. A common set: a variance is material if it exceeds 2 units and 1% of the system quantity, or if the value of the gap exceeds £20, whichever trips first. Anything smaller is logged but posts nothing. The doc also names the approver per location and the quiet hours for notifications.
- Managers. The people who sign off adjustments — usually a store or warehouse manager. Each has an email address. Material variances land with the SKU, the counted quantity, the system quantity, the gap in units and value, the suggested cause with its one-line rationale, and two buttons: Approve and Reject. A button click never moves stock by itself — Approve posts the adjustment and records who did it; Reject discards it and the count line stands.
What runs on every count (the inside)
- The count capture. A count starts a session — a row in
stkr-countsrecording the location, who’s counting, and when. Staff submit either a scanner export or a filled-in sheet; both land in S3, which fires a count-submitted event. The capture Lambda reads the file, ties it to the open session, and normalises it: de-duplicates repeat scans, drops blank rows, maps each entry to a known SKU, and converts cases to eaches where needed. Out comes a clean list of counted lines — SKU, location, counted quantity — with nothing reconciled yet. Part 2 covers this. - The variance and cause engine. Runs as soon as a session is captured. For each counted line it looks up the system quantity, subtracts, and computes the gap three ways: units, percent, and value. It applies the materiality tolerance to sort each line into in agreement, a minor variance (logged, no adjustment), or a material variance (queued). For each material gap it builds a small evidence bundle — the gap, recent sales, the last receiving, the last count date — and asks one Bedrock Haiku 4.5 call to pick a likely cause from a fixed list: shrinkage, miscount, unrecorded sale, or receiving error. The arithmetic is plain Python; the model only labels. Parts 3 and 4 cover this.
- The approval and write-back. Takes each material variance and queues it for the right manager. The email carries the full picture and the suggested cause, with Approve and Reject. On approve, the adjustment is posted to
stkr-inventoryatomically and a row is written tostkr-adjustmentswith a before-and-after snapshot, so the trail is auditable. A daily sweep re-surfaces any variance left unactioned, and a monthly summary writes a short narrative: lines counted, value caught in variances, the split of causes. Part 5 covers this.
In plain words
The Saturday team counts the fixings aisle and submits the scan. The capture turns it into clean lines and the engine reconciles them. Most agree. One does not: SKU BOLT-M6-50, counted 432, system says 480 — a gap of 48 units, 10%, worth £16.80 at £0.35 each. That clears the materiality tolerance, so it’s queued. The engine pulls the evidence: no delivery of M6 bolts in the last month, steady sales, last counted eight weeks ago. One Bedrock call returns likely cause: shrinkage with the rationale “a sustained shortage with no recent receiving and normal sales points to loss rather than a recording error.” The manager, Priya, gets an email: “BOLT-M6-50 — short 48 units (−10%, −£16.80). Likely shrinkage. [Approve] [Reject].” She recounts the rack, confirms it, and taps Approve. The system posts −48 against the SKU and logs the adjustment. The leak is now visible, dated, and explained — not discovered six months later when the numbers stop adding up.
Running this costs about $2.20 a month at SMB volume. The cost of not running it is shrinkage that hides inside “the count was a bit off,” receiving errors nobody traces back to the supplier, and a system of record that drifts further from the shelf with every month that passes.
Design rules that shaped every decision
- The arithmetic is plain Python against a tolerance in a doc. No model decides whether a gap is real or how big it is.
- Three states for every line: in agreement, a minor variance that’s logged, or a material variance that’s queued. There is no fourth.
- Four likely causes, always: shrinkage, miscount, unrecorded sale, receiving error. The model picks from that list and explains itself in a sentence.
- It never moves stock on its own. A manager approves every adjustment, and the email carries the exact gap.
- It reconciles; it does not reorder. Working out what you have is a different job from deciding what to buy.
- Every action is logged. Audit an adjustment next year and you can see who posted it, when, and why.
Why this shape
Most small teams handle a stock count one of three ways: they count, eyeball the obvious gaps, and overwrite the system with the new numbers; they count and never reconcile, so the sheet goes in a drawer; or they reconcile by hand on a spreadsheet that takes a day and gets abandoned halfway. Overwriting the system loses the variance entirely — you never learn that 48 bolts went missing, only that the number is now 432. Never reconciling means the system and the shelf drift apart until nobody trusts either. And reconciling by hand is real work that competes with everything else on a manager’s desk.
The setup above keeps the system of record as the source of truth, takes the count as a fact, and puts a small system between them that computes every gap, explains the material ones, and changes nothing until a person says so. The gaps inside tolerance get logged and ignored, so the manager only ever looks at the handful that matter. Each of those arrives with a likely cause already attached, so the decision is “recount or accept,” not “start investigating.” And nothing is ever posted automatically — the reconciler proposes, a human decides.
The next four posts walk through each piece in turn: how a count gets captured, how a variance gets found, how a likely cause gets suggested, and how an adjustment gets approved. One diagram per post. A cost breakdown and a final engineering reference at the end.
All posts