"""Independent finite-grid adversarial audit using exact integer arithmetic.
No author implementation or floating-point interval computation is used.
"""
import json, math, pathlib, sys, time
from fractions import Fraction
started=time.monotonic()
GRID=1000
NMIN,NMAX=5,100
Z=Fraction('1.959963984540054')
ZN,ZD=Z.numerator**2,Z.denominator**2
METHODS=('wald','wilson','agresti_coull','clopper_pearson')
stats={m:{'below93':0,'below95':0,'sum':Fraction(0),'minimum':Fraction(1)} for m in METHODS}
fixed={i:[] for i in (10,50,200,500)}
for n in range(NMIN,NMAX+1):
 den=GRID**n
 sums={m:0 for m in METHODS}
 minima={m:den for m in METHODS}
 A=n*ZD+ZN
 ac_B=[2*k*ZD+ZN for k in range(n+1)]
 for i in range(1,GRID):
  q=GRID-i
  weight=q**n
  weights=[weight]
  for k in range(n):
   weight,remainder=divmod(weight*(n-k)*i,(k+1)*q)
   assert remainder==0
   weights.append(weight)
  assert sum(weights)==den
  totals={m:0 for m in METHODS}
  cum=0
  for k,weight in enumerate(weights):
   diff=n*i-GRID*k
   if diff*diff*n*ZD <= ZN*k*(n-k)*GRID*GRID:
    totals['wald']+=weight
   if diff*diff*ZD <= ZN*n*i*q:
    totals['wilson']+=weight
   B=ac_B[k]
   ac_diff=2*A*i-GRID*B
   if ac_diff*ac_diff*A <= ZN*B*(2*A-B)*GRID*GRID:
    totals['agresti_coull']+=weight
   upper=den-cum
   cum+=weight
   if 40*upper>=den and 40*cum>=den:
    totals['clopper_pearson']+=weight
  assert cum==den
  for m,total in totals.items():
   stats[m]['below93']+=100*total<93*den
   stats[m]['below95']+=20*total<19*den
   sums[m]+=total
   minima[m]=min(minima[m],total)
  if i in fixed:
   fixed[i].append((n,Fraction(totals['wald'],den)))
 for m in METHODS:
  stats[m]['sum']+=Fraction(sums[m],den)
  stats[m]['minimum']=min(stats[m]['minimum'],Fraction(minima[m],den))
 if time.monotonic()-started>110:
  raise RuntimeError('Declared two-minute computation budget nearly exhausted')
pairs=(NMAX-NMIN+1)*(GRID-1)
round6=lambda x:round(float(x),6)
result={'scope':'Independent adversarial exact-arithmetic audit of the stated grid; all probabilities, comparisons and averages are integer/rational until final display rounding','n_min':NMIN,'n_max':NMAX,'pairs':pairs,'z':str(Z),'alpha':'1/20','closed_intervals':True,'share_below_low':{},'share_below_nominal':{},'mean_coverage':{},'min_coverage':{},'exact_counts':{},'wald_at_fixed_p':{}}
for m,s in stats.items():
 result['share_below_low'][m]=round6(Fraction(s['below93'],pairs))
 result['share_below_nominal'][m]=round6(Fraction(s['below95'],pairs))
 result['mean_coverage'][m]=round6(s['sum']/pairs)
 result['min_coverage'][m]=round6(s['minimum'])
 result['exact_counts'][m]={'below_0_93':s['below93'],'below_0_95':s['below95']}
for i,series in fixed.items():
 falls=[(series[j][1]-series[j+1][1],series[j][0],series[j][1],series[j+1][1]) for j in range(len(series)-1)]
 drop=max(falls)
 result['wald_at_fixed_p'][str(Fraction(i,GRID))]={'drops':sum(x[0]>0 for x in falls),'at_n_max':round6(series[-1][1]),'largest_drop':{'from_n':drop[1],'from':round6(drop[2]),'to':round6(drop[3]),'size':round6(drop[0])} if drop[0]>0 else None}
result['elapsed_seconds']=round(time.monotonic()-started,3)
pathlib.Path(sys.argv[1]).write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps(result,indent=2))
