How it works
A walk through every number the planner produces — what it means, where it comes from, and a worked example for each. Live figures below (service level, cover, container size, lead times) are read from Settings, so the examples reflect your current configuration. The through-line: because a container takes about 2–3 months to make and ship, you can't reorder faster than the boat, so the plan holds stock in three places at once — in the pipeline (being made / on the water), working stock on the shelf, and a safety reserve beneath it.
The starting point: what actually sold
Everything begins with real sales history. For each product the planner totals the units sold over the trailing 12 full months (the in-progress month is excluded — it would drag the average down) and divides by the number of months in that window to get an average per-month run-rate. (A product launched partway through the window divides by the months it actually has, not 12, so a young SKU isn't understated.) Three rules make the figure honest:
- Order date, not invoice date. Demand is counted when the customer placed the order — that's when they wanted the stock. It won't tie out to an accounting "units shipped" report, and that's deliberate: the forecast's job is to predict demand, not revenue.
- Packs become loose units. A sale of one "SL195-STD-GRY-PK (112)" counts as 112 sleepers, so pack and loose sales of the same product add up on one ruler.
- Cancelled / parked orders are dropped — drafts and credits aren't demand.
Growing the baseline on purpose
A run-rate only tells you the past. To plan for growth you set a target on the Sales targets page — an annual growth percentage. The planner multiplies the baseline by it. Two design choices matter for a detail reader:
- Targets peg to a FIXED baseline — the 12 months ending Jun 26 — not a rolling average. If you grew off a rolling window, this year's achieved growth would fold back into next month's base and compound on itself. Pegging to a fixed year means "the Jun 26 average, plus your growth %" stays exactly that all year. You bump the baseline deliberately each new financial year.
- Product beats category. A target can be set on a whole product group or on one product; the product-level one wins. A product with no target simply follows its rolling 12-month run-rate.
Spreading the level across the year — without changing the total
The target gives a level (a per-month average). Seasonality decides how that level is distributed month to month. The engine builds 12 multipliers (one per calendar month) from history, and — this is the key property — normalises them to average exactly 1.0. A summer month might be 1.4, a winter month 0.6, but the twelve always average to 1.
The consequence surprises people: the annual total is identical whether seasonality is on or off. Over a full year you touch every calendar month once, so the multipliers sum to 12 either way. Seasonality only moves demand between months; it never adds or removes any. Turning it off just sets every multiplier to 1.0 (a flat line).
| Month | Multiplier | Flat (off) | Seasonal (on) |
|---|---|---|---|
| Jan (peak) | 1.40 | 100 | 140 |
| Jun (trough) | 0.60 | 100 | 60 |
| …other 10 months… | avg 1.00 | 100 each | varies |
| Year total | sum = 12.0 | 1,200 | 1,200 |
Seasonality is currently ON. The shape is fitted from the last 24 months of history (classical decomposition with a 24-month+ window; a shorter window uses a year-over-year de-trend). One caveat the run enforces: a lookback of 12 months fits 12 multipliers on 12 data points and absorbs all variation, which collapses the safety floor to ~0 — so keep the lookback well above 12, or the run flags it. Seasonality also quietly changes the floor itself; see Traps & gotchas.
A month-by-month simulation of your stock
The Planning horizon is a 12-month forward walk of each product's stock — no black-box solver, just arithmetic you can redo by hand. Every month, in this exact order:
Closing becomes next month's opening, and the walk repeats. Expand any product to see those four rows, then a combined Working cover & safety floor line (sections 5–6) — the level orders aim to top stock back up to. Only opening, demand and arriving move closing; the floor/cover line beneath is a target to compare against, not part of the subtraction.
The stock you actually trade on
Working cover is 1 month of demand (Settings → Target cover) held above the safety floor. Crucially it's forward-looking: the cover for a month is the next month's forecast demand — the amount you must have on the shelf now to trade through until the next boat lands. It's the day-to-day stock a shipment-a-month replenishes as it sells. Raise it to 2–3 months when cash allows a bigger cushion; drop it to run leaner.
The reserve beneath the working stock
The safety floor is the buffer kept under your working stock for the unexpected — a month that outsells the forecast, or a container that runs late — while you wait 2–3 months on the next boat. It is not a fixed number of months; it's sized statistically per product from how choppy that product's own demand is:
σ (sigma) is the product's month-to-month demand scatter around its seasonal expectation — a steady seller has a small σ and a small floor; a lumpy one needs more. √lead time scales it up because a longer pipeline exposes you to more uncertain months before relief arrives. And z(service level) converts your chosen service level into a number of standard deviations.
What the % means. A service level of 92% means the floor is sized so that demand over the lead time exceeds what you've stocked only 8% of the time — i.e. you expect to cover demand from stock in about 92 of every 100 replenishment cycles, and dip into (or through) the reserve in the other 8. Chasing the last few percent gets expensive fast because z climbs steeply near 100%:
| Service level | z | Meaning | Floor in the example → |
|---|---|---|---|
| 90% | 1.28 | ~10 in 100 cycles dip into the reserve | 266 units |
| 92% ← yours | 1.41 | ~8 in 100 cycles dip into the reserve | 292 units |
| 95% | 1.64 | ~5 in 100 cycles dip into the reserve | 342 units |
| 97.5% | 1.96 | ~2.5 in 100 cycles dip into the reserve | 407 units |
| 99% | 2.33 | ~1 in 100 cycles dip into the reserve | 484 units |
The floor can be switched off entirely (Settings → Safety floor) — it is currently ON. Off means the floor is 0 for every product and the plan runs on working cover alone: a deliberate, lower-capital stance where you'd rather hold ~1 month of stock and no reserve underneath.
Little and often — top back up to floor + cover
Each month the planner reviews every product and, if needed, raises an order to bring it back up to an order-up-to level:
The "cover window" is normally 1 month of the coming months' demand — but it stretches automatically when the next shipment can't land next month (e.g. a Chinese New Year shutdown blocks a landing), so the last order before the factories close is sized to bridge the whole gap. This is a flow policy: a shipment lands roughly every month, so on-hand hovers near floor + cover rather than sawtoothing from full to floor. A few refinements:
- MOQ / run rounding. The raw need is rounded up to the mould's minimum run (in linear metres for profile moulds, allocated back to products by length). Shared moulds split a run across their products by relative need.
- Emergency orders. A product already below its floor with nothing inbound in time will order even though it lands late — the breach months stay visible rather than being hidden.
- Inbound POs are never double-covered. Anything already on the water inside the cover window is subtracted before sizing.
Volume-first packing, and a freight-vs-holding trade-off
Orders to the same manufacturer are consolidated into 40′ HC container loads. There is no pallet abstraction and no fill target — capacity is measured two ways and the load takes as many containers as the tighter one demands:
Volume is the primary constraint — real loads in the PO history cube out (~69–76 m³ of goods) well before they hit the ~27.5 t weight cap. Each product contributes its own packed volume and weight per unit, resolved from ERP data through a fallback cascade: a "-PK (n)" bundle ÷ n is best (the real bundle as loaded); failing that, box dimensions W×H×D × a nesting factor (~0.75, because C-section posts stack inside each other); failing that, weight ÷ group density. A sanity check rejects any dimension whose implied density is more than 4× off the group norm (catches fat-fingered ERP values).
Consolidation is an economic decision, not bin-packing. Freight is charged per container with no bulk discount, so pulling a future order forward only pays when it genuinely eliminates a part-empty container:
Two full monthly orders that each fill their own container stay staggered — merging saves no freight and only adds holding cost. Fill rate (by volume and by weight) is reported as an output, never chased as a goal.
Garbage in, garbage out — and the one error the tool can't catch
Every number on every page is only as good as the ERP data behind it. The planner fails loud on missing data — no cost, an unconvertible currency, no sales history, a product with no mould — it flags these rather than guessing. But it cannot catch data that is wrong but plausible. By far the most important of those is the container landing date.
Why the landing date matters more than almost anything. For every open purchase order the plan reads the ERP's expected delivery date and treats it as the month the stock lands. That single date drives three things:
- which month a product's arriving row lights up,
- whether the product looks like it's breaching its floor in the meantime, and
- the subtle one — whether a new order is raised at all, because stock already inbound inside the cover window is subtracted from what needs ordering.
So a landing date that's a month too optimistic can make the plan skip an order it should place — it believes relief is arriving sooner than it is — and hide a stockout that's actually coming. When the ERP has no delivery date the plan derives one (order date + manufacturing + shipping weeks + any CNY push) — a sound estimate, but only an estimate. Keep delivery dates current, and update them the moment a shipment slips. You can also correct a landing date per-PO on the Purchase orders page.
The rest of the inputs, by impact:
- Stock on hand — the opening balance the whole 12-month walk builds on; a miscount cascades through every month.
- Sales by order date — the forecast is only as honest as the history; miscoded or missing orders move the baseline.
- Lead times (manufacturing + shipping weeks) — size the safety floor (√lead) and the derived landing date when the ERP has none; optimistic lead times give thin floors and orders placed too late.
- Pack quantities & product config — a wrong pack size double- or half-counts demand; wrong dimensions mis-pack containers.
The tool will tell you when a number is missing. Only you can tell it when a number is wrong.
Things that surprise people
The non-obvious behaviours worth internalising before you trust — or debug — a number.
You'd expect seasonality to inflate everything. It doesn't. Working cover uses the coming month's demand, so it rises heading into a busy month and falls in the quiet season — the combined floor + cover target swings with it (for a product it might run 7k in the trough and 20k into the peak). But the safety floor moves the other way: it's sized on demand scatter around the seasonal expectation, so once seasonality explains the swing, σ shrinks and the floor drops. Turn seasonality off and floors go up (the swing is now counted as volatility). So seasonality on = higher peak cover, lower floor. Neither is a bug.
Because cover is forward-looking and the plan stock-builds ahead of the peak, the Capital out row bulges in the months before your season, not during it. That's correct — you're buying the stock you'll sell — but if working capital is tight, this is the cash-flow spike to plan for. It's the flip side of "a month of cover looks high in the pre-peak months."
The floor + cover line is the level orders top up to. Actual closing sawtooths around it — above just after a container lands, drifting down before the next — because shipments arrive in whole-container lots on a 2–3 months lead time, not in smooth monthly trickles. A closing below the target line isn't a breach; only closing below the safety floor is. Don't read the target as a line stock should sit exactly on.
Fitting 12 seasonal multipliers on exactly 12 data points absorbs every wiggle, so residual σ → 0 and the safety floor collapses to ~0. Keep the seasonal lookback comfortably above 12 months (the default is 24); the run raises a flag if it's too short, but it's easy to miss.
Saving a setting stores it immediately, but every product-level number (forecasts, floors, orders, this page's live figures vs the horizon grid) only recomputes on the next planner run. If this page says the floor is off but the horizon still shows floors, that just means the plan hasn't been re-run since you toggled it.
Because the multipliers average to 1.0, toggling seasonality reshuffles demand between months but leaves any full-year total (e.g. a year of the Sales value row) unchanged. Same total, different monthly spread — expected, not a fault.
Every value the engine uses is live config on the Settings page — no business number is hard-coded. Change one, re-run the planner, and every figure above and across the app moves with it.