"""Publish comparison evidence; renderer remains gated by the user's decision."""
from pathlib import Path
import shutil
from common import OUT,read,save,sha

root=OUT/'download';comparison=read(root/'comparison.json');summary=read(root/'validation/diagnostic_validation.json')
assert summary['status']=='PASS' and summary['displayed_observations']==9981
proposed=['03_camera27_fall','13_KISA_fall_C00_034_0005','14_KISA_fall_C00_051_0002','15_KISA_fall_C00_096_0008','SELF_C08','SELF_C09','SELF_C13','SELF_C19']
save(root/'review_status.json',dict(status='AWAITING_USER_RENDER_DECISION',candidate='FALL_SPATIAL_R1',
    requested_videos=8,completed_videos=0,proposed_inputs=proposed,
    reason='Meaningful detection-reference losses in KISA13/15; stop before batch rendering per user contract',
    source_of_acceptance='User decision, never automatic threshold or allowed-loss selection'))
lines=['# FALL 공간 후보 R1 비교 자료','',
    '현재는 비교 기록입니다. 대표8영상은 손실 절충에 대한 사용자 선택 대기이며 아직 제작하지 않았습니다.',
    '운영 코드/설정 변경, 모델/Pose 추론, 실제 Event 전송, 배포는 하지 않았습니다.','',
    '## 먼저 볼 결과',
    '- 28입력 9,981표시관측. camera27+KISA5와 자체촬영C01~C22를 사용했습니다. L02는 제외했습니다.',
    '- 같은 score/Tracking 기준에서 자체촬영 비감지 참고는806→0관측입니다. 행동 본문729→0,context77→0입니다.',
    '- 같은 검토Frame 중복58개를 제거한 union은800→0/7,002Frame입니다. 인접 행동과 중첩 사례가 독립 사례라는 뜻은 아닙니다.',
    '- 쓰러짐 참고 구간은499→487/1,914관측입니다. 기존22개 제거·10개 추가, 순감소12개(기준 대비약2.4%)입니다.',
    '- 새로 더해진 관측이 손실을 상쇄하므로 순감소만으로 판단하지 마세요. 13·15번의 개별 손실이 큽니다.','',
    '| 입력 | 기준 현재 충족 | 후보 현재 충족 | 제거 | 추가 |',
    '| --- | ---: | ---: | ---: | ---: |']
for item in comparison['inputs']:
    if item['input'].startswith('SELF_'):continue
    lines.append(f"| {item['input']} | {item['positive_baseline']} | {item['positive_candidate']} | {item['positive_removed']} | {item['positive_added']} |")
lines += ['',
    '11·12는 사람이 없었다는 뜻이 아닙니다. 해당 참고 구간에서유효 사람 관측572/610·613/619가 있었지만 현재cache의 몸통각도50도 조건을 모두 통과하지 않았습니다. 영상 의미나 Pose 정확도를 독립 판정한 결과가 아닙니다.',
    '13은 기존 충족 제거8개 중 하반신 방향 조건7·서기지지 억제1입니다. 15는 제거14개 중 서기지지11·하반신1·기존 temporal 확인과 새 현재몸통 조건 차이2입니다.',
    '관찰 anchor는 대략적입니다. 통계를 정확도·재현율·운영 오알람률로 부르지 않습니다. 다인14의 ROI 충족은5명 전원 감지가 아닙니다.','',
    '## 비교를 읽는 법',
    '- site_scores: 읽기 확인한camera27 FALL 사람0.15/관절0.30/몸통50도/direct-down을 오프라인 입력에 적용한 참고입니다. 실제 라이브 결과를 재현했다고 주장하지 않습니다.',
    '- matched_scores: 사람0.25/관절0.10으로 맞춘 기존 core입니다. 후보와 같은 입력/Tracking을 사용합니다.',
    '- stored10_matched_scores: 원래 저장T10을 쓰는 기준입니다. Tracker와 규칙 차이를 분리하기 위한 항목입니다.',
    '- 기준 current_hit는 현재 direct-down 조건 또는 그 관측의 새 temporal 확정입니다. latched 표시/새Event와 다릅니다.',
    '- 후보는 현재 사람별 몸통/하체 근거를 ROI로 통합하며 T가 없다고 차단하지 않습니다. 회복 분류 없이 실제 충족 뒤2초 재무장·10초mock cooldown을 사용합니다.',
    '- 26입력은보존된15,827개 후보의Tracking10 ID를 완전 재현한 뒤Tracking30을 파생했습니다. camera27·14는 구형cache의T10을 유지했습니다.',
    '- 숫자가 달라진T827칸은 정확도나ID switch 횟수가 아닙니다. 재계산 실행의 번호는 별개이며 현장과 동일 결과 보장도 아닙니다.',
    '- 새 후보의 자세 판정은T값에 의존하지 않습니다. 기존core는C21에서T10→30에 따라현재 충족21→22이고 다른 입력의 집계는 같았습니다.','',
    '## 파일 안내',
    '- input_comparison.csv / comparison.json: 입력별 비교와 분모, 중복 구분.',
    '- case_comparison.json: 기존 사용자 사례ID 연결. 단일anchor는 정확한 구간으로 확장하지 않았습니다.',
    '- 입력별data/diagnostics_checked.jsonl: Frame/PTS·원시특징·현재조건·표시·Event·T/q를 연결한 검증본.',
    '- data/diagnostics.jsonl은 원시 실행 evidence입니다. baseline.people.query가 당시 출력순 슬롯을 뜻하므로 검토에는 checked 파일을 사용하세요. checked는raw_order와원래q를 분리했고 판정 수치는 바꾸지 않았습니다.',
    '- data/frame_lists.json / technical_validation.json / tracking_changes.json: 변경 위치·기술검증·Tracking 참고.',
    '- conditions.json / DESIGN.ko.md / candidate_source: 공통 시험식과 코드. 추가 파라미터 탐색·채택 없음.',
    '- validation: 합성24검사 및 전체진단 감사. 영상 디코딩·가독성 검사는 영상 미생성으로 미실시입니다.',
    '무거운RGB/Pose cache·가중치·인증정보는 포함하지 않았습니다. 원본/cache는 원위치에 보존했습니다.','']
(root/'00_README_ko.md').write_text('\n'.join(lines))
code=root/'candidate_source';code.mkdir(exist_ok=True)
for name in ('fall_candidate.py','test_candidate.py'):
    shutil.copy2(OUT/'helpers'/name,code/name)
fingerprints={str(p.relative_to(OUT)):sha(p) for p in sorted((OUT/'helpers').glob('*.py'))}
save(root/'validation/helper_fingerprints.json',fingerprints)
save(root/'sha256_manifest.json',{str(p.relative_to(root)):sha(p) for p in sorted(root.rglob('*')) if p.is_file() and p.name!='sha256_manifest.json'})
print('COMPARISON_PACKAGE_PASS',len(read(root/'sha256_manifest.json')),flush=True)
