"""勝率見立て(ベース予想の数値化)。 全出走馬に対し、モデル生出力を元に「この馬が勝つと見る確率」を付与する。 レース内で合計100%に按分しない。閾値未満は「切」(0%)とする。 """ from __future__ import annotations # この値未満は「来ない」と見て切る(順位ではなく見立てとして) DEFAULT_CUT_THRESHOLD = 0.005 def _raw_model_prob(row: dict) -> float: if row.get("model_win_prob") is not None: return max(0.0, float(row["model_win_prob"])) score = float(row.get("score_accuracy") or row.get("score") or 0.0) return max(0.0, min(1.0, score / 100.0)) def assign_win_rates( rows: list[dict], *, cut_threshold: float = DEFAULT_CUT_THRESHOLD, ) -> None: """ 全馬に win_rate(0〜1)を付与する。上から順位付けの材料であり、按分はしない。 - win_rate: 勝率見立て(小数 0〜1) - cut: True なら「切」 - cut_reason: 切った理由コード(将来: injury, jockey_change 等) """ for row in rows: p = _raw_model_prob(row) reasons: list[str] = [] if p < cut_threshold: reasons.append("below_threshold") if not row.get("passed", True): reasons.append("stage1_screen_fail") if reasons: row["win_rate"] = 0.0 row["cut"] = True row["cut_reason"] = "+".join(reasons) else: row["win_rate"] = round(p, 6) row["cut"] = False row["cut_reason"] = None # 既存フィールドとの互換 row["win_probability"] = row["win_rate"] row["stage3_prob"] = row["win_rate"] def assign_rank_accuracy(rows: list[dict]) -> None: """堅実順位(score_accuracy 降順)。◎〇▲△はこの順位の上位4頭。""" for row in rows: row["score_accuracy"] = float(row.get("score_accuracy") or row.get("score") or 0.0) ordered = sorted(rows, key=lambda x: float(x["score_accuracy"]), reverse=True) for idx, row in enumerate(ordered, start=1): row["rank_accuracy"] = idx row["rank"] = idx row["score"] = float(row["score_accuracy"])