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
from datetime import datetime
from pathlib import Path
from typing import Optional, Literal
@ -282,6 +283,7 @@ def submit(
character: str = typer.Argument(..., help="Character profile name"),
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"),
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."""
profile = load_profile(character)
@ -289,7 +291,8 @@ def submit(
console.print()
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)
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]")
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:
console.print("[green]Validation passed - ready to submit[/green]")
return
# Import database functions
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:
# Fallback to old location during migration
import sys
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():
"""Update an existing player in the database."""
player_id = profile.get('player_id')
if player_type == 'pitcher':
@ -335,7 +520,7 @@ def submit(
if not player_id or not 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("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)
# Update ratings
@ -382,7 +567,10 @@ def submit(
console.print("[yellow]S3 upload not yet implemented in CLI[/yellow]")
console.print("Run manually: python check_cards_and_upload.py")
asyncio.run(do_submit())
if create:
asyncio.run(do_create())
else:
asyncio.run(do_submit())
@app.command()

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