from pathlib import Path
import math,csv,json,sys,hashlib,itertools
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
P=Path(__file__).resolve().parent

def detector(p,s,c):
 if not all(math.isfinite(v)and 0<=v<=1 for v in (p,s,c)):raise ValueError('Fractions must be finite in [0,1]')
 tp=p*s;fn=p*(1-s);fp=(1-p)*(1-c);tn=(1-p)*c;positive=tp+fp
 return dict(prevalence=p,sensitivity=s,specificity=c,true_positive=tp,false_negative=fn,false_positive=fp,true_negative=tn,positive_fraction=positive,target_given_positive=tp/positive if positive else None)
rows=[detector(p/20,s/10,c/10)for p,s,c in itertools.product(range(21),range(11),range(11))]
for r in rows:
 assert abs(sum(r[k]for k in ('true_positive','false_negative','false_positive','true_negative'))-1)<1e-14
 assert math.isclose(r['true_positive']+r['false_negative'],r['prevalence'],abs_tol=1e-15)
 # Integer contingency construction is independent of normalized products.
 p=round(r['prevalence']*20);s=round(r['sensitivity']*10);c=round(r['specificity']*10)
 yes=p*s;false=(20-p)*(10-c);ref=yes/(yes+false)if yes+false else None
 assert (ref is None and r['target_given_positive']is None)or(ref is not None and math.isclose(ref,r['target_given_positive'],abs_tol=1e-14))
guards=0
for x in [(-1,.5,.5),(.5,2,.5),(.5,.5,math.nan),(.5,.5,math.inf)]:
 try:detector(*x)
 except ValueError:guards+=1
 else:raise AssertionError(x)
b=json.loads((P/'stored-baseline.json').read_text());scores=b['subjects'];mean=sum(x['balanced_accuracy']for x in scores)/len(scores)
assert mean==b['mean']and sum(x['n']for x in scores)==2010 and b['bootstrap_interval'][0]<.25<b['bootstrap_interval'][1]
summary=[dict(mean=b['mean'],bootstrap_low=b['bootstrap_interval'][0],bootstrap_high=b['bootstrap_interval'][1],chance=.25,stored_permutation_p=b['permutation_p'])]
for name,data in [('detector.csv',rows),('stored-participants.csv',scores),('stored-summary.csv',summary)]:
 with(P/name).open('w',newline='',encoding='utf8')as f:w=csv.DictWriter(f,fieldnames=list(data[0]));w.writeheader();w.writerows(data)
plt.rcParams.update({'font.size':11,'axes.spines.top':False,'axes.spines.right':False,'savefig.dpi':180})
fig,ax=plt.subplots(figsize=(8,5),layout='constrained');p=np.linspace(0,1,201)
for c,ls in zip((.5,.9,.99),(':','--','-')):ax.plot(p,[detector(v,.9,c)['target_given_positive']for v in p],ls=ls,label=f'Assigned specificity {c:.0%}')
ax.set(xlabel='Assigned target prevalence',ylabel='P(target present | positive reading)',title='A positive reading depends on the base rate\nFictional nonclinical detector; assigned sensitivity 90%',xlim=(0,1),ylim=(0,1));ax.legend();ax.grid(alpha=.2)
for e in ('png','svg','pdf'):fig.savefig(P/f'detector-base-rate.{e}')
plt.close(fig)
fig,ax=plt.subplots(figsize=(9,5),layout='constrained');x=np.arange(1,11);ax.scatter(x,[100*r['balanced_accuracy']for r in scores],label='Stored participant scores',color='#24609b')
lo,hi=b['bootstrap_interval'];ax.errorbar(12,100*mean,yerr=[[100*(mean-lo)],[100*(hi-mean)]],fmt='s',capsize=6,color='#a64267',label='Stored mean and subject-bootstrap 95% interval');ax.axhline(25,ls='--',color='gray',label='Four-class chance reference')
ax.set(xticks=list(x)+[12],xticklabels=[str(i)for i in x]+['Mean'],xlabel='Neutral participant number; group summary at right',ylabel='Balanced accuracy (%)',title='Replot of stored exploratory EEG results\nRaw signals, training and resampling were not rerun',ylim=(19,31));ax.legend(fontsize=8,loc='upper left');ax.grid(alpha=.2)
for e in ('png','svg','pdf'):fig.savefig(P/f'stored-eeg-results.{e}')
plt.close(fig)
(P/'validation.json').write_text(json.dumps(dict(all_pass=True,detector_rows=len(rows),participant_rows=10,summary_rows=1,undefined_positive_cases=sum(r['target_given_positive']is None for r in rows),guards=guards,stored_mean_difference=mean-b['mean'],stored_trial_sum=2010,raw_analysis_replicated=False),indent=2)+'\n')
(P/'study.json').write_text(json.dumps(dict(runtime=dict(python=sys.version,numpy=np.__version__,matplotlib=matplotlib.__version__),generator_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),baseline_sha256=hashlib.sha256((P/'stored-baseline.json').read_bytes()).hexdigest(),original_result_sha256=b['source_sha256'],original_analysis_script_sha256=b['analysis_script_sha256'],detector_assigned=True,replot_only=True,source_versions=dict(dataset=b['dataset'],doi=b['dataset_doi'])),indent=2)+'\n')
print('PASS 2541 detector +10 participant +1 summary rows')

