"""Package the comparison after the user's render hold. No engine invocation."""
import bootstrap
from pathlib import Path
import csv
import json
import shutil
from data_robust import OUT,read,sha,paths

decision=read(OUT/'user_decision.json')
assert decision['decision']=='HOLD_BATCH_RENDER_RECORD_COMPARISON_ONLY'
assert not list((OUT/'download').rglob('*.mp4'))
initial=read(OUT/'reports/probe_robust.json');weak=read(OUT/'reports/probe_robust_weak.json')
selected=weak['R4'];specs=read(OUT/'specs.json');sources=read(OUT/'sources.json')
unit=read(OUT/'reports/unit_tests.json');assert unit['status']=='PASS'
dest=OUT/'download';data=dest/'data';data.mkdir(exist_ok=True)
for n in ('candidates.json','user_decision.json','feedback.json','sources.json','baseline_fingerprints.json'):
    shutil.copy2(OUT/n,data/n)
for n in ('probe_robust.json','probe_robust_weak.json','unit_tests.json'):
    shutil.copy2(OUT/'reports'/n,data/n)
allvariants={**initial,**weak};table=[];audit=[]
for clip,spec in specs.items():
    fp,bp,src=paths(clip)
    d=selected['clips'][clip];labels=d['labels']
    assert sum(x['N'] for x in labels.values())==len(spec['selected_samples'])
    assert not d['new_hit_frames']
    p=labels.get('positive_reference',{});n=labels.get('negative_reference',{})
    base_p=p.get('base_hit',0);new_p=p.get('new_hit',0)
    assert base_p-new_p==len(d['lost_positive_frames'])
    table.append(dict(clip=clip,N=sum(x['N'] for x in labels.values()),observable=sum(x['observable'] for x in labels.values()),
        positive_N=p.get('N',0),positive_baseline=base_p,positive_candidate=new_p,
        positive_loss=base_p-new_p,loss_percent=None if not base_p else 100*(base_p-new_p)/base_p,
        negative_N=n.get('N',0),negative_baseline=n.get('base_hit',0),negative_candidate=n.get('new_hit',0),
        baseline_events=sum(x['base_events'] for x in labels.values()),candidate_events=sum(x['new_events'] for x in labels.values()),
        new_hit_frames=d['new_hit_frames'],new_negative_event_times=d['new_negative_events']))
    audit.append(dict(clip=clip,baseline_sha256=sha(bp),feature_sha256=sha(fp),displayed=len(spec['selected_samples'])))
frozen=read(OUT/'baseline_fingerprints.json')
assert all(sha(Path('/evidence')/p)==h for p,h in frozen.items())
maps=read(OUT/'self_maps.json')['SELF_L02'];mapped=[]
for m in maps:
    f=int(m['clip_frame_1based']);t=float(m['clip_pts_seconds'])
    mapped.append(dict(sample=int(m['sample']),part_id='L01_P02',l02_frame=f-2542,l01_frame=f,
        review_frame=int(m['review_frame_1based']),l01_pts=t,l02_pts=t-105.352669))
assert len(mapped)==7261 and mapped[0]['l02_frame']==1 and mapped[-1]['l02_frame']==17432
assert all(b['l02_pts']>a['l02_pts'] and b['l02_frame']>a['l02_frame'] for a,b in zip(mapped,mapped[1:]))
with (data/'L02_observation_map.csv').open('w',newline='') as f:
    w=csv.DictWriter(f,fieldnames=mapped[0]);w.writeheader();w.writerows(mapped)
legacy=[r for r in table if r['clip']!='SELF_L02'];long=next(r for r in table if r['clip']=='SELF_L02')
summary=dict(status='USER_HOLD_NO_VIDEOS',candidate='R4',clips=table,
    separate_31_positive=dict(baseline=sum(r['positive_baseline'] for r in legacy),candidate=sum(r['positive_candidate'] for r in legacy)),
    L02_positive=dict(baseline=long['positive_baseline'],candidate=long['positive_candidate']),
    expected_videos=32,produced_videos=0,all_new_red_observations=0,
    caveats=['Cxx/L02 overlap; never sum as independent cases','Reference labels are approximate, not exact GT',
             'Existing action_cases negative labels beyond new feedback are not newly confirmed false alarms',
             'L01 frame8133 Event moves to8143 within the same reported shoulder-embrace interval',
             'The candidate is not adopted; no engine/model/production deployment'])
(data/'comparison.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2))
feedback_table={c:r['feedback'] for c,r in selected['clips'].items() if r['feedback']}
(data/'feedback_windows.json').write_text(json.dumps(feedback_table,ensure_ascii=False,indent=2))
validation=dict(status='PASS',unit_tests=unit,inputs=len(table),observations=sum(r['N'] for r in table),
    baseline_and_feature_hashes=audit,frozen_sources_preserved=True,L02_mapping_observations=7261,
    new_current_hit_observations=0,rendering='NOT_RUN_USER_HOLD',model_inference=0,baseline_reexecution=0,
    videos=0,trial_helper_sha256={p.name:sha(p) for p in (OUT/'helpers').glob('*.py')})
(dest/'validation/technical_audit.json').write_text(json.dumps(validation,indent=2))
lines=['# 싸움 후보 R4 비교 기록','',
    '상태: 사용자 선택으로32개 영상 제작 보류. R4는 검토용이며 채택하지 않았다.',
    '기존31입력과 L02의 저장 특징으로 CPU 규칙 계산만 수행했다. 원본·cache·기준 엔진·운영 상태는 변경하지 않았다.',
    '', '## 변경 조건',
    '손목의 어깨 상대 거리가 몸 높이의0.12 미만이고, 속도1.0 몸높이/초 이하이며, 관측 변위0.10 몸높이 이하일 때 해당 손의 근거·seed/contact 갱신을 억제한다.',
    '사람 행동 분류나 싸움 확률이 아니다. bbox0.25·관절0.10, 공간/시간창, Event 정책은 유지했다.',
    '', '## 핵심 비교',
    '- 기존31입력의 감지 희망 참고 관측:226→214,12개 감소(5.3%).',
    '- L02 감지 희망 참고 관측:58→56,2개 감소(3.4%). 위31입력과 중첩되므로 합산하지 않는다.',
    '- 지정 Cxx 비감지 관측:10→2. C11 머리 정리의2개는 남는다.',
    '- L02 지정 비감지 관측:14→7. 어깨동무1, 양손 하이파이브2, 손잡고 동행2, 머리 정리2가 남는다.',
    '- 새로운 빨간 관측0. Event는 별도이며 L01 8133의발동이8143으로 이동한 경우가 있다. 해당 구간 Event 수는1→1이다.',
    '- 특히 C10의감지 희망5→3, C12의8→6을 임의로 허용하지 않고 사용자에게 보고했으며, 사용자는 제작 보류를 선택했다.',
    '', '## 입력별 결과',
    '비감지 열은 기존 action_cases와 최신 사용자 구간을 합친 참고 label이다. 기존C14 등의 label을 에이전트가 새로 오판 확정한 것은 아니다.',
    '| 입력 | 관측 | 특징 계산 가능 | 감지 희망 빨강 | 비감지 참고 빨강 | 새 시험 Event |',
    '| --- | ---: | ---: | --- | --- | --- |']
for r in table:
    lines.append(f"| {r['clip']} | {r['N']} | {r['observable']} | {r['positive_baseline']}→{r['positive_candidate']} | {r['negative_baseline']}→{r['negative_candidate']} | {r['baseline_events']}→{r['candidate_events']} |")
lines += ['', '## L02와 한계',
    'L02는 기존 L01의Frame2543~19975를 가리키는 논리 입력이다. 물리 클립이나 새 검토 영상은 아직 없다. P02의7261관측을 그대로 사용했다.',
    'L02 Frame=L01 Frame-2542. 실제L01 PTS와 L02 상대PTS, 기존 검토본 번호는 data/L02_observation_map.csv를 따른다.',
    'sample/10 대체, 재샘플링, 보간, 두 파트 연결, 모델/Pose/Tracking 재추론과 기준 엔진 재실행은 하지 않았다.',
    'Cxx와 L02는 선택Frame·인코딩·초기 상태가 다르다. 양쪽C17/C19 억제는 개선됐으나 C13/C16 등의 불일치는 남아 안정성 해결을 선언하지 않는다.',
    '통계는 정확도·재현율·운영 오알람률이 아니다. 추가 영상 생성, 파라미터 탐색과 운영 적용은 별도 요청 전 보류한다.',
    '', '## 파일',
    '- data/comparison.json:32입력의 기준/후보 수치와 변경 위치.',
    '- data/probe_robust*.json:초기3안·약한 움직임 한정2안의 비교 근거.',
    '- data/feedback_windows.json:최신 사용자 구간의 기준/후보 비교.',
    '- data/candidates.json:고정 R4 조건. data/user_decision.json:영상 제작 보류 선택.',
    '- validation/technical_audit.json:합성 검사·입력/특징 지문·L02 대응 검증.',
    '영상 디코딩·화면 검증은 사용자 제작 보류로 수행하지 않았다.']
(dest/'00_README_ko.md').write_text('\n'.join(lines)+'\n')
manifest={str(p.relative_to(dest)):sha(p) for p in sorted(dest.rglob('*')) if p.is_file() and p.name!='manifest.json'}
(dest/'manifest.json').write_text(json.dumps(manifest,indent=2))
print('COMPARISON_PACKAGE_PASS',len(table),sum(r['N'] for r in table),'observations',len(manifest),'files; videos=0',flush=True)
