from design import *
import numpy as np
g=pd.read_parquet(R/'classified.parquet');g['day']=g.first_date.map(lambda d:d.timestamp()/86400)
chosen=['Action roguelike','Deckbuilding','Auto battler','Action RPG','Tower defense','Top-down/arena shooting','First-person shooting','Sports','Board/dice/tabletop','City/colony building','Platforming','Puzzle','Horror','Management']
rows=[]
for period in ['2023_2025','2026_JanAug']:
 base=g[g.period.eq(period)]
 for host in chosen:
  pool=base[base['host10_'+host]&base['text_'+host]]
  for definition in ['strict','direct','extended']:
   b=pool[pool[definition]];c=pool[~pool[definition]][['appid','developer_id','price_bucket','day','reviews']]
   settings=[(3,90),(5,60),(5,90),(5,180),(10,90)] if period=='2023_2025' else [(3,30),(5,14),(5,30),(5,60),(10,30)]
   for k,window in settings:
    obs={q:0 for q in [50,100,200,1000]};exp={q:0.0 for q in obs};supported=0
    for a in b[['appid','developer_id','price_bucket','day','reviews']].itertuples():
     mask=c.price_bucket.fillna('missing').eq(a.price_bucket if pd.notna(a.price_bucket) else 'missing')&c.developer_id.ne(a.developer_id)&c.day.sub(a.day).abs().le(window)
     peers=c[mask].copy();peers['distance']=peers.day.sub(a.day).abs();peers=peers.sort_values(['distance','appid']).head(k)
     if len(peers)<3:continue
     supported+=1
     for q in obs:obs[q]+=int(a.reviews>=q);exp[q]+=float(peers.reviews.ge(q).mean())
    rows.append(dict(period=period,host=host,definition=definition,k=k,window=window,build_n=len(b),supported_n=supported,
      **{f'observed{q}':obs[q] for q in obs},**{f'expected{q}':exp[q] for q in exp},**{f'ratio{q}':obs[q]/exp[q] if exp[q] else None for q in obs}))
pd.DataFrame(rows).to_csv(R/'match_sensitivity.csv',index=False)
z=pd.DataFrame(rows).query("period == '2023_2025' and definition == 'strict'")
print(z.groupby('host').ratio100.agg(['min','max']).round(3).to_string())
