from common import *
import json
c=pd.read_parquet(R/'career_games.parquet');cut=pd.Timestamp('2026-07-01',tz='UTC')
c=c[c.debut.ge(pd.Timestamp('2015-01-01',tz='UTC'))].copy()
# First entry to a current-outcome-defined catalog state; equal three-year follow-up.
states=[];cum=[];price=[]
for dev,d in c.groupby('dev',sort=False):
    d=d.sort_values(['date','appid']);seen=set();mids=0;best=0;prior=[]
    records=d.to_dict('records')
    for i,row in enumerate(records):
        mids+=50<=row['reviews']<556;best=max(best,row['reviews'])
        state='already_hit' if best>=556 else 'two_plus_mid' if mids>=2 else 'one_mid' if mids==1 else 'all_low'
        if i>=1 and state not in seen and row['date']<=cut-pd.Timedelta(days=365.25*3):
            seen.add(state);future=[q for q in records[i+1:] if q['date']<=row['date']+pd.Timedelta(days=365.25*3)]
            states.append(dict(dev=dev,state=state,entry_appid=row['appid'],entry_date=row['date'],future_release=bool(future),future556=any(q['r556'] for q in future),next556=bool(future and future[0]['r556'])))
        if i>0 and row['date']<=pd.Timestamp('2025-07-01',tz='UTC') and row['gap_days']>=30 and prior:
            p=max(prior,key=lambda q:q['reviews'])
            if 50<=p['reviews']<556 and np.isfinite(row['price']) and np.isfinite(p['price']):
                price.append(dict(dev=dev,appid=row['appid'],hit=row['r556'],price_up=row['price']>=p['price']*1.25,primary_change=row['primary']!=p['primary'],price=row['price'],gap=row['gap_days']))
        prior.append(row)
    if d.date.iloc[0]<=pd.Timestamp('2020-07-01',tz='UTC'):
        for n in [1,2,3,4,5,10]:
            if len(d)>=n:
                firstn=d.iloc[:n];cum.append(dict(dev=dev,n=n,any556=firstn.r556.any()))
s=pd.DataFrame(states);s.to_csv(R/'climber_members.csv',index=False)
rows=[]
for state,a in s.groupby('state'):rows.append(dict(state=state,n=len(a),future_release_rate=a.future_release.mean(),any556_within3=a.future556.mean(),next556_within3=a.next556.mean(),among_returners_any556=a.loc[a.future_release,'future556'].mean()))
save(rows,'climber_states.csv')
save([dict(index=n,n=len(a),any556=a.any556.mean()) for n,a in pd.DataFrame(cum).groupby('n')],'cumulative_selected_survivors.csv')
p=pd.DataFrame(price);rows=[]
for hit,a in p.groupby('hit'):rows.append(dict(next556=hit,n=len(a),priced_up=a.price_up.mean(),changed_primary=a.primary_change.mean(),median_price=a.price.median(),median_gap_days=a.gap.median()))
save(rows,'climber_breakouts.csv')
# Entry-price formula thresholds and sensitivity without claiming revenue truth.
g,t=load();p=g[g.valid&~g.free&~g.explicit&g.year.between(2023,2025)&g.price.notna()];rows=[]
for band,a in p.groupby('price_band'):
    row=dict(price_band=band,n=len(a),r100=a.r100.mean(),r556=a.r556.mean())
    for k in [25,35.9,55]:row['formula_k'+str(k)]=((a.reviews*a.price*k)>=50000).mean()
    rows.append(row)
save(rows,'revenue_formula_sensitivity.csv')
print('Career extensions complete')
