from __future__ import annotations import os import pandas as pd import streamlit as st from pipeline import run, TRAIN_WINDOW st.set_page_config(page_title="Beacon Forecast | Adaptive Demand Forecasting", page_icon="◆", layout="wide") # --------------------------------------------------------------------------- # Theme: amber / dark navy, retail-warm # --------------------------------------------------------------------------- st.markdown( """ """, unsafe_allow_html=True, ) st.markdown('
Beacon Forecast
', unsafe_allow_html=True) st.markdown( '
A demand forecaster that watches its own forecast errors, and automatically ' 'retrains itself the moment the real world stops matching what it learned.
', unsafe_allow_html=True, ) st.write("") DATA_PATH = os.path.join(os.path.dirname(__file__), "data", "sku_demand.csv") df = pd.read_csv(DATA_PATH) with st.sidebar: st.markdown("### ◆ Beacon Forecast") st.caption("Trend + seasonality forecaster with automated drift-triggered retraining.") sku = st.selectbox("SKU", options=sorted(df["sku"].unique())) st.caption( "SKU-004-ELECTRONICS has a synthetic supply-shock event injected at day 500 " "(~2025-05-15): a sudden ~42% demand drop with a slow, partial 70-day recovery." ) sub = df[df["sku"] == sku] out = run(sub) mae = abs(out.actual - out.forecast).mean() m1, m2, m3, m4 = st.columns(4) m1.metric("Forecast MAE (units/day)", f"{mae:.1f}") m2.metric("Drift checks run", len(out.drift_checks)) m3.metric("Automated retrains", len(out.retrain_events)) m4.metric("Training window", f"{TRAIN_WINDOW} days") tab1, tab2, tab3 = st.tabs(["Forecast vs actual", "Drift monitor", "Retrain log"]) chart_df = pd.DataFrame({ "date": pd.to_datetime(out.dates), "actual": out.actual, "forecast": out.forecast, }) with tab1: st.markdown("Actual daily demand vs. the pipeline's one-step-ahead forecast, walking forward day by day.") st.line_chart(chart_df.set_index("date")[["actual", "forecast"]]) if len(out.retrain_events): retrain_dates = [out.dates[list(out.days).index(e.day)] for e in out.retrain_events] st.caption("Retrain events (model refit on the most recent 120 days): " + ", ".join(retrain_dates)) with tab2: st.markdown( "Every 5 days, the pipeline compares the mean forecast error over the last 14 days against a " "baseline error distribution established right after the last (re)training, using a z-test. " "A |z| beyond the threshold (4.0) signals the real world has drifted from what the model learned." ) drift_df = pd.DataFrame({ "date": [out.dates[list(out.days).index(c.day)] for c in out.drift_checks], "z_score": [c.z_score for c in out.drift_checks], "is_drift": [c.is_drift for c in out.drift_checks], }) drift_df["date"] = pd.to_datetime(drift_df["date"]) st.bar_chart(drift_df.set_index("date")["z_score"]) st.caption("Bars beyond ±4.0 triggered an automatic retrain.") st.dataframe( pd.DataFrame([{ "Day": c.day, "z-score": round(c.z_score, 2), "KS statistic": round(c.ks_statistic, 3), "Drift triggered": "Yes" if c.is_drift else "No", } for c in out.drift_checks]), use_container_width=True, hide_index=True, ) with tab3: if not out.retrain_events: st.info("No drift-triggered retrains for this SKU in the simulated window.") for e in out.retrain_events: st.markdown( f'
Day {e.day} ({out.dates[list(out.days).index(e.day)]}) — ' f'z-score {e.z_score:+.2f}, KS statistic {e.ks_statistic:.3f} — model automatically retrained ' f'on the most recent 120 days of data.
', unsafe_allow_html=True, ) st.caption( "In production this trigger fires an Airflow DAG or AWS Lambda to retrain on the newest data " "window; here it's simulated as a direct in-process refit for the same behavior with no infra to run." )