Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- utils/encoders.py +72 -0
- utils/predictor.py +161 -0
utils/encoders.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
utils/encoders.py
|
| 3 |
+
Label-encoding maps that mirror the sklearn LabelEncoder used during training.
|
| 4 |
+
All lists are sorted alphabetically β matching the default LabelEncoder order.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
# ββ IPL Teams (alphabetical order = encoding index) ββββββββββββββββββββββββββ
|
| 8 |
+
TEAMS = sorted([
|
| 9 |
+
"Chennai Super Kings",
|
| 10 |
+
"Delhi Capitals",
|
| 11 |
+
"Gujarat Titans",
|
| 12 |
+
"Kolkata Knight Riders",
|
| 13 |
+
"Lucknow Super Giants",
|
| 14 |
+
"Mumbai Indians",
|
| 15 |
+
"Punjab Kings",
|
| 16 |
+
"Rajasthan Royals",
|
| 17 |
+
"Royal Challengers Bangalore",
|
| 18 |
+
"Sunrisers Hyderabad",
|
| 19 |
+
])
|
| 20 |
+
|
| 21 |
+
TEAM_ENC: dict[str, int] = {team: idx for idx, team in enumerate(TEAMS)}
|
| 22 |
+
|
| 23 |
+
# ββ IPL Venues (alphabetical order = encoding index) βββββββββββββββββββββββββ
|
| 24 |
+
VENUES = sorted([
|
| 25 |
+
"Arun Jaitley Stadium",
|
| 26 |
+
"Brabourne Stadium",
|
| 27 |
+
"DY Patil Stadium",
|
| 28 |
+
"Eden Gardens",
|
| 29 |
+
"Feroz Shah Kotla",
|
| 30 |
+
"MA Chidambaram Stadium",
|
| 31 |
+
"MCA Stadium",
|
| 32 |
+
"Maharashtra Cricket Association Stadium",
|
| 33 |
+
"Narendra Modi Stadium",
|
| 34 |
+
"Punjab Cricket Association Stadium",
|
| 35 |
+
"Rajiv Gandhi International Stadium",
|
| 36 |
+
"Sawai Mansingh Stadium",
|
| 37 |
+
"Wankhede Stadium",
|
| 38 |
+
])
|
| 39 |
+
|
| 40 |
+
VENUE_ENC: dict[str, int] = {venue: idx for idx, venue in enumerate(VENUES)}
|
| 41 |
+
|
| 42 |
+
# ββ Phase encoding ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 43 |
+
PHASE_LABELS = {
|
| 44 |
+
0: "Powerplay (Ov 1β6)",
|
| 45 |
+
1: "Middle Overs (Ov 7β15)",
|
| 46 |
+
2: "Death Overs (Ov 16β20)",
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def encode_team(name: str) -> int:
|
| 51 |
+
"""Return integer encoding for a team name. Defaults to 0 if not found."""
|
| 52 |
+
return TEAM_ENC.get(name, 0)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def encode_venue(name: str) -> int:
|
| 56 |
+
"""Return integer encoding for a venue name. Defaults to 0 if not found."""
|
| 57 |
+
return VENUE_ENC.get(name, 0)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def get_phase(over: int) -> int:
|
| 61 |
+
"""
|
| 62 |
+
Returns match phase (0/1/2) based on over number.
|
| 63 |
+
0 = Powerplay (overs 1β6)
|
| 64 |
+
1 = Middle overs(overs 7β15)
|
| 65 |
+
2 = Death overs (overs 16β20)
|
| 66 |
+
"""
|
| 67 |
+
if over < 6:
|
| 68 |
+
return 0
|
| 69 |
+
elif over < 15:
|
| 70 |
+
return 1
|
| 71 |
+
else:
|
| 72 |
+
return 2
|
utils/predictor.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
utils/predictor.py
|
| 3 |
+
Loads the four XGBoost models and exposes a single predict() method
|
| 4 |
+
that accepts a raw request dict and returns a structured result dict.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import pickle
|
| 9 |
+
import warnings
|
| 10 |
+
import pandas as pd
|
| 11 |
+
|
| 12 |
+
from utils.encoders import encode_team, encode_venue, get_phase, PHASE_LABELS
|
| 13 |
+
|
| 14 |
+
warnings.filterwarnings("ignore")
|
| 15 |
+
|
| 16 |
+
# ββ Model file paths ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 18 |
+
MODELS_DIR = os.path.join(BASE_DIR, "models")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class CricketPredictor:
|
| 22 |
+
"""
|
| 23 |
+
Loads all four pre-trained XGBoost models and provides
|
| 24 |
+
ball-level and match-level predictions.
|
| 25 |
+
|
| 26 |
+
Models
|
| 27 |
+
------
|
| 28 |
+
dot_model : DotBall.pkl β P(dot ball) binary
|
| 29 |
+
boundary_model : BoundaryModel.pkl β P(boundary 4/6) binary
|
| 30 |
+
run_model : RunPrediction.pkl β P(0..5 runs) multi-class
|
| 31 |
+
win_model : IPLchasingTeamWin.pklβ P(chase success) binary
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self):
|
| 35 |
+
print("Loading models...")
|
| 36 |
+
self.dot_model = self._load("DotBall.pkl")
|
| 37 |
+
self.boundary_model = self._load("BoundaryModel.pkl")
|
| 38 |
+
self.run_model = self._load("RunPrediction.pkl")
|
| 39 |
+
self.win_model = self._load("IPLchasingTeamWin.pkl")
|
| 40 |
+
print("β All 4 models loaded successfully.\n")
|
| 41 |
+
|
| 42 |
+
# ββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 43 |
+
|
| 44 |
+
def predict(self, data: dict) -> dict:
|
| 45 |
+
"""
|
| 46 |
+
Main prediction entry point.
|
| 47 |
+
|
| 48 |
+
Parameters
|
| 49 |
+
----------
|
| 50 |
+
data : dict Raw JSON body from the API request.
|
| 51 |
+
|
| 52 |
+
Returns
|
| 53 |
+
-------
|
| 54 |
+
dict with keys:
|
| 55 |
+
dot_ball_prob float β % probability of a dot ball
|
| 56 |
+
boundary_prob float β % probability of a 4 or 6
|
| 57 |
+
expected_runs float β weighted average expected runs
|
| 58 |
+
run_distribution list β [P(0), P(1), P(2), P(3), P(4), P(5)]
|
| 59 |
+
win_probability float|None β % win prob for chasing team (innings=2 only)
|
| 60 |
+
phase str β human-readable phase label
|
| 61 |
+
"""
|
| 62 |
+
# ββ 1. Parse & validate inputs ββββββββββββββββββββββββββββββββββββββββ
|
| 63 |
+
batting_team = str(data.get("batting_team", ""))
|
| 64 |
+
bowling_team = str(data.get("bowling_team", ""))
|
| 65 |
+
venue = str(data.get("venue", ""))
|
| 66 |
+
innings = int(data.get("innings", 1))
|
| 67 |
+
over = int(data.get("over", 1))
|
| 68 |
+
ball_in_over = int(data.get("ball_in_over", 1))
|
| 69 |
+
current_score = float(data.get("current_score", 0))
|
| 70 |
+
wickets_fallen = int(data.get("wickets_fallen", 0))
|
| 71 |
+
|
| 72 |
+
# Optional performance features
|
| 73 |
+
batter_sr = float(data.get("batter_sr", 130))
|
| 74 |
+
bowler_eco = float(data.get("bowler_eco", 7.5))
|
| 75 |
+
last_6_runs = float(data.get("last_6_runs", 6))
|
| 76 |
+
last_12_runs = float(data.get("last_12_runs", 12))
|
| 77 |
+
prev_runs = float(data.get("prev_runs", 1))
|
| 78 |
+
prev_wicket = int(data.get("prev_wicket", 0))
|
| 79 |
+
last_6_wickets = int(data.get("last_6_wickets", 0))
|
| 80 |
+
striker_enc = int(data.get("striker_enc", 0))
|
| 81 |
+
bowler_enc = int(data.get("bowler_enc", 0))
|
| 82 |
+
|
| 83 |
+
# ββ 2. Derived features βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 84 |
+
balls_bowled = over * 6 + ball_in_over
|
| 85 |
+
run_rate = round(current_score / max(balls_bowled, 1) * 6, 3)
|
| 86 |
+
phase = get_phase(over)
|
| 87 |
+
|
| 88 |
+
# ββ 3. Build ball-level feature DataFrame βββββββββββββββββββββββββββββ
|
| 89 |
+
ball_df = pd.DataFrame([{
|
| 90 |
+
"striker_enc" : striker_enc,
|
| 91 |
+
"bowler_enc" : bowler_enc,
|
| 92 |
+
"batting_team_enc" : encode_team(batting_team),
|
| 93 |
+
"bowling_team_enc" : encode_team(bowling_team),
|
| 94 |
+
"venue_enc" : encode_venue(venue),
|
| 95 |
+
"over" : over,
|
| 96 |
+
"ball_in_over" : ball_in_over,
|
| 97 |
+
"phase" : phase,
|
| 98 |
+
"current_score" : current_score,
|
| 99 |
+
"wickets_fallen" : wickets_fallen,
|
| 100 |
+
"run_rate" : run_rate,
|
| 101 |
+
"prev_runs" : prev_runs,
|
| 102 |
+
"prev_wicket" : prev_wicket,
|
| 103 |
+
"last_6_runs" : last_6_runs,
|
| 104 |
+
"last_12_runs" : last_12_runs,
|
| 105 |
+
"last_6_wickets" : last_6_wickets,
|
| 106 |
+
"batter_sr" : batter_sr,
|
| 107 |
+
"bowler_eco" : bowler_eco,
|
| 108 |
+
}])
|
| 109 |
+
|
| 110 |
+
# ββ 4. Run the three ball-level models ββββββββββββββββββββββββββββββββ
|
| 111 |
+
dot_prob = float(self.dot_model.predict_proba(ball_df)[0][1])
|
| 112 |
+
boundary_prob = float(self.boundary_model.predict_proba(ball_df)[0][1])
|
| 113 |
+
|
| 114 |
+
run_proba = self.run_model.predict_proba(ball_df)[0]
|
| 115 |
+
run_dist = [round(float(p), 4) for p in run_proba]
|
| 116 |
+
expected_runs = round(sum(i * run_dist[i] for i in range(len(run_dist))), 3)
|
| 117 |
+
|
| 118 |
+
# ββ 5. Win probability (2nd innings only) βββββββββββββββββββββββββββββ
|
| 119 |
+
win_prob = None
|
| 120 |
+
if innings == 2:
|
| 121 |
+
balls_remaining = int(data.get("balls_remaining", 60))
|
| 122 |
+
balls_done_chase = max(120 - balls_remaining, 1)
|
| 123 |
+
chase_rr = round(current_score / balls_done_chase * 6, 3)
|
| 124 |
+
|
| 125 |
+
win_df = pd.DataFrame([{
|
| 126 |
+
"batting_team" : encode_team(batting_team),
|
| 127 |
+
"bowling_team" : encode_team(bowling_team),
|
| 128 |
+
"venue" : encode_venue(venue),
|
| 129 |
+
"innings" : innings,
|
| 130 |
+
"current_score" : current_score,
|
| 131 |
+
"wickets_fallen" : wickets_fallen,
|
| 132 |
+
"balls_remaining": balls_remaining,
|
| 133 |
+
"run_rate" : chase_rr,
|
| 134 |
+
}])
|
| 135 |
+
win_prob = round(float(self.win_model.predict_proba(win_df)[0][1]) * 100, 1)
|
| 136 |
+
|
| 137 |
+
# ββ 6. Return structured result βββββββββββββββββββββββββββββββββββββββ
|
| 138 |
+
return {
|
| 139 |
+
"dot_ball_prob" : round(dot_prob * 100, 1),
|
| 140 |
+
"boundary_prob" : round(boundary_prob * 100, 1),
|
| 141 |
+
"expected_runs" : expected_runs,
|
| 142 |
+
"run_distribution": run_dist,
|
| 143 |
+
"win_probability" : win_prob,
|
| 144 |
+
"phase" : PHASE_LABELS[phase],
|
| 145 |
+
"run_rate" : round(run_rate, 2),
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
def models_loaded(self) -> list[str]:
|
| 149 |
+
return ["DotBall", "BoundaryModel", "RunPrediction", "IPLchasingTeamWin"]
|
| 150 |
+
|
| 151 |
+
# ββ Private helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 152 |
+
|
| 153 |
+
def _load(self, filename: str):
|
| 154 |
+
path = os.path.join(MODELS_DIR, filename)
|
| 155 |
+
if not os.path.exists(path):
|
| 156 |
+
raise FileNotFoundError(
|
| 157 |
+
f"Model file not found: {path}\n"
|
| 158 |
+
f"Make sure {filename} is inside the 'models/' folder."
|
| 159 |
+
)
|
| 160 |
+
with open(path, "rb") as f:
|
| 161 |
+
return pickle.load(f)
|