""" M5-inspired dataset loader. Generates a long-format DataFrame with realistic retail demand patterns, then delegates to synthetic.py to overlay luxury-specific signals. Avoids Kaggle auth requirements so the demo works out-of-the-box. """ import numpy as np import pandas as pd from src.data.synthetic import apply_luxury_overlay # ── Catalogue ──────────────────────────────────────────────────────────────── CATEGORIES = { "leather_goods": ["LG_001", "LG_002", "LG_003", "LG_004", "LG_005"], "ready_to_wear": ["RTW_001", "RTW_002", "RTW_003"], "accessories": ["ACC_001", "ACC_002", "ACC_003", "ACC_004"], "footwear": ["FW_001", "FW_002", "FW_003"], } STORES = ["Paris_Flagship", "Tokyo_Ginza", "NYC_5thAve", "Dubai_Mall", "London_Bond"] PRICE_MAP = { "leather_goods": (1200, 4500), "ready_to_wear": (800, 3200), "accessories": (300, 1200), "footwear": (600, 2000), } def generate_dataset( start: str = "2021-01-01", end: str = "2024-12-31", seed: int = 42, ) -> pd.DataFrame: """ Returns a long-format DataFrame with columns: unique_id, ds, y, price, category, store, event_name Ready for neuralforecast ingestion (unique_id + ds + y convention). """ rng = np.random.default_rng(seed) dates = pd.date_range(start, end, freq="D") records = [] for category, items in CATEGORIES.items(): p_low, p_high = PRICE_MAP[category] for item_id in items: for store in STORES: unique_id = f"{item_id}_{store}" base_demand = rng.integers(5, 40) price = round(rng.uniform(p_low, p_high), -1) # round to nearest 10 sales = _base_demand_series(dates, base_demand, category, rng) df_item = pd.DataFrame({ "unique_id": unique_id, "ds": dates, "y": sales.clip(0), "price": price, "category": category, "store": store, "event_name": "", }) records.append(df_item) df = pd.concat(records, ignore_index=True) df = apply_luxury_overlay(df, rng) df["y"] = df["y"].clip(0).round().astype(int) return df def _base_demand_series( dates: pd.DatetimeIndex, base: int, category: str, rng: np.random.Generator, ) -> np.ndarray: """ Compose a realistic base demand curve: - Weekly seasonality - Annual seasonality (Q4 Christmas peak, summer trough) - Slight upward trend - Gaussian noise """ n = len(dates) t = np.arange(n) # Trend trend = base + 0.003 * t # Annual seasonality — peaks in Nov/Dec, trough in Jan/Feb annual = 0.25 * base * np.sin(2 * np.pi * (t - 60) / 365) # Weekly seasonality — weekends sell more in flagship stores weekly = 0.10 * base * np.sin(2 * np.pi * t / 7) # Noise noise = rng.normal(0, base * 0.15, n) return trend + annual + weekly + noise