2026_MLB_Model / models /context_adjustment.py
Syntrex's picture
Accuracy overhaul: pitcher resolution logging, baseline recalibration, vig fix, XGBoost blend
21151ce
raw
history blame
3.02 kB
from __future__ import annotations
from typing import Any
def build_live_context(game_row: dict[str, Any], weather_row: dict[str, Any] | None = None) -> dict[str, Any]:
weather_row = weather_row or {}
def _safe_int(value: Any, default: int = 0) -> int:
try:
if value is None:
return default
text = str(value).strip().lower()
if text in {"", "nan", "none"}:
return default
return int(float(value))
except Exception:
return default
def _safe_float(value: Any, default: float | None = None) -> float | None:
try:
if value is None:
return default
text = str(value).strip().lower()
if text in {"", "nan", "none"}:
return default
return float(value)
except Exception:
return default
away_score = _safe_int(game_row.get("away_score"), 0)
home_score = _safe_int(game_row.get("home_score"), 0)
return {
"inning": _safe_int(str(game_row.get("status", "")).split()[-1] if game_row.get("status") else 0, 0),
"status": str(game_row.get("status", "") or ""),
"outs": _safe_int(game_row.get("outs"), 0),
"balls": _safe_int(game_row.get("balls"), 0),
"strikes": _safe_int(game_row.get("strikes"), 0),
"runner_on_1b": bool(game_row.get("runner_on_1b", False)),
"runner_on_2b": bool(game_row.get("runner_on_2b", False)),
"runner_on_3b": bool(game_row.get("runner_on_3b", False)),
"score_diff_away": away_score - home_score,
"temperature_f": _safe_float(weather_row.get("temperature_f")),
"wind_speed_mph": _safe_float(weather_row.get("wind_speed_mph")),
}
def compute_context_adjustment(context: dict[str, Any]) -> dict[str, Any]:
hit_adj = 0.0
hr_adj = 0.0
tb2p_adj = 0.0
reason_tags: list[str] = []
balls = int(context.get("balls", 0))
strikes = int(context.get("strikes", 0))
outs = int(context.get("outs", 0))
runner_on_2b = bool(context.get("runner_on_2b", False))
runner_on_3b = bool(context.get("runner_on_3b", False))
if balls >= 2:
hit_adj += 0.012
tb2p_adj += 0.010
reason_tags.append("Hitter-friendly count")
if strikes >= 2:
hit_adj -= 0.014
hr_adj -= 0.006
tb2p_adj -= 0.010
reason_tags.append("Two-strike penalty")
if runner_on_2b or runner_on_3b:
hit_adj += 0.008
tb2p_adj += 0.008
reason_tags.append("Run-scoring pressure")
if outs >= 2:
hit_adj -= 0.004
tb2p_adj -= 0.004
# Temperature and wind adjustments are intentionally absent here.
# environment_model.py owns all weather signals via continuous formulas.
# Adding them here would double-count the same physical effect.
return {
"hit_adj": hit_adj,
"hr_adj": hr_adj,
"tb2p_adj": tb2p_adj,
"reason_tags": reason_tags,
}