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
This commit is contained in:
22
.vscode/launch.json
vendored
22
.vscode/launch.json
vendored
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")],
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user