#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""qc_verify_d3.py —— 反验 D3：拿 run_logs 的真实超时记录核对 231 个「疑似全站翻页」脚本"""
import json
import re
import sqlite3

D = "/root/gov_crawler/"
J = json.load(open(D + "qc_out/qc_tier1_20260925.json", encoding="utf-8"))
d3 = [x["script"] for x in J["issues"].get("D3 无 --pages 且有全站翻页风险（日跑撞 600s 超时）", [])]
print("D3 名单: %d 个" % len(d3))

db = sqlite3.connect("file:/root/search.db?mode=ro", uri=True, timeout=30)
db.execute("PRAGMA busy_timeout=30000")

print("\n=== run_logs 结构 ===")
cols = [r[1] for r in db.execute("PRAGMA table_info(run_logs)")]
print("  列:", cols)
n = db.execute("SELECT COUNT(*) FROM run_logs").fetchone()[0]
print("  行数: %d" % n)
if n:
    r = db.execute("SELECT * FROM run_logs ORDER BY rowid DESC LIMIT 1").fetchone()
    print("  最新一行:", {c: (str(v)[:60] if v is not None else None) for c, v in zip(cols, r)})
    # 找时间列
    tcol = next((c for c in cols if c in ("run_date", "date", "run_time", "started_at", "created_at", "ts")), None)
    if tcol:
        print("  时间范围:", db.execute("SELECT MIN(%s)||' ~ '||MAX(%s) FROM run_logs" % (tcol, tcol)).fetchone()[0])

scol = next((c for c in cols if c in ("script", "script_name", "name")), None)
ecol = next((c for c in cols if c in ("error_detail", "error", "detail")), None)
ecol2 = next((c for c in cols if c == "elapsed_seconds"), None)
stcol = next((c for c in cols if c in ("status", "flag", "state")), None)
print("  用的列: script=%s status=%s error=%s elapsed=%s" % (scol, stcol, ecol, ecol2))
if not scol:
    raise SystemExit("找不到脚本列")

# ① 全库：有超时证据的脚本
print("\n=== ① 全部脚本的超时证据（elapsed 近 600s 或 error 含 timeout）===")
ev = {}
q = ("SELECT %s, COUNT(*), MAX(%s), %s, %s, %s FROM run_logs GROUP BY %s"
     % (scol, ecol2 or "0", stcol or "''", ecol or "''", (ecol2 or "0"), scol))
rows = list(db.execute(q))
for sc, cnt, mx, st, err, mx2 in rows:
    blob = "%s %s" % (st or "", (err or "")[:300])
    to = bool(re.search(r"timeout|超时|timed out", blob, re.I)) or (isinstance(mx2, (int, float)) and mx2 >= 590)
    ev[sc] = dict(runs=cnt, maxelapsed=mx2, timeout=to, status=(st or "")[:20])

d3ev = [s for s in d3 if ev.get(s, {}).get("timeout")]
print("  run_logs 里有记录的脚本: %d | 其中有超时证据: %d" % (len(ev), sum(1 for v in ev.values() if v["timeout"])))
print("\n=== ② D3 名单核对 ===")
print("  D3 231 个中：有超时证据 %d 个 ✅实锤" % len(d3ev))
print("             有运行记录但无超时证据 %d 个（🔸存疑，可能靠别的兜底）"
      % len([s for s in d3 if s in ev and not ev[s]["timeout"]]))
print("             run_logs 里没记录 %d 个（🔹从没被调度到过）"
      % len([s for s in d3 if s not in ev]))
print("\n  实锤样例（超时证据）:")
for s in d3ev[:12]:
    v = ev[s]
    print("     %-38s 运行%3d次 | 最长 %6.1fs | %s" % (s[:38], v["runs"], v["maxelapsed"] or 0, v["status"]))

print("\n=== ③ 反向：不在 D3 名单、但真有超时证据的脚本（漏网）===")
miss = [(s, v) for s, v in ev.items() if v["timeout"] and s not in d3]
miss.sort(key=lambda x: -(x[1]["maxelapsed"] or 0))
print("  共 %d 个：" % len(miss))
for s, v in miss[:15]:
    print("     %-38s 运行%3d次 | 最长 %6.1fs | %s" % (s[:38], v["runs"], v["maxelapsed"] or 0, v["status"]))

json.dump({"d3_total": len(d3), "d3_confirmed": d3ev,
           "d3_no_evidence": [s for s in d3 if s not in ev],
           "d3_uncertain": [s for s in d3 if s in ev and not ev[s]["timeout"]],
           "timeout_but_not_d3": [s for s, _ in miss]},
          open(D + "qc_out/d3_verify_20260925.json", "w", encoding="utf-8"),
          ensure_ascii=False, indent=1)
print("\n  已写 qc_out/d3_verify_20260925.json")
db.close()
