from common import *
from collections import Counter
import re,unicodedata
g=pd.read_parquet(R/'games.parquet');m=g[g.mature].copy();p=pd.read_csv(R/'catalogs.csv')
creators=pd.read_parquet(R.parent/'2026-09-05/exports/creators.parquet')
def norm(s):return re.sub(r'\s+',' ',unicodedata.normalize('NFKC',str(s)).casefold()).strip()
fr=creators[creators.role.eq('franchises')].groupby('appid').name.agg(lambda x:set(norm(v) for v in x if str(v).strip())).to_dict()
rows=[]
eligible=set(p.loc[p.n.between(4,10)&pd.to_datetime(p.first_date,utc=True).ge(pd.Timestamp('2010-01-01',tz='UTC')),'developer_id'])
for dev,a in m[m.developer_id.isin(eligible)].groupby('developer_id'):
    c=Counter()
    for appid in a.appid:c.update(fr.get(appid,set()))
    rows.append(dict(developer_id=dev,n=len(a),same_publisher=a.pub.nunique()==1,all_selfpub=a.selfpub.all(),primary_labels=a.primary.nunique(),franchise_coverage=sum(bool(fr.get(v)) for v in a.appid)/len(a),largest_franchise_share=max(c.values(),default=0)/len(a),shared_franchise_name=c.most_common(1)[0][0] if c else '',every100=a.reviews.ge(100).all(),every1000=a.reviews.ge(1000).all(),every1000_and80=(a.reviews.ge(1000)&a.pct.ge(80)).all(),peak=a.reviews.max(),floor=a.reviews.min()))
d=pd.DataFrame(rows);d.to_csv(R/'consistency_context_catalogs.csv',index=False);summary=[]
for label,mask in [('all_with_1000_peak',d.peak.ge(1000)),('same_publisher',d.peak.ge(1000)&d.same_publisher),('different_publishers',d.peak.ge(1000)&~d.same_publisher),('all_selfpub',d.peak.ge(1000)&d.all_selfpub),('reported_franchise_in_majority',d.peak.ge(1000)&d.largest_franchise_share.ge(.5)),('other_or_unreported_franchise',d.peak.ge(1000)&d.largest_franchise_share.lt(.5))]:
    a=d[mask];summary.append(dict(group=label,n=len(a),every100=int(a.every100.sum()),every1000=int(a.every1000.sum()),every1000_and80=int(a.every1000_and80.sum()),every100_rate=a.every100.mean(),every1000_rate=a.every1000.mean(),median_floor=a.floor.median()))
save(summary,'consistency_context.csv')
e=pd.read_csv(R/'first_strong_events.csv');e=e[e.threshold.eq(1000)];indexed=g.set_index('appid');rows=[]
for q in e.itertuples():
    a=m[m.developer_id.eq(q.developer_id)].sort_values('date');anchor=indexed.loc[q.anchor_appid];before=a[a.date.lt(anchor.date)];after=a[a.date.gt(anchor.date)&a.date.le(anchor.date+pd.Timedelta(days=365.25*5))]
    prev=before.iloc[-1] if len(before) else None;nextg=after.iloc[0] if len(after) else None
    rows.append(dict(developer_id=q.developer_id,credit=q.credit,first_strong_index=q.first_strong_index,timing='first_game' if q.first_strong_index==1 else 'second_third' if q.first_strong_index<=3 else 'fourth_plus',anchor_price=anchor.price,previous_price=prev.price if prev is not None else np.nan,next_price=nextg.price if nextg is not None else np.nan,price_ratio_to_previous=anchor.price/prev.price if prev is not None and pd.notna(prev.price) and prev.price>0 else np.nan,publisher_label_changed=anchor.pub!=prev.pub if prev is not None and anchor.pub and prev.pub else np.nan,primary_label_changed=anchor.primary!=prev.primary if prev is not None else np.nan,anchor_shared_franchise_with_previous=bool(fr.get(anchor.name,set())&fr.get(prev.appid,set())) if prev is not None else np.nan,next_shared_franchise=bool(fr.get(q.anchor_appid,set())&fr.get(nextg.appid,set())) if nextg is not None else np.nan,next_reviews=nextg.reviews if nextg is not None else np.nan))
z=pd.DataFrame(rows);z.to_csv(R/'breakthrough_context_members.csv',index=False)
save([dict(timing=k,n=len(a),median_anchor_price=a.anchor_price.median(),paired_price_n=int(a.price_ratio_to_previous.notna().sum()),median_price_ratio=a.price_ratio_to_previous.median(),publisher_changed_n=int(a.publisher_label_changed.notna().sum()),publisher_changed_rate=a.publisher_label_changed.dropna().astype(float).mean(),primary_changed_rate=a.primary_label_changed.dropna().astype(float).mean()) for k,a in z.groupby('timing')],'breakthrough_context.csv')
print('Context comparisons complete')
