#!/usr/bin/env python3
"""最终汇总: 主结果 + 身份重判合并 → final_report.md"""
import json, time
from collections import defaultdict, Counter
from pathlib import Path

OUT = Path('/home/kai/shared/shenyuan-iframe-eval')
rows=[json.loads(l) for l in open(OUT/'results.jsonl') if l.strip()]
by_id={}
for r in rows:
    if 'verdict' in r: by_id[r['id']]=r
rechk={}
for l in open(OUT/'results_identity_recheck.jsonl'):
    if not l.strip(): continue
    r=json.loads(l)
    if 'recheck' in r: rechk[r['id']]=r['recheck']

slots=defaultdict(dict)
for rid,r in by_id.items():
    slot,variant=rid.rsplit('_v',1)
    v=dict(r['verdict'])
    if rid in rechk:
        v['identity_match']=rechk[rid].get('identity_match')
        v['identity_note']=rechk[rid].get('identity_note','')
    v['final']='fail'
    fs=[d for d in ['framing_ok','content_match','tone_ok','defects_ok'] if v.get(d) is False]
    if v['verdict']=='pass' and not fs: v['final']='pass'
    elif v['verdict']=='pass' and fs: v['final']='borderline'  # 身份修复后升
    elif v['verdict']=='borderline': v['final']='borderline'
    elif v['verdict']=='fail' and not fs and v.get('identity_match') is True: v['final']='pass*'
    slots[slot][variant]=v

L=[]
L.append("# 《她从深渊回来》条件帧评估报告 (90 variants)")
L.append("")
L.append(f"生成: {time.strftime('%Y-%m-%d %H:%M')} | 引擎: qwen-eye (Qwen3.8-27B VL q3) | 两段式判定+宴会服身份重判")
L.append("")
L.append("## 一、总览")
c=Counter(v['final'] for s in slots.values() for v in s.values())
L.append(f"- 90 帧: **pass {c['pass']} / pass\\* {c['pass*']} (身份误报修正后合格) / borderline {c['borderline']} / fail {c['fail']}**")
L.append(f"- pass\\* 说明: 首轮用日常装 turnaround 锚定导致身份误判, 经宴会服(tier1_shenzhiyi_banquet_v2)重判身份恢复一致, 且构图/内容/色调/画质全维合格")
L.append("")
L.append("## 二、per-slot 推荐 (30 slot)")
L.append("")
L.append("| slot | v1 | v2 | v3 | 推荐 |")
L.append("|---|---|---|---|---|")
recs={}
for slot in sorted(slots):
    vs=slots[slot]
    best=None
    for k in ['1','2','3']:
        v=vs.get(k)
        if not v: continue
        rank={'pass':4,'pass*':3,'borderline':2,'fail':1}[v['final']]
        key=(rank, v.get('score',0))
        if best is None or key>best[0]: best=(key,k,v)
    _,k,v=best
    if v['final'] in ('pass','pass*'):
        rec=f"✅ **v{k}**"
    elif v['final']=='borderline':
        rec=f"⚠️ v{k} (勉强)"
    else:
        rec="❌ 重生成"
    recs[slot]=(k,v)
    cells=[]
    for kk in ['1','2','3']:
        vv=vs.get(kk)
        mark={'pass':'✅','pass*':'✅*','borderline':'⚠️','fail':'❌'}[vv['final']] if vv else '—'
        cells.append(f"{mark}{vv.get('score','')}" if vv else '—')
    L.append(f"| {slot} | {cells[0]} | {cells[1]} | {cells[2]} | {rec} |")

L.append("")
L.append("## 三、可直接选定 (9)")
L.append("")
for slot,(k,v) in sorted(recs.items()):
    if v['final']=='pass':
        L.append(f"- **{slot}_v{k}** (score {v.get('score')}): {v.get('reasons','')[:100]}")
L.append("")
L.append("## 四、身份修正后可选定 (pass\\*, 20)")
L.append("")
for slot,(k,v) in sorted(recs.items()):
    if v['final']=='pass*':
        L.append(f"- **{slot}_v{k}** (score {v.get('score')}): {v.get('identity_note') or v.get('reasons','')[:90]}")
L.append("")
L.append("## 五、必须重生成 (真 fail)")
L.append("")
fails=[]
for slot,(k,v) in sorted(recs.items()):
    if v['final']=='fail':
        fs=[d for d in ['framing_ok','content_match','tone_ok','defects_ok','identity_match'] if v.get(d) is False]
        fails.append((slot,k,v,fs))
        L.append(f"- **{slot}** (best=v{k} score {v.get('score')}): {v.get('reasons','')[:110]}")
L.append("")
L.append(f"### fail 维度统计")
dim=Counter()
for _,_,v,fs in fails:
    for d in fs: dim[d]+=1
L.append("`"+"  ".join(f"{k}={v}" for k,v in dim.most_common())+"`")
L.append("")
L.append("## 六、系统性问题诊断")
L.append("")
L.append("1. **锚点文档缺陷 (根因, 影响评估与生成本身)**: shot prompt 的[角色一致性锚点]只写 `shenzhiyi（保持角色形象与装扮一致）`, 未指明本场景着装=宴会服(银白鱼尾裙)。评估首轮被日常装 turnaround 误导 (63 帧身份误报); 生成侧 dreamina i2i 的参考图也可能同样错配 → 建议 gen_iframes 的 identity hint 按 scene_ref 映射到 costume_turnaround")
L.append("2. **「前世闪回冷色调+破碎感」全局 style 头 vs 暖金场景描述互相矛盾**: 多帧因\"画面暖色调清晰无破碎\"被判 tone_ok=false 或 borderline。S01/S02 的 SPEC 头部都有这段全局样式, 但画面主体描述又是宴会厅暖金 — 生成模型自然选了主体描述。需要明确: 冷色调闪回只用于真正闪回镜头 (S01_B01), 其余删掉该段")
L.append("3. **构图/景别漂移**: framing_ok fail 42 帧中最常见=SPEC 要求 BCU/CU/INSERT 但生成中景/中近景; 面部位于上1/4的要求普遍未满足。dreamina i2i 对景别的文字控制力弱, 建议: 特写类镜头改用更近的参考图或裁剪后处理")
L.append("4. **道具缺失**: 红色丝绸/金笔/协议文件/控制台等 SPEC 指定道具在多帧缺失 (content_match fail)。dreamina 对小道具遵循率低, 关键道具镜头建议 composited 或二次 img2img 局部重绘")
L.append("")
L.append("## 附: 数据文件")
L.append(f"- 明细: `{OUT}/results.jsonl` (90 verdict) + `{OUT}/results_identity_recheck.jsonl` (81 身份重判)")
L.append(f"- 脚本: `{OUT}/eval_iframes.py` / `recheck_identity.py` / `summarize_final.py`")

(OUT/'final_report.md').write_text('\n'.join(L))
print(f"→ {OUT/'final_report.md'}")
print(f"pass={c['pass']} pass*={c['pass*']} borderline={c['borderline']} fail={c['fail']}")
print(f"可直接选定: {sum(1 for _,v in recs.values() if v['final']=='pass')} / 修正后可选: {sum(1 for _,v in recs.values() if v['final']=='pass*')} / 重生成: {sum(1 for _,v in recs.values() if v['final']=='fail')}")
