"""Shared loaders, parameters and helpers for the Binzagr simulation (sim_v1).""" import os, json, numpy as np, pandas as pd B = '/mnt/user-data/outputs/Binzagr_Data/02_Cleaned/data/' OUT = '/mnt/user-data/outputs/Binzagr_Data/10_Simulated_Datasets' SIMV, SEED = 'sim_v1', 20261027 START, END = pd.Timestamp('2026-03-01'), pd.Timestamp('2026-10-20') # same window as the real Riyadh sales os.makedirs(OUT, exist_ok=True) # ---------------- parameters (every assumption in one place; written to parameters.csv) P = { 'channel_share': {'GR': 0.40, 'MT': 0.30, 'WS': 0.15, 'DS': 0.08, 'EC': 0.07}, 'channel_discount': {'GR': 0.0, 'MT': 0.12, 'WS': 0.20, 'DS': 0.18, 'EC': 0.10}, 'channel_terms_days': {'GR': 30, 'MT': 75, 'WS': 30, 'DS': 45, 'EC': 30}, 'channel_otif_target': {'GR': None, 'MT': 0.95, 'WS': 0.90, 'DS': 0.95, 'EC': 0.97}, 'division_margin': {'Drinks': 0.16, 'Ice Cream': 0.22, 'Unilever': 0.12, 'Food': 0.18, 'Chilled': 0.15, 'Non-Food': 0.20}, 'division_shelf_life_days': {'Drinks': 270, 'Ice Cream': 365, 'Unilever': 730, 'Food': 365, 'Chilled': 45, 'Non-Food': 1095}, 'division_temp': {'Drinks': 'ambient', 'Ice Cream': 'frozen', 'Unilever': 'ambient', 'Food': 'ambient', 'Chilled': 'chilled', 'Non-Food': 'ambient'}, 'division_cluster': {'Food': 'CL1', 'Chilled': 'CL2', 'Drinks': 'CL2', 'Ice Cream': 'CL2', 'Non-Food': 'CL6', 'Unilever': 'CL7'}, 'breakevens': {'Chilled': (61016, 75338, 84737), 'Food': (95826, 119425, 134912), 'Drinks': (92448, 111842, 124570), 'Non-Food': (89718, 111756, 126219), 'Unilever': (102362, 126977, 143131), 'Ice Cream': (59384, 72184, 80584)}, 'mt_accounts_stores': [60, 45, 35, 30, 25, 25, 20, 20], 'ds_chains_stores': [40, 35, 25, 20], 'ws_count': 60, 'ec_platforms': 4, 'city_mix_stores': {'Riyadh': 0.5, 'Jeddah': 0.3, 'Dammam': 0.2}, 'growth_target_by_division': {'Drinks': 0.08, 'Ice Cream': 0.10, 'Unilever': 0.05, 'Food': 0.07, 'Chilled': 0.12, 'Non-Food': 0.06}, 'strike_rate_target': (0.70, 0.85), 'van_cost_margin_for_T1': 0.16, } OLD2DIV = {'Ice Cream': 'Ice Cream', 'Unilever HPC': 'Unilever', 'Malt Beverages': 'Drinks', 'Tea & Dairy Foods': 'Chilled', 'Snacks & Confectionery': 'Food', 'Personal Care': 'Non-Food'} CITY_XY = {'Riyadh': (24.7136, 46.6753), 'Jeddah': (21.5433, 39.1728), 'Dammam': (26.4207, 50.0888)} def rng(k=0): return np.random.default_rng(SEED + k) def save(df, name, label='Simulated', csv=True): df = df.copy() if 'basis' not in df: df['basis'] = label df['sim_version'] = SIMV df.to_parquet(f'{OUT}/{name}.parquet', index=False) if csv: comp = 'gzip' if len(df) > 200_000 else None df.to_csv(f'{OUT}/{name}.csv' + ('.gz' if comp else ''), index=False, encoding='utf-8-sig', compression=comp) print(f' saved {name}: {len(df):,} rows, {df.shape[1]} cols') return df def load(name): return pd.read_parquet(f'{OUT}/{name}.parquet') def real_sales(): s = pd.read_parquet(B + 'riyadh_sales_lines.parquet') s['doc_date'] = pd.to_datetime(s.doc_date) s['outlet_id'] = s.dist_cd.astype(str) + '-' + s.cust_cd.astype(str) return s def real_returns(): r = pd.read_parquet(B + 'riyadh_return_lines.parquet') r['doc_date'] = pd.to_datetime(r.doc_date) r['outlet_id'] = r.dist_cd.astype(str) + '-' + r.cust_cd.astype(str) return r def calendar(): c = pd.read_parquet(B + 'dim_calendar.parquet'); c['date'] = pd.to_datetime(c.date) return c[(c.date >= START) & (c.date <= END)].reset_index(drop=True) def van_division(): v = json.load(open('/home/claude/binzagr/boost/site_vans.json'))['all'] return {k: OLD2DIV[x] for k, x in v} def write_params(): rows = [] for k, v in P.items(): rows.append({'parameter': k, 'value': json.dumps(v)}) pd.DataFrame(rows).to_csv(f'{OUT}/parameters.csv', index=False)