#!/usr/bin/env python3
"""T2 首批 20 张批量驱动 — qwen21 896x1344, 双卡并发 2 (threading + UGQ 由 KAP 路由).
矩阵: T2_matrix/matrix_300.csv (真源); ledger: manifest/t2_ledger.jsonl
"""
import json, time, os, threading, queue as tq, urllib.request

ROOT = "/home/kai/shared/virtual-idol/week1"
MD = f"{ROOT}/T2_matrix"
OUT = f"{MD}/first20"
os.makedirs(OUT, exist_ok=True)
os.chdir(ROOT)
LEDGER = open("manifest/t2_ledger.jsonl", "a")

rows = {int(r["combo_id"]): r for r in
        __import__("csv").DictReader(open(f"{MD}/matrix_300.csv"))}
ids = json.load(open(f"{MD}/first20_ids.json"))
jobs = [rows[i] for i in ids]

def log(m): print(f"[{time.strftime('%H:%M:%S')}] {m}", flush=True)
def led(rec):
    rec["ts"] = time.time()
    LEDGER.write(json.dumps(rec, ensure_ascii=False) + "\n"); LEDGER.flush()

WORK = tq.Queue()
for r in jobs: WORK.put(r)
LOCK = threading.Lock()
stats = {"ok": 0, "fail": 0}

def worker(name):
    while True:
        try: r = WORK.get_nowait()
        except tq.Empty: return
        cid = int(r["combo_id"])
        out = f"T2_matrix/first20/T2_{cid:03d}_{r['A']}_{r['B']}_{r['C']}.png"
        with LOCK:
            if os.path.exists(out) and os.path.getsize(out) > 100_000:
                log(f"[{name}] {cid}: exists skip"); stats["ok"] += 1; WORK.task_done(); continue
        body = json.dumps({"prompt": r["prompt_full"], "seed": int(cid),
                           "width": 896, "height": 1344}).encode()
        req = urllib.request.Request("http://127.0.0.1:10588/api/production/qwen21/generate",
            data=body, headers={"Content-Type": "application/json"})
        t0 = time.time()
        try:
            with urllib.request.urlopen(req, timeout=600) as resp:
                j = json.load(resp)
            fn = j["data"]["images"][0]["filename"]
            src = j["data"]["images"][0].get("url") or f"http://127.0.0.1:8188/view?filename={fn}&subfolder=&type=output"
            src = src.replace("localhost", "127.0.0.1")
            urllib.request.urlretrieve(src, out)  # 1018 实录: 禁硬编码 8195 — KAP 双实例路由, 产物实例以响应 url 为准
            el = time.time() - t0
            log(f"[{name}] {cid}({r['A']}_{r['B']}_{r['C']}): {el:.0f}s {os.path.getsize(out)//1024}KB")
            with LOCK:
                stats["ok"] += 1
            led({"event": "gen", "phase": "T2-first20", "asset_id": f"T2_{cid:03d}",
                 "engine": "qwen21", "mode": "t2i", "seed": int(cid),
                 "params": {"width": 896, "height": 1344},
                 "final_prompt": r["prompt_full"], "output_path": os.path.abspath(out),
                 "elapsed": round(el, 1), "status": "ok"})
        except Exception as e:
            log(f"[{name}] {cid}: FAIL {str(e)[:180]}")
            with LOCK: stats["fail"] += 1
            led({"event": "gen", "phase": "T2-first20", "asset_id": f"T2_{cid:03d}",
                 "engine": "qwen21", "mode": "t2i", "seed": int(cid),
                 "final_prompt": r["prompt_full"], "status": "fail", "err": str(e)[:200]})
        WORK.task_done()

threads = [threading.Thread(target=worker, args=(f"w{i}",)) for i in range(1)]  # 单 worker: KAP 双并发下 qwen21 第二路 404（1018 首批实录 10/10 失败签名）
for t in threads: t.start()
for t in threads: t.join()
log(f"FIRST20_DONE ok={stats['ok']} fail={stats['fail']}")
