One pipeline, run per SKU, per month. Every stage below is a real rule in the code — nothing here is decorative. Follow the line top to bottom.
Bundles are split into their component Singles × quantity. If the History tab and Main both cover the same day, Main wins — nothing is double-counted. Same basis as DOH and Sellthrough, so numbers line up across the dashboard.
Every feature is computed from the 30 days right before the target month, so nothing from the target month itself ever leaks in.
Every SKU is tagged with exactly one demand pattern from those 30-day features. Incompatible patterns are never trained or predicted together.
Independently of behavior, every SKU also falls into a volume segment. The forecasting method is chosen per segment, because one method does not win everywhere.
For Segment A, every candidate method below is scored against real past months, using models that never saw those months. Lowest real error wins — the forecast can't quietly end up worse than doing nothing.
Trained on log(1 + next-month demand), so an uninformative model lands on "same as last 30 days" instead of drifting to the catalogue average. Non-Linear / Volatile SKUs get a hurdle model instead: probability of selling at all × expected amount.
A non-ML estimate, one formula per behavior. This is what the ML forecast is blended against and capped around — a transparent number you can explain without touching the model.
| Behavior | Baseline formula |
|---|---|
| Shifting Up | max(30d total, last10×3, last7×4) |
| Shifting Down | 0.6×(first7×4) + 0.4×(last14×30/14) |
| Steady | mean day × 30 |
| Non-Linear / Volatile | max(maxWeek, maxDay×7, 30d total) |
| Spiky | 30-day total |
w rises when the model agrees with the behavior pattern and volatility (CV) is moderate. It drops when the model and the pattern disagree — the forecast leans back on the transparent baseline instead.
| Behavior | Cap |
|---|---|
| Shifting Up | floor ≥ 0.85 × (last7×4) |
| Shifting Down | ceiling ≤ 1.10 × baseline |
| Steady | within ±15% of its level |
| Non-Linear / Volatile | cap ≤ 2.75 × 30d total |
| Spiky | cap ≤ 1.5 × 30d total |
SKUs that missed by 100%+ last month, or that are thin and erratic (≤5 selling days, CV > 2), get flagged. Their deviation from the baseline is halved before the guardrail is applied — a second, targeted brake on the SKUs most likely to overshoot. Toggle with V2 On/Off.
For a month whose real opening stock is known (every backtest, plus the current month), a segment-A forecast is capped at 2.5 × what could actually be supplied = beginning inventory + inbound. It's an accuracy correction — you can't confirm more than you can supply — that removes wild over-forecasts on SKUs that never had the stock.
Every "past months" reference above — the method scoring in step 5, the model training in step 6, the confidence range in step 11 — uses only month-pairs that ended before the month being predicted. A backtest for March is trained as if April never happened. The Naive column in the Accuracy Trend table shows what simply repeating last month would have scored, so Lift = what this whole pipeline actually adds over doing nothing.