# -*- coding: utf-8 -*-
"""代表フレーム抽出の高度化（フレーム品質ゲート版）の効果検証。

LLM再評価なし（既存LLM出力を再利用、追加課金ゼロ）。
既存フレームの品質指標(steam_haze/sharpness/blowout)で低品質フレームを「要確認」に分岐し、
打ち手1（assessable=1）に上乗せした効果を5-fold CVで測定する。

ゲート(緩, データ駆動で選定): steam_haze_score>=0.12 or sharpness<20 or blowout_ratio>=0.006

出力(utf-8-sig):
  - 06_frame_quality/frame_gate_cv_results.csv
  - 06_frame_quality/frame_gate_summary.md
"""
from __future__ import annotations
import importlib.util, sys, json
from pathlib import Path
import numpy as np, pandas as pd
from sklearn.model_selection import KFold
from sklearn.pipeline import Pipeline
from sklearn.base import clone

ROOT = Path(__file__).resolve().parents[2]
LEGACY = ROOT / "検証" / "scripts" / "run_downstream_model_comparison.py"
INPUT = ROOT / "local_batch_api" / "output_full" / "training_dataset.csv"
JSONL = ROOT / "local_batch_api" / "output_full" / "training_dataset.jsonl"
OUTDIR = ROOT / "精度改善_20260619" / "06_frame_quality"
SEED, N_SPLITS = 42, 5
GATE = dict(steam=0.12, sharp=20.0, blow=0.006)


def load_legacy():
    spec = importlib.util.spec_from_file_location("legacy_cmp", LEGACY)
    mod = importlib.util.module_from_spec(spec); sys.modules["legacy_cmp"] = mod
    spec.loader.exec_module(mod); return mod


def frame_metrics_df():
    rows = []
    for line in open(JSONL, encoding="utf-8"):
        d = json.loads(line); fm = d.get("frame_metrics", {}) or {}
        rows.append({"sample_id": str(d.get("metadata", {}).get("ID", "")),
                     "sharpness": fm.get("sharpness"), "blowout_ratio": fm.get("blowout_ratio"),
                     "steam_haze_score": fm.get("steam_haze_score")})
    return pd.DataFrame(rows)


def frame_ok(df):
    return ~((df["steam_haze_score"] >= GATE["steam"]) | (df["sharpness"] < GATE["sharp"]) | (df["blowout_ratio"] >= GATE["blow"]))


def oof(data, fs, target, mod, kf):
    X = data[list(fs.numeric) + list(fs.categorical)]; y = data[target].to_numpy(); pred = np.zeros(len(y))
    for tr, te in kf.split(X):
        p = Pipeline([("prep", mod.preprocessor(list(fs.numeric), list(fs.categorical))), ("model", clone(mod.model_specs(SEED)[1][1]))])
        p.fit(X.iloc[tr], y[tr]); pred[te] = np.clip(p.predict(X.iloc[te]), 0, 100)
    return y, pred


def stats(y, pred, grade):
    return {"n": len(y), "MAE": round(float(np.abs(y - pred).mean()), 3),
            "グレード一致率": round(float((grade(y) == grade(pred)).mean()) * 100, 1),
            "±10点以内": round(float((np.abs(y - pred) <= 10).mean()) * 100, 1),
            "±15点以内": round(float((np.abs(y - pred) <= 15).mean()) * 100, 1)}


def main():
    OUTDIR.mkdir(parents=True, exist_ok=True)
    mod = load_legacy()
    feats = mod.build_features(INPUT)
    feats = feats[feats["target_out_of_range"] == 0].reset_index(drop=True)
    feats["sample_id"] = feats["sample_id"].astype(str)
    fm = frame_metrics_df()
    feats = feats.merge(fm, on="sample_id", how="left", suffixes=("", "_fm"))

    a1 = feats[feats["assessable"] == 1].reset_index(drop=True)
    gated = a1[frame_ok(a1)].reset_index(drop=True)
    conditions = {"打ち手1のみ(assessable=1)": a1, "打ち手1+フレームゲート": gated}
    fsets = {fs.name: fs for fs in mod.feature_sets()}
    kf = KFold(N_SPLITS, shuffle=True, random_state=SEED)

    rows = []
    for cond, data in conditions.items():
        for target, fsn in [("human_total_score", "visual_plus_brix"), ("human_soup_score", "visual_only")]:
            y, pred = oof(data, fsets[fsn], target, mod, kf)
            s = stats(y, pred, mod.grade); s.update({"condition": cond, "target": target})
            rows.append(s)
    res = pd.DataFrame(rows)[["condition", "target", "n", "MAE", "グレード一致率", "±10点以内", "±15点以内"]]
    res.to_csv(OUTDIR / "frame_gate_cv_results.csv", index=False, encoding="utf-8-sig")
    print(res.to_string(index=False))
    print(f"\nゲート除外: assessable=1 {len(a1)}件 中 {len(a1)-len(gated)}件を要確認へ → 残{len(gated)}件")

    lines = ["# 代表フレーム高度化（品質ゲート）効果検証 — 5-fold CV", "",
             "LLM再評価なし・既存出力再利用（**追加課金ゼロ**）。線形回帰。", "",
             f"ゲート: steam_haze>={GATE['steam']} or sharpness<{GATE['sharp']} or blowout>={GATE['blow']} のいずれかで「要確認」へ。",
             f"assessable=1 の {len(a1)}件中 {len(a1)-len(gated)}件を除外し、残{len(gated)}件をスコア。", "",
             "| 条件 | target | n | MAE | グレード一致率 | ±10点以内 | ±15点以内 |",
             "|---|---|---:|---:|---:|---:|---:|"]
    for _, r in res.iterrows():
        t = "total" if r.target == "human_total_score" else "soup"
        lines.append(f"| {r.condition} | {t} | {r.n} | {r.MAE} | {r['グレード一致率']}% | {r['±10点以内']}% | {r['±15点以内']}% |")
    (OUTDIR / "frame_gate_summary.md").write_text("\n".join(lines), encoding="utf-8")


if __name__ == "__main__":
    main()
