File size: 3,188 Bytes
89ea727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""
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