#!/usr/bin/env python3
"""W2 主形态 140 衍生资产审核页 — 表情6 + 姿态4 + KV2, vision预筛标注, 8098优良中差"""
import os, shutil, time

ROOT = "/home/kai/shared/virtual-idol/week1"
SRC = f"{ROOT}/W2_persona/derivatives"
PAGE_DIR = f"{ROOT}/review/w2_deriv_140_1025"
os.makedirs(PAGE_DIR, exist_ok=True)

shutil.copy(f"{ROOT}/W2_persona/main_140_anchor_front.png", f"{PAGE_DIR}/anchor140.png")
FILES = [f for f in os.listdir(SRC) if f.endswith('.png') and not f.startswith('_m')]
for f in FILES:
    shutil.copy(f"{SRC}/{f}", f"{PAGE_DIR}/{f}")

PRE = {  # vision 预筛结论
 "expr1_neutral": ("优", "平静≈0漂移, 基准格"),
 "expr2_smile":   ("优", "微笑自然, 粒子带稳定"),
 "expr3_singing": ("良", "演唱情绪到位, 口内细节略糊"),
 "expr4_wink":    ("优", "单眼闭合控制精准"),
 "expr5_away":    ("良", "侧目命中, 忧郁强度偏淡"),
 "expr6_laugh":   ("优", "大笑牙齿控制良好"),
 "pose1_mic":     ("优", "舞台顶光+全息麦, 全身含脚"),
 "pose2_twirl":   ("优", "旋转动态最佳, 无缺陷"),
 "pose3_sit_stars":("良","星空坐姿氛围强, 单脚被裙纱吞"),
 "pose4_back":    ("良", "背面粒子逆升, 手部含糊可inpaint"),
 "kv1_dome_wide": ("优", "主封面候选, 手部待放大复检"),
 "kv2_close_glow":("优", "副KV特写, 粒子发弱化(严卡则良)"),
}
GROUPS = [
    ("表情差分 ×6", ["expr1_neutral","expr2_smile","expr3_singing","expr4_wink","expr5_away","expr6_laugh"]),
    ("姿态差分 ×4", ["pose1_mic","pose2_twirl","pose3_sit_stars","pose4_back"]),
    ("主视觉 KV ×2", ["kv1_dome_wide","kv2_close_glow"]),
]

cards_all = ""
for title, ids in GROUPS:
    cards = ""
    for iid in ids:
        g, note = PRE[iid]
        color = {"优":"#3fb950","良":"#d29922","中":"#f85149"}[g]
        cards += f'''<div class="card" data-slot="{iid}">
      <div class="slothead">{iid} <b style="color:{color}">[{g}]</b></div>
      <img src="{iid}.png" loading="lazy">
      <div class="note">{note}</div>
      <div class="vote">{''.join(f'<label><input type="radio" name="{iid}" value="{v}">{v}</label>' for v in ("优","良","中","差"))}</div>
    </div>'''
    cards_all += f'<h2>{title}</h2><div class="grid">{cards}</div>'

html = f'''<!DOCTYPE html><html lang="zh"><head><meta charset="utf-8">
<title>W2 主形态140 衍生资产 · 1025</title><style>
body{{background:#0d1117;color:#e6edf3;font-family:system-ui;margin:0;padding:20px}}
h1{{font-size:20px}} h2{{font-size:16px;margin:26px 0 10px;border-left:4px solid #58a6ff;padding-left:10px}}
.meta{{color:#8b949e;font-size:13px;margin:6px 0}}
.grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:14px}}
.kvgrid{{grid-template-columns:repeat(auto-fill,minmax(420px,1fr))}}
.card{{background:#161b22;border:1px solid #30363d;border-radius:10px;padding:10px}}
.card.ref{{border:1px dashed #d29922}}
.slothead{{font-size:13px;color:#8b949e;margin-bottom:6px}}
.card img{{width:100%;border-radius:6px;display:block}}
.note{{font-size:12px;color:#8b949e;margin-top:5px}}
.vote{{margin-top:6px;display:flex;gap:8px;font-size:14px}}
.vote label{{cursor:pointer}}
#bar{{position:sticky;top:0;background:#0d1117e0;padding:8px 12px;border-bottom:1px solid #30363d;z-index:9;font-size:14px}}
</style></head><body>
<div id="bar"><b>W2 主形态 140 衍生资产</b> · 12 件（表情6/姿态4/KV2）· vision预筛标注 · 投票自动入箱 <span id="vbstat"></span></div>
<div class="meta">身份源 = anchor_140（fd=20.3）。预筛结论: 12/12 无废格无身份漂移; 待你终审定入库档。{time.strftime('%Y-%m-%d %H:%M')}</div>
<h2>◈ 身份锚参照（禁评）</h2>
<div class="grid"><div class="card ref"><div class="slothead">anchor_140 Col2 正面锚</div><img src="anchor140.png"></div></div>
{cards_all}
<script>
const VB_URL='http://100.124.72.88:8098/report', VB_PAGE='w2_deriv_140_1025';
let VB_TIMER;
function reportBox(){{try{{const votes={{}},notes={{}};
document.querySelectorAll('input[type=radio]:checked').forEach(r=>{{votes[r.name]=r.value;}});
fetch(VB_URL,{{method:'POST',headers:{{'Content-Type':'application/json'}},
body:JSON.stringify({{page:VB_PAGE,votes:votes,notes:notes,client_ts:Date.now()}})}})
.then(r=>r.json()).then(j=>{{const el=document.getElementById('vbstat');if(el)el.textContent=j.ok?'✓ 已自动入箱':'⚠ 上报失败';}})
.catch(()=>{{const el=document.getElementById('vbstat');if(el)el.textContent='⚠ 上报失败';}});}}catch(e){{}}}}
document.querySelectorAll('input[type=radio]').forEach(r=>r.addEventListener('change',()=>{{clearTimeout(VB_TIMER);VB_TIMER=setTimeout(reportBox,400);}}));
</script></body></html>'''
out = f"{PAGE_DIR}/w2_deriv_140_review.html"
open(out, "w").write(html)

import urllib.request, json as J
imgs=[f for f in os.listdir(PAGE_DIR) if f.endswith('.png')]
missing=[f for f in imgs if f not in html]
ok=0
for f in imgs:
    try:
        r=urllib.request.urlopen(f'http://127.0.0.1:8082/virtual-idol/week1/review/w2_deriv_140_1025/{f}',timeout=5)
        ok+= 1 if r.status==200 else 0
    except Exception: pass
funnel=urllib.request.urlopen('https://kais-engine.taile7d1c8.ts.net/virtual-idol/week1/review/w2_deriv_140_1025/w2_deriv_140_review.html',timeout=8).status
print(J.dumps({"page":out,"imgs":len(imgs),"missing":missing or "NONE","http":f"{ok}/{len(imgs)}","funnel":funnel}))
