Syntrex commited on
Commit
abeeae7
·
verified ·
1 Parent(s): 19f4ff6

Create batter_realization.py

Browse files
Files changed (1) hide show
  1. analytics/batter_realization.py +100 -0
analytics/batter_realization.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import pandas as pd
6
+
7
+
8
+ def _safe_text(value: Any) -> str:
9
+ return str(value or "").strip()
10
+
11
+
12
+ def _event_is_hit(event: str) -> int:
13
+ e = str(event or "").strip().lower()
14
+ return int(e in {"single", "double", "triple", "home_run"})
15
+
16
+
17
+ def _event_is_hr(event: str) -> int:
18
+ e = str(event or "").strip().lower()
19
+ return int(e == "home_run")
20
+
21
+
22
+ def _event_total_bases(event: str) -> int:
23
+ e = str(event or "").strip().lower()
24
+ if e == "single":
25
+ return 1
26
+ if e == "double":
27
+ return 2
28
+ if e == "triple":
29
+ return 3
30
+ if e == "home_run":
31
+ return 4
32
+ return 0
33
+
34
+
35
+ def build_batter_realization_rows(
36
+ batter_prop_outcomes_df: pd.DataFrame,
37
+ statcast_df: pd.DataFrame,
38
+ graded_at: str,
39
+ ) -> pd.DataFrame:
40
+ """
41
+ First-pass realized batter outcome scaffold.
42
+
43
+ Uses currently loaded statcast rows to determine whether a batter:
44
+ - recorded at least one hit
45
+ - recorded at least one HR
46
+ - recorded 2+ total bases
47
+
48
+ This is not yet game-perfect by game_pk, but it upgrades outcomes
49
+ from pending to actual observed values within the current loaded dataset.
50
+ """
51
+ if batter_prop_outcomes_df is None or batter_prop_outcomes_df.empty:
52
+ return pd.DataFrame()
53
+
54
+ if statcast_df is None or statcast_df.empty:
55
+ return pd.DataFrame()
56
+
57
+ if "player_name" not in statcast_df.columns or "events" not in statcast_df.columns:
58
+ return pd.DataFrame()
59
+
60
+ rows: list[dict[str, Any]] = []
61
+
62
+ for _, outcome_row in batter_prop_outcomes_df.iterrows():
63
+ batter_name = _safe_text(outcome_row.get("batter_name"))
64
+ if not batter_name:
65
+ continue
66
+
67
+ batter_events = statcast_df[
68
+ statcast_df["player_name"].astype(str).str.strip() == batter_name
69
+ ].copy()
70
+
71
+ if batter_events.empty:
72
+ realized_hit = None
73
+ realized_hr = None
74
+ realized_tb2p = None
75
+ grade_status = "pending"
76
+ outcome_source = "statcast_no_match"
77
+ else:
78
+ events = batter_events["events"].fillna("").astype(str)
79
+
80
+ hit_count = sum(_event_is_hit(e) for e in events)
81
+ hr_count = sum(_event_is_hr(e) for e in events)
82
+ total_bases = sum(_event_total_bases(e) for e in events)
83
+
84
+ realized_hit = int(hit_count > 0)
85
+ realized_hr = int(hr_count > 0)
86
+ realized_tb2p = int(total_bases >= 2)
87
+ grade_status = "graded"
88
+ outcome_source = "statcast_loaded_window"
89
+
90
+ row_dict = outcome_row.to_dict()
91
+ row_dict["graded_at"] = graded_at
92
+ row_dict["realized_hit"] = realized_hit
93
+ row_dict["realized_hr"] = realized_hr
94
+ row_dict["realized_tb2p"] = realized_tb2p
95
+ row_dict["grade_status"] = grade_status
96
+ row_dict["outcome_source"] = outcome_source
97
+
98
+ rows.append(row_dict)
99
+
100
+ return pd.DataFrame(rows)