Select Git revision
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
main.py 30.68 KiB
import os
import csv
import math
import glob
from operator import truediv
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.colors import LinearSegmentedColormap
from pathlib import Path
import numpy as np
from datetime import datetime
import multiprocessing as mp
# Constants for Elo calculation
K_FACTOR = 32 # Standard chess K-factor
INITIAL_ELO = 1500 # Starting Elo for all teams
def expected_score(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 clean_filename(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 hex_to_normalized_rgb(hex_color):
# Remove the '#' symbol if present
hex_color = hex_color.lstrip('#')
# Convert the hex values to integers
red = int(hex_color[0:2], 16)
green = int(hex_color[2:4], 16)
blue = int(hex_color[4:6], 16)
# Normalize the values by dividing by 255
normalized_rgb = (red / 255, green / 255, blue / 255)
return normalized_rgb
class EloSystem:
def __init__(self, csv_directory, output_directory, competition_type):
self.competition_type = competition_type
self.events_directory = os.path.join(csv_directory, "events")
self.competition_meta_data_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.competition_history = {} # Dictionary to store competition results
self.initialize_output_directory()
self.competition_meta_data = self.load_competition_meta_data()
if self.competition_type == "electric":
self.BORDER_COLOR = "#ffff1e"
self.LINK_COLOR = "#ffc400"
self.LINK_COLOR_HOVER = "#cc9d00"
elif self.competition_type == "combustion":
self.BORDER_COLOR = "#ff8c00"
self.LINK_COLOR = "#ff4500"
self.LINK_COLOR_HOVER = "#cc3700"
else:
raise
def load_competition_meta_data(self):
if self.competition_type == "combustion":
meta_data = pd.read_csv(os.path.join(self.competition_meta_data_directory, "FSC.csv"))
elif self.competition_type == "electric":
meta_data = pd.read_csv(os.path.join(self.competition_meta_data_directory, "FSE.csv"))
else:
raise
meta_data.sort_values(by=['id'])
return meta_data
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 get_competition_name(self, id):
return self.competition_meta_data[self.competition_meta_data["id"] == int(id)]['abbr'].values[0].strip()
def get_competition_date(self, id):
date_string = self.competition_meta_data[self.competition_meta_data["id"] == int(id)]['date'].values[0]
return datetime.strptime(date_string, "%Y-%m-%d")
def get_competition_type(self, id):
return self.competition_meta_data[self.competition_meta_data["id"] == int(id)]['kind'].values[0].strip()
def load_and_process_csv_files(self):
"""Load all CSV files and process them to calculate Elo ratings"""
csv_files = self.competition_meta_data
csv_files = csv_files.sort_values('date')
csv_files = csv_files['id'].tolist()
csv_files = [os.path.join(self.events_directory, str(x) + ".csv") for x in csv_files]
print(f"Found {len(csv_files)} CSV files to process")
# Process each CSV file (representing a competition)
for csv_file in csv_files:
print(f"Processing {os.path.basename(csv_file)}...")
competition_id = os.path.basename(csv_file).split(".")[0]
teams_in_competition = []
# Read the CSV file
try:
with open(csv_file, 'r', newline='', encoding='utf-8') as file:
reader = csv.reader(file)
skipped = False
for row in reader:
if not skipped:
skipped = True
continue
if not row: # Skip empty rows
continue
rank = row[3].strip()
team_name = row[0].strip()
kind = row[2].strip()
if kind.lower() == self.competition_type:
# Initialize the team if not seen before
if team_name not in self.teams:
self.teams[team_name] = {
'elo': INITIAL_ELO,
'competitions': 0,
'history': [],
'delta_elo': 0
}
teams_in_competition.append((rank, team_name))
except FileNotFoundError:
print(f"file {csv_file} not found: skipping")
continue
# Sort teams by rank
teams_in_competition.sort(key=lambda x: int(x[0]))
# Store competition results
date = self.get_competition_date(os.path.basename(csv_file).split(".")[0])
self.competition_history[competition_id] = {
'date': date,
'results': teams_in_competition
}
# Extract just team names in order
team_names = [team[1] for team in teams_in_competition]
# Update competition count for each team
for team_name in team_names:
self.teams[team_name]['competitions'] += 1
# Update Elo ratings based on this competition's results
self.update_elo_for_competition(team_names, competition_id)
def update_elo_for_competition(self, teams_in_order, competition_id):
"""Update Elo ratings for all teams in a competition"""
# For each team, compare with teams ranked below them
date = self.get_competition_date(competition_id) # 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_id, date)
# Store post-competition Elo ratings and changes
for team in teams_in_order:
rank = teams_in_order.index(team) + 1
# Store in team history
self.teams[team]['history'].append({
'date': date.strftime("%Y-%m-%d"),
'competition': self.get_competition_name(competition_id),
'rank': rank,
'participants': len(teams_in_order),
'previous_elo': self.teams[team]['elo'],
'new_elo': self.teams[team]['elo'] + self.teams[team]['delta_elo'],
'elo_change': self.teams[team]['delta_elo']
})
self.teams[team]['elo'] = self.teams[team]['elo'] + self.teams[team]['delta_elo']
self.teams[team]['delta_elo'] = 0
def update_elo_pair(self, team_a, team_b, score_a, score_b, competition_id, 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 = expected_score(rating_a, rating_b)
expected_b = expected_score(rating_b, rating_a)
# Compute preliminary Elo ratings
delta_rating_a = K_FACTOR * (score_a - expected_a)
delta_rating_b = K_FACTOR * (score_b - expected_b)
# Store match in history
match_info = {
'date': date,
'competition': competition_id,
'team_a': team_a,
'team_b': team_b,
'old_elo_a': rating_a,
'old_elo_b': rating_b,
'delta_elo_a': delta_rating_a,
'delta_elo_b': delta_rating_b,
'score_a': score_a,
'score_b': score_b
}
self.match_history.append(match_info)
# Update team preliminary Elo ratings
self.teams[team_a]['delta_elo'] = self.teams[team_a]['delta_elo'] + delta_rating_a
self.teams[team_b]['delta_elo'] = self.teams[team_b]['delta_elo'] + delta_rating_b
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)
# If num_processes not specified, use number of CPU cores
num_processes = int(mp.cpu_count() / 2)
print(f"staring computation on {num_processes} cores")
# Create a pool of workers
pool = mp.Pool(processes=num_processes)
pool.map(self.create_team_page, self.teams)
# 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>Formula Student {self.competition_type} Elo Rankings</title>
<style>
body {{
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
min-height: 100vh;
margin: 0;
font-family: 'Helvetica Neue', sans-serif;
background: linear-gradient(135deg, #ebebeb, #c9c9c9);
animation: gradientAnimation 10s ease infinite;
background-size: 200% 200%;
padding: 20px;
}}
@keyframes gradientAnimation {{
0%, 100% {{
background-position: 0% 50%;
}}
50% {{
background-position: 100% 50%;
}}
}}
.content-container {{
max-width: 1200px;
width: 100%;
animation: fadeInUp 0.5s ease-out forwards;
}}
h1 {{
font-size: 40px;
text-transform: uppercase;
text-align: center;
color: #333;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
h2 {{
text-align: center;
color: #555;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
margin-top: 30px;
}}
p {{
text-align: center;
color: #777;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
th, td {{
padding: 12px 15px;
text-align: left;
}}
th {{
background-color: #f2f2f2;
color: #333;
font-weight: bold;
text-transform: uppercase;
border-bottom: 2px solid {self.BORDER_COLOR};
}}
tr {{
background-color: white;
transition: background-color 0.3s ease;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f5f5f5;
}}
.chart-container {{
margin: 30px 0;
display: flex;
justify-content: center;
width: 100%;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
background-color: white;
padding: 20px;
box-sizing: border-box;
}}
.chart-container img {{
max-width: 100%;
height: auto;
}}
a {{
color: {self.LINK_COLOR};
text-decoration: none;
transition: color 0.3s ease;
}}
a:hover {{
color: {self.LINK_COLOR_HOVER};
text-decoration: none;
}}
.timestamp {{
font-size: 12px;
color: #777;
text-align: center;
margin-top: 20px;
animation: fadeInUp 0.5s ease-out 0.7s forwards;
opacity: 0;
}}
.nav-button {{
margin-top: 30px;
padding: 1em 2em;
font-size: larger;
text-transform: uppercase;
width: 12em;
text-align: center;
border: none;
border-radius: 5px;
background: linear-gradient(135deg, #ffff1e, #ffc400);
color: black;
cursor: pointer;
box-shadow: 0 10px 20px rgba(255, 255, 30, 0.3);
animation: fadeInUp 0.5s ease-out 0.8s forwards;
opacity: 0;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}}
.nav-button:hover {{
box-shadow: 0 15px 30px rgba(255, 255, 30, 0.4);
transform: translateY(-2px);
}}
.nav-button::before {{
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.7s ease;
}}
.nav-button:hover::before {{
left: 100%;
}}
@keyframes fadeInUp {{
from {{
opacity: 0;
transform: translateY(20px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
</style>
</head>
<body>
<div class="content-container">
<h1>Formula Student electric Elo Rankings</h1>
<p>Rankings based on 126 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_{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>
<div class="timestamp">
<p>Generated on {}</p>
</div>
<div style="display: flex; justify-content: center;">
<a href="index.html"><button class="nav-button">Back to Home</button></a>
</div>
</div>
</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"""
print(f"Generating Team site for {team_name}")
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)
# Filter history for this team's competitions
team_competitions = sorted(team_data['history'], key=lambda x: x['date'], 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_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;
animation: fadeInUp 0.5s ease-out forwards;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
th, td {{
padding: 12px 15px;
text-align: left;
}}
th {{
background-color: #f2f2f2;
color: #333;
font-weight: bold;
text-transform: uppercase;
border-bottom: 2px solid {self.BORDER_COLOR};
}}
tr {{
background-color: white;
transition: background-color 0.3s ease;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f5f5f5;
}}
p {{
animation: fadeInUp 0.5s ease-out forwards;
}}
.chart-container {{
margin-top: 30px;
display: flex;
justify-content: center;
width: 100%;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
background-color: white;
padding: 20px;
box-sizing: border-box;
}}
.chart-container img {{
max-width: 100%;
height: auto;
}}
a {{
color: {self.LINK_COLOR};
text-decoration: none;
transition: color 0.3s ease;
}}
a:hover {{
color: {self.LINK_COLOR_HOVER};
text-decoration: none;
}}
.summary-stats {{
display: flex;
justify-content: space-around;
margin: 20px 0;
flex-wrap: wrap;
animation: fadeInUp 0.5s ease-out forwards;
}}
.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;
}}
.positive {{
color: green;
}}
.negative {{
color: red;
}}
@keyframes fadeInUp {{
from {{
opacity: 0;
transform: translateY(20px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
</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 average ranking if available
if team_competitions:
avg_rank = sum(comp['rank'] for comp in team_competitions) / len(team_competitions)
best_rank = min(comp['rank'] for comp in team_competitions)
html_content += f"""
<div class="stat-box">
<div class="stat-value">{avg_rank:.1f}</div>
<div class="stat-label">Avg. Placement</div>
</div>
<div class="stat-box">
<div class="stat-value">{best_rank}</div>
<div class="stat-label">Best Placement</div>
</div>
"""
html_content += """
</div>
<div class="chart-container">
<img src="images/{}_history.png" alt="{} Elo History">
</div>
<h2>Competition History</h2>
<table>
<tr>
<th>Date</th>
<th>Competition</th>
<th>Placement</th>
<th>Elo Before</th>
<th>Elo After</th>
<th>Elo Change</th>
</tr>
""".format(clean_filename(team_name), team_name)
# Add rows for each competition
for comp in team_competitions:
elo_change = comp['elo_change']
change_class = "positive" if elo_change > 0 else "negative" if elo_change < 0 else ""
html_content += f"""
<tr>
<td>{comp['date']}</td>
<td>{comp['competition']}</td>
<td>{comp['rank']} / {comp['participants']}</td>
<td>{int(comp['previous_elo'])}</td>
<td>{int(comp['new_elo'])}</td>
<td class="{change_class}">{'+' 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_{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"""
fig = plt.figure(figsize=(12, 8))
ax = fig.add_axes([0.3, 0.1, 0.6, 0.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='none')
# 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')
# Create custom colormap
colors = [hex_to_normalized_rgb(self.LINK_COLOR), hex_to_normalized_rgb(self.BORDER_COLOR)] # RGB values for #ffff1e and #ffc400
custom_yellow_cmap = LinearSegmentedColormap.from_list('custom_yellow', colors)
for bar in bars:
# Get the coordinates of the bar
x0, y0 = bar.get_xy()
width = bar.get_width()
height = bar.get_height()
# Create a gradient for the bar
gradient = np.linspace(0, 1, 100).reshape(1, -1)
# Get colors from the colormap
colors = custom_yellow_cmap(gradient)
# Set the bar's face color to the gradient
bar.set_facecolor('none') # Remove default color
# Add the gradient rectangle
gradient_rect = patches.Rectangle((x0, y0), width, height,
linewidth=0,
fill=True,
facecolor=custom_yellow_cmap(0.5))
# To create a gradient, add an axial gradient transform
gradient_rect.set_facecolor('none')
ax.add_patch(gradient_rect)
# Create multiple thin rectangles to simulate a gradient
steps = 50
for i in range(steps):
left = x0 + (i/steps) * width
rect_width = width/steps
color = custom_yellow_cmap(i/steps)
rect = patches.Rectangle((left, y0), rect_width, height,
linewidth=0,
color=color)
ax.add_patch(rect)
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 custom yellow gradient colormap (#ffff1e to #ffc400)
colors = [hex_to_normalized_rgb(self.LINK_COLOR), hex_to_normalized_rgb(self.BORDER_COLOR)]
custom_yellow_cmap = LinearSegmentedColormap.from_list('custom_yellow', colors)
# Create histogram
counts, bin_edges, _ = plt.hist(elo_ratings, bins=20, alpha=0)
# Draw bars with gradients
bin_width = bin_edges[1] - bin_edges[0]
for i in range(len(counts)):
# For each bar in the histogram
left = bin_edges[i]
bottom = 0
width = bin_width
height = counts[i]
# Create the gradient effect by stacking multiple thin rectangles
gradient_steps = 50
for step in range(gradient_steps):
# Calculate position and width of each thin rectangle
rect_left = left
rect_width = width
# Calculate height of this piece of the gradient
rect_bottom = bottom + (step / gradient_steps) * height
rect_height = height / gradient_steps
# Get color from our custom colormap
color = custom_yellow_cmap(step / gradient_steps)
# Add the rectangle patch
rect = patches.Rectangle(
(rect_left, rect_bottom),
rect_width,
rect_height,
linewidth=0,
facecolor=color,
edgecolor=None
)
plt.gca().add_patch(rect)
# Add black edges to each bar
for i in range(len(counts)):
left = bin_edges[i]
width = bin_width
height = counts[i]
rect = patches.Rectangle(
(left, 0),
width,
height,
fill=False,
edgecolor='black',
linewidth=1
)
plt.gca().add_patch(rect)
# Set axis limits to make sure the entire histogram is visible
plt.xlim(bin_edges[0], bin_edges[-1])
plt.ylim(0, max(counts) * 1.1)
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['new_elo'] for entry in team_history]
# Create line chart
plt.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='gray')
# Add competition labels
for i, entry in enumerate(team_history):
plt.annotate(entry['competition'] + f" (#{entry['rank']})",
(i, entry['new_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'{clean_filename(team_name)}_history.png'),
dpi=100)
plt.close()
def main():
"""Main function to run the Elo ranking system"""
import argparse
from time import time
parser = argparse.ArgumentParser(description='Process competition results and generate Elo rankings website')
parser.add_argument('--input', default='csv_files', help='Directory containing CSV result files')
parser.add_argument('--output', default='public/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 + "_combustion", "combustion")
elo_system.load_and_process_csv_files()
elo_system.generate_website()
elo_system = EloSystem(args.input, args.output + "_electric", "electric")
elo_system.load_and_process_csv_files()
elo_system.generate_website()
if __name__ == "__main__":
main()