A shelf that has to be restocked
A grocery aisle has stock in two places: on the shelf, where shoppers can reach it, and in the storeroom, where it waits for someone to put it out. A sale is lost either way — and the two losses have different owners and opposite remedies.
| an article is | whose problem | which lever |
|---|---|---|
| on the shelf | — | — |
| not on the shelf, but in the storeroom | shelf-stocking | another stacker |
| not ordered at all | purchasing | a higher reorder point |
⚠️ A model with one store cannot tell those apart. It has one stockouts counter, and a
manager reading it cannot know whether to hire somebody or to phone the supplier.
Any unfamiliar word is in the glossary.
The shape
Two stores, and a restock task that moves stock from one to the other:
⭐ The restock is an ordinary product with an ordinary route. It queues for the stacker like any other work, so its lead time is an outcome of the model rather than a number somebody typed — which is the whole reason a second stacker can change the answer.
The shelf is filled by a task rather than by a purchase order:
id: shelf
capacity: 240.0
initial: 240.0
reorder_at: 120.0
max_outstanding: 2
replenished_by: { kind: task, product: restock }
and the step that does the moving both draws and puts back:
resource: stacker
process_seconds: { kind: constant, value: 600.0 }
consumes:
- consumable: storeroom
quantity: { kind: constant, value: 12.0 }
produces:
- consumable: shelf
quantity: { kind: constant, value: 12.0 }
The model
# yaml-language-server: $schema=https://clockwright.dev/schema/model.json
name: grocery-aisle
tick_scale_per_second: 1000
horizon_seconds: 1209600.0
warmup_seconds: 86400.0
resources:
- { id: till, servers: 3 }
- { id: stacker, servers: 2 }
consumables:
- id: shelf
capacity: 240.0
initial: 240.0
reorder_at: 120.0
max_outstanding: 2
replenished_by: { kind: task, product: restock }
- id: storeroom
capacity: 8000.0
initial: 8000.0
reorder_at: 4000.0
reorder_quantity: 6000.0
lead_time_seconds: { kind: constant, value: 43200.0 }
products:
- id: shopper
steps:
- resource: till
process_seconds: { kind: exponential, mean: 95.0 }
consumes:
- consumable: shelf
quantity: { kind: constant, value: 1.0 }
- id: restock
steps:
- resource: stacker
process_seconds: { kind: constant, value: 600.0 }
consumes:
- consumable: storeroom
quantity: { kind: constant, value: 12.0 }
produces:
- consumable: shelf
quantity: { kind: constant, value: 12.0 }
releases:
- product: shopper
interarrival_seconds: { kind: exponential, mean: 40.0 }
model_hash: 264bb3198400d9ae
What it says
Two weeks, one warm-up day, development laptop (AMD Ryzen 7 7840U). Three shops, differing in one thing each:
| shelf stockouts | mean wait for stock | storeroom stockouts | storeroom low-water | stacker busy | sold | |
|---|---|---|---|---|---|---|
| as written | 0 | 0 s | 0 | 2,888 | 62.2% | 32,605 |
| ⚠️ one stacker | 30,086 | 131,550 s | 0 | 3,128 | 100.0% | 26,207 |
⚠️ reorder_at 200 in the storeroom | 8,943 | 4,580 s | 10 | 8 | 61.9% | 32,574 |
⭐⭐ The middle row is the point
With one stacker the shop loses 6,398 sales, and the storeroom never runs short — it still holds 3,128 units at its lowest. The stock was in the building. Nobody put it on the shelf.
⚠️ A single-store model reports that as a stockout, which reads as a purchasing failure and would send a manager to raise the reorder point — the one lever that cannot help. The stacker is at 100%; the answer is another pair of hands.
⚠️ And the third row shows why you must read both stores
A storeroom that is ordered too late also empties the shelf: 8,943 shelf stockouts, because a restock task that cannot get stock out of the storeroom cannot put any on the shelf. The shelf's own numbers look like the second row all over again.
⭐ What separates them is the storeroom: 10 stockouts and a low-water mark of 8 units against 3,128. And the stacker is only 62% busy, so it is plainly not the constraint. Two shelves that look equally empty, two different phone calls.
The general rule. When a resource fills a store and something else drains it, ask which of
them ran out. min_level is the field that answers it — a store that ran dry at hour three and
refilled by hour four ends the run looking exactly like one that never ran short.
What to try
- Give the one-stacker shop a third till. Nothing improves: the tills were never the
constraint, and the model says so by leaving
tillutilisation where it was. - Raise the shelf's
reorder_atfrom 120 to 200. The stacker starts earlier and the shelf holds up longer — at the cost of more trips for the same stock. - Set
max_outstandingto 1. ⚠️ The second stacker stops helping entirely, because only one restock trip is ever in flight. That field is how many replenishments may be outstanding, and with tasks it is what decides whether extra people can be used at all.