◆ Data Science layer · flagship

how-to-sales-forecasting

Twelve months of data show two demand spikes. One is real; the other is a retry bug wearing a spike costume - and a naive forecast would have bought $109k of stock for it. This skill forecasts honestly: data-quality gate first, measure what 12 months can prove, declare what they cannot, and turn a once-observed peak into a costed buy a P&L owner can actually sign. Seed 42, learn-python env, real Everrest marts.

raw dumplakewarehousemartsforecast + buyagent (layer 4)
Case: Everrest · B2B2C retail platform seed = 42 pandas · statsmodels · numpy 17 executed charts output = a buy paper, not a point estimate
01
Step 1

Input - the marts, and the field that decides what counts as demand

This skill reads the marts produced by how-to-schema-and-warehouse. Two fields decide everything: orders.status (only delivered orders consumed stock) and orders.order_id - because if that key is not unique, every join fans out and the "demand" is partly fiction. It was not unique.

orders 50,600 rows
  • order_id · 600 rows duplicated
  • merchant_id · fk
  • order_ts · timestamp
  • status · delivered / cancelled / shipped / paid
order_items 109,154 rows
  • order_id · fk
  • qty · units - the demand signal
  • unit_price · prices the buy
merchants 400 rows
  • merchant_id · pk
  • category · 8 canonical - the buy is per category
returns 2,672 rows
  • order_id · fk only - no line detail
  • so the return rate is an order-level upper bound
02
Step 2

Sample data - the same seeded marts, and the traps a forecast walks into

No new generator: the marts already exist (seed 42). What matters is what is hiding in them - each trap planted on purpose, each one caught (or honestly declared unmeasurable) in Step 5.

👻 The phantom spike600 orders double-fired by a retry bug in March 2026 - joins fan out 4x and manufacture a 1.49x "demand spike"
🎄 The real spikeNovember runs 2.2x a normal month - planted once, observed once, and the whole buy hangs on whether it repeats
📅 Weekend rhythmweekends run 45% above weekdays - 47+ observations each, the one seasonality that is actually provable
🐘 The wrong outlierM0007 dominates GMV but is 0.3% of units - the revenue-metric outlier treatment would be the wrong move here
🕳️ n = 1 year12 months = one observation per calendar month - annual seasonality cannot be validated, only declared
🚦 Mixed status21% of orders are cancelled or in-flight - counting them inflates demand that never consumed stock
03
Step 3

Objective - a buy that survives finance, not a model that wins a leaderboard

Everrest runs consignment fulfilment: the platform commits inventory in advance. Committing low burns margin; committing high burns cash in holding and markdown. The question is never "which model has the best MAPE".

How many units - and how many dollars - does each category stage for November 2026, and do we repeat the March buy?
  • Is every spike in the data actually demand - or a data defect wearing a costume?
  • What can 12 months of history prove, and what must be declared as an assumption?
  • Which model earns the right to produce the baseline, at the real planning distance?
  • What service level does the margin/overstock asymmetry justify, per category?
  • How does the buy split into a firm tranche now and an option tranche later?
04
Step 4

Find-skills - the forecasting toolbox

A demand plan needs a series layer, candidate models, a validation harness that cannot peek, and the economics that turn a distribution into a signable number.

OSS tool

pandas

Order lines to daily delivered units, total and per category - plus the dedup gate that decides the whole analysis.

OSS tool

statsmodels

Holt-Winters exponential smoothing - the short-horizon winner, and honestly beaten at 120 days by a 9-parameter regression.

Pattern

Rolling-origin backtest

Expanding train window, untouched test window, at BOTH horizons - 28 days and the 120-day planning distance. No peeking, ever.

Pattern

Leave-month-out

Size a spike against a baseline that never saw it - with every spike month held out, or the estimate contaminates itself.

Pattern

Newsvendor economics

q* = margin / (margin + overstock). The service level is a cost decision, not a taste - and the cost rates get their own sensitivity chart.

Skill

upstream Everrest skills

The warehouse skill built the marts; the EDA skills profiled them. This layer inherits their lineage and their planted traps.

05
Step 5

Build - gate, measure, declare, then cost the decision

One deterministic script (seeded per random purpose - two runs are byte-identical) walks from raw marts to a buy paper. Grab the real code:

5a · The data-quality gate runs before anything is measured

The single most valuable "forecasting" step touched no model. 600 duplicate order rows - a retry bug, all in March - fan out against order_items and manufacture a whole phantom month.

Daily delivered units across twelve months with the November spike shaded amber as real and the March spike drawn in red as duplicate rows

One year, two spikes - and only one survives the gate. The red trace is what the marts say as read; the cyan series is demand after deduplication.

Two panels of March 2026 daily units, before the gate at 1.49 times baseline in red and after the gate at 1.00 times baseline in cyan

Same month, same baseline, before and after the gate: 1.49x becomes 1.00x. The v1 draft of this analysis skipped the gate, measured the phantom, and priced a repeat buy at $109,212 - the March recommendation is an engineering ticket, not a purchase order.

5b · Count the evidence before trusting any seasonality

Log-scale bar chart comparing 47 observations per weekday slot against 1 observation per month-of-year slot
Monthly units with a twelve-parameter month-dummy fit passing through every point exactly, labeled zero residual degrees of freedom

Left: weekday seasonality has 47+ observations per slot - knowledge. Month-of-year has exactly one - a guess. Right: the trap most tutorials fall into, shown rather than named: month dummies fit 12 points with 12 parameters, R-squared 1.000, and prove nothing.

5c · Two backtests, two honest winners

Horizontal bars of 28-day MAPE for five models, Holt-Winters weekly first at 12.4 percent
Horizontal bars of 120-day MAPE for all five models, log-linear plus weekday first at 10.7 percent just ahead of Holt-Winters

Left: at 28 days Holt-Winters wins (12.4% MAPE, 8 rolling folds). Right: at the 120-day planning distance the ranking flips - all five models race on a spike-imputed series, and log-linear + weekday takes it by a nose (10.7% vs 10.8%). The chart also confesses its own weakness: 3 overlapping folds are about 2 independent comparisons. The buy is 153 days out - past the last validated point, and the memo says so.

Line chart of forecast error growing with lead time across the 28-day validated window, annotated that the November buy is 153 days out

5d · Size the one real spike against a baseline that never saw it

November 2025 daily units in amber towering over a held-out baseline line, 2.18 times, 12,067 extra units
Bootstrap histogram of the November multiplier centered at 2.18 with a five percent spread

Left: November ran 2.18x a baseline fitted with the spike held out - 12,067 extra units. Right: the multiplier is tight within its month (5% spread). The dangerous uncertainty is year-to-year - unobservable at n = 1 - so it enters as a declared sigma with a sensitivity chart, never as a fake confidence interval.

5e · Turn the distribution into a signable buy

35,338 units · $683,810 at cost
Firm tranche now: 30,536 units ($590,814) at P50 · option tranche released on the autumn run-rate: 4,802 units ($92,996) · 63% of the cash rides on the once-observed November peak repeating - stated, priced, and signed off as a bet, not buried.
Histogram of 10,000 simulated November totals with P50 at 29,887, the buy at 35,338, and P90 at 41,639 marked

10,000 seeded draws: measured baseline error x measured day variation x the declared year sigma. The buy stands at the newsvendor optimum - above the middle, because being short costs 20% of price and being long costs 8%.

U-shaped expected cost versus service level curve with the minimum marked at q star 0.71
Dot-range chart of per-category commitments between P50 and P90 with the commit marked in amber

Left: the analytic q* (0.71) lands on the empirical cost minimum - the model checks itself. Right: every category simulated on its own baseline and its own multiplier - a real per-category newsvendor, not one pooled quantile split by share.

Line chart showing P50 flat while commit and P90 fan out as the assumed year sigma runs from zero to fifty percent
Horizontal bars of the commitment under five margin and overstock scenarios spanning 154 thousand dollars of committed cash

Left: the ONE declared unknown, swept: the commit moves 31% across sigma 0-50%; the P90 nearly doubles its distance. Right: the sweep most forecasts never run - the margin/overstock assumption swings the buy by $154k of committed cash, as hard as the model itself. Both are inputs; both get charts.

Trap in the dataNaive forecastThis buildStatus
Duplicate-row phantom spikebuys $109,212 of stockDQ gate: 1.49x → 1.00x✓ caught before modeling
Once-observed November peakhidden inside a CIdeclared sigma + sensitivity✓ named as input
Month-dummy overfitR² = 1.000, "seasonality!"12 obs, 12 params, shown✓ trap demonstrated
28-day winner extrapolated 5 monthsHolt-Winters everywheresecond race at 120d - new winner✓ horizon-honest
Units-only deliverable"32k units" - unsignable$683,810 at cost, tranched✓ priced for finance
Revenue-outlier reflex (M0007)trims the top merchant0.3% of units - kept in✓ right metric, right treatment
06
Step 6

Expert review - the panel that found a phantom, a sign error, and a fake trend

Five senior reviewer agents - forecasting methodology, supply-chain planning, executive insight, data engineering, commercial P&L - reviewed the v1 code and its real outputs. This was not polish: three findings changed the numbers, one changed the story. Every fix below is in the shipped code.

Data Engineering Lead
12 yrs · reproducible analytics

"Your March 'spike' is 100% the duplicate-row bug. orders.csv has 600 duplicated order_ids - all in March - and the join fans out 4x. The generator has no March demand term at all. Your memo says no driver exists in the marts; the driver IS the marts."

Fix applied: DQ gate added before any measurement - March collapses from 1.49x to 1.00x, the March buy is cancelled, and the phantom is now the page's opening chart instead of its blind spot.
Forecasting Methodology Lead
15 yrs · retail demand

"Two real defects. Your error propagation multiplies by (1+e) where the algebra requires dividing - the residuals are asymmetric, so the whole distribution shifts the wrong way. And your 'median-preserving' lognormal subtracts sigma-squared-over-2, which preserves the mean instead - your sensitivity chart shows expected demand FALLING as uncertainty rises. That is an artifact, not a finding."

Fix applied: base = pred / (1 + e); the sigma^2/2 term dropped. P50 is now flat across sigma - exactly what a median-preserving spread should do - and the commit moved by about 4%.
Supply-Chain Planning Lead
12 yrs · seasonal buys

"Commit/P50 is 1.151 in all eight rows - that is not eight service levels, that is one platform quantile split by share, spending the pooling benefit without earning it. And no planner signs one irrevocable number 153 days out with no tranches and no weekly phasing."

Fix applied: each category simulated on its own baseline and multiplier; the buy split into a firm P50 tranche + an option tranche; november_weekly_staging.csv emitted from the observed within-month shape.
Commercial P&L Owner
18 yrs · category P&L

"I cannot sign units. Where is the cash? And your 2.5x March asymmetry is just your two assumed cost rates restated as a finding - margin over overstock, the units cancel. Put the dollars in and sweep the rates you assumed."

Fix applied: the buy paper leads with $683,810 at cost, names the $432k riding on the once-observed peak, and cost_sensitivity.png sweeps the rates - a $154k swing, as large as any modelling choice.
Head of Insights
14 yrs · exec committees

"Your flagship chart is titled 'Simulated November units' - a technique label. And the long-horizon chart claims the weekly models 'cannot be fitted' on a gapped series. They can; the real reason is weekday phase. A false claim on the page path is worse than a wrong number."

Fix applied: every title now states the finding; the long-horizon race imputes the spike month so all five models compete, and the caption reports effective (overlap-adjusted) folds, not just nominal.
before → after · the sign error that moved the whole distribution
# v1 (before): agg_rel_pct = (pred - actual) / actual, so truth = pred / (1+e).
# Multiplying applies the model's bias in the WRONG direction:
base = baseline_total * (1 + RNG.choice(long_resid, size=n))   # shifts LOW
yoy  = np.exp(RNG.normal(0, sigma, n) - sigma**2 / 2)          # mean-preserving, median sags

# v2 (after): divide to invert, drop the sigma^2/2 so the MEDIAN stays put
base = baseline_total / (1.0 + resid_draws)                    # under-forecast bias corrected UP
yoy  = np.exp(z * sigma)                                       # median 1.0 at every sigma

Run this skill on your own demand data

Install once, point it at your marts, set the calendar constants and the cost rates finance confirms - the same 6 steps produce your buy paper.

terminal
/plugin marketplace add phoebefu6/phoebe-data-skills
/plugin install how-to-sales-forecasting@phoebe-data-skills