|
|
import numpy as np |
|
|
import pandas as pd |
|
|
from sklearn.preprocessing import MinMaxScaler |
|
|
from typing import Tuple, Dict, Optional |
|
|
import pandas as pd |
|
|
import random |
|
|
|
|
|
from config import ( |
|
|
FIGHT_STATS_PATH, |
|
|
FIGHTER_STATS_PATH, |
|
|
FIGHTER_DETAILS_PATH, |
|
|
MODEL_DATA_PATH |
|
|
) |
|
|
|
|
|
class FightPredictor: |
|
|
def __init__(self, model): |
|
|
self.model = model |
|
|
self._load_data() |
|
|
|
|
|
def _load_data(self): |
|
|
"""Load required datasets""" |
|
|
self.df = pd.read_csv(FIGHT_STATS_PATH) |
|
|
self.df_fighters = pd.read_csv(FIGHTER_STATS_PATH) |
|
|
self.df_fighters_details = pd.read_csv(FIGHTER_DETAILS_PATH, parse_dates=['DOB']) |
|
|
self.df_model = pd.read_csv(MODEL_DATA_PATH, parse_dates=True) |
|
|
|
|
|
|
|
|
today = pd.Timestamp.today() |
|
|
self.df_fighters_details['AGE'] = self.df_fighters_details['DOB'].apply( |
|
|
lambda x: (today - pd.Timestamp(x)).days / 365.25 |
|
|
).round(1) |
|
|
|
|
|
def _validate_fighters(self, f1: str, f2: str): |
|
|
"""Validate that both fighters exist in dataset""" |
|
|
for fighter in [f1, f2]: |
|
|
if fighter not in self.df_fighters['FIGHTER'].values: |
|
|
raise ValueError(f"Fighter '{fighter}' not found in database") |
|
|
|
|
|
def _get_fighter_stats(self, f1: str, f2: str, verbose: bool) -> Tuple[np.ndarray, Dict]: |
|
|
"""Get fighter statistics and compute input features""" |
|
|
f1_df = self.df_fighters.loc[self.df_fighters['FIGHTER'] == f1] |
|
|
f2_df = self.df_fighters.loc[self.df_fighters['FIGHTER'] == f2] |
|
|
|
|
|
|
|
|
agediff = ( |
|
|
self.df_fighters_details[self.df_fighters_details['FIGHTER'] == f1]['AGE'].values[0] - |
|
|
self.df_fighters_details[self.df_fighters_details['FIGHTER'] == f2]['AGE'].values[0] |
|
|
) |
|
|
|
|
|
|
|
|
form_scores = [f1_df['form_skore_fighter'].values[0], f2_df['form_skore_fighter'].values[0]] |
|
|
no_of_fights = [f1_df['Fights'].values[0], f2_df['Fights'].values[0]] |
|
|
W_D_NC = ( |
|
|
f1_df[['Win', 'DRAW', 'No_contest']].values.tolist()[0] + |
|
|
f2_df[['Win', 'DRAW', 'No_contest']].values.tolist()[0] |
|
|
) |
|
|
|
|
|
|
|
|
stats_f1, stats_f2 = [], [] |
|
|
for col in self.df_fighters.columns[10:]: |
|
|
splited = col.split('_') |
|
|
if 'CTRL' in splited: |
|
|
stats_f1.append((f1_df[col] / f1_df['TotalTime']).values[0]) |
|
|
stats_f2.append((f2_df[col] / f2_df['TotalTime']).values[0]) |
|
|
if 'attemps' in splited: |
|
|
stats_f1.append((f1_df[col.replace('attemps', 'landed')] / f1_df[col]).values[0]) |
|
|
stats_f1.append((f1_df[col.replace('attemps', 'landed')] / f1_df['TotalTime']).values[0] * 300) |
|
|
stats_f2.append((f2_df[col.replace('attemps', 'landed')] / f2_df[col]).values[0]) |
|
|
stats_f2.append((f2_df[col.replace('attemps', 'landed')] / f2_df['TotalTime']).values[0] * 300) |
|
|
|
|
|
stats_list = stats_f1 + stats_f2 |
|
|
|
|
|
|
|
|
vstup = np.array([1] + |
|
|
[f1_df.iloc[0][col] - f2_df.iloc[0][col] for col in ['HEIGHT_fighter', 'REACH_fighter']] + |
|
|
[agediff] + form_scores + no_of_fights + W_D_NC + stats_list |
|
|
) |
|
|
|
|
|
|
|
|
details = {} |
|
|
if verbose: |
|
|
details = { |
|
|
"age_difference": f"{agediff:.1f}", |
|
|
f"{f1}_form_score": f"{form_scores[0]:.2f}", |
|
|
f"{f2}_form_score": f"{form_scores[1]:.2f}", |
|
|
f"{f1}_total_fights": int(no_of_fights[0]), |
|
|
f"{f2}_total_fights": int(no_of_fights[1]) |
|
|
} |
|
|
|
|
|
return vstup, details |
|
|
|
|
|
def _scale_input(self, vstup: np.ndarray) -> np.ndarray: |
|
|
"""Scale input features""" |
|
|
scaler = MinMaxScaler(feature_range=(0, 1)) |
|
|
combined_df = pd.concat( |
|
|
[self.df_model, pd.DataFrame([vstup], columns=self.df_model.columns)], |
|
|
ignore_index=True |
|
|
) |
|
|
vstup_scaled = scaler.fit_transform(combined_df.iloc[:, 1:])[-200:, :] |
|
|
return np.nan_to_num(vstup_scaled) |
|
|
|
|
|
def get_random_fighters(self) -> Tuple[str, str]: |
|
|
"""Select two random fighters from the database""" |
|
|
|
|
|
all_fighters = self.df_fighters['FIGHTER'].unique().tolist() |
|
|
|
|
|
|
|
|
fighter1 = random.choice(all_fighters) |
|
|
|
|
|
fighter2 = random.choice([f for f in all_fighters if f != fighter1]) |
|
|
|
|
|
return fighter1, fighter2 |
|
|
|
|
|
def get_prediction(self, f1: str, f2: str, verbose: bool = False) -> Optional[Tuple[Dict, Dict, Dict]]: |
|
|
""" |
|
|
Generate fight prediction between two fighters |
|
|
|
|
|
Args: |
|
|
f1: Name of first fighter (or None for random) |
|
|
f2: Name of second fighter (or None for random) |
|
|
verbose: Whether to return additional details |
|
|
|
|
|
Returns: |
|
|
Tuple of (fighter1_dict, fighter2_dict, details_dict) |
|
|
Returns None if prediction fails |
|
|
""" |
|
|
try: |
|
|
|
|
|
if not f1 and not f2: |
|
|
f1, f2 = self.get_random_fighters() |
|
|
|
|
|
|
|
|
self._validate_fighters(f1, f2) |
|
|
|
|
|
|
|
|
vstup, raw_details = self._get_fighter_stats(f1, f2, verbose=True) |
|
|
vstup_scaled = self._scale_input(vstup) |
|
|
|
|
|
|
|
|
new_data = np.reshape(vstup_scaled, (1, 200, vstup_scaled.shape[1])) |
|
|
pred_1 = self.model.predict(new_data, verbose=0) |
|
|
|
|
|
|
|
|
vstup_rev, _ = self._get_fighter_stats(f2, f1, False) |
|
|
vstup_rev_scaled = self._scale_input(vstup_rev) |
|
|
new_data_rev = np.reshape(vstup_rev_scaled, (1, 200, vstup_rev_scaled.shape[1])) |
|
|
pred_2 = self.model.predict(new_data_rev, verbose=0) |
|
|
|
|
|
|
|
|
f1_prob = float(((1 - pred_1) + pred_2) / 2) |
|
|
f2_prob = round(1 - f1_prob, 4) |
|
|
f1_prob = round(f1_prob, 4) |
|
|
|
|
|
|
|
|
fighter1_data = { |
|
|
'name': f1, |
|
|
'form_score': raw_details.get(f"{f1}_form_score", "0.00"), |
|
|
'total_fights': int(raw_details.get(f"{f1}_total_fights", 0)), |
|
|
'win_percentage': f"{f1_prob * 100:.2f}%", |
|
|
'prob': f1_prob |
|
|
} |
|
|
|
|
|
fighter2_data = { |
|
|
'name': f2, |
|
|
'form_score': raw_details.get(f"{f2}_form_score", "0.00"), |
|
|
'total_fights': int(raw_details.get(f"{f2}_total_fights", 0)), |
|
|
'win_percentage': f"{f2_prob * 100:.2f}%", |
|
|
'prob': f2_prob |
|
|
} |
|
|
|
|
|
details = { |
|
|
'age_difference': raw_details.get("age_difference", "0.0"), |
|
|
} |
|
|
|
|
|
return fighter1_data, fighter2_data, details |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Prediction failed: {e}") |
|
|
return None |