Spaces:
Running
Running
| 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, | |
| } |