import csv,calendar,hashlib,json,urllib.request
from decimal import Decimal as D
from pathlib import Path
p=Path('/tmp/factory-temperature-outline-stage/stories/2026-temperature-record-src')
rows=list(csv.DictReader(x for x in (p/'global-monthly.csv').read_text().splitlines() if not x.startswith('#')))
by={}
for r in rows:
 y,m,d=map(int,r['month'].split('-')); assert d==1
 assert (y,m) not in by
 by[y,m]={k:D(v) for k,v in r.items() if k!='month'}
 assert all(v.is_finite() for v in by[y,m].values())
expected=[(y,m) for y in range(1940,2027) for m in range(1,13) if y<2026 or m<=9]
assert list(by)==expected
for m in range(1,13):
 for field in ['clim_91-20','offset_pi']:
  assert len({r[field] for (y,mm),r in by.items() if mm==m})==1
assert all(r['ano_pi']==r['ano_91-20']+r['offset_pi'] for r in by.values())
print('rows',len(rows),'max_absolute_identity_error',max(abs(r['2t']-r['clim_91-20']-r['ano_91-20']) for r in by.values()))
print('September top five', sorted(((r['ano_91-20'],y) for (y,m),r in by.items() if m==9),reverse=True)[:5])
print('2023 Q4 months',[(m,str(by[2023,m]['ano_91-20'])) for m in [10,11,12]])
output={}
for mode in ['equal','days']:
 def weight(y,m): return 1 if mode=='equal' else calendar.monthrange(y,m)[1]
 def avg(y,months):
  return sum(by[y,m]['ano_91-20']*weight(y,m) for m in months)/sum(weight(y,m) for m in months)
 target=avg(2024,range(1,13)); current=avg(2026,range(1,10))
 obs_w=sum(weight(2026,m) for m in range(1,10)); rem_w=sum(weight(2026,m) for m in [10,11,12]); total_w=obs_w+rem_w
 hurdle=(target*total_w-current*obs_w)/rem_w
 history=sorted(((avg(y,[10,11,12]),y) for y in range(1940,2026)),reverse=True)
 annual=sorted(((avg(y,range(1,13)),y) for y in range(1940,2026)),reverse=True)
 ytd=sorted(((avg(y,range(1,10)),y) for y in range(1940,2027)),reverse=True)
 scenario=(current*obs_w+sum(by[2023,m]['ano_91-20']*weight(2026,m) for m in [10,11,12]))/total_w
 bound=D('0.00005')*(D(total_w)+obs_w)/rem_w
 cumul={y:[avg(y,range(1,m+1)) for m in range(1,13 if y==2024 else 10)] for y in [2024,2026]}
 scenario_cumul=[(current*obs_w+sum(by[2023,m]['ano_91-20']*weight(2026,m) for m in range(10,end+1)))/(obs_w+sum(weight(2026,m) for m in range(10,end+1))) for end in [10,11,12]]
 vals={'target':target,'current':current,'2024_ytd':avg(2024,range(1,10)),'hurdle':hurdle,'q4_max':history[0], 'q4_min':history[-1], 'hurdle_minus_max':hurdle-history[0][0],'repeat_scenario':scenario,'annual_gap':target-scenario,'ytd_gap':avg(2024,range(1,10))-current,'rounding_bound':bound,'history_count':len(history),'ytd_top5':ytd[:5],'annual_top5':annual[:5],'cumulative':cumul,'scenario_cumulative':scenario_cumul,'weights':(obs_w,rem_w,total_w)}
 output[mode]=vals
 print(mode,json.dumps(vals,default=str,indent=2))
print('conservative gap',min(output[m]['hurdle']-output[m]['rounding_bound'] for m in output)-max(output[m]['q4_max'][0]+D('0.00005') for m in output))
for info in json.loads((p/'retrievals.json').read_text()):
 actual=hashlib.sha256((p/info['file']).read_bytes()).hexdigest(); assert actual==info['sha256'],info['file']
print('ALL SNAPSHOT HASHES MATCH')
info=json.loads((p/'retrievals.json').read_text())[0]
fresh=urllib.request.urlopen(info['url']).read()
assert hashlib.sha256(fresh).hexdigest()==info['sha256']
print('LIVE CSV MATCHES SNAPSHOT',len(fresh),info['sha256'])
Path('/tmp/temperature-independent-numbers.json').write_text(json.dumps(output,default=str,indent=2))
# Only now inspect author's derived artifact, to compare against independent calculations.
author=json.loads((p/'statistics.json').read_text())
for mode in ['equal','days']:
 def weight(y,m): return 1 if mode=='equal' else calendar.monthrange(y,m)[1]
 def avg(y,months): return sum(by[y,m]['ano_91-20']*weight(y,m) for m in months)/sum(weight(y,m) for m in months)
 a=author['modes']['months' if mode=='equal' else 'days']; b=output[mode]
 mapping={'annual_2024':'target','ytd_2026':'current','q4_tie_threshold':'hurdle','threshold_minus_q4_record':'hurdle_minus_max','repeat_q4_2023_annual_2026':'repeat_scenario','repeat_q4_2023_shortfall_vs_2024':'annual_gap','threshold_input_rounding_bound':'rounding_bound'}
 def close(x,y): assert abs(D(str(x))-D(str(y)))<D('0.000000000001'),(x,y)
 for ak,bk in mapping.items(): close(a[ak],b[bk])
 close(a['q4_record'],b['q4_max'][0]); assert a['q4_record_year']==2023
 close(a['annual_2024_preindustrial_offset_applied'],b['target']+D('0.88'))
 close(a['repeat_annual_2026_preindustrial_offset_applied'],b['repeat_scenario']+D('0.88'))
 for r in a['q4_history']: close(r['value'],avg(r['year'],[10,11,12]))
 for r in a['cumulative_recent']: close(r['value'],avg(r['year'],range(1,r['through_month']+1)))
 for r,v in zip(a['scenario_cumulative_2026_repeat_2023_q4'],b['scenario_cumulative']): close(r['value'],v)
 print(mode,'author statistics match: all scalar claims, all 86 historical Q4s, all cumulative points, 3 scenario points, annual baseline conversions')
