Spaces:
Runtime error
Runtime error
| """ | |
| Generates SYNTHETIC daily SKU-level demand data: trend, weekly seasonality, | |
| yearly seasonality, noise -- and, for one SKU, an injected supply-shock | |
| event partway through the series (a sudden demand drop that persists at a | |
| new, lower baseline) to exercise the drift-detection pipeline. No real | |
| Walmart, retailer, or sales data is used anywhere in this repo. | |
| """ | |
| import csv | |
| import os | |
| from datetime import date, timedelta | |
| import numpy as np | |
| np.random.seed(11) | |
| N_DAYS = 730 | |
| START = date(2024, 1, 1) | |
| SKUS = { | |
| "SKU-001-PANTRY": dict(base=180, trend=0.05, weekly_amp=25, yearly_amp=35, noise=12, shock_day=None), | |
| "SKU-002-BEVERAGE": dict(base=260, trend=0.08, weekly_amp=40, yearly_amp=60, noise=18, shock_day=None), | |
| "SKU-003-SEASONAL-DECOR": dict(base=90, trend=0.02, weekly_amp=15, yearly_amp=140, noise=10, shock_day=None), | |
| "SKU-004-ELECTRONICS": dict(base=140, trend=0.06, weekly_amp=20, yearly_amp=30, noise=14, shock_day=500), | |
| } | |
| SHOCK_DROP_PCT = 0.42 | |
| SHOCK_RECOVERY_DAYS = 70 | |
| SHOCK_PERMANENT_LOSS_PCT = 0.15 # the new baseline never fully recovers -- a supply chain re-shaping, not a blip | |
| def generate_series(cfg, n_days): | |
| days = np.arange(n_days) | |
| trend = cfg["base"] + cfg["trend"] * days | |
| weekday = days % 7 | |
| weekly = cfg["weekly_amp"] * np.sin(2 * np.pi * weekday / 7 + 1.5) | |
| yearly = cfg["yearly_amp"] * np.sin(2 * np.pi * days / 365.25 - 1.2) | |
| noise = np.random.normal(0, cfg["noise"], size=n_days) | |
| values = trend + weekly + yearly + noise | |
| if cfg["shock_day"] is not None: | |
| shock_day = cfg["shock_day"] | |
| multiplier = np.ones(n_days) | |
| for d in range(shock_day, n_days): | |
| days_since_shock = d - shock_day | |
| if days_since_shock < SHOCK_RECOVERY_DAYS: | |
| # sharp drop, gradual partial recovery over SHOCK_RECOVERY_DAYS | |
| recovery_frac = days_since_shock / SHOCK_RECOVERY_DAYS | |
| dip = SHOCK_DROP_PCT * (1 - recovery_frac) + SHOCK_PERMANENT_LOSS_PCT * recovery_frac | |
| else: | |
| dip = SHOCK_PERMANENT_LOSS_PCT | |
| multiplier[d] = 1 - dip | |
| values = values * multiplier | |
| return np.maximum(values, 0).round(1) | |
| def main(): | |
| out_path = os.path.join(os.path.dirname(__file__), "data", "sku_demand.csv") | |
| rows = [] | |
| for sku, cfg in SKUS.items(): | |
| series = generate_series(cfg, N_DAYS) | |
| for i, v in enumerate(series): | |
| d = START + timedelta(days=i) | |
| rows.append({"sku": sku, "date": d.isoformat(), "units_sold": v}) | |
| with open(out_path, "w", newline="") as f: | |
| writer = csv.DictWriter(f, fieldnames=["sku", "date", "units_sold"]) | |
| writer.writeheader() | |
| writer.writerows(rows) | |
| print(f"Wrote {len(rows)} rows ({len(SKUS)} SKUs x {N_DAYS} days) to {out_path}") | |
| print("SKU-004-ELECTRONICS has an injected supply-shock event at day 500 (~2025-05-15)") | |
| if __name__ == "__main__": | |
| main() | |