import collections,csv,json,statistics
from pathlib import Path
ROOT=Path(__file__).parent
samples=[json.loads(x) for x in (ROOT/'a13/samples.jsonl').read_text().splitlines()]
a,b=samples[0],samples[-1];seconds=b['t']-a['t'];lanes=[]
for camera,r in b['channels'].items():
    for provider,v in r['providers'].items():
        old=a['channels'][camera]['providers'][provider];x,y=old['metadata_path'],v['metadata_path'];u,w=old['publisher_metrics'],v['publisher_metrics']
        row=dict(camera=int(camera),provider=provider,seconds=seconds,
            completed_fps=(y['providers'][provider]['responses']-x['providers'][provider]['responses'])/seconds,
            processed_fps=(y.get('finalized',0)-x.get('finalized',0))/seconds,
            redis_fps=(w.get('sent_frames',0)-u.get('sent_frames',0))/seconds,
            queue_expired=y.get('completed_queue_expired',0)-x.get('completed_queue_expired',0),
            queue_drop=y.get('completed_queue_drop',0)-x.get('completed_queue_drop',0),
            sender_replaced=w.get('replaced',0)-u.get('replaced',0),sender_expired=w.get('expired',0)-u.get('expired',0),
            sender_error=w.get('send_error',0)-u.get('send_error',0),
            events=y.get('events_created',0)-x.get('events_created',0))
        lanes.append(row)
models=dict(pose='RFDETRKeypointPreview',fire='RFDETRFireSmoke',crowd='Awiros_person_and_head_detection')
summary=[]
for p,m in models.items():
    group=[x for x in lanes if x['provider']==p]
    x=a['resources']['triton'][m];y=b['resources']['triton'][m]
    bx={int(i['batch_size']):int(i['compute_infer']['count']) for i in x['batch_stats']}
    batch={int(i['batch_size']):int(i['compute_infer']['count'])-bx.get(int(i['batch_size']),0) for i in y['batch_stats']}
    timing={}
    for key,value in y['inference_stats'].items():
        old=x['inference_stats'][key];n=int(value['count'])-int(old['count']);ns=int(value['ns'])-int(old['ns'])
        timing[key]=dict(count=n,mean_ms=ns/n/1e6 if n else None)
    row=dict(provider=p,**{k:statistics.mean(x[k] for x in group) for k in ('completed_fps','processed_fps','redis_fps')},
        redis_min=min(x['redis_fps'] for x in group),redis_std=statistics.pstdev(x['redis_fps'] for x in group),
        **{k:sum(x[k] for x in group) for k in ('queue_expired','queue_drop','sender_replaced','sender_expired','sender_error','events')},
        batch_executions=batch,triton=timing)
    row['reference_pass']=row['redis_fps']>=9.5 and row['redis_min']>=9
    summary.append(row)
x=list(map(int,a['resources']['host_cpu'].split()[1:9]));y=list(map(int,b['resources']['host_cpu'].split()[1:9]));d=[v-u for u,v in zip(x,y)]
resources=dict(cpu_mean_percent=100*(sum(d)-d[3]-d[4])/sum(d),gpu_mean_percent=statistics.mean(float(s['gpu'].split(',')[0]) for s in samples),
    gpu_memory_max_mib=max(float(s['gpu'].split(',')[2]) for s in samples),renderer_max_sampled=max(s['artifacts'].get('rendering',0) for s in samples),
    sample_collection_max_s=max(s['finished']-s['t'] for s in samples))
observations=[json.loads(line) for line in (ROOT/'a13/redis.jsonl').read_text().splitlines()]
points=collections.defaultdict(list)
for r in observations:
    if r['kind']=='frame':points[(r['camera'],r['provider'])].append(r)
redis=[]
for (camera,p),rows in points.items():
    assert all(x['required']==['pose','fire','crowd'] and x['model']==models[p] for x in rows)
    ordered=all(y['seq']>x['seq'] and y['pts']>x['pts'] and y['run']==x['run'] for x,y in zip(rows,rows[1:]))
    redis.append(dict(camera=camera,provider=p,messages=len(rows),ordered=ordered,
        max_gap_ms=max((int(y['redis_id'].split('-')[0])-int(x['redis_id'].split('-')[0]) for x,y in zip(rows,rows[1:])),default=0),
        states=dict(collections.Counter(z['status'] for x in rows for z in (x.get('regions') or [])))))
assert len(redis)==39 and all(x['ordered'] for x in redis)
prior=json.loads(Path('/tmp/codex/ltb1021/2026-09-27_icerti-ac-scale_r01/comparison-sot.json').read_text())
historical=[{k:r[k] for k in ('provider','complete_fps','finalized_fps','redis_fps','redis_min','cpu_busy_pct','gpu_util_pct')} for r in prior['summary'] if r['mode']=='mixed-a' and r['level']==13]
result=dict(seconds=seconds,summary=summary,resources=resources,lanes=lanes,redis=redis,historical_a13=historical,
    limits=['Single fresh-start A13; not an incremental curve or repeated A/B.',
            'Crowd compressed cache/Event path added; natural input/Event phase differs from historical A13.',
            'Redis reader is a separate approximately40-second window; FPS table uses application counters over the measured interval.',
            'Queue restoration normalization errors were outside measurement; recovery is recorded separately.'])
with (ROOT/'measurement-sot.json').open('x') as f:json.dump(result,f,indent=2)
with (ROOT/'fps.csv').open('x',newline='') as f:
    writer=csv.DictWriter(f,fieldnames=list(lanes[0]));writer.writeheader();writer.writerows(lanes)
print(json.dumps(dict(seconds=seconds,summary=summary,resources=resources,historical_a13=historical),indent=2))
