import csv import os from datetime import datetime from ..config import FIGHTS_CSV_PATH, FIGHTERS_CSV_PATH # --- ELO Configuration --- INITIAL_ELO = 1500 K_FACTOR = 40 # --- End Configuration --- def calculate_expected_score(rating1, rating2): """Calculates the expected score for player 1 against player 2.""" return 1 / (1 + 10 ** ((rating2 - rating1) / 400)) def update_elo(winner_elo, loser_elo): """Calculates the new ELO ratings for a win/loss scenario.""" expected_win = calculate_expected_score(winner_elo, loser_elo) change = K_FACTOR * (1 - expected_win) return winner_elo + change, loser_elo - change def update_elo_draw(elo1, elo2): """Calculates the new ELO ratings for a draw.""" expected1 = calculate_expected_score(elo1, elo2) change1 = K_FACTOR * (0.5 - expected1) expected2 = calculate_expected_score(elo2, elo1) change2 = K_FACTOR * (0.5 - expected2) return elo1 + change1, elo2 + change2 def process_fights_for_elo(fights_data=FIGHTS_CSV_PATH): """ Processes fights chronologically to calculate ELO scores. Accepts either a CSV file path or a pre-loaded list of fights. """ fights = [] if isinstance(fights_data, str): # If a string is passed, treat it as a file path if not os.path.exists(fights_data): print(f"Error: Fights data file not found at '{fights_data}'.") return None with open(fights_data, 'r', encoding='utf-8') as f: fights = list(csv.DictReader(f)) elif isinstance(fights_data, list): # If a list is passed, use it directly fights = fights_data else: print(f"Error: Invalid data type passed to process_fights_for_elo: {type(fights_data)}") return None # Sort fights by date to process them in chronological order. # This is crucial if loading from a file and a good safeguard if a list is passed. try: fights.sort(key=lambda x: datetime.strptime(x['event_date'], '%B %d, %Y')) except (ValueError, KeyError) as e: print(f"Error sorting fights by date: {e}") return None elos = {} for fight in fights: fighter1 = fight.get('fighter_1') fighter2 = fight.get('fighter_2') winner = fight.get('winner') # Initialize ELO for new fighters if fighter1 not in elos: elos[fighter1] = INITIAL_ELO if fighter2 not in elos: elos[fighter2] = INITIAL_ELO elo1 = elos[fighter1] elo2 = elos[fighter2] if winner == fighter1: elos[fighter1], elos[fighter2] = update_elo(elo1, elo2) elif winner == fighter2: elos[fighter2], elos[fighter1] = update_elo(elo2, elo1) elif winner == "Draw": elos[fighter1], elos[fighter2] = update_elo_draw(elo1, elo2) # NC (No Contest) fights do not affect ELO return elos def add_elo_to_fighters_csv(elos, fighters_csv_path=FIGHTERS_CSV_PATH): """ Adds the final ELO scores as a new column to the fighters CSV data. """ if not elos: print("No ELO data to process. Aborting.") return if not os.path.exists(fighters_csv_path): print(f"Error: Fighters data file not found at '{fighters_csv_path}'. Cannot add ELO column.") return rows = [] with open(fighters_csv_path, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) headers = reader.fieldnames # Ensure 'elo' column is added if not present if 'elo' not in headers: headers.append('elo') rows = list(reader) for row in rows: full_name = f"{row.get('first_name', '')} {row.get('last_name', '')}".strip() row['elo'] = round(elos.get(full_name, INITIAL_ELO)) with open(fighters_csv_path, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=headers) writer.writeheader() writer.writerows(rows) print(f"Successfully updated '{fighters_csv_path}' with ELO ratings.") def main(): print("--- Starting ELO Calculation ---") final_elos = process_fights_for_elo() if final_elos: add_elo_to_fighters_csv(final_elos) # Sort fighters by ELO and print the top 10 sorted_fighters = sorted(final_elos.items(), key=lambda item: item[1], reverse=True) print("\n--- Top 10 Fighters by ELO Rating ---") for i, (fighter, elo) in enumerate(sorted_fighters[:10]): print(f"{i+1}. {fighter}: {round(elo)}") print("------------------------------------")