Part 4 of 7 · Stock count reconciler series ~8 min read

How a likely cause gets suggested

A number on its own — minus 48 bolts — tells a manager what changed but not why, and the why is what decides what to do about it. Shrinkage means a security conversation. A miscount means recount before you touch anything. An unrecorded sale means a till or process problem. A receiving error means a word with the supplier. This post is about how the system turns a bare variance into a likely cause: the small bundle of evidence it assembles for each gap, the single Bedrock Haiku 4.5 call that picks one cause from a fixed list of four and explains itself in a sentence, and the hard line that keeps the model labelling numbers rather than moving stock.

Key takeaways

  • Only material variances reach the model. Lines in agreement and minor variances never cost a call.
  • For each gap the system builds a small evidence bundle: the gap, recent sales, last receiving, last count date.
  • One Bedrock Haiku 4.5 call returns a cause from a fixed list of four, with a one-line rationale.
  • The four causes are shrinkage, miscount, unrecorded sale, and receiving error — and nothing else.
  • The model labels a number the code already computed. It cannot move stock, change a gap, or invent a SKU.

A number needs a why

Part 3 left us with a proven, sized gap: BOLT-M6-50, short 48 units, −10%, −£16.80. That tells a manager what changed, but the action depends entirely on why. Shrinkage points to a security or process conversation. A miscount means recount before touching anything. An unrecorded sale points at a till or a workflow that isn’t capturing every transaction. A receiving error means a word with the supplier and a check of the delivery paperwork. Same −48 on the screen; four completely different next steps. The job of this piece is to put the most likely why next to the number, so the manager starts from a hypothesis rather than a blank page.

The evidence bundle

The model doesn’t get the whole database, and it doesn’t get a vague prompt. For each material variance the engine assembles a small, structured evidence bundle — the handful of facts that actually discriminate between the four causes — all pulled with plain queries before any call is made:

  • The gap itself. Direction and size in units, percent, and value. A shortage and an overage point at different causes; a 10% shortage and a 200% overage point at different causes again.
  • Recent sales for the SKU. Units sold since the last count. A shortage that matches a sales spike is unremarkable; a shortage with flat sales is not.
  • The last receiving. When stock was last booked in against this SKU, and how much. A gap right after a delivery smells like a receiving error; a gap with no recent delivery does not.
  • The last count date. How long the gap had to accumulate. Forty-eight units lost over eight weeks reads differently from 48 lost since Tuesday.

Keeping the bundle small is deliberate: it makes the call cheap, keeps the model focused on the signals that matter, and means the same gap with the same evidence gets a stable answer. The model is reasoning over four or five numbers and dates, not trawling a warehouse.

A material variance and its evidence go to one Bedrock call that returns a likely cause A left-to-right flow. On the left, a box "Material variance" showing BOLT-M6-50, minus 48 units, minus 10 percent. Below it, an "Evidence bundle" box listing four facts pulled by plain queries: gap minus 48 over 8 weeks; recent sales steady, no spike; last receiving none in 30 days; last counted 8 weeks ago. Both feed a central box, "Bedrock Haiku 4.5 (stkr-cause)", inside a dotted AWS account container. From that box an arrow goes right to a "Suggested cause" box that shows the chosen label "Shrinkage" with a one-line rationale: "sustained shortage, no recent receiving, normal sales — points to loss, not a recording error." Below the model box, a fixed list of the only four allowed causes is shown: shrinkage, miscount, unrecorded sale, receiving error. Three guardrail labels sit along the bottom: the model picks from the fixed list only; it returns a label and a sentence, never an action; if the response is off-list or unparseable it falls back to "cause unknown" and still queues. A note reads: the model labels the gap — it never changes the number, moves stock, or invents a SKU. Material variance BOLT-M6-50 −48 (−10%) Evidence bundle gap −48 / 8 weeks sales steady, no spike receiving none / 30d last counted 8w ago AWS account Bedrock Haiku 4.5 stkr-cause one call per gap picks one of, only: · shrinkage · miscount · unrecorded sale · receiving error Suggested cause: Shrinkage “sustained shortage, no recent receiving, normal sales — points to loss, not a recording error” The model labels the gap — it never changes the number, moves stock, or invents a SKU. An off-list or unparseable reply falls back to “cause unknown” and still queues.
Fig 4. A material variance plus its evidence bundle go to one Bedrock call. The model returns a cause from the fixed list of four and a one-sentence rationale. It cannot return anything outside the list, and it never touches the stock.

One call, four causes

The bundle goes to a single Bedrock Haiku 4.5 call — the stkr-cause Lambda — via Global cross-Region inference. The prompt is tight: here is the gap, here is the evidence, pick exactly one cause from this list and give one sentence of reasoning. The list is closed:

  • Shrinkage. Stock that left without being recorded — theft, breakage, spoilage. The signature is a sustained shortage with normal sales and no recent receiving, which is exactly our bolt line.
  • Miscount. The count itself is wrong — a section skipped, a pack size misread, a digit transposed. The signature is often a gap that’s suspiciously round or suspiciously large, or an overage that has no source.
  • Unrecorded sale. The item left as a legitimate sale that never made it into the system. The signature is a shortage that lines up with foot traffic or a known till issue, with no matching record.
  • Receiving error. The wrong quantity was booked in — a delivery short-shipped, double-counted at the dock, or keyed against the wrong SKU. The signature is a gap that appears right after a recent receiving.

For BOLT-M6-50, the evidence — a steady 10% shortfall, no delivery in 30 days, flat sales, eight weeks since the last count — points away from receiving (no recent delivery) and away from a one-off sale spike, and toward slow, unrecorded loss. The call returns shrinkage with the rationale: “a sustained shortage with no recent receiving and normal sales points to loss rather than a recording error.” That label and that sentence ride along with the variance into the approval queue.

The guardrails on the model

This is the only place a model touches the system, so the rails are explicit:

  • It picks from a fixed list. The four causes are the entire vocabulary. A reply that isn’t one of them is rejected by the code that reads it.
  • It returns a label and a sentence, never an action. The response schema is a cause plus a rationale — there is no field in which the model could say “post −48” even if it wanted to. The adjustment is computed by code in Part 3 and posted by a human in Part 5.
  • It fails safe. If Bedrock errors, times out, or returns something off-list or unparseable, the variance doesn’t vanish — it’s queued anyway, labelled cause unknown, so a manager still sees the gap. A missing suggestion is a smaller problem than a hidden variance.
  • It never reads or writes stock. The stkr-cause Lambda’s IAM role can call Bedrock and read the evidence it was handed. It has no permission to write stkr-inventory. The model literally cannot move stock.

This is the same division of labour as the bill matcher: code owns every number and every decision that touches the books; the model is allowed to be helpful exactly where being occasionally wrong is cheap — a suggestion a human reads before acting. A wrong cause label costs a manager a moment’s thought. A wrong number would cost real money.

Why this shape

  • Only material variances are sent, so the model never runs on lines that don’t matter — and the cost tracks variances, not SKUs.
  • A small, structured evidence bundle keeps the call cheap, focused, and stable across re-runs.
  • A closed list of four causes makes the output checkable — off-list replies are rejected, not displayed.
  • The response is a label and a sentence, with no field that could express an action.
  • Fail-safe: a model failure downgrades to “cause unknown” and still queues. The gap is never lost.
  • The cause Lambda can’t write inventory. The guardrail is enforced by IAM, not just intention.

The gap is sized and the cause is suggested. The last piece puts both in front of a person: how an adjustment gets approved, and how an approved one is written back to inventory with a trail behind it.

All posts