# -*- coding: utf-8 -*-
"""最終パイプライン 報告用検証。

構成: 上流ゲート(assessable=1) → 線形回帰 → 特徴量 visual_plus_brix_slot
評価: 5-fold OOF 交差検証（実運用に近い条件）
出力指標: MAE / 相関(Pearson) / グレード一致% / ±15点以内%
比較: 改修前(全件 baseline_all) と 改修後(assessable=1ゲート) を併記。
LLM 再実行なし・追加課金ゼロ。
"""
from __future__ import annotations
import importlib.util, sys
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"
OUTDIR = ROOT / "精度改善_20260619" / "11_final_validation"
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 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]))])  # [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):
    corr = float(np.corrcoef(y, pred)[0, 1]) if np.std(pred) > 0 else float("nan")
    return {
        "MAE": round(float(np.abs(y - pred).mean()), 2),
        "相関": round(corr, 3),
        "グレード一致%": 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)
    g = f[f["assessable"] == 1].reset_index(drop=True)  # 採点ゲート
    fsets = {x.name: x for x in mod.feature_sets()}
    kf = KFold(N, shuffle=True, random_state=SEED)

    print("=" * 64)
    print(" 最終パイプライン 報告用検証 (5-fold OOF / 線形回帰)")
    print("=" * 64)
    print(f"全件(範囲内): {len(f)}  /  assessable=1(採点対象): {len(g)}"
          f"  /  要確認分岐: {len(f) - len(g)} ({(len(f)-len(g))/len(f)*100:.1f}%)")
    print(f"slot分布(採点対象): 開店前={int(g['slot_is_opening'].sum())}"
          f" 夕方17-19={int(g['slot_is_evening'].sum())} / {len(g)}")
    print()

    rows = []
    # 採用構成: visual_plus_brix_slot
    for target, tname in [("human_total_score", "total"), ("human_soup_score", "soup")]:
        # 改修前(参考): 全件 baseline
        y0, p0 = oof(f, fsets["visual_plus_brix_slot"], target, mod, kf)
        s0 = stat(y0, p0, mod.grade); s0.update({"target": tname, "条件": "改修前(全件)", "n": len(f)})
        rows.append(s0)
        # 改修後(採用): assessable=1 ゲート
        y1, p1 = oof(g, fsets["visual_plus_brix_slot"], target, mod, kf)
        s1 = stat(y1, p1, mod.grade); s1.update({"target": tname, "条件": "改修後(採点対象)", "n": len(g)})
        rows.append(s1)

    res = pd.DataFrame(rows)[["target", "条件", "n", "MAE", "相関", "グレード一致%", "±15点以内%"]]
    res.to_csv(OUTDIR / "final_validation.csv", index=False, encoding="utf-8-sig")
    for t in ["total", "soup"]:
        print(f"[{t}]")
        print(res[res.target == t].to_string(index=False))
        print()

    def md_table(df):
        cols = list(df.columns)
        out = ["| " + " | ".join(cols) + " |", "| " + " | ".join(["---"] * len(cols)) + " |"]
        for _, r in df.iterrows():
            out.append("| " + " | ".join(str(r[c]) for c in cols) + " |")
        return "\n".join(out)

    # 報告用サマリ
    summary = ["# 最終パイプライン 報告用検証値", "",
               "- 構成: 上流ゲート(RF-DETR可読性) + 採点ゲート(LLM assessable=1) → 線形回帰",
               "- 特徴量: visual_plus_brix_slot (動画LLM評価 + 濃度 + 時間slot)",
               f"- 評価: {N}-fold OOF 交差検証 / seed={SEED}",
               f"- 全件(範囲内) {len(f)} 件中 採点対象 {len(g)} 件 / 要確認分岐 {len(f)-len(g)} 件", "",
               "## 結果", "",
               md_table(res)]
    (OUTDIR / "summary.md").write_text("\n".join(summary), encoding="utf-8")
    print(f"saved: {OUTDIR}")


if __name__ == "__main__":
    main()
