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:
parent
0fe549449d
commit
fe0ab5e1bd
@ -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())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
75
pd_cards/custom/profiles/adm_ball_traits.yaml
Normal file
75
pd_cards/custom/profiles/adm_ball_traits.yaml
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user