restockiq / README.md
RV302001's picture
RestockIQ v1: M5 quantile forecasting + reorder decision dashboard
3d4c9ec
|
Raw
History Blame Contribute Delete
4.65 kB
metadata
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

  1. 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.csv under data/raw/.
  2. pip install -r requirements.txt (Python 3.13)
  3. 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
    
  4. pytest eval/ β€” hand-checked tests for the inventory math.
  5. uvicorn app.main:app --port 7860 and 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.