#!/usr/bin/env python3 """Task 5: compare EC_0.25_SD vs EC_0.25 native (../HW ope1b) EHDE skill. Reads the two final_verify JSONs (identical structure, identical scoring code, identical 416 valid-init test set) and answers the operational question: does downscaling the free 1.5 deg download reproduce the skill of the native 0.25 deg reforecast, or must 0.25 deg be downloaded? Writes ehd_sd_vs_native.html and prints the national table. """ from __future__ import annotations import json, os, sys SD = "outputs_sd/final_verify_sd.json" NATIVE = "/home/tanpv/Progs/claude/HW/outputs/AI_Results/final_verify_ope1b.json" OUT = "ehd_sd_vs_native.html" WEEKS = ["W1", "W2", "W3", "W4", "W5", "W6"] METRICS = ["POD", "POFD", "FAR", "FBI", "CSI", "SEDI", "EDI"] TARGET = {"POD": (">", 0.6), "POFD": ("<", 0.2), "FAR": ("<", 0.2), "CSI": (">", 0.4), "FBI": ("~", 0.75)} def load(p): return json.load(open(p)) if os.path.exists(p) else None def cell(v): return "%.3f" % v if isinstance(v, (int, float)) else "—" def main(): sd = load(SD); nat = load(NATIVE) if sd is None: sys.exit(f"missing {SD} (run submit_sd_verify.slurm first)") if nat is None: sys.exit(f"missing {NATIVE}") # ---- console national table ---- print(f"\n{'':>6}", "".join(f"{m:>22}" for m in ["POD", "FAR", "FBI", "CSI", "SEDI"])) print("national: native (0.25 goc) vs SD (0.25 downscale tu 1.5)") for w in WEEKS: n = nat["national"][w]; s = sd["national"][w] row = " ".join(f"{m}:{n.get(m,float('nan')):.3f}/{s.get(m,float('nan')):.3f}" for m in ["POD", "FAR", "FBI", "CSI", "SEDI"]) print(f"{w:>6} {row}") # ---- HTML ---- def mrow(w): n = nat["national"][w]; s = sd["national"][w] tds = [f"{w}"] for m in METRICS: nv, sv = n.get(m), s.get(m) d = (sv - nv) if isinstance(nv, (int, float)) and isinstance(sv, (int, float)) else None dcls = "" if d is None else (" pos" if (d > 0) == (m in ("POD", "CSI", "SEDI", "EDI")) else " neg") tds.append(f"{cell(nv)}{cell(sv)}" f"{'' if d is None else '%+.3f'%d}") return "" + "".join(tds) + "" hdr = "tuần" + "".join(f"{m}" for m in METRICS) sub = "" + "".join("gốcSDΔ" for _ in METRICS) rows = "".join(mrow(w) for w in WEEKS) # W1 target verdict for the SD system s1 = sd["national"]["W1"] verdict = [] for m, (op, t) in TARGET.items(): v = s1.get(m) ok = (v > t if op == ">" else v < t if op == "<" else abs(v - t) < 0.15) if isinstance(v, (int, float)) else False verdict.append(f"
  • {m} {op} {t}: SD={cell(v)} → {'ĐẠT' if ok else 'KHÔNG'}
  • ") css = ("body{font-family:system-ui,Arial;margin:24px;color:#1a1a1a}" "table{border-collapse:collapse;margin:12px 0;font-size:13px}" "th,td{border:1px solid #ccc;padding:3px 7px;text-align:center}" "th{background:#f0efe9}.wk{font-weight:700;background:#faf8f2}" ".sd{background:#eef5ff}.d{font-weight:600}.pos{color:#127a2e}.neg{color:#b21f2d}" "h1{font-size:20px}h2{font-size:16px;margin-top:22px}") html = f"""EC_0.25_SD vs EC_0.25 gốc

    Dự báo EHDE: EC_0.25_SD (downscale từ 1.5°) so với EC_0.25 gốc (../HW ope1b)

    Cùng hệ Ope1b, cùng bộ test 416 init của 4 năm kiểm định, cùng code chấm điểm (final_verify.py). Khác biệt DUY NHẤT: đầu vào EC ở 0.25° gốc hay downscale từ 1.5°. Mỗi ô: gốc | SD | Δ(SD−gốc). n_init SD={sd.get('n_init','?')}.

    Kỹ năng toàn quốc (national), W1–W6

    {hdr}{sub}{rows}

    Chỉ tiêu nghiệp vụ tại W1 (hệ EC_0.25_SD)

    Kết luận tải-0.25-hay-1.5 điền sau khi đọc bảng: nếu Δ nhỏ trên toàn W1–W6 thì 1.5°+downscale là đủ; nếu SD kém hẳn (đặc biệt W1) thì cần tải 0.25° gốc.

    """ open(OUT, "w").write(html) print(f"\n[report] -> {OUT}") if __name__ == "__main__": main()