--- title: BEACON FORECAST emoji: 📈 colorFrom: yellow colorTo: gray sdk: streamlit sdk_version: "1.38.0" python_version: "3.10" app_file: app.py pinned: false --- # Beacon Forecast — Adaptive Demand Forecaster with Drift Detection **Portfolio project 5 of 5** — a demo response to the "edge case breakage" problem retail forecasting faces: rigid models that assume tomorrow looks like yesterday break when a supply shock, economic shift, or extreme weather event changes the underlying demand pattern, causing over-stocking or shortages until someone notices and manually retrains. > ⚠️ **All data in this project is synthetic.** `data/sku_demand.csv` is > generated by `generate_synthetic_data.py`: 4 SKUs, 2 years of daily data, > with trend, weekly seasonality, and yearly seasonality. One SKU > (SKU-004-ELECTRONICS) has an injected supply-shock event at day 500 (a > sudden ~42% demand drop with a slow, partial 70-day recovery) to exercise > the drift-detection pipeline. No real Walmart, retailer, or sales data is > used. ## Why this exists A forecaster that's accurate on stable historical data can still be dangerously wrong the week a real disruption hits, because it keeps predicting off stale assumptions until someone manually intervenes. This project closes that loop automatically: the pipeline watches its own forecast errors day by day, and the moment they drift from what's normal for that model, it retrains itself on the newest data window without waiting for a human to notice. ## Architecture ``` daily SKU demand (synthetic, walked forward day by day) | v Forecaster <- forecasting/model.py trend + weekly seasonality + yearly Fourier terms, fit with Ridge regression (regularized to stay stable when retrained on short windows -- a real bug caught during testing: plain OLS produced wildly unstable trend coefficients that exploded on extrapolation) | v One-step-ahead forecast, day by day | v Drift monitor <- forecasting/drift.py every 5 days, z-test comparing the last 14 days of forecast error against the baseline error distribution from right after the last (re)training; a Kolmogorov-Smirnov test is also computed as a secondary check on error *shape*, not just mean | v |z| > 4.0 ? ---- no ----> keep current model, keep monitoring | yes v Automatic retrain on the most recent 120 days <- stand-in for an Airflow DAG / AWS Lambda retrain trigger ``` ## Try it ```bash pip install -r requirements.txt streamlit run app.py ``` Pick a SKU in the sidebar. Three tabs: actual vs. forecast over the full 2-year walk-forward simulation (with retrain events marked), the drift monitor's z-score history, and a log of every automatic retrain with its triggering statistics. Compare **SKU-004-ELECTRONICS** (has the injected shock) against any other SKU: the shock triggers a retrain within days, with a clearly larger z-score than routine periodic retrains. The other SKUs also retrain periodically as ordinary forecast staleness accumulates from a lightweight linear model — a legitimate, expected MLOps pattern, not just noise: even without an external shock, a simple model drifts stale over time and benefits from periodic refitting. ## Project structure ``` demand-forecaster/ ├── app.py # Streamlit UI ├── pipeline.py # walk-forward simulation + drift-triggered retraining ├── generate_synthetic_data.py # produces data/sku_demand.csv ├── forecasting/ │ ├── model.py # trend + seasonality forecaster (Ridge regression) │ └── drift.py # z-test + KS-test drift detection ├── data/ │ └── sku_demand.csv # SYNTHETIC daily SKU demand, 4 SKUs x 2 years └── requirements.txt ``` ## Production upgrade path | Demo component | Production equivalent | |---|---| | Trend + Fourier + Ridge regression | Prophet and/or a Temporal Fusion Transformer, per the original architecture brief | | In-process walk-forward loop | Apache Airflow scheduled runs, containerized with Docker | | z-test + KS-test on residuals | Evidently AI or Great Expectations for full data-drift reporting (feature drift, not just target/residual drift) | | Direct in-process retrain | AWS Lambda triggered by the drift-monitoring step, writing a new model version to a registry | ## Project landing page `docs/index.html` is a standalone, single-file static landing page (no build step) summarizing the project's results, method, and findings. To host it live on GitHub Pages: repo **Settings → Pages → Source: Deploy from a branch → Branch: main, folder: /docs → Save**. It'll be live within a minute or two at `https://data-geek-astronomy.github.io/BEACON_FORECAST/`.