From 2a521e9016e1b5a3f8daef3051b5a94aa5fb0715 Mon Sep 17 00:00:00 2001 From: Anthony Correa Date: Tue, 26 Aug 2025 16:42:19 -0500 Subject: [PATCH] Update calendar generation and data normalization; add read check command - Add config file support for calendar colors and logos in generate_calendar - Extend normalization mappings in normalization.toml for fields and teams - Add 'read check' command to summarize game and field data from input files - Fix normalization to correctly handle visitor field and value lookups - Update launch configurations for new data and options --- .vscode/launch.json | 22 +++++++++++--- normalization.toml | 18 +++++++++++ src/apps/generate/calendar.py | 5 ++-- src/apps/generate/calendar_utils.py | 46 ++++++++++++++++------------- src/apps/read/read.py | 44 ++++++++++++++++++++++++++- src/utils/normalize.py | 10 +++---- 6 files changed, 112 insertions(+), 33 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8a7c5b0..15e2dc7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,8 @@ "args": [ "convert", "sportspress", - "data/2025-hounds.csv", - "data/output/out.csv", + "/Users/asc/Downloads/2025 CSYBA vs CMBA SCHEDULE.xlsx", + "data/output/2025-hounds.csv", ], "console": "integratedTerminal", "justMyCode": true @@ -27,7 +27,8 @@ "args": [ "generate", "calendar", - "./data/2025-hounds.csv" + "./data/2025-hounds.csv", + "-c", "./data/output/calendar_config.toml" ], "console": "integratedTerminal", "justMyCode": true @@ -41,11 +42,24 @@ "args": [ "generate", "calendar-config", - "./data/2024-hounds.csv", + "./data/2025-hounds.csv", "./data/output/" ], "console": "integratedTerminal", "justMyCode": true + }, + { + "name": "read check", + "type": "debugpy", + "request": "launch", + "cwd": "${workspaceFolder}", + "module": "src", + "args": [ + "read", "check", + "./data/2024.csv" + ], + "console": "integratedTerminal", + "justMyCode": true } ] } \ No newline at end of file diff --git a/normalization.toml b/normalization.toml index b7342c7..aec7b14 100644 --- a/normalization.toml +++ b/normalization.toml @@ -40,6 +40,18 @@ normalized = "Taft High School" [[field.values]] original = ["Southwest"] normalized = "Southwest Park" +[[field.values]] +original = ["Comed", "COMED", "ComEd"] +normalized = "ComEd Recreation Center" +[[field.values]] +original = ["Laramie"] +normalized = "Laramie Park" +[[field.values]] +original = ["78"] +normalized = "The 78" +[[field.values]] +original = ["Kilbourn"] +normalized = "Kilbourn Park" [results] potential_keys = ["Final Score", "Score", "Result", "Outcome"] @@ -54,4 +66,10 @@ normalized = "Red Sox" [[team.values]] original = ["NorthSide White Sox"] normalized = "North Side White Sox" +[[team.values]] +original = ["Chicago Rebels", "CHICAGO REBELS"] +normalized = "Rebels" +[[team.values]] +original = ["Lombard Expors", "LOMBARD EXPORS"] +normalized = "Lombard Expos" diff --git a/src/apps/generate/calendar.py b/src/apps/generate/calendar.py index ca4685e..a4fdd84 100644 --- a/src/apps/generate/calendar.py +++ b/src/apps/generate/calendar.py @@ -1,6 +1,6 @@ import typer from rich.console import Console -from typing import Annotated, List +from typing import Annotated, List, Optional from pathlib import Path from ...utils.sportspress import validate_keys from ...utils.normalize import normalize_header_key, load_config @@ -15,13 +15,14 @@ app = typer.Typer() @app.command(name="calendar") def generate_calendar_app( input_file: Annotated[List[Path], typer.Argument(..., help="Path(s) to the CSV file")], + config_file: Annotated[Optional[typer.FileText], typer.Option(..., "--config", "-c", help="Path to a config file")]=None ): # Read CSV data data = read_and_normalize_csv_or_xlsx(input_file) data = personalize_data_for_team(data, "Hounds") # data = parse_datetime(data) - generate_calendar(data) + generate_calendar(data, config_file) pass @app.command(name="calendar-config") diff --git a/src/apps/generate/calendar_utils.py b/src/apps/generate/calendar_utils.py index 7186347..2c1b530 100644 --- a/src/apps/generate/calendar_utils.py +++ b/src/apps/generate/calendar_utils.py @@ -3,6 +3,7 @@ from calendar import Calendar from PIL import Image, ImageDraw, ImageFont from typing import Tuple from pathlib import Path +import toml calendar_cell_size = (400, 500) calendar_cell_width, calendar_cell_height = calendar_cell_size @@ -72,10 +73,13 @@ def calendar_cell( return cell_img -def generate_calendar(data): +def generate_calendar(data, config_file=None): result_calendar = Calendar() result_calendar.setfirstweekday(6) baseball_bat = Image.open(f"data/logos/baseball_bat_2.png") + if config_file: + config = toml.load(config_file) + baseball_bat = baseball_bat.resize((90, 90)) for year, month in {(row['datetime'].year, row['datetime'].month) for row in data}: month_days=list(result_calendar.monthdayscalendar(year, month)) @@ -83,21 +87,15 @@ def generate_calendar(data): first_thursday=(month, [w[4] for w in month_days if w[4] != 0][0]) colors = { - # 'proviso-west': (139, 69, 19, 256), - 'winnemac': (37, 89, 164, 256), - # 'walther': (251, 231, 77, 256), - # 'taft': (64, 119, 0, 256), - 'southwest': (230, 136, 60, 256), - # 'maywood': (107, 5, 4, 256), - # 'ozinga': (170, 143, 102, 256), - # 'simeon':(167,192,226), - 'Skokie':(72,159,88), - # 'comed':(206,45,137), - 'default': (0, 0, 0, 256), - 'Loyola': (206,45,137), - 'Piotrowski': (251, 231, 77, 256), - 'Baseball Alley': (167,192,226), + 'default': (128, 128, 128, 256), } + team_logos={} + + if config: + for field, field_options in config['fields'].items(): + colors[field] = tuple(field_options.get('bg_color', colors.get('default'))) + for team, team_options in config['teams'].items(): + team_logos[team] = team_options.get('logo') for week_num, week in enumerate(month_days): for day_num, date in enumerate(week): @@ -111,9 +109,9 @@ def generate_calendar(data): # Gen square that has one game if len (filtered_data) == 1: game = filtered_data[0] - opponent_logo_path = Path(f"data/logos/{game['opponent'].lower()}.png") - if opponent_logo_path.exists(): - opponent_logo = Image.open(f"data/logos/{game['opponent'].lower()}.png") + opponent_logo_path = team_logos.get(game['opponent']) + if opponent_logo_path and (opponent_logo_path := Path(opponent_logo_path)) and opponent_logo_path.exists(): + opponent_logo = Image.open(opponent_logo_path) else: opponent_logo = text_rectangle(text=game['opponent'][0].upper(),width=500, height=500, font_size=400, font="data/fonts/college.ttf") is_home_game = game['homevisitor'] == "home" @@ -124,7 +122,7 @@ def generate_calendar(data): img = calendar_cell( height=calendar_cell_height, width=calendar_cell_width, - background_color=colors[game['field']], + background_color=colors.get(game['field'], colors['default']), left_top_corner = text_rectangle('H' if is_home_game else "A", "data/fonts/refrigerator-deluxe-bold.otf", 80, @@ -134,7 +132,7 @@ def generate_calendar(data): width=calendar_cell_width*.2), right_top_corner = date_text_image, center = opponent_logo.resize((int(opponent_logo.width*.5), int(opponent_logo.height*.5))), - bottom_center = text_rectangle(f"{game['time']:%-I:%M}", + bottom_center = text_rectangle(f"{game['time']:%-I:%M}" if game.get('time') else "", "data/fonts/refrigerator-deluxe-bold.otf", 120, foreground_color='white', @@ -145,10 +143,16 @@ def generate_calendar(data): # img.show() elif len(filtered_data) == 2: game1, game2 = filtered_data[:2] + opponent_logo_path = team_logos.get(game1['opponent']) + if opponent_logo_path and (opponent_logo_path := Path(opponent_logo_path)) and opponent_logo_path.exists(): + opponent_logo = Image.open(opponent_logo_path) + else: + opponent_logo = text_rectangle(text=game1['opponent'][0].upper(),width=500, height=500, font_size=400, font="data/fonts/college.ttf") + img = calendar_cell( height=calendar_cell_height, width=calendar_cell_width, - background_color='red', + background_color=colors.get(game1['field'], colors['default']), left_top_corner = text_rectangle('DH', "data/fonts/refrigerator-deluxe-bold.otf", 80, diff --git a/src/apps/read/read.py b/src/apps/read/read.py index b5457b2..61aa4f6 100644 --- a/src/apps/read/read.py +++ b/src/apps/read/read.py @@ -50,10 +50,52 @@ def print_table( # breakpoint() for row in data: - table.add_row(*[row[key] for key in keys]) + table.add_row(*[str(row[key]) for key in keys]) console.print(table) +@app.command() +def check( + input_file: Annotated[List[Path], typer.Argument(..., help="Path(s) to the CSV or XLSX file")] + ): + # Read CSV data + data = [] + for f in input_file: + data.extend(read_and_normalize_csv_or_xlsx(f)) + teams = set([row['visitor'] for row in data] + [row['home'] for row in data]) + fields = set([row['field'] for row in data]) + console = Console() + + table = Table("Team", "Number of Games") + for team in teams: + rows = [row for row in data if row['visitor']==team or row['home']==team] + table.add_row(team, str(len(rows))) + console.print(table) + + table = Table("Field", "Number of Games") + for field in fields: + rows = [row for row in data if row['field']==field] + table.add_row(field, str(len(rows))) + console.print(table) + + table = Table("Field", "Datetime", "Games") + field_times = [(row['field'], row['datetime']) for row in data] + for field, datetime in field_times: + rows = [row for row in data if row['field'] == field and row['datetime'] == datetime] + if len(rows) != 1: + table.add_row(str(field), str(datetime), str(len(rows))) + console.print(table) + + matchups = set([tuple([*sorted((row['home'], row['visitor']))]) for row in data]) + table =Table("Team 1", "Team 2", "Games") + for team1, team2 in matchups: + rows = [row for row in data if (row['visitor']==team1 or row['home']==team1) and (row['visitor']==team2 or row['home']==team2)] + table.add_row(str(team1), str(team2), str(len(rows))) + console.print(table) + + pass + + @app.command() def standings( input_file: Annotated[List[Path], typer.Argument(..., help="Path(s) to the CSV or XLSX file")], diff --git a/src/utils/normalize.py b/src/utils/normalize.py index b1e93ec..e4f951a 100644 --- a/src/utils/normalize.py +++ b/src/utils/normalize.py @@ -22,9 +22,9 @@ def normalize_header_key(original_key: str, normalization_config) -> str: def normalize_value(value, key, normalization_config): value = value.strip() - for value in normalization_config.get(key,{}).get('values',[]): - if value in value["original"]: - value = value["normalized"] + for normalization_pair in normalization_config.get(key if not key == "home" or key == "away" else "team",{}).get('values',[]): + if value in normalization_pair["original"]: + value = normalization_pair["normalized"] match key.lower(): case "date": if value: @@ -33,7 +33,7 @@ def normalize_value(value, key, normalization_config): pass case "home": value = value.title() - case "away": + case "visitor": value = value.title() case "time": if value: @@ -48,7 +48,7 @@ def normalize_value(value, key, normalization_config): def normalize_keyvalue(key: str,value:str, normalization_config): key, value = key.strip(), value.strip() normalized_k = normalize_header_key(key, normalization_config) - normalized_v = normalize_value(value, key, normalization_config) + normalized_v = normalize_value(value, normalized_k, normalization_config) return normalized_k, normalized_v def normalize_row(row:dict, normalization_config):