"""Independent reconstruction from registry records and the frozen selection/coding.
Does not import the author's analysis or use its result files to construct estimates.
"""
import csv,hashlib,json,math,pathlib,sys
from collections import defaultdict
root=pathlib.Path(sys.argv[1])
def load(name): return json.loads((root/name).read_text())
digests=load('plan/ctgov_digests.json')
for name,digest in digests.items():
 assert 'sha256:'+hashlib.sha256((root/'data/ctgov'/name).read_bytes()).hexdigest()==digest
unchanged=[]
for old in sorted((root/'plan/data').glob('*')):
 current=root/'data'/old.name
 assert old.read_bytes()==current.read_bytes();unchanged.append('data/'+old.name)
assert (root/'code/analyze.py').read_bytes()==(root/'plan/code/analyze.py').read_bytes()
unchanged.append('code/analyze.py')
screen=load('data/screening.json'); definitions=load('data/outcomes.json'); coding=load('data/term_coding.json')
term_outcomes={x['term']:set(x['outcomes']) for x in coding}
estimates=defaultdict(list); comparisons=0;eligible=[]
for trial in screen:
 if not trial['eligible']: continue
 eligible.append(trial['nct']); record=load('data/ctgov/'+trial['nct']+'.json')
 ae=record['resultsSection']['adverseEventsModule']; groups={g['id']:g for g in ae['eventGroups']}
 for comparison in trial['comparisons']:
  arms=[]
  for role in ['glp1_groups','placebo_groups']:
   selected=set(comparison[role]);den=0;totals=defaultdict(int);deaths=0;deathden=0;deathknown=True;serious=0
   for gid in selected:
    g=groups[gid];serious+=g.get('seriousNumAffected') or 0
    fallback=max([s.get('numAtRisk') or 0 for event in ae.get('seriousEvents',[]) for s in event.get('stats',[]) if s['groupId']==gid],default=0)
    den+=g.get('seriousNumAtRisk') or fallback or g.get('otherNumAtRisk') or g.get('deathsNumAtRisk') or 0
    if g.get('deathsNumAffected') is None or not g.get('deathsNumAtRisk'): deathknown=False
    else: deaths+=g['deathsNumAffected'];deathden+=g['deathsNumAtRisk']
   for event in ae.get('seriousEvents',[]):
    term=' '.join(event.get('term','').split()).lower()
    for stat in event.get('stats',[]):
     if stat['groupId'] in selected:
      for oid in term_outcomes.get(term,set()): totals[oid]+=stat.get('numAffected') or 0
   arm={o['id']:(min(totals[o['id']],den),den) for o in definitions}
   for o in definitions:
    if o.get('count')=='death':arm[o['id']]=(deaths,deathden) if deathknown else None
    elif o.get('count')=='any_serious':arm[o['id']]=(serious,den)
   arms.append((den,arm))
  if any(a[0]==0 for a in arms):continue
  comparisons+=1
  for o in definitions:
   a=arms[0][1][o['id']];b=arms[1][1][o['id']]
   if a is not None and b is not None:estimates[o['id']].append((*a,*b))
expected={**load('results/R2.json'),**load('results/R3.json')}; checked=0
for oid,rows in estimates.items():
 a,n1,c,n0=map(sum,zip(*rows));result={'glp1_affected':a,'glp1_at_risk':n1,'placebo_affected':c,'placebo_at_risk':n0}
 numer=math.fsum(a*n0/(n1+n0) for a,n1,c,n0 in rows);denom=math.fsum(c*n1/(n1+n0) for a,n1,c,n0 in rows)
 if numer and denom:
  variance=math.fsum(((a+c)*n1*n0-a*c*(n1+n0))/(n1+n0)**2 for a,n1,c,n0 in rows)/(numer*denom)
  se=math.sqrt(variance);logrr=math.log(numer/denom)
  result.update(rr=numer/denom,lo=math.exp(logrr-1.959964*se),hi=math.exp(logrr+1.959964*se),p=math.erfc(abs(logrr/se)/math.sqrt(2)))
 for field,value in result.items():
  target=expected[oid]['main'][field]
  assert math.isclose(value,target,rel_tol=5e-6,abs_tol=1e-14),(oid,field,value,target)
  checked+=1
print(json.dumps({'records_with_verified_preregistered_digest':len(digests),'unchanged_preregistered_files':unchanged,'eligible_trials':len(eligible),'comparisons':comparisons,'independent_outcomes':len(estimates),'matched_primary_fields':checked,'note':'Selection and term mappings were treated as frozen inputs; their clinical validity was not independently adjudicated.'},indent=2))
