paper-dynasty-card-creation/pd_cards/commands/scouting.py
Cal Corum a2f4d02b18 Add scouting upload CLI command
- Add `pd-cards scouting upload` command to upload scouting CSVs to database server via SCP
- Update CLAUDE.md with critical warning: scouting must always run for ALL cardsets
- Document full workflow: `pd-cards scouting all && pd-cards scouting upload`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:17:14 -06:00

317 lines
10 KiB
Python

"""
Scouting report generation commands.
Commands for generating scouting reports and ratings comparisons.
"""
import asyncio
from pathlib import Path
from typing import Optional, List
import typer
from rich.console import Console
app = typer.Typer(no_args_is_help=True)
console = Console()
@app.command()
def batters(
cardset_ids: Optional[List[int]] = typer.Option(None, "--cardset-id", "-c", help="Cardset ID(s) to include (can specify multiple)"),
output_dir: Path = typer.Option(Path("scouting"), "--output", "-o", help="Output directory"),
):
"""
Generate batting scouting reports.
Creates CSV files with batting ratings and comparisons.
Example:
pd-cards scouting batters --cardset-id 27 --cardset-id 29
"""
console.print()
console.print("=" * 70)
console.print("[bold]BATTING SCOUTING REPORT[/bold]")
console.print("=" * 70)
if cardset_ids:
console.print(f"Cardset IDs: {cardset_ids}")
else:
console.print("Cardset IDs: All")
console.print(f"Output: {output_dir}")
console.print()
try:
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import scouting_batters as sb
import copy
async def run_scouting():
# Ensure output directory exists
output_dir.mkdir(parents=True, exist_ok=True)
console.print("Pulling scouting data...")
batting_dfs = await sb.get_scouting_dfs(cardset_ids or [])
console.print(f" Received {len(batting_dfs)} rows")
console.print("Generating basic scouting report...")
await sb.post_calc_basic(copy.deepcopy(batting_dfs))
console.print(f" [green]✓ Saved to {output_dir}/batting-basic.csv[/green]")
console.print("Generating ratings guide...")
await sb.post_calc_ratings(copy.deepcopy(batting_dfs))
console.print(f" [green]✓ Saved to {output_dir}/batting-ratings.csv[/green]")
console.print()
console.print("[green]✓ Batting scouting complete[/green]")
asyncio.run(run_scouting())
except ImportError as e:
console.print(f"[red]Error importing modules: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
import traceback
traceback.print_exc()
raise typer.Exit(1)
@app.command()
def pitchers(
cardset_ids: Optional[List[int]] = typer.Option(None, "--cardset-id", "-c", help="Cardset ID(s) to include (can specify multiple)"),
output_dir: Path = typer.Option(Path("scouting"), "--output", "-o", help="Output directory"),
):
"""
Generate pitching scouting reports.
Creates CSV files with pitching ratings and comparisons.
Example:
pd-cards scouting pitchers --cardset-id 27 --cardset-id 29
"""
console.print()
console.print("=" * 70)
console.print("[bold]PITCHING SCOUTING REPORT[/bold]")
console.print("=" * 70)
if cardset_ids:
console.print(f"Cardset IDs: {cardset_ids}")
else:
console.print("Cardset IDs: All")
console.print(f"Output: {output_dir}")
console.print()
try:
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import scouting_pitchers as sp
import copy
async def run_scouting():
# Ensure output directory exists
output_dir.mkdir(parents=True, exist_ok=True)
console.print("Pulling scouting data...")
pitching_dfs = await sp.get_scouting_dfs(cardset_ids or [])
console.print(f" Received {len(pitching_dfs)} rows")
console.print("Generating basic scouting report...")
await sp.post_calc_basic(copy.deepcopy(pitching_dfs))
console.print(f" [green]✓ Saved to {output_dir}/pitching-basic.csv[/green]")
console.print("Generating ratings guide...")
await sp.post_calc_ratings(copy.deepcopy(pitching_dfs))
console.print(f" [green]✓ Saved to {output_dir}/pitching-ratings.csv[/green]")
console.print()
console.print("[green]✓ Pitching scouting complete[/green]")
asyncio.run(run_scouting())
except ImportError as e:
console.print(f"[red]Error importing modules: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
import traceback
traceback.print_exc()
raise typer.Exit(1)
@app.command()
def all(
cardset_ids: Optional[List[int]] = typer.Option(None, "--cardset-id", "-c", help="Cardset ID(s) to include"),
output_dir: Path = typer.Option(Path("scouting"), "--output", "-o", help="Output directory"),
):
"""
Generate all scouting reports (batters and pitchers).
Example:
pd-cards scouting all --cardset-id 27
"""
console.print()
console.print("=" * 70)
console.print("[bold]FULL SCOUTING REPORT[/bold]")
console.print("=" * 70)
console.print()
# Run batters
console.print("[bold]Phase 1: Batters[/bold]")
try:
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import scouting_batters as sb
import scouting_pitchers as sp
import copy
async def run_all_scouting():
output_dir.mkdir(parents=True, exist_ok=True)
# Batters
console.print("Pulling batting data...")
batting_dfs = await sb.get_scouting_dfs(cardset_ids or [])
console.print(f" Received {len(batting_dfs)} batter rows")
await sb.post_calc_basic(copy.deepcopy(batting_dfs))
console.print(f" [green]✓ batting-basic.csv[/green]")
await sb.post_calc_ratings(copy.deepcopy(batting_dfs))
console.print(f" [green]✓ batting-ratings.csv[/green]")
console.print()
console.print("[bold]Phase 2: Pitchers[/bold]")
# Pitchers
console.print("Pulling pitching data...")
pitching_dfs = await sp.get_scouting_dfs(cardset_ids or [])
console.print(f" Received {len(pitching_dfs)} pitcher rows")
await sp.post_calc_basic(copy.deepcopy(pitching_dfs))
console.print(f" [green]✓ pitching-basic.csv[/green]")
await sp.post_calc_ratings(copy.deepcopy(pitching_dfs))
console.print(f" [green]✓ pitching-ratings.csv[/green]")
console.print()
console.print("=" * 70)
console.print("[bold green]✓ ALL SCOUTING REPORTS COMPLETE[/bold green]")
console.print("=" * 70)
asyncio.run(run_all_scouting())
except ImportError as e:
console.print(f"[red]Error importing modules: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
import traceback
traceback.print_exc()
raise typer.Exit(1)
@app.command()
def upload(
input_dir: Path = typer.Option(Path("scouting"), "--input", "-i", help="Input directory with scouting CSVs"),
remote_host: str = typer.Option("sba-db", "--host", "-h", help="Remote host to upload to"),
remote_dir: str = typer.Option("/home/cal/container-data/pd-database/storage", "--remote-dir", "-r", help="Remote directory path"),
dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Show what would be uploaded without uploading"),
):
"""
Upload scouting reports to remote server via SCP.
Uploads the generated CSV files to the Paper Dynasty database server.
Example:
pd-cards scouting upload
pd-cards scouting upload --dry-run
pd-cards scouting upload --host sba-db --remote-dir /path/to/storage
"""
import subprocess
console.print()
console.print("=" * 70)
console.print("[bold]SCOUTING REPORT UPLOAD[/bold]")
console.print("=" * 70)
console.print()
FILES_TO_UPLOAD = [
'batting-basic.csv',
'batting-ratings.csv',
'pitching-basic.csv',
'pitching-ratings.csv'
]
# Verify files exist
console.print("[bold]Verifying files...[/bold]")
missing_files = []
for filename in FILES_TO_UPLOAD:
filepath = input_dir / filename
if not filepath.exists():
missing_files.append(str(filepath))
else:
console.print(f" [green]✓[/green] {filepath}")
if missing_files:
console.print()
console.print(f"[red]Missing files:[/red]")
for f in missing_files:
console.print(f" [red]✗[/red] {f}")
console.print()
console.print("[yellow]Run 'pd-cards scouting all' first to generate reports.[/yellow]")
raise typer.Exit(1)
console.print()
console.print(f"[bold]Upload destination:[/bold] {remote_host}:{remote_dir}")
console.print()
if dry_run:
console.print("[yellow]DRY RUN - No files will be uploaded[/yellow]")
console.print()
for filename in FILES_TO_UPLOAD:
console.print(f" Would upload: {input_dir / filename}")
console.print()
console.print("=" * 70)
console.print("[bold yellow]✓ DRY RUN COMPLETE[/bold yellow]")
console.print("=" * 70)
return
# Upload files via SCP
console.print("[bold]Uploading files...[/bold]")
try:
local_files = [str(input_dir / f) for f in FILES_TO_UPLOAD]
scp_command = [
'scp',
*local_files,
f"{remote_host}:{remote_dir}/"
]
result = subprocess.run(
scp_command,
check=True,
capture_output=True,
text=True
)
for filename in FILES_TO_UPLOAD:
console.print(f" [green]✓[/green] {filename}")
console.print()
console.print("=" * 70)
console.print("[bold green]✓ SCOUTING UPLOAD COMPLETE[/bold green]")
console.print("=" * 70)
except subprocess.CalledProcessError as e:
console.print(f"[red]Upload failed: {e}[/red]")
if e.stderr:
console.print(f"[red]{e.stderr}[/red]")
raise typer.Exit(1)
except FileNotFoundError:
console.print("[red]Error: 'scp' command not found. Ensure SSH is installed.[/red]")
raise typer.Exit(1)