#!/usr/bin/env python3
"""샷별 장소 3면 비교 갤러리 (프로젝트 데이터 / Claude / Codex) — 실험 전용, 커밋 금지"""
import json, html, os

BASE = os.path.dirname(os.path.abspath(__file__))
D = json.load(open(os.path.join(BASE, "shot_loc_data.json")))
CL = json.load(open(os.path.join(BASE, "claude_judgments.json")))
CX = json.load(open(os.path.join(BASE, "codex_judgments.json")))
OUT = os.path.join(BASE, "..", "shot_location_compare.html")

def esc(s): return html.escape(str(s if s is not None else ""))

# 판정 노트: key → (등급, 노트)  등급: ok/warn/bad
NOTES = {
"3-3": ("bad", "데이터 과단정 — 원문은 뉴스 몽타주 속 '씬1·2 현장 감식'으로 숲/골목 특정 불가인데 VE가 L01 숲으로 단정 (양측 판정 일치)"),
"8-5": ("bad", "배경 오배정 — 마트 '외부' 샷(VE도 L09 외부로 정확)에 매장 '내부' 장소(L08)의 배경 L08B02 첨부. L09 배경은 애초에 계획·생성되지 않음"),
"19-5": ("bad", "배경 오배정 — S8sh5와 동일 계열: 외벽 CCTV 샷에 내부 L08B03 첨부 (L09 배경 부재)"),
"11-5": ("warn", "이웃 공간 배경 — 샷은 옥상 위(VE L03 정확)인데 첨부 배경은 철제 계단(L11B01). L03 배경 3장이 존재하는데도 계단 배경 선택"),
"5-9": ("warn", "Claude↔Codex 판정차 — Claude=거실 식탁 부근 / Codex=현관(신발 신는 컷 이후). 원문 동선상 Codex가 더 충실. 데이터는 VE 공백+prev만"),
"25-9": ("warn", "Claude↔Codex 판정차 — 사진 쥔 손의 시점 해석(나가기 직전 vs 침대 주저앉음). 원문상 둘 다 가능"),
"27-3": ("warn", "장소 정보 전무 — 옥탑방 안 씬인데 VE 장소·배경 ref 모두 공백"),
"24-2": ("warn", "배경 부재 — 소나기 해안도로(L05) 배경이 미계획이라 ref 없이 생성 (VE 공백)"),
"15-2": ("warn", "VE 장소·배경 ref 모두 공백 — 정류장(L12) 배경 미계획"),
"12-18": ("warn", "VE 장소·배경 ref 모두 공백 (타이트 샷이라 영향 제한적)"),
"18-15": ("warn", "VE 공백 — 헤딩은 '안방'이나 원문·양측 판정은 거실 식탁 (씬 내 공간 이동을 헤딩이 못 담음)"),
"18-16": ("warn", "VE 공백 — 거실 (prev로만 연결)"),
"7-6": ("warn", "VE 장소·배경 ref 모두 공백"),
"19-6": ("warn", "VE 장소·배경 ref 모두 공백"),
"29-4": ("warn", "VE 공백·배경 없음 — 조타실 (prev 없이 텍스트만)"),
"29-8": ("warn", "VE 공백·배경 없음 — 조타실"),
}

rows = []
stats = {"ok":0, "warn":0, "bad":0}
for s in D["shots"]:
    k = f"{s['scene']}-{s['shot']}"
    grade, note = NOTES.get(k, ("ok", ""))
    stats[grade] += 1
    ve = "<br>".join(esc(v) for v in s["ve_locs"]) or '<span class=miss>VE 장소 없음</span>'
    bg = "<br>".join(esc(b) for b in s["bg_refs"]) or '<span class=miss>배경 ref 없음</span>'
    head = esc(s.get("heading") or "")
    rows.append(
        f"<tr class=g-{grade}><td class=sid>S{s['scene']}sh{s['shot']}</td>"
        f"<td class=desc>{esc(s['desc'])}</td>"
        f"<td><span class=hd>{head}</span><br>{ve}<hr class=mini>{bg}</td>"
        f"<td>{esc(CL.get(k,''))}</td>"
        f"<td>{esc(CX.get(k,''))}</td>"
        f"<td class=note>{esc(note) or '정합'}</td></tr>")
table = "\n".join(rows)

HTML = f"""<!DOCTYPE html>
<html lang=ko><head><meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>샷별 장소 3면 비교 — 프로젝트 데이터 / Claude / Codex</title>
<style>
:root {{ color-scheme: dark; }}
body {{ margin:0; padding:24px; background:#0f1216; color:#e6e6e6; font:14px/1.6 -apple-system,'Apple SD Gothic Neo',sans-serif; }}
h1 {{ font-size:20px; }} h2 {{ font-size:17px; margin-top:36px; border-bottom:1px solid #333; padding-bottom:6px; }}
table {{ border-collapse:collapse; width:100%; margin:12px 0; }}
th, td {{ border:1px solid #2a2f36; padding:6px 9px; text-align:left; vertical-align:top; font-size:13px; }}
th {{ background:#1c2128; position:sticky; top:0; z-index:2; }}
td.sid {{ font-family:ui-monospace,monospace; white-space:nowrap; font-weight:700; }}
td.desc {{ color:#b9c2cc; min-width:200px; }}
td.note {{ min-width:200px; color:#d5c6a1; }}
.hd {{ color:#8a939e; font-size:11.5px; }}
.miss {{ color:#ff9d9d; font-size:12px; }}
hr.mini {{ border:0; border-top:1px dashed #2a2f36; margin:4px 0; }}
tr.g-bad td {{ background:#33171a; }}
tr.g-warn td {{ background:#2e2712; }}
.box {{ background:#161b22; border:1px solid #2a2f36; border-radius:8px; padding:12px 16px; margin:12px 0; }}
.badge {{ display:inline-block; padding:2px 10px; border-radius:10px; font-size:12px; margin-right:8px; }}
.b-ok {{ background:#14432a; color:#7ee2a8; }} .b-warn {{ background:#4a3d17; color:#ffd76e; }} .b-bad {{ background:#5a1f24; color:#ff9d9d; }}
</style></head><body>
<h1>샷별 장소 3면 비교 — 금월도 5회차(8ad94c03) 선택 샷 58개</h1>
<div class=box>
<b>비교 방법</b>: ①<b>프로젝트 현재 데이터</b> = 씬 헤딩 + VE 장소 배정(scene_director 확정) + 스틸에 실제 첨부된 배경 ref(actual_attached_refs) ②<b>Claude</b> = 씬 원본 30개 전문 정독 후 독립 판정 ③<b>Codex(GPT-5.6)</b> = 동일 과제 독립 수행(서로 판정 미공유, 원문 근거만).<br>
<span class="badge b-ok">정합 {stats['ok']}</span><span class="badge b-warn">주의 {stats['warn']}</span><span class="badge b-bad">불일치·결함 {stats['bad']}</span>
</div>

<div class=box style="border-left:4px solid #ff9d9d">
<h3 style="margin-top:0">핵심 발견 (Claude 종합)</h3>
<ul>
<li><b>Claude↔Codex 판정은 58건 중 56건 실질 일치</b> — 씬 원문 기준 장소 판정은 안정적. 차이 2건(S5sh9·S25sh9)은 씬 내 마이크로 위치 해석차이며, S5sh9는 Codex(현관)가 원문 동선에 더 충실.</li>
<li><b>배경 오배정 2건 (구조 결함)</b>: 마트 '외부' 샷 S8sh5·S19sh5에 매장 '내부' 배경(L08B02/B03)이 첨부됨 — VE는 L09(외부)로 정확한데, <u>L09 배경이 master_plan에서 아예 계획되지 않아</u> 인접 장소 배경이 대신 붙은 것. 실내 배경이 야외 샷의 룩을 오염시킬 수 있는 지점.</li>
<li><b>이웃 공간 배경 1건</b>: S11sh5(옥상 위)에 철제 계단 배경(L11B01) 첨부 — L03 배경 3장이 있는데도 계단 배경 선택.</li>
<li><b>데이터 과단정 1건</b>: S3sh3 — 원문은 뉴스 몽타주 속 감식 현장(숲/골목 특정 불가)인데 VE가 L01 숲으로 단정. 양측 판정 모두 "특정 불가".</li>
<li><b>배경 미계획 장소 8곳</b>: L01 숲·L02 골목·L05 해안도로·L06 보호원·L09 마트외부·L12 정류장·L16 PC방·L17 포구 — 배경 그림이 아예 없어 해당 샷들은 prev/텍스트만으로 생성. 특히 <b>S24sh2(소나기 해안도로)·S15sh2(정류장)·S26 포구 계열</b>은 장소 룩의 SOT가 없는 상태.</li>
<li><b>VE 장소 공백 22건</b> (그중 배경 ref도 없는 완전 공백 9건) — 대부분 타이트 샷이라 영향 제한적이나, S27sh3(옥탑방 안)·S29sh4/8(조타실)은 공간 정보 전무.</li>
</ul>
</div>

<h2>샷별 3면 비교표 (빨강 = 불일치·결함 / 노랑 = 주의·공백)</h2>
<table>
<tr><th>샷</th><th>샷 설명 (참고)</th><th>① 프로젝트 현재 데이터<br><span style="font-weight:400">헤딩 · VE 장소 / 첨부 배경</span></th><th>② Claude 판정 (씬 원본 정독)</th><th>③ Codex(GPT) 판정 (독립)</th><th>일치 분석 (Claude)</th></tr>
{table}
</table>
<p style="color:#8a939e;font-size:12px">생성 2026-07-11 · 데이터: scene_still(VE·is_selected)+image_asset.pipeline_metadata_json(actual_attached_refs)+scene_save 체크포인트 원문 · 빌더 scratchpad/forest_exp/pipeline_doc/build_loc_compare.py</p>
</body></html>"""

with open(OUT, "w") as f:
    f.write(HTML)
print("written", os.path.abspath(OUT), len(HTML), stats)
