title: RestockIQ
emoji: π¦
colorFrom: green
colorTo: blue
sdk: docker
app_port: 7860
pinned: false
π¦ RestockIQ
Probabilistic demand forecasting + reorder decisions for a multi-SKU retailer, built on the M5 Forecasting - Accuracy dataset (Walmart via Kaggle): 30,490 daily series (3,049 items x 10 stores, CA/TX/WI), ~5.4 years of history, real sell prices, calendar events, and SNAP flags.
Three global LightGBM quantile models (P10 / P50 / P90, one model per quantile,
trained across all series at once) produce a forecast band per (store, item, day). The
band feeds classical inventory math β demand_std β (P90 β P50) / 1.2816 β so uncertain
SKUs automatically get bigger safety stocks. A day-by-day simulation over the final 28
days compares the quantile-driven reorder policy against a naive fixed-reorder baseline.
No LLM anywhere. Classical ML only: LightGBM + Prophet/seasonal-naive baselines. Frontend is plain HTML/CSS/JS + Chart.js. Everything served here is precomputed offline; the Space does fast lookups only.
Results (28-day holdout: d_1914βd_1941)
Forecast quality
| Model | Scope | WAPE |
|---|---|---|
| Seasonal-naive | all 30,490 series | 0.862 |
| Prophet (per-SKU) | 12 sampled series | 0.804 (vs 1.029 seasonal-naive on the same sample) |
| Global LightGBM P50 | all 30,490 series | 0.679 |
P10βP90 empirical coverage: 80.6% (nominal 80% β the band is well calibrated).
Policy backtest (the headline)
| Policy | Stockout rate | Avg holding units |
|---|---|---|
| Naive (historical mean x lead time, no buffer) | 15.52% | 2.93 |
| Recommended (quantile band + safety stock) | 12.42% | 4.46 |
The quantile-driven policy trades ~1.5 extra units of average holding for a ~20% relative reduction in stockout days β the safety stock is doing exactly what the uncertainty band says it should. Series with volatile demand get bigger buffers; stable ones don't pay for protection they don't need.
Both policies share identical simulation mechanics (daily review, 7-day lead time, orders arrive after the lead time, unmet demand lost); only the reorder point differs.
How it works
M5 CSVs ββ> prepare_data.py ββ> 46.9M-row feature table (lags, rollings, price, events, SNAP)
β
ββ> train_baseline.py seasonal-naive + Prophet (WAPE/MAPE)
ββ> train_quantile_models.py 3 global LightGBM models (Ξ±=0.1/0.5/0.9)
β ββ> recursive 28-day P10/P50/P90 forecast, all series
ββ> backtest.py naive vs recommended policy simulation
ββ> FastAPI + Chart.js dashboard (this Space)
- Time-based split, asserted in code: train β€ d_1913, holdout d_1914βd_1941. Never a random row split.
- No leakage in the holdout: multi-step forecasts are recursive β lag/rolling features inside the holdout use the model's own P50 predictions, never actual holdout sales.
- Decision layer:
safety_stock = zΒ·ΟΒ·βlead_time,reorder_point = P50 demand over lead time + safety stock,order_qty = max(0, reorder_point β current_inventory). Service level, lead time, and current inventory are configurable, clearly-labeled assumptions (M5 has no inventory column).
API
| Route | Purpose |
|---|---|
GET /api/skus |
store/item lists for the dropdowns |
GET /api/forecast/{store}/{item} |
dates + p10/p50/p90 + actuals for charting |
GET /api/recommendation/{store}/{item}?current_inventory=0&service_level=0.95&lead_time_days=7 |
reorder recommendation (all params optional) |
GET /api/backtest_summary |
naive-vs-recommended comparison, computed offline |
GET /health |
liveness |
Reproducing locally
- Download the M5 data (manual, no API token): go to
the competition data page,
accept the rules, Download All, and place
calendar.csv,sell_prices.csv,sales_train_evaluation.csvunderdata/raw/. pip install -r requirements.txt(Python 3.13)- Run the pipeline in order:
python pipeline/prepare_data.py python pipeline/train_baseline.py python pipeline/train_quantile_models.py python pipeline/backtest.py pytest eval/β hand-checked tests for the inventory math.uvicorn app.main:app --port 7860and open http://localhost:7860.
Design decisions (dataset choice, quantile-vs-point reasoning, safety-stock derivation, naive-policy definition, why no LLM) are logged in DECISIONS.md.