Skip to content
Snippets Groups Projects
Commit dc658c30 authored by Marius Heidenreich's avatar Marius Heidenreich
Browse files

claude first version

parents
Branches
No related tags found
No related merge requests found
main.py 0 → 100644
import os
import csv
import math
import glob
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
import numpy as np
from datetime import datetime
# Constants for Elo calculation
K_FACTOR = 32 # Standard chess K-factor
INITIAL_ELO = 1500 # Starting Elo for all teams
class EloSystem:
def __init__(self, csv_directory, output_directory):
self.csv_directory = csv_directory
self.output_directory = output_directory
self.teams = {} # Dictionary to store team Elo ratings
self.match_history = [] # List to store all match results for history tracking
self.initialize_output_directory()
def initialize_output_directory(self):
"""Create output directory if it doesn't exist"""
os.makedirs(self.output_directory, exist_ok=True)
os.makedirs(os.path.join(self.output_directory, 'images'), exist_ok=True)
def load_and_process_csv_files(self):
"""Load all CSV files and process them to calculate Elo ratings"""
csv_files = glob.glob(os.path.join(self.csv_directory, '*.csv'))
if not csv_files:
print(f"No CSV files found in {self.csv_directory}")
return
print(f"Found {len(csv_files)} CSV files to process")
# Process each CSV file (representing a competition)
for csv_file in sorted(csv_files):
print(f"Processing {os.path.basename(csv_file)}...")
competition_name = os.path.splitext(os.path.basename(csv_file))[0]
teams_in_competition = []
# Read the CSV file
with open(csv_file, 'r', newline='', encoding='utf-8') as file:
reader = csv.reader(file)
# Skip header if exists (assuming first row might be headers)
try:
first_row = next(reader)
# If the first row doesn't start with a number, assume it's a header
if not first_row[0].strip().isdigit():
rank = 1
else:
# It's not a header, it's actual data
rank = int(first_row[0].strip())
team_name = first_row[1].strip() if len(first_row) > 1 else first_row[0].strip()
# Initialize the team if not seen before
if team_name not in self.teams:
self.teams[team_name] = {
'elo': INITIAL_ELO,
'competitions': 0,
'history': []
}
teams_in_competition.append(team_name)
except StopIteration:
# File is empty
continue
# Process remaining rows
for row in reader:
if not row: # Skip empty rows
continue
# Check if row starts with a number (ranking)
if row[0].strip().isdigit():
rank = int(row[0].strip())
team_name = row[1].strip() if len(row) > 1 else row[0].strip()
else:
# If no explicit ranking, use row index
team_name = row[0].strip()
# Initialize the team if not seen before
if team_name not in self.teams:
self.teams[team_name] = {
'elo': INITIAL_ELO,
'competitions': 0,
'history': []
}
teams_in_competition.append(team_name)
# Update competition count for each team
for team_name in teams_in_competition:
self.teams[team_name]['competitions'] += 1
# Update Elo ratings based on this competition's results
self.update_elo_for_competition(teams_in_competition, competition_name)
def update_elo_for_competition(self, teams_in_order, competition_name):
"""Update Elo ratings for all teams in a competition"""
# For each team, compare with teams ranked below them
date = datetime.now().strftime("%Y-%m-%d") # Use current date as competition date
for i in range(len(teams_in_order)):
for j in range(i + 1, len(teams_in_order)):
team_a = teams_in_order[i]
team_b = teams_in_order[j]
# Team A is ranked higher than Team B
self.update_elo_pair(team_a, team_b, 1, 0, competition_name, date)
def update_elo_pair(self, team_a, team_b, score_a, score_b, competition_name, date):
"""Update Elo ratings for a pair of teams based on their match outcome"""
# Get current Elo ratings
rating_a = self.teams[team_a]['elo']
rating_b = self.teams[team_b]['elo']
# Calculate expected scores
expected_a = self.expected_score(rating_a, rating_b)
expected_b = self.expected_score(rating_b, rating_a)
# Update Elo ratings
new_rating_a = rating_a + K_FACTOR * (score_a - expected_a)
new_rating_b = rating_b + K_FACTOR * (score_b - expected_b)
# Store match in history
match_info = {
'date': date,
'competition': competition_name,
'team_a': team_a,
'team_b': team_b,
'old_elo_a': rating_a,
'old_elo_b': rating_b,
'new_elo_a': new_rating_a,
'new_elo_b': new_rating_b,
'score_a': score_a,
'score_b': score_b
}
self.match_history.append(match_info)
# Update team Elo ratings and history
self.teams[team_a]['elo'] = new_rating_a
self.teams[team_b]['elo'] = new_rating_b
self.teams[team_a]['history'].append({'date': date, 'elo': new_rating_a, 'competition': competition_name})
self.teams[team_b]['history'].append({'date': date, 'elo': new_rating_b, 'competition': competition_name})
def expected_score(self, rating_a, rating_b):
"""Calculate the expected score for a player with rating_a against a player with rating_b"""
return 1 / (1 + math.pow(10, (rating_b - rating_a) / 400))
def generate_website(self):
"""Generate static HTML website with Elo ratings and visualizations"""
# Create main index page
self.create_index_page()
# Create team-specific pages
for team_name in self.teams:
self.create_team_page(team_name)
# Create visualizations
self.create_visualizations()
print(f"Website generated in {self.output_directory}")
def create_index_page(self):
"""Create the main index.html page"""
# Sort teams by Elo rating
sorted_teams = sorted(self.teams.items(), key=lambda x: x[1]['elo'], reverse=True)
html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Team Elo Rankings</title>
<style>
body {{
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}}
h1, h2 {{
color: #333;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}}
th, td {{
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}}
th {{
background-color: #f2f2f2;
font-weight: bold;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f5f5f5;
}}
.chart-container {{
margin-top: 30px;
display: flex;
justify-content: center;
}}
.chart-container img {{
max-width: 100%;
height: auto;
}}
a {{
color: #0066cc;
text-decoration: none;
}}
a:hover {{
text-decoration: underline;
}}
</style>
</head>
<body>
<h1>Team Elo Rankings</h1>
<p>Rankings based on {len(self.match_history)} matches from {self.count_competitions()} competitions.</p>
<div class="chart-container">
<img src="images/top_teams_elo.png" alt="Top Teams Elo Chart">
</div>
<h2>Current Rankings</h2>
<table>
<tr>
<th>Rank</th>
<th>Team</th>
<th>Elo Rating</th>
<th>Competitions</th>
</tr>
"""
# Add rows for each team
for rank, (team_name, team_data) in enumerate(sorted_teams, 1):
html_content += f"""
<tr>
<td>{rank}</td>
<td><a href="team_{self.clean_filename(team_name)}.html">{team_name}</a></td>
<td>{int(team_data['elo'])}</td>
<td>{team_data['competitions']}</td>
</tr>"""
html_content += """
</table>
<div class="chart-container">
<img src="images/elo_distribution.png" alt="Elo Distribution">
</div>
<p><small>Generated on {}</small></p>
</body>
</html>
""".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# Write the HTML file
with open(os.path.join(self.output_directory, 'index.html'), 'w', encoding='utf-8') as f:
f.write(html_content)
def create_team_page(self, team_name):
"""Create a dedicated page for a specific team"""
team_data = self.teams[team_name]
# Generate Elo history chart for this team
if team_data['history']:
self.create_team_elo_history_chart(team_name)
# Get matches involving this team
team_matches = [match for match in self.match_history
if match['team_a'] == team_name or match['team_b'] == team_name]
html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{team_name} - Elo Profile</title>
<style>
body {{
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}}
h1, h2 {{
color: #333;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}}
th, td {{
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}}
th {{
background-color: #f2f2f2;
font-weight: bold;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f5f5f5;
}}
.chart-container {{
margin-top: 30px;
display: flex;
justify-content: center;
}}
.chart-container img {{
max-width: 100%;
height: auto;
}}
a {{
color: #0066cc;
text-decoration: none;
}}
a:hover {{
text-decoration: underline;
}}
.summary-stats {{
display: flex;
justify-content: space-around;
margin: 20px 0;
flex-wrap: wrap;
}}
.stat-box {{
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
margin: 10px;
flex: 1;
min-width: 150px;
text-align: center;
background-color: #f9f9f9;
}}
.stat-value {{
font-size: 24px;
font-weight: bold;
color: #333;
}}
.stat-label {{
font-size: 14px;
color: #666;
}}
</style>
</head>
<body>
<h1>{team_name} - Elo Profile</h1>
<p><a href="index.html">← Back to Rankings</a></p>
<div class="summary-stats">
<div class="stat-box">
<div class="stat-value">{int(team_data['elo'])}</div>
<div class="stat-label">Current Elo</div>
</div>
<div class="stat-box">
<div class="stat-value">{team_data['competitions']}</div>
<div class="stat-label">Competitions</div>
</div>
"""
# Calculate win rate if applicable
if team_matches:
wins = sum(1 for match in team_matches
if (match['team_a'] == team_name and match['score_a'] > match['score_b'])
or (match['team_b'] == team_name and match['score_b'] > match['score_a']))
win_rate = (wins / len(team_matches)) * 100
html_content += f"""
<div class="stat-box">
<div class="stat-value">{len(team_matches)}</div>
<div class="stat-label">Total Matches</div>
</div>
<div class="stat-box">
<div class="stat-value">{win_rate:.1f}%</div>
<div class="stat-label">Win Rate</div>
</div>
"""
html_content += """
</div>
<div class="chart-container">
<img src="images/{}_history.png" alt="{} Elo History">
</div>
<h2>Match History</h2>
<table>
<tr>
<th>Date</th>
<th>Competition</th>
<th>Opponent</th>
<th>Result</th>
<th>Elo Change</th>
</tr>
""".format(self.clean_filename(team_name), team_name)
# Add rows for each match
for match in sorted(team_matches, key=lambda x: x['date'], reverse=True):
if match['team_a'] == team_name:
opponent = match['team_b']
old_elo = match['old_elo_a']
new_elo = match['new_elo_a']
result = "Win" if match['score_a'] > match['score_b'] else "Loss"
else:
opponent = match['team_a']
old_elo = match['old_elo_b']
new_elo = match['new_elo_b']
result = "Win" if match['score_b'] > match['score_a'] else "Loss"
elo_change = new_elo - old_elo
change_color = "green" if elo_change > 0 else "red"
html_content += f"""
<tr>
<td>{match['date']}</td>
<td>{match['competition']}</td>
<td><a href="team_{self.clean_filename(opponent)}.html">{opponent}</a></td>
<td>{result}</td>
<td style="color: {change_color}">{'+' if elo_change > 0 else ''}{elo_change:.1f}</td>
</tr>"""
html_content += """
</table>
<p><small>Generated on {}</small></p>
</body>
</html>
""".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# Write the HTML file
with open(os.path.join(self.output_directory, f'team_{self.clean_filename(team_name)}.html'), 'w',
encoding='utf-8') as f:
f.write(html_content)
def create_visualizations(self):
"""Create various visualizations for the website"""
# Top teams Elo chart
self.create_top_teams_chart()
# Elo distribution histogram
self.create_elo_distribution_chart()
def create_top_teams_chart(self):
"""Create a bar chart of the top teams by Elo rating"""
plt.figure(figsize=(12, 8))
# Get top 15 teams by Elo
top_teams = sorted(self.teams.items(), key=lambda x: x[1]['elo'], reverse=True)[:15]
team_names = [team[0] for team in top_teams]
elo_ratings = [team[1]['elo'] for team in top_teams]
# Create bar chart
bars = plt.barh(team_names, elo_ratings, color='skyblue')
# Add Elo values at the end of each bar
for i, bar in enumerate(bars):
plt.text(bar.get_width() + 5, bar.get_y() + bar.get_height() / 2,
f"{int(elo_ratings[i])}", va='center')
plt.xlabel('Elo Rating')
plt.title('Top Teams by Elo Rating')
plt.tight_layout()
# Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', 'top_teams_elo.png'), dpi=100)
plt.close()
def create_elo_distribution_chart(self):
"""Create a histogram of Elo rating distribution"""
plt.figure(figsize=(10, 6))
# Get all Elo ratings
elo_ratings = [team_data['elo'] for team_data in self.teams.values()]
# Create histogram
plt.hist(elo_ratings, bins=20, color='skyblue', edgecolor='black')
plt.xlabel('Elo Rating')
plt.ylabel('Number of Teams')
plt.title('Distribution of Team Elo Ratings')
plt.grid(axis='y', alpha=0.75)
plt.tight_layout()
# Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', 'elo_distribution.png'), dpi=100)
plt.close()
def create_team_elo_history_chart(self, team_name):
"""Create a line chart showing a team's Elo history over time"""
plt.figure(figsize=(10, 6))
team_history = self.teams[team_name]['history']
# Sort history by date
team_history.sort(key=lambda x: x['date'])
dates = [entry['date'] for entry in team_history]
elo_values = [entry['elo'] for entry in team_history]
# Create line chart
plt.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='blue')
# Add competition labels
for i, entry in enumerate(team_history):
plt.annotate(entry['competition'],
(i, entry['elo']),
textcoords="offset points",
xytext=(0, 10),
ha='center',
fontsize=8,
rotation=45)
plt.xticks(range(len(dates)), dates, rotation=45)
plt.ylabel('Elo Rating')
plt.title(f'{team_name} - Elo Rating History')
plt.grid(True, alpha=0.3)
plt.tight_layout()
# Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', f'{self.clean_filename(team_name)}_history.png'),
dpi=100)
plt.close()
def count_competitions(self):
"""Count the number of unique competitions"""
return len(set(match['competition'] for match in self.match_history))
def clean_filename(self, filename):
"""Clean a string to make it suitable for use as a filename"""
# Replace spaces and special characters
return ''.join(c if c.isalnum() else '_' for c in filename).lower()
def main():
"""Main function to run the Elo ranking system"""
import argparse
parser = argparse.ArgumentParser(description='Process competition results and generate Elo rankings website')
parser.add_argument('--input', default='data', help='Directory containing CSV result files')
parser.add_argument('--output', default='elo_website', help='Output directory for the website')
args = parser.parse_args()
# Create and run the Elo system
elo_system = EloSystem(args.input, args.output)
elo_system.load_and_process_csv_files()
elo_system.generate_website()
if __name__ == "__main__":
main()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment