# -*- coding: utf-8 -*-
"""論点3（判定可能ゲートの選択）＋論点1（visual_density廃止）＋論点2（目標精度）を一括検証。

LLM再実行なし・既存出力再利用（追加課金ゼロ）。線形回帰・5-fold OOF。

ゲート候補:
  - baseline_all          : 全有効行
  - LLM_assessable        : LLM出力4軸が判定不可でない（打ち手1）
  - rfdetr_readable       : density_cache.readable=True（RF-DETRスープ可読）
  - rfdetr_assessable     : density_cache.assessable=True（可読性ゲート, 最厳）
  - both(LLM & rfdetr)    : 両方

各ゲートで total/soup の n・MAE・グレード一致率・±15点以内を測定。
さらに visual_density を特徴量から外した場合(no_vd)も比較。
"""
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
from dataclasses import replace

ROOT = Path(__file__).resolve().parents[2]
LEGACY = ROOT / "検証" / "scripts" / "run_downstream_model_comparison.py"
INPUT = ROOT / "local_batch_api" / "output_full" / "training_dataset.csv"
CACHE = ROOT / "local_batch_api" / "density_cache.jsonl"
OUTDIR = ROOT / "精度改善_20260619" / "08_gate_comparison"
SEED, N = 42, 5


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


def cache_df():
    rows = [json.loads(l) for l in open(CACHE, encoding="utf-8")]
    return pd.DataFrame([{ "sample_id": str(r["stem"]),
                           "rfdetr_readable": bool(r.get("readable")),
                           "rfdetr_assessable": bool(r.get("assessable")) } for r in rows])


def drop_vd(fs):
    numeric = tuple(x for x in fs.numeric if x != "visual_density_num")
    categorical = tuple(x for x in fs.categorical if x != "visual_density")
    return replace(fs, numeric=numeric, categorical=categorical)


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 stat(y, pred, grade, n_all):
    return {"n": len(y), "カバレッジ%": round(len(y)/n_all*100, 1),
            "MAE": round(float(np.abs(y-pred).mean()), 2),
            "グレード一致%": round(float((grade(y)==grade(pred)).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()
    f = mod.build_features(INPUT)
    f = f[f["target_out_of_range"] == 0].reset_index(drop=True)
    f["sample_id"] = f["sample_id"].astype(str)
    f = f.merge(cache_df(), on="sample_id", how="left")
    n_all = len(f)
    gates = {
        "baseline_all": f,
        "LLM_assessable(打ち手1)": f[f["assessable"] == 1],
        "rfdetr_readable": f[f["rfdetr_readable"] == True],
        "rfdetr_assessable(最厳)": f[f["rfdetr_assessable"] == True],
        "both(LLM&rfdetr)": f[(f["assessable"] == 1) & (f["rfdetr_assessable"] == True)],
    }
    fsets = {x.name: x for x in mod.feature_sets()}
    kf = KFold(N, shuffle=True, random_state=SEED)
    TARGS = [("human_total_score", "visual_plus_brix"), ("human_soup_score", "visual_only")]

    print("=== ゲート比較（visual_density あり）===")
    rows = []
    for gname, g in gates.items():
        g = g.reset_index(drop=True)
        for target, fsn in TARGS:
            y, pred = oof(g, fsets[fsn], target, mod, kf)
            s = stat(y, pred, mod.grade, n_all); s.update({"gate": gname, "target": "total" if "total" in target else "soup"})
            rows.append(s)
    res = pd.DataFrame(rows)[["gate", "target", "n", "カバレッジ%", "MAE", "グレード一致%", "±15点%"]]
    res.to_csv(OUTDIR / "gate_comparison.csv", index=False, encoding="utf-8-sig")
    for t in ["total", "soup"]:
        print(f"\n[{t}]"); print(res[res.target == t].to_string(index=False))

    print("\n\n=== visual_density 廃止の影響（rfdetr_assessableゲート上）===")
    g = gates["rfdetr_assessable(最厳)"].reset_index(drop=True)
    rows2 = []
    for target, fsn in TARGS:
        for label, fs in [("vd_あり", fsets[fsn]), ("vd_なし", drop_vd(fsets[fsn]))]:
            y, pred = oof(g, fs, target, mod, kf)
            s = stat(y, pred, mod.grade, n_all); s.update({"設定": label, "target": "total" if "total" in target else "soup"})
            rows2.append(s)
    res2 = pd.DataFrame(rows2)[["target", "設定", "n", "MAE", "グレード一致%", "±15点%"]]
    res2.to_csv(OUTDIR / "visual_density_ablation.csv", index=False, encoding="utf-8-sig")
    print(res2.to_string(index=False))


if __name__ == "__main__":
    main()
