"""Post-run independent score reaggregation; imports no experimental module.
Recomputes targets and all scoring from saved public external streams/responses.
This author audit does not rerun constructors or imply independent reproduction.
"""
import collections, csv, hashlib, json, math, pathlib, statistics, sys, time
ROOT=pathlib.Path(__file__).resolve().parent
def load(p): return json.loads(p.read_text())
def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest()
def dump(p,x): p.write_text(json.dumps(x,indent=2,sort_keys=True)+'\n')
def scored(event,target,responses,deadline,model_failure=False):
    rr=[r for r in responses if r['id']==event['id']]
    flags=dict(missing=int(not rr),incorrect=int(any(r['v']!=target for r in rr)),duplicate=int(len(rr)>1),
               late=int(any(not 1<=r['t']-event['t']<=deadline for r in rr)))
    flags['success']=int(len(rr)==1 and not flags['incorrect'] and not flags['late'] and not model_failure)
    return flags
def summarize(values):
    return dict(n=len(values),mean=statistics.mean(values),min=min(values),max=max(values)) if values else None
def main():
    cpu=time.process_time(); wall=time.perf_counter(); cfg=load(ROOT/'config.json'); cases=load(ROOT/'results/cases.json'); summary=load(ROOT/'results/summary.json')
    streams=load(ROOT/'results/events.json'); histories={}; event_lookup={}
    for stream in streams:
        bank=[0]*stream['K']; last=[None]*stream['K']; key=(stream['group'],stream['K'],stream['episode']); target={}
        for e in stream['events']:
            event_lookup[key+(e['id'],)]=e
            if e['kind']=='W': bank[e['a']]=e['v']; last[e['a']]=e['t']
            else: target[e['id']]=(bank[e['a']],e['t']-last[e['a']])
        histories[key]=target
    setting_K={s['id']:s['K'] for s in cfg['settings']}; recs={(r['group'],r['setting'],r['arm']):r for r in cases}
    rows=list(csv.DictReader((ROOT/'results/queries.csv').open())); seen=set(); counts=collections.defaultdict(collections.Counter); latencies=collections.defaultdict(list)
    query_cases=collections.defaultdict(list); per_episode=collections.defaultdict(list)
    for r in rows:
        g,ep,ev=int(r['group']),int(r['episode']),int(r['event']); K=setting_K[r['setting']]; key=(g,K,ep)
        identity=(g,r['setting'],r['arm'],ep,ev); assert identity not in seen; seen.add(identity)
        e=event_lookup[key+(ev,)]; target,age=histories[key][ev]
        assert e['kind']=='Q' and int(r['t'])==e['t'] and int(r['address'])==e['a'] and int(r['target'])==target and int(r['retention'])==age
        responses=json.loads(r['responses']); flags=scored(e,target,responses,cfg['deadline'],bool(int(r['model_failure'])))
        for k,v in flags.items(): assert int(r[k])==v
        arm=r['arm']; counts[arm]['queries']+=1
        for k,v in flags.items(): counts[arm][k]+=v
        counts[arm]['model_failure']+=int(r['model_failure'])
        counts[arm]['value_correct_single_response']+=int(len(responses)==1 and responses[0]['v']==target and not int(r['model_failure']))
        if len(responses)==1:
            latency=responses[0]['t']-e['t']; assert json.loads(r['latency'])==latency; latencies[arm].append(latency)
        query_cases[(g,r['setting'],arm)].append(flags['success']); per_episode[(g,r['setting'],arm,ep)].append(flags['success'])
    primary_case={}; pairs=collections.defaultdict(collections.Counter); replay_episodes=0; replay_steps=0
    for identity,r in recs.items():
        expected=sum(len(histories[(r['group'],setting_K[r['setting']],e)]) for e in range(4))
        assert len(query_cases[identity])==expected==r['queries']; assert sum(query_cases[identity])==r['correct']
        pair_success=[]
        for p in r['pairs']:
            assert len(p['queries'])==2
            assert p['queries'][0]['target']==-p['queries'][1]['target']
            results=[]
            for q in p['queries']:
                assert p['bank'][q['event']['a']]==q['target']
                flags=scored(q['event'],q['target'],q['score']['responses'],cfg['deadline'],r['failure'] is not None)
                for k,v in flags.items(): assert q['score'][k]==v
                results.append(flags['success']); pairs[r['arm']]['individual_correct']+=flags['success']; pairs[r['arm']]['individual_total']+=1
            both=int(all(results)); assert p['both']==both; pair_success.append(both); pairs[r['arm']]['pairs_both_correct']+=both; pairs[r['arm']]['pairs_total']+=1
        for ep in r['episodes']:
            scores=per_episode[identity+(ep['episode'],)]; assert ep['queries']==len(scores) and ep['correct']==sum(scores)
            if ep['replay'] and ep['replay']['pass_replay']:
                replay_episodes+=1; replay_steps+=ep['replay']['steps']
        ok=int(r['valid'] and r['failure'] is None and all(query_cases[identity]) and all(pair_success) and all(ep['replay'] and ep['replay']['pass_replay'] for ep in r['episodes']))
        assert r['case_success']==ok; primary_case[identity]=ok
    group_values=[]
    held=[s['id'] for s in cfg['settings'][1:]]
    for g in cfg['groups']:
        d=sum(primary_case[(g,s,'D')] for s in held)/7; b=sum(primary_case[(g,s,'B')] for s in held)/7
        row=dict(group=g,D=d,B=b,difference=d-b); assert row==summary['groups'][g]; group_values.append(row)
    primary={a:sum(g[a] for g in group_values)/12 for a in ('D','B')}; primary['difference']=primary['D']-primary['B']; assert primary==summary['primary']
    assert len(cases)==192 and summary['complete'] and len(recs)==192
    by_setting=[]; mutually=[]; all_success=collections.Counter()
    for setting in cfg['settings']:
        entry=dict(setting=setting['id'],L=setting['L'],K=setting['K'],p=setting['p'])
        for arm in ('D','B'):
            rr=[r for r in cases if r['setting']==setting['id'] and r['arm']==arm]
            entry[arm]=dict(cases=len(rr),successful=sum(r['case_success'] for r in rr),queries=sum(r['queries'] for r in rr),correct=sum(r['correct'] for r in rr))
            entry[arm]['query_latency']=summarize([float(row['latency']) for row in rows if row['setting']==setting['id'] and row['arm']==arm and row['latency']!=''])
            for metric in ('candidate_sites','peak_occupied','occupied_sites','active_execution_cells','unused_sites','selected_edges','wire_length','persistent_logical_bytes','routing_bytes','development_messages','development_ticks','global_builder_work'):
                entry[arm][metric]=summarize([r['cost'][metric] for r in rr if 'cost' in r])
        by_setting.append(entry)
    for g in cfg['groups']:
        for s in cfg['settings']:
            d,b=recs[(g,s['id'],'D')],recs[(g,s['id'],'B')]
            if d['case_success'] and b['case_success']: mutually.extend([d,b])
            all_success['pairs']+=1; all_success['mutually_successful']+=bool(d['case_success'] and b['case_success'])
    metrics={}
    for arm in ('D','B'):
        rr=[r for r in cases if r['arm']==arm]; cc=[r['cost'] for r in rr if 'cost' in r]; eps=[e for r in rr for e in r['episodes']]
        metrics[arm]=dict(cases=len(rr),successes=sum(r['case_success'] for r in rr),query_latency=summarize(latencies[arm]),
                         construction_cpu_seconds=sum(c['construction_cpu_seconds'] for c in cc),construction_wall_seconds=sum(c['construction_wall_seconds'] for c in cc),
                         execution_cpu_seconds=sum(e['execution_cpu_seconds'] or 0 for e in eps),execution_wall_seconds=sum(e['execution_wall_seconds'] or 0 for e in eps),
                         replay_cpu_seconds=sum(e['replay']['cpu_seconds'] if e['replay'] else 0 for e in eps),
                         task_messages=sum(e['messages'] or 0 for e in eps),task_payload_bytes=sum(e['payload_bytes'] or 0 for e in eps),
                         peak_queue=max(e['peak_queue'] or 0 for e in eps),peak_buffered_logical_bytes=max(e['peak_buffered_logical_bytes'] or 0 for e in eps),
                         peak_serialized_state_bytes=max(e['peak_serialized_state_bytes'] or 0 for e in eps))
        for field in ('candidate_sites','peak_occupied','active_execution_cells','unused_sites','selected_edges','wire_length','persistent_logical_bytes','routing_bytes','structure_bytes','development_messages','development_bytes','development_ticks','peak_total_development_state_bytes','global_builder_work'):
            metrics[arm][field]=summarize([c[field] for c in cc])
    hosts=load(ROOT/'results/hosts.json'); rejections=sum(len(h['rejections']) for h in hosts)
    topology_equal=sum(recs[(g,s['id'],'D')]['cost']['circuit_hash']==recs[(g,s['id'],'B')]['cost']['circuit_hash'] for g in cfg['groups'] for s in cfg['settings'])
    hash_agree=all(sha(ROOT/name)==value for name,value in load(ROOT/'freeze.json')['files'].items()); assert hash_agree
    preservation=load(ROOT/'preservation.json')
    originals_present=all((ROOT.parent/name).is_file() for name in preservation)
    preservation_match=all(sha(ROOT.parent/name)==value for name,value in preservation.items()) if originals_present else None
    if originals_present: assert preservation_match
    resources=dict(source_bytes={f.name:f.stat().st_size for f in ROOT.glob('*.py')},python=sys.version,
                   job_ledger=load(ROOT/'attempts.json'),recorded_incremental_job_cpu_seconds=sum(r['cpu_seconds'] for r in load(ROOT/'attempts.json')),
                   recorded_incremental_job_wall_seconds=sum(r['wall_seconds'] for r in load(ROOT/'attempts.json')),
                   measured_job_cpu_seconds=sum(r['cpu_seconds'] for r in load(ROOT/'attempts.json') if 'accounting' not in r),
                   measured_job_wall_seconds=sum(r['wall_seconds'] for r in load(ROOT/'attempts.json') if 'accounting' not in r),
                   peak_sampled_tree_rss_bytes=max(r['peak_sampled_tree_rss_bytes'] or 0 for r in load(ROOT/'attempts.json')),
                   prior_inclusive_combined_cpu_seconds=248.178326,prior_inclusive_combined_wall_seconds=214.930280,
                   setup_analysis_allowance_seconds=60,retained_study_bytes=sum(p.stat().st_size for p in ROOT.rglob('*') if p.is_file()),
                   note='First monitor failure charged 120 s conservatively, not measured. Remote publication CPU unmeasured. Whole-job interpreter/audit and monitor work included; ordinary model timings include trajectory audit. Compact field bytes differ from JSON and RSS.')
    analysis=dict(primary=primary,groups=group_values,settings=by_setting,ordinary=dict(counts),opposite_pairs=dict(pairs),metrics=metrics,
                  resource_comparison_scope='All 96/96 paired cases mutually successful; conditional cost coverage 100%, so recorded costs also cover all scheduled cases. No expensive failures were excluded.',
                  coverage=dict(all_success),identical_full_circuit_hashes=topology_equal,paired_hosts=96,admissibility_rejections=rejections,
                  host_count=len(hosts),accepted_candidate_site_counts=summarize([len(h['host']['positions']) for h in hosts]),
                  unavailable_site_counts=summarize([len(h['host']['unavailable']) for h in hosts]),
                  uncertainty='All 12 outer differences zero; no nondegenerate interval or powered equivalence/universal zero-uncertainty claim.',resources=resources)
    audit=dict(passed=True,independent_external_history_targets=True,unique_ordinary_query_rows=len(rows),replay_episode_records=replay_episodes,replay_delivery_boundary_steps=replay_steps,
               primary=primary,frozen_bytes_match=hash_agree,original_preservation_hashes_match=preservation_match,
               checked='Recomputed history targets, retention, each failure flag, all denominators, opposite targets/pairs, all case gates and group weights. Replay records checked; executable verification is separately the actual author rerun.',
               label='post-run author score reaggregation; not independent scientific reproduction',cpu_seconds=time.process_time()-cpu,wall_seconds=time.perf_counter()-wall)
    resources['score_audit_cpu_seconds']=audit['cpu_seconds']; resources['score_audit_wall_seconds']=audit['wall_seconds']
    resources['conservative_combined_cpu_seconds']=248.178326+60+resources['recorded_incremental_job_cpu_seconds']+audit['cpu_seconds']
    resources['conservative_combined_wall_seconds']=214.930280+60+resources['recorded_incremental_job_wall_seconds']+audit['wall_seconds']
    dump(ROOT/'analysis.json',analysis); dump(ROOT/'verification.json',audit); dump(ROOT/'resources.json',resources)
    with (ROOT/'aggregate.csv').open('w',newline='') as f:
        fields=['group','setting','arm','valid','case_success','queries','correct','pair_individual_correct','pairs_both_correct']; w=csv.DictWriter(f,fieldnames=fields); w.writeheader()
        for r in cases: w.writerow({k:r[k] for k in fields})
    print(json.dumps(audit)); print(json.dumps(metrics))
if __name__=='__main__': main()
