- luan_arroto_final.py: Card definition with 0.850 OPS target - submit_luan_arroto.py: Initial card creation script - update_luan_arroto.py: Update existing card ratings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
279 lines
9.8 KiB
Python
279 lines
9.8 KiB
Python
"""
|
|
Submit Luan Arroto to Paper Dynasty Database
|
|
|
|
FINAL VERSION with:
|
|
- Platoon splits (better vs RHP)
|
|
- No triples
|
|
- Custom groundball ratios (3:2:1 vs L, 3:3:1 vs R)
|
|
- Spray chart variation (10% pull vs L, 33% pull vs R)
|
|
"""
|
|
|
|
import asyncio
|
|
from custom_cards.archetype_definitions import BatterArchetype
|
|
from custom_cards.archetype_calculator import BatterRatingCalculator, calculate_total_ops
|
|
from db_calls import db_get, db_post, db_patch, db_put
|
|
from creation_helpers import mlbteam_and_franchise
|
|
import boto3
|
|
from datetime import datetime
|
|
import aiohttp
|
|
from exceptions import logger
|
|
|
|
# AWS Configuration
|
|
AWS_BUCKET_NAME = 'paper-dynasty'
|
|
AWS_REGION = 'us-east-1'
|
|
S3_BASE_URL = f'https://{AWS_BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com'
|
|
|
|
# Configuration
|
|
CARDSET_NAME = "2005 Custom"
|
|
CARDSET_ID = 29
|
|
SEASON = 2005
|
|
TEAM_ABBREV = "FA" # Free Agent
|
|
PLAYER_DESCRIPTION = "2005 Custom"
|
|
|
|
|
|
async def main():
|
|
"""Create Luan Arroto with final specifications."""
|
|
|
|
print("\n" + "="*70)
|
|
print("SUBMITTING LUAN ARROTO TO DATABASE")
|
|
print("="*70)
|
|
|
|
# Step 1: Design archetype
|
|
print("\nCalculating ratings...")
|
|
archetype = BatterArchetype(
|
|
name="Luan Arroto Final",
|
|
description="Contact hitter with platoon advantage and spray approach",
|
|
avg_vs_r=0.310, obp_vs_r=0.450, slg_vs_r=0.385,
|
|
bb_pct_vs_r=0.14, k_pct_vs_r=0.10,
|
|
avg_vs_l=0.285, obp_vs_l=0.455, slg_vs_l=0.355,
|
|
bb_pct_vs_l=0.17, k_pct_vs_l=0.09,
|
|
hr_per_hit=0.025, triple_per_hit=0.00, double_per_hit=0.22,
|
|
gb_pct=0.48, fb_pct=0.27, ld_pct=0.25,
|
|
hard_pct=0.28, med_pct=0.52, soft_pct=0.20,
|
|
pull_pct=0.215, center_pct=0.35, oppo_pct=0.435,
|
|
ifh_pct=0.09, hr_fb_pct=0.06,
|
|
speed_rating=8, steal_jump=3, xbt_pct=0.58, hit_run_skill=9,
|
|
primary_positions=["1B", "RF"], defensive_rating=6
|
|
)
|
|
|
|
# Step 2: Calculate ratings
|
|
calc = BatterRatingCalculator(archetype)
|
|
ratings = calc.calculate_ratings(battingcard_id=0)
|
|
baserunning = calc.calculate_baserunning()
|
|
|
|
# Step 3: Apply customizations
|
|
ratings[1]['pull_rate'] = 0.33 # vs R
|
|
ratings[0]['pull_rate'] = 0.10 # vs L
|
|
for rating in ratings:
|
|
rating['slap_rate'] = 1.0 - rating['pull_rate'] - rating['center_rate']
|
|
|
|
baserunning.update({
|
|
'steal_low': 3, 'steal_high': 20,
|
|
'steal_auto': 0, 'steal_jump': 0.0277777, 'running': 10
|
|
})
|
|
|
|
# Step 4: Custom groundball ratios
|
|
vl = ratings[0]
|
|
vr = ratings[1]
|
|
|
|
total_gb_vl = vl['groundout_a'] + vl['groundout_b'] + vl['groundout_c']
|
|
total_gb_vr = vr['groundout_a'] + vr['groundout_b'] + vr['groundout_c']
|
|
|
|
# vs L: 3:2:1 ratio
|
|
vl['groundout_a'] = (3/6) * total_gb_vl
|
|
vl['groundout_b'] = (2/6) * total_gb_vl
|
|
vl['groundout_c'] = (1/6) * total_gb_vl
|
|
|
|
# vs R: 3:3:1 ratio
|
|
vr['groundout_a'] = (3/7) * total_gb_vr
|
|
vr['groundout_b'] = (3/7) * total_gb_vr
|
|
vr['groundout_c'] = (1/7) * total_gb_vr
|
|
|
|
# Verify
|
|
total_ops = calculate_total_ops(vl, vr, is_pitcher=False)
|
|
print(f" ✓ Combined OPS: {total_ops:.3f}")
|
|
print(f" ✓ ISO vs L: {vl['slg']-vl['avg']:.3f}")
|
|
print(f" ✓ ISO vs R: {vr['slg']-vr['avg']:.3f}")
|
|
print(f" ✓ Groundball ratios: vs L (3:2:1), vs R (3:3:1)")
|
|
|
|
# Step 5: Use existing cardset
|
|
print(f"\nUsing cardset '{CARDSET_NAME}' (ID: {CARDSET_ID})...")
|
|
cardset = {'id': CARDSET_ID, 'name': CARDSET_NAME, 'season': SEASON}
|
|
print(f" ✓ Cardset ID: {cardset['id']}")
|
|
|
|
# Step 6: Get team info
|
|
print(f"\nGetting team info for '{TEAM_ABBREV}'...")
|
|
mlb_team_id, franchise_id = mlbteam_and_franchise(TEAM_ABBREV)
|
|
print(f" ✓ MLB Team ID: {mlb_team_id}, Franchise ID: {franchise_id}")
|
|
|
|
# Step 7: Skip MLBPlayer (API constraint issues)
|
|
print("\nSkipping MLBPlayer creation...")
|
|
bbref_id = "custom_arrotl01"
|
|
mlbplayer_id = None # Leave null per user request
|
|
print(f" ✓ Skipped MLBPlayer")
|
|
|
|
# Step 8: Create Player
|
|
print("\nCreating Player record...")
|
|
now = datetime.now()
|
|
release_date = f"{now.year}-{now.month}-{now.day}"
|
|
|
|
player_payload = {
|
|
'p_name': 'Luan Arroto',
|
|
'cost': '88',
|
|
'image': 'change-me',
|
|
'mlbclub': mlb_team_id,
|
|
'franchise': franchise_id,
|
|
'cardset_id': cardset['id'],
|
|
'set_num': 99999,
|
|
'rarity_id': 3, # Starter
|
|
'pos_1': '1B',
|
|
'description': PLAYER_DESCRIPTION,
|
|
'bbref_id': bbref_id,
|
|
'fangr_id': 0,
|
|
'mlbplayer_id': mlbplayer_id,
|
|
}
|
|
|
|
player = await db_post('players', payload=player_payload)
|
|
player_id = player['player_id']
|
|
print(f" ✓ Created Player ID: {player_id}")
|
|
|
|
# Step 9: Create BattingCard
|
|
print("\nCreating BattingCard...")
|
|
batting_card_payload = {
|
|
'cards': [{
|
|
'player_id': player_id,
|
|
'key_bbref': bbref_id,
|
|
'key_fangraphs': 0,
|
|
'key_mlbam': 0,
|
|
'key_retro': '',
|
|
'name_first': 'Luan',
|
|
'name_last': 'Arroto',
|
|
'steal_low': baserunning['steal_low'],
|
|
'steal_high': baserunning['steal_high'],
|
|
'steal_auto': baserunning['steal_auto'],
|
|
'steal_jump': baserunning['steal_jump'],
|
|
'hit_and_run': baserunning['hit_and_run'],
|
|
'running': baserunning['running'],
|
|
'hand': 'L',
|
|
}]
|
|
}
|
|
|
|
await db_put('battingcards', payload=batting_card_payload, timeout=10)
|
|
|
|
bc_query = await db_get('battingcards', params=[('player_id', player_id)])
|
|
battingcard_id = bc_query['cards'][0]['id']
|
|
print(f" ✓ Created BattingCard ID: {battingcard_id}")
|
|
|
|
# Step 10: Create BattingCardRatings
|
|
print("\nCreating BattingCardRatings...")
|
|
for rating in ratings:
|
|
rating['battingcard_id'] = battingcard_id
|
|
rating['bat_hand'] = 'L'
|
|
|
|
ratings_payload = {'ratings': ratings}
|
|
await db_put('battingcardratings', payload=ratings_payload, timeout=10)
|
|
print(f" ✓ Created ratings for vs L and vs R")
|
|
|
|
# Step 11: Create CardPositions
|
|
print("\nCreating CardPositions...")
|
|
positions_payload = {
|
|
'positions': [
|
|
{
|
|
'player_id': player_id,
|
|
'position': '1B',
|
|
'range': 4,
|
|
'error': 10,
|
|
'arm': None,
|
|
'fielding_pct': None
|
|
},
|
|
{
|
|
'player_id': player_id,
|
|
'position': 'RF',
|
|
'range': 4,
|
|
'error': 6,
|
|
'arm': 2,
|
|
'fielding_pct': None
|
|
}
|
|
]
|
|
}
|
|
|
|
await db_put('cardpositions', payload=positions_payload, timeout=10)
|
|
print(f" ✓ Created positions: 1B (range 4, error 10), RF (range 4, arm +2, error 6)")
|
|
|
|
# Step 12: Generate and upload card image
|
|
print("\nGenerating card image...")
|
|
api_image_url = f"https://pd.manticorum.com/api/v2/players/{player_id}/battingcard?d={release_date}"
|
|
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(api_image_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
|
if resp.status == 200:
|
|
image_bytes = await resp.read()
|
|
image_size_kb = len(image_bytes) / 1024
|
|
print(f" ✓ Fetched card image ({image_size_kb:.1f} KB)")
|
|
|
|
# Upload to S3
|
|
print("\nUploading to S3...")
|
|
s3_client = boto3.client('s3', region_name=AWS_REGION)
|
|
cardset_str = f'{cardset["id"]:03d}'
|
|
s3_key = f'cards/cardset-{cardset_str}/player-{player_id}/battingcard.png'
|
|
|
|
s3_client.put_object(
|
|
Bucket=AWS_BUCKET_NAME,
|
|
Key=s3_key,
|
|
Body=image_bytes,
|
|
ContentType='image/png',
|
|
CacheControl='public, max-age=300',
|
|
Metadata={
|
|
'player-id': str(player_id),
|
|
'card-type': 'batting',
|
|
'upload-date': datetime.now().isoformat()
|
|
}
|
|
)
|
|
|
|
s3_url = f'{S3_BASE_URL}/{s3_key}?d={release_date}'
|
|
print(f" ✓ Uploaded to S3: {s3_key}")
|
|
|
|
# Update player with S3 URL
|
|
await db_patch('players', object_id=player_id, params=[('image', s3_url)])
|
|
print(f" ✓ Updated player record with S3 URL")
|
|
|
|
else:
|
|
error_text = await resp.text()
|
|
print(f" ⚠ Card generation failed (HTTP {resp.status}): {error_text}")
|
|
|
|
except Exception as e:
|
|
print(f" ⚠ Error during card generation/upload: {e}")
|
|
print(f" Player created but image not uploaded")
|
|
|
|
# Summary
|
|
print("\n" + "="*70)
|
|
print("✓ LUAN ARROTO CREATED SUCCESSFULLY!")
|
|
print("="*70)
|
|
print(f"\nPlayer ID: {player_id}")
|
|
print(f"Cardset: {cardset['name']} (ID: {cardset['id']})")
|
|
print(f"Hand: L")
|
|
print(f"Positions: 1B (range 4, error 10), RF (range 4, arm +2, error 6)")
|
|
print(f"\nOffensive Profile:")
|
|
print(f" vs LHP: .285/.455/.355 (.810 OPS)")
|
|
print(f" vs RHP: .310/.450/.385 (.835 OPS)")
|
|
print(f" Combined OPS: {total_ops:.3f}")
|
|
print(f" Pull rates: 10% vs L, 33% vs R")
|
|
print(f"\nGroundball Distribution:")
|
|
print(f" vs LHP: 3:2:1 (A:9.85, B:6.57, C:3.28)")
|
|
print(f" vs RHP: 3:3:1 (A:8.36, B:8.36, C:2.79)")
|
|
print(f"\nBaserunning:")
|
|
print(f" Running: {baserunning['running']}/17")
|
|
print(f" Steal: {baserunning['steal_low']}-{baserunning['steal_high']}")
|
|
print(f" Jump: {baserunning['steal_jump']:.7f}")
|
|
print(f"\nView card:")
|
|
print(f" PNG: {api_image_url}")
|
|
print(f" HTML: {api_image_url}&html=true")
|
|
if 's3_url' in locals():
|
|
print(f" S3: {s3_url}")
|
|
print("")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|