from net import R
import pandas as pd,numpy as np,json
g=pd.read_parquet(R.parent/'fable-verification-2026-09-06/games.parquet').set_index('appid')
source=pd.read_parquet(R.parent/'developer-lives-2026-09-07/historical_reviews.parquet')[['appid','recommendationid','timestamp_created']]
parts=[source]
for path in (R.parent/'game-lifetimes-2026-09-07/completed').glob('*.parquet'):parts.append(pd.read_parquet(path)[['appid','recommendationid','timestamp_created']])
d=pd.concat(parts).drop_duplicates(['appid','recommendationid']);dates={a:np.sort(x.timestamp_created.to_numpy()) for a,x in d.groupby('appid')};events=pd.read_csv(R/'events.csv');results=pd.read_csv(R/'event_results.csv');rows=[]
def count(a,lo,hi):return int(np.searchsorted(dates[a],hi.timestamp())-np.searchsorted(dates[a],lo.timestamp()))
for ev in events.itertuples():
    t=pd.Timestamp(ev.event,tz='UTC');target=g.loc[ev.appid];baseline=int(results[results.appid.eq(ev.appid)&results.anchor.eq('first_public_update')].pre365.iloc[0]);age=(t-target.date).days/365.25;candidates=[]
    for a in dates:
        if a not in g.index or a in set(events.appid):continue
        meta=g.loc[a];cage=(t-meta.date).days/365.25
        if cage<2 or abs(cage-age)>5:continue
        before=count(a,t-pd.Timedelta(days=365),t)
        if not max(1,baseline/3)<=before<=max(3,baseline*3):continue
        score=abs(np.log((before+.5)/(baseline+.5)))+.15*abs(cage-age)
        candidates.append(dict(event_appid=ev.appid,event_name=ev.name,control_appid=a,control_name=meta['name'],control_date=meta.date,pre365=before,pre90=count(a,t-pd.Timedelta(days=90),t),post90=count(a,t,t+pd.Timedelta(days=90)),score=score))
    rows.extend(sorted(candidates,key=lambda x:x['score'])[:5])
pd.DataFrame(rows).to_csv(R/'control_candidates.csv',index=False);d.to_parquet(R/'cached_control_reviews.parquet',index=False)
print(pd.DataFrame(rows)[['event_name','control_name','pre365','pre90','post90']].to_string(index=False))
