#!/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()