"""レース内の勝率(New)・期待値(New)(単勝オッズ×勝率)の共通計算。""" from __future__ import annotations import math from typing import Any def win_prob_new_pcts(model_scores: list[float | None], *, temperature: float = 10.0) -> list[float]: """ モデルスコア(predictions.win_probability に保存されている値)から、 レース内合計100%の勝率(New)%を返す(softmax・温度付き)。 """ if not model_scores: return [] max_s: float | None = None for s in model_scores: if s is not None: max_s = s if max_s is None else max(max_s, s) weights: list[float] = [] sum_w = 0.0 for s in model_scores: if s is None or max_s is None: w = 1.0 else: z = (s - max_s) / temperature if z < -50.0: z = -50.0 w = math.exp(z) weights.append(w) sum_w += w if sum_w <= 0.0: sum_w = float(len(weights)) weights = [1.0] * len(weights) pcts = [round((w / sum_w) * 100.0, 1) for w in weights] delta = round(100.0 - sum(pcts), 1) if pcts and abs(delta) <= 5.0: pcts[-1] = round(pcts[-1] + delta, 1) return pcts def expected_value_new(prob_pct: float | None, odds: float | None) -> float | None: """期待値(New) = (勝率%/100) × 単勝オッズ(倍率)。""" if prob_pct is None or odds is None: return None o = float(odds) if o <= 0: return None return round((float(prob_pct) / 100.0) * o, 4) def attach_win_prob_and_ev( horses: list[dict[str, Any]], *, score_key: str = "model_score", odds_key: str = "odds", temperature: float = 10.0, ) -> list[dict[str, Any]]: """ 各要素に win_prob_new_pct / ev_new を付与して返す(破壊的更新)。 horses: model_score, odds を持つ dict のリスト """ scores = [h.get(score_key) for h in horses] norm_scores: list[float | None] = [] for s in scores: if s is None or s == "": norm_scores.append(None) else: norm_scores.append(float(s)) pcts = win_prob_new_pcts(norm_scores, temperature=temperature) for i, h in enumerate(horses): pct = pcts[i] if i < len(pcts) else None h["win_prob_new_pct"] = pct od = h.get(odds_key) odds_f = float(od) if od is not None and od != "" else None h["ev_new"] = expected_value_new(pct, odds_f) return horses def marks_by_ev_new(horses: list[dict[str, Any]], *, top_n: int = 4) -> list[dict[str, Any]]: """期待値(New)の降順で ◎〜△ を付与したマーク行(最大 top_n 頭)。""" labels = ["◎本命", "〇対抗", "▲単穴", "△連下"] ranked = sorted( horses, key=lambda x: ( float(x.get("ev_new") or 0.0), float(x.get("win_prob_new_pct") or 0.0), ), reverse=True, ) out: list[dict[str, Any]] = [] for i, row in enumerate(ranked[:top_n]): out.append( { "mark": labels[i] if i < len(labels) else f"候補{i + 1}", "horse_number": row.get("horse_number"), "horse_name": row.get("horse_name") or "", "win_prob_new_pct": row.get("win_prob_new_pct"), "ev_new": row.get("ev_new"), "odds": row.get("odds"), "model_score": row.get("model_score"), } ) return out