import warnings; warnings.filterwarnings("ignore") """Shared paths and helpers for the 05_Predictions layer (Binzagr PoV).""" import os, numpy as np, pandas as pd ROOT = os.environ.get('BZ_ROOT', '/mnt/user-data/outputs/Binzagr_Data') CLEAN = os.path.join(ROOT, '02_Cleaned', 'data') SIM = os.path.join(ROOT, '03_Simulated', 'data') OUT = os.path.join(ROOT, '05_Predictions') WORK = os.environ.get('BZ_WORK', '/home/claude/binzagr/pred/work') os.makedirs(WORK, exist_ok=True) DATA_END = pd.Timestamp('2026-10-20') # last sales day (Tuesday) WEEK_FREQ = 'W-TUE' # weeks run Wednesday -> Tuesday, so the last week ends on DATA_END FIRST_FULL_WEEK_END = pd.Timestamp('2026-03-10') TRAIN_END = pd.Timestamp('2026-09-22') # forecast models trained to here (wireframe: "trained to 22 Sep 2026") HOLDOUT_WEEKS = [pd.Timestamp(d) for d in ('2026-09-29', '2026-10-06', '2026-10-13', '2026-10-20')] RECO_CUTOFF = pd.Timestamp('2026-08-25') # next-best SKU picks made 25 Aug 2026, tested on the 8 unseen weeks after it NEXT_WEEK = pd.date_range('2026-10-21', '2026-10-27') # "next week" for van plans def out_dir(sub): d = os.path.join(OUT, sub) os.makedirs(d, exist_ok=True) return d def write_csv(df, sub, name, float_fmt=None): """UTF-8 with BOM (Arabic names display in Excel), ISO dates, no index.""" p = os.path.join(out_dir(sub), name) df = df.copy() for c in df.columns: if pd.api.types.is_datetime64_any_dtype(df[c]): df[c] = df[c].dt.strftime('%Y-%m-%d') df.to_csv(p, index=False, encoding='utf-8-sig', float_format=float_fmt) print(f' wrote {sub}/{name}: {len(df):,} rows x {df.shape[1]} cols') return p def week_end(d): """Tuesday that closes the Wed-Tue week containing date d.""" d = pd.to_datetime(d) return (d + pd.to_timedelta((1 - d.dt.weekday) % 7, unit='D')).dt.normalize() def wape(y, f): y = np.asarray(y, float); f = np.asarray(f, float) s = np.abs(y).sum() return float(np.abs(y - f).sum() / s) if s > 0 else np.nan def outlet_id(dist, cust): return dist.astype(str) + '-' + cust.astype(str)