Add --create flag to custom submit command

- Add --create/-c flag to create new players directly from YAML profiles
- Skip MLBPlayer creation (not needed for custom players)
- Auto-populate required API fields (cost, rarity, mlbclub, etc.)
- Update YAML profile with player_id and card_id after creation
- Add Adm Ball Traits custom player profile

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-12-22 14:23:20 -06:00
parent 0fe549449d
commit fe0ab5e1bd
2 changed files with 268 additions and 5 deletions

View File

@ -6,6 +6,7 @@ Supports both batters and pitchers with their respective schemas.
""" """
import asyncio import asyncio
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional, Literal from typing import Optional, Literal
@ -282,6 +283,7 @@ def submit(
character: str = typer.Argument(..., help="Character profile name"), character: str = typer.Argument(..., help="Character profile name"),
dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Preview changes without saving"), dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Preview changes without saving"),
skip_s3: bool = typer.Option(False, "--skip-s3", help="Skip S3 upload after submit"), skip_s3: bool = typer.Option(False, "--skip-s3", help="Skip S3 upload after submit"),
create: bool = typer.Option(False, "--create", "-c", help="Create new player in database (requires cardset_id in profile)"),
): ):
"""Submit a custom character to the database.""" """Submit a custom character to the database."""
profile = load_profile(character) profile = load_profile(character)
@ -289,7 +291,8 @@ def submit(
console.print() console.print()
console.print("=" * 70) console.print("=" * 70)
console.print(f"[bold]SUBMITTING {profile['name']} ({player_type})[/bold]") action = "CREATING" if create else "SUBMITTING"
console.print(f"[bold]{action} {profile['name']} ({player_type})[/bold]")
console.print("=" * 70) console.print("=" * 70)
if dry_run: if dry_run:
@ -307,20 +310,202 @@ def submit(
console.print(f"[red]ERROR: {vs_hand} ratings total is {total:.2f}, must be 108.0[/red]") console.print(f"[red]ERROR: {vs_hand} ratings total is {total:.2f}, must be 108.0[/red]")
raise typer.Exit(1) raise typer.Exit(1)
# Validate create requirements
if create:
if not profile.get('cardset_id'):
console.print("[red]ERROR: cardset_id required in profile for --create[/red]")
raise typer.Exit(1)
if profile.get('player_id'):
console.print("[red]ERROR: player_id already set - use submit without --create to update[/red]")
raise typer.Exit(1)
if dry_run: if dry_run:
console.print("[green]Validation passed - ready to submit[/green]") console.print("[green]Validation passed - ready to submit[/green]")
return return
# Import database functions # Import database functions
try: try:
from pd_cards.core.db import db_get, db_put, db_patch from pd_cards.core.db import db_get, db_put, db_patch, db_post
except ImportError: except ImportError:
# Fallback to old location during migration # Fallback to old location during migration
import sys import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from db_calls import db_get, db_put, db_patch from db_calls import db_get, db_put, db_patch, db_post
async def do_create():
"""Create a new player in the database."""
# Parse name
name_parts = profile['name'].split()
name_first = name_parts[0]
name_last = ' '.join(name_parts[1:]) if len(name_parts) > 1 else name_parts[0]
# Generate bbref_id for custom player (with timestamp to avoid conflicts)
timestamp = int(datetime.now().timestamp())
bbref_id = f"custom_{name_last.lower().replace(' ', '')}{name_first[0].lower()}{timestamp}"
cardset_id = profile['cardset_id']
hand = profile.get('hand', 'R')
console.print(f"Name: {name_first} {name_last}")
console.print(f"BBRef ID: {bbref_id}")
console.print(f"Cardset: {cardset_id}")
console.print()
# Step 1: Create Player record (custom players don't need MLBPlayer)
console.print("Creating Player record...")
now = datetime.now()
release_date = f"{now.year}-{now.month}-{now.day}"
# Get first position for pos_1
positions = list(profile.get('positions', {}).keys())
pos_1 = positions[0] if positions else 'DH'
player_payload = {
'p_name': f"{name_first} {name_last}",
'bbref_id': bbref_id,
'fangraphs_id': 0,
'mlbam_id': 0,
'retrosheet_id': '',
'hand': hand,
'mlb_team_id': 1,
'franchise_id': 1, # Default franchise
'cardset_id': cardset_id,
'description': 'Custom',
'is_custom': True,
# Required fields
'cost': 100, # Default cost
'image': '', # Will be patched after
'mlbclub': 'Custom Ballplayers',
'franchise': 'Custom Ballplayers',
'set_num': 9999,
'rarity_id': 5, # Common
'pos_1': pos_1,
}
new_player = await db_post('players', payload=player_payload)
player_id = new_player['player_id']
console.print(f"[green] Created Player (ID: {player_id})[/green]")
# Step 3: Patch Player with image URL
card_type = 'batting' if player_type == 'batter' else 'pitching'
image_url = f"https://pd.manticorum.com/api/v2/players/{player_id}/{card_type}card?d={release_date}"
await db_patch('players', object_id=player_id, params=[('image', image_url)])
console.print("[green] Updated Player with image URL[/green]")
# Step 4: Create Card
if player_type == 'batter':
console.print("Creating BattingCard...")
baserunning = profile.get('baserunning', {})
batting_card_payload = {
'cards': [{
'player_id': player_id,
'key_bbref': bbref_id,
'key_fangraphs': 0,
'key_mlbam': 0,
'key_retro': '',
'name_first': name_first,
'name_last': name_last,
'steal_low': baserunning.get('steal_low', 5),
'steal_high': baserunning.get('steal_high', 12),
'steal_auto': 1 if baserunning.get('steal_auto') else 0,
'steal_jump': baserunning.get('steal_jump', 0.15),
'hit_and_run': baserunning.get('hit_and_run', 'C'),
'running': baserunning.get('running', 10),
'bunting': baserunning.get('bunting', 'C'),
'hand': hand,
}]
}
await db_put('battingcards', payload=batting_card_payload, timeout=10)
# Get the card ID
bc_query = await db_get('battingcards', params=[('player_id', player_id)])
card_id = bc_query['cards'][0]['id']
card_id_field = 'battingcard_id'
ratings_endpoint = 'battingcardratings'
console.print(f"[green] Created BattingCard (ID: {card_id})[/green]")
else:
console.print("Creating PitchingCard...")
pitching = profile.get('pitching', {})
pitching_card_payload = {
'cards': [{
'player_id': player_id,
'key_bbref': bbref_id,
'key_fangraphs': 0,
'key_mlbam': 0,
'key_retro': '',
'name_first': name_first,
'name_last': name_last,
'hand': hand,
'starter_rating': pitching.get('starter_rating', 5),
'relief_rating': pitching.get('relief_rating', 5),
'closer_rating': pitching.get('closer_rating'),
}]
}
await db_put('pitchingcards', payload=pitching_card_payload, timeout=10)
# Get the card ID
pc_query = await db_get('pitchingcards', params=[('player_id', player_id)])
card_id = pc_query['cards'][0]['id']
card_id_field = 'pitchingcard_id'
ratings_endpoint = 'pitchingcardratings'
console.print(f"[green] Created PitchingCard (ID: {card_id})[/green]")
# Step 5: Create ratings
console.print("Creating ratings...")
ratings_list = []
for vs_hand, ratings in profile['ratings'].items():
hand_char = vs_hand[-1] # 'L' or 'R'
rating_data = {
card_id_field: card_id,
'vs_hand': hand_char,
**ratings
}
ratings_list.append(rating_data)
await db_put(ratings_endpoint, payload={'ratings': ratings_list}, timeout=10)
console.print("[green] Created ratings[/green]")
# Step 6: Create positions
console.print("Creating positions...")
positions_list = []
for pos, stats in profile.get('positions', {}).items():
if isinstance(stats, dict):
positions_list.append({
'player_id': player_id,
'position': pos,
**stats
})
else:
positions_list.append({
'player_id': player_id,
'position': pos,
})
if positions_list:
await db_put('cardpositions', payload={'positions': positions_list}, timeout=10)
console.print(f"[green] Created {len(positions_list)} position(s)[/green]")
# Step 7: Update YAML profile with IDs
console.print("Updating profile with IDs...")
profile['player_id'] = player_id
if player_type == 'batter':
profile['batting_card_id'] = card_id
else:
profile['pitching_card_id'] = card_id
profile_path = get_profile_path(character)
with open(profile_path, 'w') as f:
yaml.dump(profile, f, default_flow_style=False, sort_keys=False)
console.print(f"[green] Updated {profile_path.name}[/green]")
console.print()
console.print(f"[bold green]Successfully created {profile['name']}![/bold green]")
console.print(f"Player ID: {player_id}")
console.print(f"Card ID: {card_id}")
console.print(f"URL: https://pd.manticorum.com/api/v2/players/{player_id}/{card_type}card?d={release_date}")
async def do_submit(): async def do_submit():
"""Update an existing player in the database."""
player_id = profile.get('player_id') player_id = profile.get('player_id')
if player_type == 'pitcher': if player_type == 'pitcher':
@ -335,7 +520,7 @@ def submit(
if not player_id or not card_id: if not player_id or not card_id:
id_field = 'pitching_card_id' if player_type == 'pitcher' else 'batting_card_id' id_field = 'pitching_card_id' if player_type == 'pitcher' else 'batting_card_id'
console.print(f"[red]ERROR: Profile missing player_id or {id_field}[/red]") console.print(f"[red]ERROR: Profile missing player_id or {id_field}[/red]")
console.print("Create the player first, then update the profile with IDs.") console.print("Use --create to create a new player, or add IDs to the profile.")
raise typer.Exit(1) raise typer.Exit(1)
# Update ratings # Update ratings
@ -382,6 +567,9 @@ def submit(
console.print("[yellow]S3 upload not yet implemented in CLI[/yellow]") console.print("[yellow]S3 upload not yet implemented in CLI[/yellow]")
console.print("Run manually: python check_cards_and_upload.py") console.print("Run manually: python check_cards_and_upload.py")
if create:
asyncio.run(do_create())
else:
asyncio.run(do_submit()) asyncio.run(do_submit())

View File

@ -0,0 +1,75 @@
name: Adm Ball Traits
player_type: batter
hand: R
target_ops: 0.8
cardset_id: 29
player_id: 13361
batting_card_id: 6244
positions:
3B:
range: 2
error: 8
SS:
range: 3
error: 10
baserunning:
steal_jump: 0.083333
steal_high: 15
steal_low: 9
steal_auto: true
running: 13
hit_and_run: B
bunting: B
ratings:
vs_L:
homerun: 0.0
bp_homerun: 1.0
triple: 2.75
double_three: 0.0
double_two: 2.0
double_pull: 3.75
single_two: 6.5
single_one: 5.0
single_center: 7.75
bp_single: 5.0
walk: 12.0
hbp: 4.0
strikeout: 16.0
lineout: 4.0
popout: 2.0
flyout_a: 0.0
flyout_bq: 2.0
flyout_lf_b: 4.0
flyout_rf_b: 4.0
groundout_a: 9.0
groundout_b: 11.0
groundout_c: 6.25
pull_rate: 0.2
center_rate: 0.5
slap_rate: 0.3
vs_R:
homerun: 0.0
bp_homerun: 1.0
triple: 0.5
double_three: 0.0
double_two: 4.5
double_pull: 4.5
single_two: 5.0
single_one: 8.0
single_center: 5.5
bp_single: 5.0
walk: 11.0
hbp: 1.0
strikeout: 14.5
lineout: 2.0
popout: 2.0
flyout_a: 0.0
flyout_bq: 3.0
flyout_lf_b: 5.0
flyout_rf_b: 8.0
groundout_a: 5.0
groundout_b: 12.0
groundout_c: 10.5
pull_rate: 0.18
center_rate: 0.42
slap_rate: 0.4