Assignment 2
This commit is contained in:
parent
9207cc2eb1
commit
53ea335f9e
@ -17,7 +17,6 @@ def transcript(text: str) -> str:
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Rot13App(App):
|
||||
"""A Textual app for ROT13 encryption and decryption."""
|
||||
|
||||
|
||||
246
Practice Assignment 2/main.py
Executable file
246
Practice Assignment 2/main.py
Executable file
@ -0,0 +1,246 @@
|
||||
#!/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()
|
||||
21
Practice Assignment 2/player_data.txt
Normal file
21
Practice Assignment 2/player_data.txt
Normal file
@ -0,0 +1,21 @@
|
||||
Date Player Level Laps Speed Lap Time(s)
|
||||
11 Jan Harry Keane 9 11 12.5 5.76
|
||||
11 Jan Nat Sturridge 8 10 11.5 6.55
|
||||
11 Jan Dwayne Vardy 10 11 13 5.54
|
||||
11 Jan Kyle Baker 8 10 12 6
|
||||
11 Jan Elliott Ramsey 10 11 13 5.54
|
||||
18 Jan Harry Keane 9.2 11 12.6 5.74
|
||||
18 Jan Nat Sturridge 8.1 10 11.6 6.52
|
||||
18 Jan Dwayne Vardy 10.1 11 13.1 5.53
|
||||
18 Jan Kyle Baker 8.3 11 12.2 5.95
|
||||
18 Jan Elliott Ramsey 10.2 11 13.1 5.52
|
||||
25 Jan Harry Keane 9.4 12 12.8 5.70
|
||||
25 Jan Nat Sturridge 8.3 11 11.8 6.48
|
||||
25 Jan Dwayne Vardy 10.3 12 13.2 5.50
|
||||
25 Jan Kyle Baker 8.5 11 12.4 5.90
|
||||
25 Jan Elliott Ramsey 10.4 12 13.2 5.50
|
||||
01 Feb Harry Keane 9.6 12 13 5.68
|
||||
01 Feb Nat Sturridge 8.5 11 12 6.45
|
||||
01 Feb Dwayne Vardy 10.5 12 13.3 5.48
|
||||
01 Feb Kyle Baker 8.7 12 12.6 5.85
|
||||
01 Feb Elliott Ramsey 10.6 12 13.3 5.48
|
||||
1
test/.python-version
Normal file
1
test/.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.14
|
||||
0
test/README.md
Normal file
0
test/README.md
Normal file
17
test/main.py
Executable file
17
test/main.py
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import typer
|
||||
from typing import Annotated
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@app.command()
|
||||
def goodbye(name: Annotated[str, typer.Option(prompt="What is your name?")] = "World"):
|
||||
typer.echo(f"goodbye {name}")
|
||||
|
||||
@app.command()
|
||||
def main():
|
||||
typer.echo(f"Hello")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
19
test/pyproject.toml
Normal file
19
test/pyproject.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Uthman Fatih", email = "kingvcheese@gmail.com" }
|
||||
]
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"typer>=0.27.2",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
test = "test:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.12.7,<0.13.0"]
|
||||
build-backend = "uv_build"
|
||||
2
test/src/test/__init__.py
Normal file
2
test/src/test/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
def main() -> None:
|
||||
print("Hello from test!")
|
||||
99
test/uv.lock
generated
Normal file
99
test/uv.lock
generated
Normal file
@ -0,0 +1,99 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.14"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "15.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "typer", specifier = ">=0.27.2" }]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.27.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "rich" },
|
||||
{ name = "shellingham" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" },
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user