#!/usr/bin/env python3
"""Meaningful A09 tests; all educational fixtures are synthetic."""
import sys
sys.dont_write_bytecode=True
import csv,copy,json,math,pathlib,tempfile,unittest
import numpy as np
import research_tools as rt

class A09Tests(unittest.TestCase):
 def test_geometry(self):
  for m,xy in [(0,(1,0)),(math.pi,(-1,0)),(math.pi/2,(0,1))]:
   out=rt.orbit_xy(1,0,m);self.assertAlmostEqual(out['xAU'],xy[0],places=12);self.assertAlmostEqual(out['yAU'],xy[1],places=12)
  self.assertAlmostEqual(rt.orbit_xy(2,.4,0)['xAU'],1.2)
  self.assertAlmostEqual(rt.orbit_xy(2,.4,math.pi)['xAU'],-2.8)
  for vals in [(0,.2,1),(1,-.1,1),(1,1,1),(1,.95,1),(1,.1,float('nan'))]:
   with self.assertRaises(ValueError):rt.orbit_xy(*vals)
 def roster(self,blocks=3):
  return [{'school_id':f'S{b}','instructor_id':f'I{b}','section_id':f'C{b}-{c}'} for b in range(blocks) for c in range(4)]
 def test_assignment(self):
  x=rt.randomize(self.roster(),44,'synthetic');self.assertEqual(x,rt.randomize(self.roster(),44,'synthetic'))
  self.assertEqual(x['blocks'],3)
  for b in range(3):self.assertEqual({(r['immersion'],r['revision']) for r in x['assignments'] if r['instructor_id']==f'I{b}'},set(rt.CELLS))
  with self.assertRaises(ValueError):rt.randomize(self.roster(),44,'study')
  r=self.roster();r[-1]['section_id']=r[0]['section_id']
  with self.assertRaises(ValueError):rt.randomize(r,1,'synthetic')
  r=self.roster();r[-1]['school_id']='S0'
  with self.assertRaises(ValueError):rt.randomize(r,1,'synthetic')
  with self.assertRaises(ValueError):rt.randomize(self.roster()[:-1],1,'synthetic')
 def test_hand_contrast(self):
  # interactions 2,3,4 => mean3, SE1/sqrt3; revision means3,2.5,2; immersion means2,3.5,5.
  a=np.array([[5,7,6,10],[6,7,8,12],[4,4,7,11]],dtype=float)
  d=rt.factorial_estimates(a)
  self.assertAlmostEqual(d['interaction']['estimate'],3)
  self.assertAlmostEqual(d['interaction']['standardError'],1/math.sqrt(3))
  self.assertAlmostEqual(d['revisionMain']['estimate'],2.5)
  self.assertAlmostEqual(d['immersionMain']['estimate'],3.5)
  self.assertEqual(d['interaction']['degreesOfFreedom'],2)
  with self.assertRaises(ValueError):rt.factorial_estimates([[1,1,1,1]]*3)
 def participant_rows(self):
  a=[[5,7,6,10],[6,7,8,12],[4,4,7,11]];out=[]
  for b in range(3):
   for c,(i,r) in enumerate(rt.CELLS):
    for k in range(24):out.append({'school_id':f'S{b}','instructor_id':f'I{b}','section_id':f'C{b}-{c}','student_id':f'P{b}-{c}-{k}','immersion':str(i),'revision':str(r),'delayed_score':str(a[b][c])})
  return out
 def test_complete_rejects_invalid(self):
  rows=self.participant_rows();x=rt.analyze_complete(rows,'synthetic');self.assertEqual(x['students'],288);self.assertAlmostEqual(x['contrasts']['interaction']['estimate'],3)
  with self.assertRaises(ValueError):rt.analyze_complete(rows,'study')
  with self.assertRaises(ValueError):rt.analyze_complete(rows[:-1],'synthetic')
  for value in ['', 'nan','-1','21']:
   r=copy.deepcopy(rows);r[-1]['delayed_score']=value
   with self.assertRaises(ValueError):rt.analyze_complete(r,'synthetic')
  r=copy.deepcopy(rows);r[-1]['student_id']=r[0]['student_id']
  with self.assertRaises(ValueError):rt.analyze_complete(r,'synthetic')
  r=copy.deepcopy(rows)
  for row in r:
   if row['school_id']=='S2':row['school_id']='S0'
  with self.assertRaises(ValueError):rt.analyze_complete(r,'synthetic')
 def test_audit_fixture(self):
  base={k:'' for k in rt.REQUIRED};base.update({'pl_name':'Synthetic 1','hostname':'Synthetic host','default_flag':'1','soltype':'Published Confirmed','discoverymethod':'Transit','pl_controv_flag':'0','sy_snum':'1','cb_flag':'0','pl_refname':'Synthetic provenance','pl_orbper':'10','pl_orbsmax':'1','pl_orbeccen':'.3'})
  for k in ['pl_orbper','pl_orbsmax','pl_orbeccen']:base.update({k+'lim':'0',k+'err1':'.1',k+'err2':'-.1'})
  rows=[]
  for n in range(5):r=base.copy();r['pl_name']=f'Synthetic {n}';rows.append(r)
  rows[1]['pl_orbeccen']='';rows[2]['soltype']='TESS Project Candidate';rows[3]['discoverymethod']='Imaging';rows[4]['pl_orbeccen']='0'
  with tempfile.TemporaryDirectory() as d:
   p=pathlib.Path(d)/'test.csv'
   def write(rs):
    with p.open('w',newline='') as f:w=csv.DictWriter(f,fieldnames=rt.REQUIRED);w.writeheader();w.writerows(rs)
   write(rows);a=rt.audit_csv(p);self.assertEqual(a['rows'],5);self.assertEqual(a['coreStimulusCandidates'],2);self.assertEqual(a['uncertaintyChallengeCandidates'],1);self.assertEqual(a['coreEccentricityZeroRows'],1)
   rows[1]['pl_name']=rows[0]['pl_name'];write(rows)
   with self.assertRaises(ValueError):rt.audit_csv(p)
 def test_executed_outputs(self):
  p=pathlib.Path(__file__).parent;d=json.loads((p/'design-analysis.json').read_text());self.assertEqual(len(d['scenarios']),9);self.assertEqual(d['replicatesPerScenario'],10000)
  for r in d['scenarios']:
   self.assertTrue(.03<r['nullRejection']<.07);self.assertAlmostEqual(r['powerMonteCarloSE'],math.sqrt(r['power']*(1-r['power'])/10000));self.assertEqual(r['schools'],r['instructorBlocks'])
  a=json.loads((p/'stimulus-feasibility-audit.json').read_text());self.assertEqual(a['coreStimulusCandidates'],1692);self.assertEqual(a['uncertaintyChallengeCandidates'],1220);self.assertFalse(a['educationalOutcomesPresent']);self.assertEqual(a['actualTrialParticipants'],0)

if __name__=='__main__':unittest.main(verbosity=2)
