Standardize formatting with black and apply ruff auto-fixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
337 lines
12 KiB
Python
337 lines
12 KiB
Python
"""
|
|
Live series card update commands.
|
|
|
|
Commands for generating cards from current season FanGraphs/Baseball Reference data.
|
|
"""
|
|
|
|
import asyncio
|
|
import datetime
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
|
|
app = typer.Typer(no_args_is_help=True)
|
|
console = Console()
|
|
|
|
|
|
@app.command()
|
|
def update(
|
|
cardset: str = typer.Option(
|
|
..., "--cardset", "-c", help="Target cardset name (e.g., '2025 Season')"
|
|
),
|
|
season: int = typer.Option(
|
|
None, "--season", "-s", help="Season year (defaults to cardset year)"
|
|
),
|
|
games_played: int = typer.Option(
|
|
162, "--games", "-g", help="Number of games played (1-162)"
|
|
),
|
|
description: str = typer.Option(
|
|
None, "--description", "-d", help="Player description (defaults to year)"
|
|
),
|
|
pull_fielding: bool = typer.Option(
|
|
True,
|
|
"--pull-fielding/--no-pull-fielding",
|
|
help="Pull fielding stats from Baseball Reference",
|
|
),
|
|
post_batters: bool = typer.Option(
|
|
True, "--post-batters/--skip-batters", help="Post batting cards and ratings"
|
|
),
|
|
post_pitchers: bool = typer.Option(
|
|
True, "--post-pitchers/--skip-pitchers", help="Post pitching cards and ratings"
|
|
),
|
|
post_fielders: bool = typer.Option(
|
|
True, "--post-fielders/--skip-fielders", help="Post card positions"
|
|
),
|
|
post_players: bool = typer.Option(
|
|
True, "--post-players/--skip-players", help="Post player updates"
|
|
),
|
|
is_live: bool = typer.Option(
|
|
True, "--live/--not-live", help="Look up current MLB clubs from statsapi"
|
|
),
|
|
ignore_limits: bool = typer.Option(
|
|
False, "--ignore-limits", help="Ignore minimum PA/TBF requirements"
|
|
),
|
|
dry_run: bool = typer.Option(
|
|
False, "--dry-run", "-n", help="Preview without saving to database"
|
|
),
|
|
):
|
|
"""
|
|
Update live series cards from FanGraphs/Baseball Reference data.
|
|
|
|
Reads CSV files from data-input/{cardset} Cardset/ and generates batting/pitching cards.
|
|
|
|
Example:
|
|
pd-cards live-series update --cardset "2025 Season" --games 81
|
|
"""
|
|
console.print()
|
|
console.print("=" * 70)
|
|
console.print(f"[bold]LIVE SERIES UPDATE - {cardset}[/bold]")
|
|
console.print("=" * 70)
|
|
|
|
if dry_run:
|
|
console.print("[yellow]DRY RUN - no changes will be made[/yellow]")
|
|
console.print()
|
|
|
|
# Validate games_played
|
|
if games_played < 1 or games_played > 162:
|
|
console.print(
|
|
f"[red]Error: games_played must be between 1 and 162, got {games_played}[/red]"
|
|
)
|
|
raise typer.Exit(1)
|
|
|
|
season_pct = games_played / 162
|
|
|
|
# Import the necessary modules
|
|
try:
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
import batters.creation
|
|
import pitchers.creation
|
|
import pandas as pd
|
|
from creation_helpers import pd_players_df, pd_positions_df
|
|
from db_calls import db_get, db_patch, DB_URL
|
|
from exceptions import logger
|
|
except ImportError as e:
|
|
console.print(f"[red]Error importing modules: {e}[/red]")
|
|
console.print("Make sure you're running from the card-creation directory")
|
|
raise typer.Exit(1)
|
|
|
|
CARD_BASE_URL = f"{DB_URL}/v2/players"
|
|
|
|
async def run_update():
|
|
# Look up cardset
|
|
console.print(f"Searching for cardset: {cardset}")
|
|
c_query = await db_get("cardsets", params=[("name", cardset)])
|
|
if c_query is None or c_query["count"] == 0:
|
|
console.print(f"[red]Cardset '{cardset}' not found[/red]")
|
|
raise typer.Exit(1)
|
|
|
|
cardset_data = c_query["cardsets"][0]
|
|
input_path = f'data-input/{cardset_data["name"]} Cardset/'
|
|
|
|
# Determine season from cardset name if not provided
|
|
actual_season = season
|
|
if actual_season is None:
|
|
# Try to extract year from cardset name
|
|
import re
|
|
|
|
match = re.search(r"\b(19|20)\d{2}\b", cardset)
|
|
if match:
|
|
actual_season = int(match.group())
|
|
else:
|
|
actual_season = datetime.datetime.now().year
|
|
|
|
# Determine description
|
|
actual_description = description if description else str(actual_season)
|
|
|
|
console.print(f"Cardset ID: {cardset_data['id']} / Season: {actual_season}")
|
|
console.print(f"Game count: {games_played} / Season %: {season_pct:.2%}")
|
|
console.print(f"Description: {actual_description}")
|
|
console.print()
|
|
|
|
if dry_run:
|
|
console.print("[green]Validation passed - ready to run[/green]")
|
|
console.print()
|
|
console.print("Would execute:")
|
|
console.print(f" - Input path: {input_path}")
|
|
if post_batters:
|
|
console.print(" - Process batting cards")
|
|
if post_pitchers:
|
|
console.print(" - Process pitching cards")
|
|
if post_fielders:
|
|
console.print(" - Process card positions")
|
|
if post_players:
|
|
console.print(" - Update player records")
|
|
return
|
|
|
|
start_time = datetime.datetime.now()
|
|
release_directory = f"{start_time.year}-{start_time.month}-{start_time.day}"
|
|
|
|
# Run batters
|
|
console.print("[bold]Processing batters...[/bold]")
|
|
data = await batters.creation.run_batters(
|
|
cardset_data,
|
|
input_path,
|
|
post_players,
|
|
CARD_BASE_URL,
|
|
release_directory,
|
|
actual_description,
|
|
season_pct,
|
|
post_batters,
|
|
pull_fielding,
|
|
actual_season,
|
|
is_live,
|
|
ignore_limits,
|
|
)
|
|
|
|
batter_time = datetime.datetime.now()
|
|
batter_runtime = batter_time - start_time
|
|
console.print("[green]✓ Batter updates complete[/green]")
|
|
console.print(f" Total batting cards: {data['tot_batters']}")
|
|
console.print(f" New cardset batters: {data['new_batters']}")
|
|
console.print(f" Runtime: {round(batter_runtime.total_seconds())} seconds")
|
|
console.print()
|
|
|
|
# Run pitchers
|
|
console.print("[bold]Processing pitchers...[/bold]")
|
|
data = await pitchers.creation.run_pitchers(
|
|
cardset_data,
|
|
input_path,
|
|
CARD_BASE_URL,
|
|
actual_season,
|
|
release_directory,
|
|
actual_description,
|
|
season_pct,
|
|
post_players,
|
|
post_pitchers,
|
|
is_live,
|
|
ignore_limits,
|
|
)
|
|
pitching_stats = data["pitching_stats"]
|
|
|
|
pitcher_time = datetime.datetime.now()
|
|
pitcher_runtime = pitcher_time - batter_time
|
|
console.print("[green]✓ Pitcher updates complete[/green]")
|
|
console.print(f" Total pitching cards: {data['tot_pitchers']}")
|
|
console.print(f" New cardset pitchers: {data['new_pitchers']}")
|
|
console.print(f" Runtime: {round(pitcher_runtime.total_seconds())} seconds")
|
|
console.print()
|
|
|
|
# Run player position updates
|
|
if "promos" not in cardset.lower():
|
|
console.print("[bold]Processing player positions...[/bold]")
|
|
all_pos = await pd_positions_df(cardset_data["id"])
|
|
all_players = await pd_players_df(cardset_data["id"])
|
|
|
|
player_updates = {}
|
|
|
|
def set_all_positions(df_data):
|
|
pos_series = all_pos.query(f'player_id == {df_data["player_id"]}')[
|
|
"position"
|
|
]
|
|
pos_updates = []
|
|
count = 1
|
|
for this_pos in pos_series:
|
|
if this_pos == "P":
|
|
try:
|
|
this_pitcher = pitching_stats.loc[df_data["bbref_id"]]
|
|
except KeyError:
|
|
pos_updates.append((f"pos_{count}", "RP"))
|
|
count += 1
|
|
break
|
|
|
|
if this_pitcher["starter_rating"] > 3:
|
|
pos_updates.append((f"pos_{count}", "SP"))
|
|
count += 1
|
|
if this_pitcher["relief_rating"] > 1 or not pd.isna(
|
|
this_pitcher["closer_rating"]
|
|
):
|
|
pos_updates.append((f"pos_{count}", "RP"))
|
|
count += 1
|
|
else:
|
|
pos_updates.append((f"pos_{count}", "RP"))
|
|
count += 1
|
|
|
|
if not pd.isna(this_pitcher["closer_rating"]):
|
|
pos_updates.append((f"pos_{count}", "CP"))
|
|
count += 1
|
|
else:
|
|
pos_updates.append((f"pos_{count}", this_pos))
|
|
count += 1
|
|
|
|
if count == 1:
|
|
pos_updates.append(("pos_1", "DH"))
|
|
count += 1
|
|
|
|
while count <= 9:
|
|
pos_updates.append((f"pos_{count}", "False"))
|
|
count += 1
|
|
|
|
if len(pos_updates) > 0:
|
|
if df_data.player_id not in player_updates.keys():
|
|
player_updates[df_data.player_id] = pos_updates
|
|
else:
|
|
player_updates[df_data.player_id].extend(pos_updates)
|
|
|
|
all_players.apply(set_all_positions, axis=1)
|
|
|
|
console.print(
|
|
f"Sending {len(player_updates)} player updates to database..."
|
|
)
|
|
if post_players:
|
|
for player_id in player_updates:
|
|
await db_patch(
|
|
"players", object_id=player_id, params=player_updates[player_id]
|
|
)
|
|
|
|
position_time = datetime.datetime.now()
|
|
position_runtime = position_time - pitcher_time
|
|
console.print("[green]✓ Player position updates complete[/green]")
|
|
console.print(
|
|
f" Runtime: {round(position_runtime.total_seconds())} seconds"
|
|
)
|
|
console.print()
|
|
|
|
total_runtime = datetime.datetime.now() - start_time
|
|
console.print("=" * 70)
|
|
console.print("[bold green]✓ LIVE SERIES UPDATE COMPLETE[/bold green]")
|
|
console.print(f"Total runtime: {round(total_runtime.total_seconds())} seconds")
|
|
console.print("=" * 70)
|
|
|
|
asyncio.run(run_update())
|
|
|
|
|
|
@app.command()
|
|
def status(
|
|
cardset: str = typer.Option(None, "--cardset", "-c", help="Filter by cardset name"),
|
|
):
|
|
"""Show status of live series cardsets."""
|
|
console.print()
|
|
|
|
try:
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
from db_calls import db_get
|
|
except ImportError as e:
|
|
console.print(f"[red]Error importing modules: {e}[/red]")
|
|
raise typer.Exit(1)
|
|
|
|
async def get_status():
|
|
params = []
|
|
if cardset:
|
|
params.append(("name", cardset))
|
|
|
|
result = await db_get("cardsets", params=params if params else None)
|
|
|
|
if result is None or result["count"] == 0:
|
|
if cardset:
|
|
console.print(f"[yellow]No cardset found matching '{cardset}'[/yellow]")
|
|
else:
|
|
console.print("[yellow]No cardsets found[/yellow]")
|
|
return
|
|
|
|
from rich.table import Table
|
|
|
|
table = Table(title="Cardsets")
|
|
table.add_column("ID", justify="right")
|
|
table.add_column("Name")
|
|
table.add_column("Season", justify="right")
|
|
table.add_column("Players", justify="right")
|
|
|
|
for cs in result["cardsets"]:
|
|
# Get player count for each cardset
|
|
players = await db_get("players", params=[("cardset_id", cs["id"])])
|
|
player_count = players["count"] if players else 0
|
|
|
|
table.add_row(
|
|
str(cs["id"]), cs["name"], str(cs.get("season", "-")), str(player_count)
|
|
)
|
|
|
|
console.print(table)
|
|
|
|
asyncio.run(get_status())
|