Skip to main content

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​

keytyperequiredwhat it means
idstringyesThe store's name, referenced by a step's consumes.
capacitynumberyesThe most that can be held.
initialnumbernoStock 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_atnumbernoOrder when the inventory position falls to or below this. Absent means never — a store filled only by a step's produces.
replenished_byobjectnoWhat 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_quantitynumbernoHow much to order, Q. Constant.
max_outstandingnumbernoAt most this many orders may be in flight at once. Absent means no limit.
lead_time_secondsdistnoHow long an order takes to arrive.
lead_time_profilestringnoA 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.

A cap is a claim about your supplier, not a tuning knob

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.

warning

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 1no cap
share of draws that had to wait25.0%10.0%
average stock on hand1.251.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:

keytyperequiredwhat it means
consumablestringyesWhich store this step draws from, by id.
quantitydistyesHow much is drawn per entity. The step waits if the store cannot meet it.
A store that cannot keep up stalls its own step

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.

Check the claim identity

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.