#!/usr/bin/env python3
"""Meaningful guards, pinned-source reproduction and article consistency tests."""
import argparse,importlib.util,json,pathlib,sys,tempfile,unittest,zipfile
from unittest.mock import patch
sys.dont_write_bytecode=True
HERE=pathlib.Path(__file__).resolve().parent
spec=importlib.util.spec_from_file_location('a07_tools',HERE/'research_tools.py');tools=importlib.util.module_from_spec(spec);spec.loader.exec_module(tools)
parser=argparse.ArgumentParser();parser.add_argument('--source-dir');parser.add_argument('--respondent');parser.add_argument('--activity');opts,unittestargs=parser.parse_known_args()
RESP=opts.respondent or (str(pathlib.Path(opts.source_dir)/'respondent.zip') if opts.source_dir else None)
ACT=opts.activity or (str(pathlib.Path(opts.source_dir)/'activity.zip') if opts.source_dir else None)
R='77566d2864f1b96412a46ceeee9ea53709bc191b092f3573af04c17b2d409873';A='664314e9f0c7373534f10bb1450a1dfc2b50d04c7932ecfea090ff4a1c65bca3'
def fixtures():
 r=[{'TUCASEID':'x','TUFINLWGT':'1','TELFS':'1','TEIO1OCD':'2310','TUDIARYDAY':'2','TUDIARYDATE':'20250101','TUMONTH':'1'}]
 a=[{'TUCASEID':'x','TUACTIVITY_N':'1','TUACTDUR24':'60','TRCODE':'050101'},{'TUCASEID':'x','TUACTIVITY_N':'2','TUACTDUR24':'1380','TRCODE':'010101'}]
 return r,a
class Guards(unittest.TestCase):
 def call(self,r,a):
  with patch.object(tools,'read_zip',side_effect=[(r,R,'r_dat'),(a,A,'a_dat')]):return tools.audit('r','a')
 def test_hand_worked_ratio_and_closure(self):
  r,a=fixtures();z=self.call(r,a);self.assertEqual(z['respondent_rows'],1);self.assertEqual(z['selected_teachers_all_days']['weighted_main_job_minutes_per_diary_day'],60)
 def test_duplicate_person(self):
  r,a=fixtures()
  with self.assertRaisesRegex(ValueError,'Duplicate respondent'):self.call(r+r,a)
 def test_duplicate_episode(self):
  r,a=fixtures()
  with self.assertRaisesRegex(ValueError,'Duplicate diary'):self.call(r,a+a[:1])
 def test_orphan_episode(self):
  r,a=fixtures();a[0]['TUCASEID']='other'
  with self.assertRaisesRegex(ValueError,'Orphan'):self.call(r,a)
 def test_bad_closure(self):
  r,a=fixtures();a[0]['TUACTDUR24']='59'
  with self.assertRaisesRegex(ValueError,'1440'):self.call(r,a)
 def test_invalid_duration(self):
  r,a=fixtures();a[0]['TUACTDUR24']='-1'
  with self.assertRaisesRegex(ValueError,'duration'):self.call(r,a)
 def test_invalid_weight(self):
  r,a=fixtures();r[0]['TUFINLWGT']='nan'
  with self.assertRaisesRegex(ValueError,'weight'):self.call(r,a)
 def test_unapproved_source_version(self):
  r,a=fixtures()
  with patch.object(tools,'read_zip',side_effect=[(r,'changed','r'),(a,A,'a')]):
   with self.assertRaisesRegex(ValueError,'SHA256'):tools.audit('r','a')
 def test_excludes_mixed_preschool_kindergarten(self):
  r,a=fixtures();r[0]['TEIO1OCD']='2300';z=self.call(r,a);self.assertEqual(z['selected_teachers_all_days']['n'],0);self.assertIsNone(z['selected_teachers_all_days']['weighted_main_job_minutes_per_diary_day'])
 def test_zero_work_day_retained(self):
  r,a=fixtures();a[0]['TRCODE']='010101';z=self.call(r,a);self.assertEqual(z['selected_teachers_all_days']['weighted_main_job_minutes_per_diary_day'],0);self.assertIsNone(z['selected_teachers_all_days']['weighted_minutes_on_days_with_main_job_work'])
 def test_reject_too_few_simulations(self):
  with self.assertRaisesRegex(ValueError,'100'):tools.simulate(reps=50)
 def test_reproducible_simulation(self):
  x=tools.simulate(seed=9,reps=1000);self.assertEqual(x,tools.simulate(seed=9,reps=1000));self.assertEqual(len(x['scenarios']),9)
 def test_saved_simulation_and_uncertainty(self):
  x=json.loads((HERE/'design-analysis.json').read_text());y=tools.simulate();self.assertEqual(x,y)
  for r in x['scenarios']:
   self.assertAlmostEqual(r['power_mcse'],(r['power']*(1-r['power'])/10000)**.5);self.assertTrue(.035<r['null_rejection_rate']<.065)
 def test_content_boundaries_and_all_references_cited(self):
  import re
  s=(HERE/'article.md').read_text();body=s.split('## References')[0];nums={int(x) for group in re.findall(r'\[([\d,\s]+)\]',body) for x in re.findall(r'\d+',group)};self.assertEqual(nums,set(range(1,18)));self.assertIn('zero recruited participants',s);self.assertIn('not an approved adequately powered',s);self.assertIn('no human peer review',s.lower())
 def test_audit_source_manifest_consistent(self):
  a=json.loads((HERE/'atus-feasibility-audit.json').read_text());m=json.loads((HERE/'source-audit.json').read_text());self.assertEqual(a['respondent_zip_sha256'],R);self.assertEqual(a['activity_zip_sha256'],A);self.assertEqual(a['respondent_rows'],6146);self.assertEqual(a['activity_rows'],114151);self.assertEqual(a['selected_teachers_all_days']['n'],120);self.assertEqual(a['selected_teachers_weekdays']['n'],53);self.assertEqual(a['selected_teachers_weekends']['n_with_main_job_work'],17);self.assertEqual(a['diaries_in_shutdown_gap'],0);self.assertFalse(m['blanketFullTextVerificationClaim']);self.assertEqual(len(m['referenceVerification']),17)
 @unittest.skipUnless(RESP and ACT,'No source paths supplied; real-file reproduction not performed')
 def test_actual_pinned_files(self):
  fresh=tools.audit(RESP,ACT);self.assertEqual(fresh,json.loads((HERE/'atus-feasibility-audit.json').read_text()))

if __name__=='__main__':unittest.main(argv=[sys.argv[0],*unittestargs],verbosity=2)
