# -*- coding: utf-8 -*-
"""打ち手1（判断不能画像をスコアリングから分岐）の効果検証。

3条件で 5-fold CV を回して後段モデルを比較する:
  - baseline_all        : 全有効行(519) … 現状（判断不能行も後段でスコア）
  - exclude_assessable0 : assessable=1 のみ(346) … 推奨カット
  - exclude_retake1     : assessable=1 AND retake_recommended=0(190) … 厳格カット（参考）

プロンプトは変更せず、既存LLM出力をそのまま使う（再課金なし）。
既存 検証/scripts/run_downstream_model_comparison.py の特徴量/モデル/前処理を流用。

出力(utf-8-sig):
  - 精度改善_20260619/05_branch_comparison/branch_cv_results.csv
  - 精度改善_20260619/05_branch_comparison/branch_summary.md
"""
from __future__ import annotations
import importlib.util, sys
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import KFold
from sklearn.pipeline import Pipeline

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" / "05_branch_comparison"
SEED, N_SPLITS = 42, 5


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 pearson(a, b):
    if np.std(a) == 0 or np.std(b) == 0:
        return float("nan")
    return float(np.corrcoef(a, b)[0, 1])


def cv_one(data, numeric, categorical, estimator, target, mod, kf):
    from sklearn.base import clone
    numeric, categorical = list(numeric), list(categorical)
    X = data[numeric + categorical]
    y = data[target].to_numpy()
    maes, corrs = [], []
    for tr, te in kf.split(X):
        if estimator is None:
            pred = data["fixed_score"].to_numpy()[te]
        else:
            pipe = Pipeline([
                ("prep", mod.preprocessor(numeric, categorical)),
                ("model", clone(estimator)),
            ])
            pipe.fit(X.iloc[tr], y[tr])
            pred = np.clip(pipe.predict(X.iloc[te]), 0, 100)
        maes.append(mean_absolute_error(y[te], pred))
        corrs.append(pearson(y[te], pred))
    return float(np.mean(maes)), float(np.std(maes)), float(np.nanmean(corrs))


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)

    conditions = {
        "baseline_all": feats,
        "exclude_assessable0": feats[feats["assessable"] == 1].reset_index(drop=True),
        "exclude_retake1": feats[(feats["assessable"] == 1) & (feats["retake_recommended"] == 0)].reset_index(drop=True),
    }
    targets = ["human_total_score", "human_soup_score"]
    fsets = {fs.name: fs for fs in mod.feature_sets()}
    # 代表特徴量: total=visual_plus_brix, soup=visual_only（C結果のbest）。両方も併記。
    use_fsets = ["visual_only", "visual_plus_brix"]
    specs = mod.model_specs(SEED)
    kf = KFold(n_splits=N_SPLITS, shuffle=True, random_state=SEED)

    rows = []
    for cond_name, data in conditions.items():
        for target in targets:
            for fsname in use_fsets:
                fs = fsets[fsname]
                for model_name, est in specs:
                    mae, std, corr = cv_one(data, fs.numeric, fs.categorical, est, target, mod, kf)
                    rows.append({
                        "condition": cond_name, "n": len(data), "target": target,
                        "feature_set": fsname, "model": model_name,
                        "mae_mean": round(mae, 4), "mae_std": round(std, 4), "corr_mean": round(corr, 4),
                    })
    res = pd.DataFrame(rows)
    res.to_csv(OUTDIR / "branch_cv_results.csv", index=False, encoding="utf-8-sig")

    # best per (condition,target) excluding 固定配点
    def md(df):
        cols = list(df.columns)
        out = ["| " + " | ".join(cols) + " |", "| " + " | ".join(["---"] * len(cols)) + " |"]
        for _, r in df.iterrows():
            out.append("| " + " | ".join(f"{v:.4g}" if isinstance(v, float) else str(v) for v in r) + " |")
        return "\n".join(out)

    learned = res[res.model != "固定配点"]
    best = learned.loc[learned.groupby(["condition", "target"])["mae_mean"].idxmin()].sort_values(["target", "condition"])
    lines = [
        "# 打ち手1（判断不能画像の分岐）後段比較 — 5-fold CV",
        "",
        "プロンプト不変・既存LLM出力を再利用（LLM再課金なし）。",
        "",
        "| 条件 | 件数 | 内容 |",
        "|---|---:|---|",
        "| baseline_all | 519 | 現状。判断不能行も後段でスコア |",
        "| exclude_assessable0 | 346 | assessable=1のみ（推奨カット） |",
        "| exclude_retake1 | 190 | assessable=1 AND retake=0（厳格・参考） |",
        "",
        "## 条件×target別 ベストモデル（mae_mean昇順、固定配点除く）",
        "",
        md(best[["target", "condition", "n", "feature_set", "model", "mae_mean", "mae_std", "corr_mean"]]),
        "",
        "## 解釈",
        "- baseline比でMAEが下がれば、判断不能行を後段に流さない効果。",
        "- exclude_retake1 は件数が190へ激減（photo_quality≠良いを全除外するため）。過剰カットの副作用に注意。",
    ]
    (OUTDIR / "branch_summary.md").write_text("\n".join(lines), encoding="utf-8")
    print(best[["target", "condition", "n", "feature_set", "model", "mae_mean", "mae_std", "corr_mean"]].to_string(index=False))


if __name__ == "__main__":
    main()
