Programming-Challenges/Practice Assignment 2/main.py
2026-08-31 20:08:56 +01:00

246 lines
8.2 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import sys
from collections import defaultdict
from datetime import datetime
# Constants for fitness levels
FITNESS_LEVELS = {
(0, 9.0): "Fitness level below pace for long games",
(9.1, 10.0): "Fitness level for amateur player",
(10.1, 11.0): "Fitness level for above average amateur player",
(11.1, 12.0): "Fitness level for elite player",
(12.1, 13.0): "Very high fitness level"
}
class ValidationError(Exception):
pass
class PlayerFitnessData:
def __init__(self, date, player, level, laps, speed, lap_time):
self.date = date
self.player = player
self.level = level
self.laps = laps
self.speed = speed
self.lap_time = lap_time
def __repr__(self):
return f"PlayerFitnessData({self.player}, {self.date})"
class PlayerFitnessAnalyzer:
def __init__(self, data_file):
self.data_file = data_file
self.player_data = defaultdict(list)
self.errors = []
def validate_data(self, date, player, level, laps, speed, lap_time):
# Validate date format (DD Mon)
if not isinstance(date, str) or len(date.split()) != 2:
raise ValidationError(f"Invalid date format: {date}. Expected 'DD Mon'")
if not isinstance(player, str) or not player.strip():
raise ValidationError("Player name cannot be empty")
try:
level = float(level)
if level < 0 or level > 13:
raise ValidationError(f"Level {level} out of range (0-13)")
except ValueError:
raise ValidationError(f"Level must be numeric: {level}")
try:
laps = int(laps)
if laps <= 0:
raise ValidationError(f"Laps must be positive: {laps}")
except ValueError:
raise ValidationError(f"Laps must be integer: {laps}")
try:
speed = float(speed)
if speed <= 0:
raise ValidationError(f"Speed must be positive: {speed}")
except ValueError:
raise ValidationError(f"Speed must be numeric: {speed}")
try:
lap_time = float(lap_time)
if lap_time <= 0:
raise ValidationError(f"Lap time must be positive: {lap_time}")
except ValueError:
raise ValidationError(f"Lap time must be numeric: {lap_time}")
return level, laps, speed, lap_time
def load_data(self):
"""
Load and parse player data from file.
Expected format (space/tab separated):
Date Player Level Laps Speed Lap Time(s)
11 Jan Harry Keane 9 11 12.5 5.76
...
"""
if not os.path.exists(self.data_file):
raise FileNotFoundError(f"Data file not found: {self.data_file}")
try:
with open(self.data_file, 'r') as f:
lines = f.readlines()
except IOError as e:
raise IOError(f"Cannot read file {self.data_file}: {e}")
if not lines:
raise ValidationError("Data file is empty")
# Skip header line if present
start_line = 0
if lines[0].strip().lower().startswith('date'):
start_line = 1
for line_num, line in enumerate(lines[start_line:], start=start_line + 1):
line = line.strip()
if not line or line.startswith('#'):
continue
try:
parts = line.split()
if len(parts) < 8:
self.errors.append(f"Line {line_num}: Not enough fields: {line}")
continue
date = f"{parts[0]} {parts[1]}"
player = f"{parts[2]} {parts[3]}"
level_str = parts[4]
laps_str = parts[5]
speed_str = parts[6]
lap_time_str = parts[7]
level, laps, speed, lap_time = self.validate_data(
date, player, level_str, laps_str, speed_str, lap_time_str
)
data = PlayerFitnessData(date, player, level, laps, speed, lap_time)
self.player_data[player].append(data)
except ValidationError as e:
self.errors.append(f"Line {line_num}: {str(e)}")
except (IndexError, ValueError) as e:
self.errors.append(f"Line {line_num}: Parse error: {str(e)}")
def get_fitness_level_description(self, average_level):
for (min_val, max_val), description in FITNESS_LEVELS.items():
if min_val <= average_level <= max_val:
return description
return "Unknown fitness level"
def analyze_player(self, player_name, weeks=4):
if player_name not in self.player_data:
return None
data_list = self.player_data[player_name]
if len(data_list) < weeks:
recent_data = data_list
weeks_used = len(data_list)
else:
recent_data = data_list[-weeks:]
weeks_used = weeks
if not recent_data:
return None
avg_level = sum(d.level for d in recent_data) / len(recent_data)
avg_laps = sum(d.laps for d in recent_data) / len(recent_data)
avg_speed = sum(d.speed for d in recent_data) / len(recent_data)
avg_lap_time = sum(d.lap_time for d in recent_data) / len(recent_data)
return {
'player': player_name,
'weeks_analyzed': weeks_used,
'average_level': round(avg_level, 2),
'average_laps': round(avg_laps, 2),
'average_speed': round(avg_speed, 2),
'average_lap_time': round(avg_lap_time, 2),
'fitness_description': self.get_fitness_level_description(avg_level)
}
def generate_report(self):
if not self.player_data:
print("No player data loaded.")
return
print("\n" + "=" * 80)
print("PENDLETOWN FS - MONTHLY FITNESS REPORT")
print("=" * 80)
for player_name in sorted(self.player_data.keys()):
summary = self.analyze_player(player_name)
if summary:
print(f"\nPlayer: {summary['player']}")
print("-" * 80)
print(f" Weeks Analyzed: {summary['weeks_analyzed']}")
print(f" Average Level: {summary['average_level']:.2f}")
print(f" Average Laps: {summary['average_laps']:.2f}")
print(f" Average Speed: {summary['average_speed']:.2f} km/h")
print(f" Average Lap Time: {summary['average_lap_time']:.2f} seconds")
print(f"\n Fitness Level: {summary['fitness_description']}")
print("\n" + "=" * 80 + "\n")
def print_errors(self):
if self.errors:
print("\n" + "!" * 80)
print("DATA VALIDATION ERRORS:")
print("!" * 80)
for error in self.errors:
print(f" {error}")
print("!" * 80 + "\n")
return True
return False
def main():
data_file = "player_data.txt"
if len(sys.argv) > 1:
data_file = sys.argv[1]
print(f"\nLoading player data from: {data_file}")
try:
analyzer = PlayerFitnessAnalyzer(data_file)
analyzer.load_data()
has_errors = analyzer.print_errors()
if analyzer.player_data:
analyzer.generate_report()
if has_errors:
print("Note: Some lines had errors but valid data was processed.\n")
else:
print("No valid data to process.")
sys.exit(1)
except FileNotFoundError as e:
print(f"ERROR: {e}")
sys.exit(1)
except IOError as e:
print(f"ERROR: {e}")
sys.exit(1)
except ValidationError as e:
print(f"ERROR: {e}")
sys.exit(1)
except Exception as e:
print(f"UNEXPECTED ERROR: {e}")
sys.exit(1)
if __name__ == "__main__":
main()