from common import *
import json
g=pd.read_parquet(R/'games.parquet');m=g[g.mature].copy();m['strong']=m.reviews.ge(1000);m['moderate']=m.reviews.ge(100);m['quiet']=m.reviews.lt(100)
p=m.groupby('developer_id').agg(credit=('credit','last'),developer_url=('developer_page_url','last'),link_type=('developer_link_type','last'),n=('appid','size'),first_date=('date','first'),last_date=('date','last'),peak=('reviews','max'),minimum=('reviews','min'),median=('reviews','median'),total=('reviews','sum'),hits=('strong','sum'),substantial=('moderate','sum'),quiet=('quiet','sum'))
p['peak_share']=p.peak/p.total.where(p.total.gt(0));p['observed_total_n']=g.groupby('developer_id').size();p['immature_n']=p.observed_total_n-p.n
seqs=m.groupby('developer_id').review_band.agg(list).to_dict();reviews=m.groupby('developer_id').reviews.agg(list).to_dict();names=m.groupby('developer_id')['name'].agg(list).to_dict();ids=m.groupby('developer_id').appid.agg(list).to_dict()
patterns=[];firstpos=[];aftercounts=[];runnerup=[]
for dev,row in p.iterrows():
    rs=reviews[dev];h=[i for i,r in enumerate(rs) if r>=1000];first=h[0] if h else None;after=rs[first+1:] if h else []
    if not h:pattern='no_1000_review_game'
    elif len(h)==len(rs):pattern='every_mature_game_1000plus'
    elif len(h)==1:
        if len(after)==0:pattern='one_hit_no_mature_followup'
        elif len(after)==1:pattern='one_hit_one_mature_followup'
        elif max(after)<100:pattern='one_hit_then_only_quiet'
        elif min(after)>=100:pattern='one_hit_then_moderate_floor'
        else:pattern='one_hit_then_mixed_nonhits'
    elif all(r>=1000 for r in after):pattern='breakthrough_then_all_strong'
    elif any(r<100 for r in rs[h[0]+1:h[-1]]):pattern='strong_quiet_strong'
    else:pattern='repeated_strong_with_variation'
    patterns.append(pattern);firstpos.append(first+1 if first is not None else np.nan);aftercounts.append(len(after));runnerup.append(sorted(rs,reverse=True)[1] if len(rs)>1 else np.nan)
p['pattern']=patterns;p['first_strong_index']=firstpos;p['mature_followups_after_first_strong']=aftercounts;p['runner_up']=runnerup;p['sequence']=[' '.join(seqs[k]) for k in p.index]
p['titles']=[' | '.join(names[k]) for k in p.index];p['appids']=[';'.join(map(str,ids[k])) for k in p.index]
p['core_shape']=p.n.between(3,10)&p.first_date.ge(pd.Timestamp('2010-01-01',tz='UTC'))&p.first_date.lt(pd.Timestamp('2020-09-01',tz='UTC'))
p.reset_index().to_csv(R/'catalogs.csv',index=False)
rows=[]
for scope,mask in [('all_core',p.core_shape),('core_with_hit',p.core_shape&p.hits.ge(1)),('core_2015plus',p.core_shape&p.first_date.ge(pd.Timestamp('2015-01-01',tz='UTC'))),('core_3to5',p.core_shape&p.n.le(5)),('core_6to10',p.core_shape&p.n.ge(6))]:
    a=p[mask]
    for pattern,x in a.groupby('pattern'):rows.append(dict(scope=scope,pattern=pattern,n=len(x),share=len(x)/len(a),denominator=len(a),median_titles=x.n.median(),median_peak=x.peak.median(),median_runner_up=x.runner_up.median(),median_catalog_median=x['median'].median()))
save(rows,'pattern_counts.csv')
# Exhaustive one-hit states among old enough first releases, including one-title catalogs.
one=p[p.hits.eq(1)&p.first_date.between(pd.Timestamp('2010-01-01',tz='UTC'),pd.Timestamp('2020-08-31',tz='UTC'))].copy()
one['followup_state']=np.select([one.mature_followups_after_first_strong.eq(0)&one.immature_n.eq(0),one.mature_followups_after_first_strong.eq(0)&one.immature_n.gt(0),one.mature_followups_after_first_strong.eq(1)],['none_observed_after_peak','only_immature_followup','one_mature_followup'],default='atleast_two_mature_followups')
save([dict(state=k,n=len(x),median_peak=x.peak.median(),median_runnerup=x.runner_up.median()) for k,x in one.groupby('followup_state')],'one_hit_states.csv')
one.reset_index().to_csv(R/'one_hit_catalogs.csv',index=False)
# Consistency uses >=4 mature games, not a two-game success story.
c=p[p.n.between(4,10)&p.first_date.ge(pd.Timestamp('2010-01-01',tz='UTC'))].copy();c['hit_fraction']=c.hits/c.n;c['floor100']=c.minimum.ge(100)
rows=[]
for group,mask in [('all',pd.Series(True,index=c.index)),('peak1000',c.peak.ge(1000)),('peak10000',c.peak.ge(10000))]:
    a=c[mask];rows.append(dict(group=group,n=len(a),all100=int(a.minimum.ge(100).sum()),all1000=int(a.minimum.ge(1000).sum()),atleast_half1000=int(a.hit_fraction.ge(.5).sum()),median_minimum=a.minimum.median(),median_catalog_median=a['median'].median(),median_peak_share=a.peak_share.median()))
save(rows,'consistency.csv');c.reset_index().to_csv(R/'consistency_catalogs.csv',index=False)
# First currently qualifying release plus a fixed five-year follow-up window.
events=[];followups=[];paired=[]
for threshold in [500,1000,2000]:
    hitdev=set(m.loc[m.reviews.ge(threshold),'developer_id'])
    core=m[m.developer_id.isin(hitdev)][['developer_id','appid','name','date','reviews','price','pub','primary','credit','developer_page_url','game_url']]
    for dev,a in core.groupby('developer_id',sort=False):
        a=a.sort_values(['date','appid']);h=a[a.reviews.ge(threshold)].iloc[0];anchor=h.date
        if not pd.Timestamp('2010-01-01',tz='UTC')<=a.date.iloc[0] or not pd.Timestamp('2015-01-01',tz='UTC')<=anchor<pd.Timestamp('2020-09-01',tz='UTC'):continue
        before=a[a.date<anchor];after=a[(a.date>anchor)&(a.date<=anchor+pd.Timedelta(days=365.25*5))];n=len(after);first=after.head(3)
        events.append(dict(threshold=threshold,developer_id=dev,credit=h.credit,developer_url=h.developer_page_url,anchor_appid=h.appid,anchor_name=h['name'],anchor_url=h.game_url,anchor_date=anchor,anchor_reviews=h.reviews,first_strong_index=len(before)+1,prior_n=len(before),prior_median=before.reviews.median(),prior_best=before.reviews.max(),followup_n=n,followup_any=n>0,repeat_strong=after.reviews.ge(threshold).any(),next_reviews=after.reviews.iloc[0] if n else np.nan,followup_median=after.reviews.median(),followup_minimum=after.reviews.min(),first3_min=first.reviews.min() if n>=3 else np.nan,first3_median=first.reviews.median() if n>=3 else np.nan,first3_repeat=first.reviews.ge(threshold).any() if n>=3 else np.nan))
        for j,row in enumerate(after.itertuples()):followups.append(dict(threshold=threshold,developer_id=dev,anchor_appid=h.appid,position=j+1,appid=row.appid,name=row.name,reviews=row.reviews,price=row.price,primary=row.primary,pub=row.pub,date=row.date,anchor_reviews=h.reviews,prior_n=len(before)))
        if threshold==1000 and len(before)>=3 and n>=3:
            pre=before.tail(3);post=after.head(3)
            paired.append(dict(developer_id=dev,credit=h.credit,pre_min=pre.reviews.min(),post_min=post.reviews.min(),pre_median=pre.reviews.median(),post_median=post.reviews.median()))
e=pd.DataFrame(events);e.to_csv(R/'first_strong_events.csv',index=False);pd.DataFrame(followups).to_csv(R/'first_strong_followups.csv',index=False);save(paired,'paired_floor_before_after.csv')
summ=[]
for threshold,a in e.groupby('threshold'):
    for timing,mask in [('all',pd.Series(True,index=a.index)),('first_game',a.first_strong_index.eq(1)),('second_or_third',a.first_strong_index.between(2,3)),('fourth_plus',a.first_strong_index.ge(4))]:
        z=a[mask];returned=z[z.followup_n.ge(1)];three=z[z.followup_n.ge(3)]
        summ.append(dict(threshold=threshold,timing=timing,n=len(z),returned=len(returned),return_rate=len(returned)/len(z) if len(z) else np.nan,repeat_any=int(z.repeat_strong.sum()),repeat_rate=z.repeat_strong.mean(),repeat_among_returners=returned.repeat_strong.mean(),next100_rate=returned.next_reviews.ge(100).mean(),next_strong_rate=returned.next_reviews.ge(threshold).mean(),median_followup_median=returned.followup_median.median(),three_followups=len(three),first3_floor100_rate=three.first3_min.ge(100).mean(),first3_floor_strong_rate=three.first3_min.ge(threshold).mean(),first3_median=three.first3_median.median()))
save(summ,'first_strong_summary.csv')
print('Catalog patterns',len(p),'core',int(p.core_shape.sum()),'core with hit',int((p.core_shape&p.hits.ge(1)).sum()),flush=True)
