"""Evaluate exactly one additive candidate against stored R1, with mock policies."""
import json,sys,os,time,copy
from collections import Counter
from pathlib import Path
sys.path.insert(0,'/evidence/2026-11-01_contextra-fall-spatial-28clips_r01/helpers')
from common import OUT,read,save,sha
from fall_candidate import SpatialState
from depth_candidate import decide
FROZEN=Path('/evidence/2026-11-01_contextra-fall-spatial-28clips_r01')
FOCUS=['11_KISA_fall_C00_001_0018','12_KISA_fall_C00_010_0004','15_KISA_fall_C00_096_0008']+['SELF_'+x for x in ('C01','C08','C09','C13','C15','C18','C19','C20','C21')]

def evaluate(clip):
 start=time.monotonic();config=read(OUT/'conditions.json');policy=read(FROZEN/'conditions.json')
 work=OUT/'work'/clip;dest=OUT/'download'/clip/'data';dest.mkdir(parents=True,exist_ok=True)
 validation=read(work/'features_validation.json');assert sha(work/'features.jsonl')==validation['trace_sha256']
 if (dest/'summary.json').exists():
  old=read(dest/'summary.json');assert old['conditions_sha256']==sha(OUT/'conditions.json') and old['diagnostics_sha256']==sha(dest/'R2_diagnostics.jsonl');print('REUSE_R2',clip);return old
 state=SpatialState(policy,clip+'_R2');counts=Counter();frame_lists={k:[] for k in ('r1','r2','added','removed','r1_events','r2_events','new_negative')}
 spans={};open_span={};last=None;origin=None;rows_seen=0
 with (work/'features.jsonl').open() as source,(dest/'R2_diagnostics.jsonl').open('x') as output:
  for line in source:
   r=json.loads(line);rows_seen+=1
   if origin is None:origin=r['seconds']
   assert last is None or r['seconds']>last;last=r['seconds']
   decisions=[decide(p,x,config) for p,x in zip(r['people'],r['extra'])]
   people=[dict(p,hit=d['hit'],rule=p['rule'] if p['hit'] else '+'.join(d['added_rules']) or None) for p,d in zip(r['people'],decisions)]
   result=state.evaluate(r['seconds'],people,monotonic_now=r['seconds']-origin)
   assert all(not p['hit'] or d['hit'] for p,d in zip(r['people'],decisions))
   assert not r['r1']['hit'] or result['hit']
   record=dict(r,r2=result,additional=decisions)
   output.write(json.dumps(record,ensure_ascii=False,separators=(',',':'),allow_nan=False)+'\n')
   if not r['displayed']:continue
   a=r['r1']['hit'];b=result['hit'];label=r['annotation']['reference'];point=dict(sample=r['sample'],source_frame=r['source_frame'],seconds=r['seconds'],l02_frame=r['l02_frame'],part_id=r['part_id'],review_frame=r['annotation']['review_frame'],reference=label)
   for k,v in [('observations',1),('observable',result['observable']),('r1',a),('r2',b),('added',b and not a),('removed',a and not b),('r1_events',bool(r['r1']['event'])),('r2_events',bool(result['event'])),('r1_hold_only',r['r1']['display_hold']),('r2_hold_only',result['display_hold']),('extra_available_persons',sum(x['available'] for x in r['extra']))]:
    counts[k]+=v
    if k in ('observations','observable','r1','r2','added','removed'):counts[label+'_'+k]+=v
   for key,hit in [('r1',a),('r2',b),('added',b and not a),('removed',a and not b),('r1_events',bool(r['r1']['event'])),('r2_events',bool(result['event'])),('new_negative',label=='negative_reference' and b and not a)]:
    if hit:frame_lists[key].append(point)
   for rule in ('D1_COMPACT_EXTENDED','D2_REVERSED_ORDER'):
    firing=any(rule in d['added_rules'] for d in decisions)
    counts['rule_'+rule]+=firing;counts[label+'_rule_'+rule]+=firing
   for key,truth in [('r1',a),('r2',b),('new_negative',label=='negative_reference' and b and not a)]:
    if truth:
     if key not in open_span:open_span[key]=dict(first=point,last=point,observations=1)
     else:open_span[key]['last']=point;open_span[key]['observations']+=1
    elif key in open_span:spans.setdefault(key,[]).append(open_span.pop(key))
 for key,s in open_span.items():spans.setdefault(key,[]).append(s)
 for values in spans.values():
  for s in values:s['first_to_last_pts_seconds']=s['last']['seconds']-s['first']['seconds']
 save(dest/'frame_lists.json',frame_lists);save(dest/'condition_runs.json',spans)
 result=dict(status='PASS',clip=clip,counts=dict(counts),input_observations=rows_seen,
  baseline_source='new frozen R1 on independent P02' if clip=='SELF_L02' else 'saved checked R1 diagnostics',
  r1_subset=True,conditions_sha256=sha(OUT/'conditions.json'),features_sha256=validation['trace_sha256'],
  diagnostics_sha256=sha(dest/'R2_diagnostics.jsonl'),elapsed_seconds=time.monotonic()-start,
  rule_uses_names_frames_labels=False,notes='Reference intervals are approximate; Event shifts are not current-condition losses')
 save(dest/'summary.json',result)
 print('R2_PASS',clip,json.dumps(result['counts']),flush=True);return result

if __name__=='__main__':
 arg=os.environ['CLIP'];names=FOCUS if arg=='focus' else list(read(OUT/'specs.json')) if arg=='all' else [arg]
 for clip in names:evaluate(clip)
