A production-style demand forecasting system for logistics lane volume prediction — built to reflect the architecture of real-world supply chain forecasting pipelines.
The core design choice: demand in a middle-mile network is not a time-series problem — it's a supervised regression problem. Volume is driven by upstream shipment flows, lead time variability, event calendars, and operational decisions that pure time-series models cannot capture. This repo demonstrates why that framing change matters.
| Layer | What's implemented |
|---|---|
| Data | Synthetic daily demand for 5 logistics lanes (27 months), with shipment flows, lead times, event signals, and realistic seasonality |
| Baselines | Moving Average (7-day), Holt-Winters Exponential Smoothing |
| ML Models | XGBoost, LightGBM — multivariate supervised regression |
| Feature Engineering | Lag features, rolling stats, lead time distributions (mean + P95), upstream outflow, event proximity signals |
| Evaluation | MAPE / RMSE / MAE at daily granularity + weekly (cycle-level) aggregation |
| Multi-lane | Runs independently per lane; comparable summary across all lanes |
| Approach | What it captures | What it misses |
|---|---|---|
| ARIMA / Prophet | Trend, seasonality, simple holidays | Cross-lane signals, lead time impact, event pre-buildup |
| XGBoost / LightGBM | All of the above + operational features | Requires feature engineering; less interpretable |
The moment you add cross-series signals (upstream outflow, lead time variance), you need a supervised framework. Prophet and ARIMA break when features span multiple series.
| Lane | Moving Avg | Holt-Winters | XGBoost | LightGBM |
|---|---|---|---|---|
| BLR-DEL | 23.67% | 55.93% | 14.23% | 13.99% |
| BLR-MUM | 28.98% | 33.94% | 10.62% | 10.76% |
| DEL-HYD | 26.72% | 39.91% | 14.30% | 14.68% |
| DEL-KOL | 24.92% | 22.85% | 12.18% | 12.55% |
| MUM-CHE | 30.33% | 31.48% | 11.43% | 11.25% |
| Lane | Moving Avg | Holt-Winters | XGBoost | LightGBM |
|---|---|---|---|---|
| BLR-DEL | 16.53% | 50.56% | 9.52% | 9.20% |
| BLR-MUM | 23.07% | 29.84% | 6.68% | 7.42% |
| DEL-HYD | 18.37% | 34.32% | 6.89% | 7.36% |
| DEL-KOL | 18.91% | 18.46% | 6.60% | 7.61% |
| MUM-CHE | 25.40% | 28.03% | 5.05% | 5.40% |
XGBoost and LightGBM reduce daily MAPE by ~50% over baselines. Weekly MAPE drops further — cycle-level planning benefits most from the ML models.
Features are grouped into five families — mirroring a production pipeline:
1. Calendar : day-of-week (sin/cos encoded), month (sin/cos), week-of-year, is_weekend
2. Lag features : volume at t-1, t-2, t-3, t-7, t-14, t-21
3. Rolling stats : rolling mean and std at 3/7/14-day windows (shift-1 to avoid leakage)
4. Lead time : current lead_time_days, 3-day rolling mean, P95 over 14 days
5. Event signals : is_event, days_to_next_event, days_since_last_event,
pre_event flag (≤3 days), post_event flag (≤2 days)
6. Upstream flow : upstream_outflow lag-1, 3-day rolling mean
Mean lead time understates reliability risk. P95 tells the model "this lane is structurally unreliable" — which should inflate safety stock signals. This is a feature that time-series models structurally cannot use.
Demand starts building 2–3 days before a sale event (customer anticipation, pre-positioning) and dips slightly after (post-event hangover). days_to_next_event and days_since_last_event give the model this structure explicitly.
middle-mile-forecasting-simulator/
├── generate_data.py # Synthetic data generation with event calendar
├── forecast.py # Feature engineering + models + evaluation pipeline
├── daily_demand.csv # Input: 4,105 rows × 5 lanes × 27 months
├── forecast_results.csv # Output: actuals + predictions per model per lane
├── model_comparison.csv # Output: MAPE/RMSE/MAE summary table
├── requirements.txt # Dependencies
└── README.md
# Install dependencies
pip install -r requirements.txt
# Generate fresh synthetic data (optional — daily_demand.csv already included)
python generate_data.py
# Run all models across all lanes
python forecast.py
# Run a specific model on a specific lane
python forecast.py --model xgb --lane BLR-DEL
python forecast.py --model lgb --lane MUM-CHE
# Run only baselines
python forecast.py --model ma
python forecast.py --model hwLog-transform on target Volume is right-skewed; training on raw volume causes the model to over-optimise for high-volume days. Log1p transform stabilises variance across scales and lanes.
Walk-forward split (not random) Random train/test split causes data leakage in time-series — future data leaks into training. Fixed temporal split (80/20 chronological) is the correct evaluation protocol for forecasting.
Lag features shift by 1 to avoid leakage
Rolling stats are computed on volume.shift(1) — the model never sees the current day's volume when predicting it.
Why both XGBoost and LightGBM? They often have similar accuracy but different failure modes (XGBoost handles outliers more conservatively; LightGBM captures finer seasonal patterns via leaf-level splits). In production, an ensemble of both reduces variance.
Swap in your own data by matching the daily_demand.csv schema:
| Column | Type | Description |
|---|---|---|
date |
date | Daily date |
lane |
str | Origin-Destination identifier |
volume |
int | Daily shipment volume |
lead_time_days |
float | Transit time for the lane |
upstream_outflow |
int | Shipment volume from upstream FC |
day_of_week |
int | 0=Mon … 6=Sun |
month |
int | 1–12 |
week_of_year |
int | ISO week number |
is_event |
int | 1 if date falls in a sale event window |
days_to_next_event |
int | Days until next event (capped at 30) |
days_since_last_event |
int | Days since last event ended (capped at 30) |
pandas>=2.0
numpy>=1.24
scikit-learn>=1.3
xgboost>=2.0
lightgbm>=4.0
statsmodels>=0.14
- THANOS Network Optimization Demo — Monte Carlo simulation for supply chain network planning
- Email Task Assignment — BERT-based NLP pipeline for operational task routing
Built to mirror production forecasting architecture — not a tutorial.