"""第3段階(期待値予想): ベース予想の勝率に単勝オッズを掛けて期待値を算出する。 ◎〇▲△の決定には使わない(堅実順位はベース予想で確定済み)。 """ from __future__ import annotations from ai.four_mark_ranking import apply_prob_odds_ranking, normalize_win_probabilities def attach_win_odds(rows: list[dict], odds_map: dict[int, float]) -> int: attached = 0 for row in rows: hn = row.get("horse_number") if hn is None: row["win_odds"] = None continue horse_number = int(hn) win_odds = odds_map.get(horse_number) if win_odds and win_odds > 0: row["win_odds"] = float(win_odds) attached += 1 else: row["win_odds"] = None return attached def apply_ev_forecast( rows: list[dict], *, enabled: bool = True, min_prob_for_value: float = 0.03, odds_cap: float = 80.0, ) -> None: """ 期待値予想: 各馬の win_rate × 単勝オッズ → expected_roi(%)。 印・順位は変更しない。 """ for row in rows: row["expected_roi"] = None row["stage3_value_score"] = None row["expected_roi_aggressive"] = None prob = float(row.get("win_rate") or row.get("win_probability") or 0.0) if not enabled or row.get("win_odds") is None or prob <= 0: continue odds = min(float(row["win_odds"]), odds_cap) effective_prob = max(0.0, prob - min_prob_for_value) expected_roi = effective_prob * odds * 100.0 row["expected_roi"] = round(expected_roi, 2) row["stage3_value_score"] = round(expected_roi - 100.0, 2) row["expected_roi_aggressive"] = row["expected_roi"] def apply_stage3_value( rows: list[dict], enabled: bool = True, min_prob_for_value: float = 0.02, odds_cap: float = 80.0, aggressive_min_prob: float = 0.0, aggressive_odds_cap: float = 300.0, *, use_legacy_normalized_marks: bool = False, apply_prob_odds_marks: bool = False, ) -> None: """後方互換: 予想1〜5 等の旧パイプライン。""" if use_legacy_normalized_marks: normalize_win_probabilities(rows, score_key="score_accuracy") for row in rows: row["expected_roi"] = None row["stage3_value_score"] = None prob = float(row.get("win_probability") or row.get("stage3_prob") or row.get("win_rate") or 0.0) if enabled and row.get("win_odds") is not None: odds = min(float(row["win_odds"]), odds_cap) effective_prob = max(0.0, prob - min_prob_for_value) expected_roi = effective_prob * odds * 100.0 row["expected_roi"] = round(expected_roi, 2) row["stage3_value_score"] = round(expected_roi - 100.0, 2) rows.sort(key=lambda x: float(x.get("score_accuracy", 0.0)), reverse=True) for idx, row in enumerate(rows, start=1): row["rank_accuracy"] = idx rows.sort( key=lambda x: ( -999999.0 if x.get("expected_roi") is None else float(x["expected_roi"]), float(x.get("score_accuracy", 0.0)), ), reverse=True, ) for idx, row in enumerate(rows, start=1): row["rank_value"] = idx for row in rows: row["expected_roi_aggressive"] = None row["rank_value_aggressive"] = None if enabled and row.get("win_odds") is not None: odds_a = min(float(row["win_odds"]), float(aggressive_odds_cap)) prob = float(row.get("stage3_prob") or row.get("win_rate") or 0.0) eff_a = max(0.0, prob - float(aggressive_min_prob)) row["expected_roi_aggressive"] = round(eff_a * odds_a * 100.0, 2) rows.sort( key=lambda x: ( -999999.0 if x.get("expected_roi_aggressive") is None else float(x["expected_roi_aggressive"]), float(x.get("win_odds") or 0.0), float(x.get("score_accuracy", 0.0)), ), reverse=True, ) for idx, row in enumerate(rows, start=1): row["rank_value_aggressive"] = idx if apply_prob_odds_marks: apply_prob_odds_ranking(rows) rows.sort(key=lambda x: int(x["rank_accuracy"])) for row in rows: row["rank"] = int(row["rank_accuracy"]) row["score"] = float(row["score_accuracy"])