Consumables
Any unfamiliar word is in the glossary.
A consumable is material a step draws down: photoresist, adhesive, test fixtures, anything that runs out and is replenished. A step that cannot get what it needs waits.
consumables:
- id: resist
capacity: 200.0
initial: 200.0
reorder_at: 50.0
reorder_quantity: 150.0
lead_time_seconds: { kind: constant, value: 3600 }
Every key
| key | type | required | what it means |
|---|---|---|---|
id | string | yes | The store's name, referenced by a step's consumes. |
capacity | number | yes | The most that can be held. |
initial | number | no | Stock at the start of the run. ⚠️ Omitting it starts the store FULL — the level becomes capacity, which is usually what you want and is never what you would guess from the word. A store meant to start empty has to say 0. ⚠️ Note it is absent from the parsed document rather than set to a number: the substitution happens when the model is compiled, so a tool reading your JSON back will see no initial at all. |
reorder_at | number | no | Order when the inventory position falls to or below this. Absent means never — a store filled only by a step's produces. |
replenished_by | object | no | What the reorder point triggers: { "kind": "order" } buys stock from outside the model (the default, and what every model written before this did), or { "kind": "task", "product": "restock" } releases an entity and lets its route put the stock back. |
reorder_quantity | number | no | How much to order, Q. Constant. |
max_outstanding | number | no | At most this many orders may be in flight at once. Absent means no limit. |
lead_time_seconds | dist | no | How long an order takes to arrive. |
lead_time_profile | string | no | A calendar profile the lead time observes, by id. Absent means the supplier delivers around the clock. ⚠️ The factor scales the duration, so 0.0 is a segment in which the order makes no progress — and it is integrated across every boundary it spans rather than fixed at the factor in force when the order was placed. |
The reorder policy, and the word "position"
⚠️ The three reorder keys must be given together or not at all. validate refuses a
subset, because giving some but not others would silently disable replenishment — a store that
never reorders looks exactly like one whose demand happened to stay low.
The three reorder keys together are a continuous-review (s, Q) policy, the standard
textbook one: when stock runs down to s (reorder_at), order Q
(reorder_quantity), and it arrives after lead_time_seconds.
The word that matters is inventory position: what is on hand plus what is already on order. The policy reorders on the position, never on the stock sitting in the store.
That is not a technicality. Placing an order does not put anything on the shelf, so a store that reordered on the stock on hand would place a fresh order at every draw until the first delivery turned up -- one shortage would become a hundred orders. The position rises the instant an order is placed, so the threshold stops being met and the policy settles by itself.
How many orders may be in flight
By default, as many as the policy wants. max_outstanding caps it.
Two ways a store fills
{ "kind": "order" } is a supplier: reorder_quantity arrives after lead_time_seconds, and
that lead time is a number you typed.
{ "kind": "task", "product": "restock" } is somebody doing the work. The reorder point releases
one entity of that product, and the store is filled by whatever its route
produces into it.
⭐⭐ The lead time is then an outcome of the model rather than an input. A restock that queues behind a busy stacker takes longer, so a second stacker can change the answer — which is the whole point when you are trying to tell nobody restocked the shelf from nobody ordered any. See a shelf that has to be restocked.
max_outstanding decides whether extra people can help, and it defaults to 1 for a task.
A task raises no inventory position when it is released — how much arrives, and when, is decided by the route — so without a bound a shelf below its reorder point would spawn a fresh restock per arriving customer.
⚠️ But one is also a real constraint: with only ever one trip in flight, a second stacker has nothing to do. Measured, the two arms of that comparison reported the same 120,135 stockouts to the digit. Set it to the number of restocks that may genuinely be under way at once.
⚠️ reorder_quantity, lead_time_seconds and lead_time_profile have no meaning alongside a
task, and validate refuses them rather than ignoring them. ⭐ A restock task can be slowed
by a calendar — just by one on the steps of its route, because
that is where its duration lives.
Setting max_outstanding to 1 says this supplier will not accept a second purchase
order until the first has been delivered. That is a real arrangement and some suppliers
work that way -- but it changes the model underneath, not just a number.
With no cap, orders never wait for one another, so the lead times overlap. With a cap of one, orders queue behind each other, and a busy store spends much of its time waiting for permission to order rather than waiting for stock.
The difference is large enough to see immediately. The same store, demand of 0.5 a second, a lead time averaging one second, reordering at 1 with a stock of 2:
| capped at 1 | no cap | |
|---|---|---|
| share of draws that had to wait | 25.0% | 10.0% |
| average stock on hand | 1.25 | 1.50 |
If you are unsure which your supplier is, leave max_outstanding out: an uncapped
pipeline is the ordinary case.
:::
Drawing from one
On the step:
| key | type | required | what it means |
|---|---|---|---|
consumable | string | yes | Which store this step draws from, by id. |
quantity | dist | yes | How much is drawn per entity. The step waits if the store cannot meet it. |
An entity that cannot draw its material waits — but it is not holding a server while it does. Materials are drawn before a server is requested, so a short entity occupies nothing and the resource carries on with whatever else it can run. Only the consuming work stalls. A store sized on average demand will run short the first time demand is above average, which is most of the time.
Every entity draws its quantity exactly once. If total taken exceeds claims × quantity, something is being charged twice — the cheapest possible check, and the one that finds a double-charge at the first occurrence rather than the hundredth.