paper-dynasty-card-creation/custom_cards/update_luan_arroto.py
Cal Corum 0a17745389 Run black and ruff across entire codebase
Standardize formatting with black and apply ruff auto-fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:24:33 -05:00

218 lines
7.2 KiB
Python

"""
Update Luan Arroto to 0.850 Combined OPS
Changes:
- vs R: .310/.450/.385 → .322/.467/.400
- vs L: .285/.455/.355 → .296/.473/.369
- Combined OPS: 0.818 → 0.850
Scale factor: 1.0387 applied to all stats proportionally.
All ratios (groundball, spray, etc.) remain identical.
"""
import asyncio
from custom_cards.archetype_definitions import BatterArchetype
from custom_cards.archetype_calculator import (
BatterRatingCalculator,
calculate_total_ops,
)
from db_calls import db_put, db_patch
import boto3
from datetime import datetime
import aiohttp
# 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"
# Known IDs from database
PLAYER_ID = 13156
BATTINGCARD_ID = 6163
CARDSET_ID = 29
async def main():
"""Update Luan Arroto to target 0.850 OPS."""
print("\n" + "=" * 70)
print("UPDATING LUAN ARROTO TO 0.850 OPS")
print("=" * 70)
# Step 1: Calculate new ratings with scaled stats
print("\nCalculating new ratings (target OPS: 0.850)...")
# Target stats (scaled by 1.0387 from original)
archetype = BatterArchetype(
name="Luan Arroto Updated",
description="Contact hitter with platoon advantage and spray approach",
# SCALED STATS for 0.850 OPS target
avg_vs_r=0.322,
obp_vs_r=0.467,
slg_vs_r=0.400,
bb_pct_vs_r=0.14,
k_pct_vs_r=0.10,
avg_vs_l=0.296,
obp_vs_l=0.473,
slg_vs_l=0.369,
bb_pct_vs_l=0.17,
k_pct_vs_l=0.09,
# All ratios unchanged
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=BATTINGCARD_ID)
# Step 3: Apply same customizations as original
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"]
# Step 4: Custom groundball ratios (same as original)
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 OPS
total_ops = calculate_total_ops(vl, vr, is_pitcher=False)
print(f" ✓ Combined OPS: {total_ops:.3f}")
print(
f" ✓ vs LHP: .{int(vl['avg']*1000):03d}/.{int(vl['obp']*1000):03d}/.{int(vl['slg']*1000):03d} ({vl['obp']+vl['slg']:.3f} OPS)"
)
print(
f" ✓ vs RHP: .{int(vr['avg']*1000):03d}/.{int(vr['obp']*1000):03d}/.{int(vr['slg']*1000):03d} ({vr['obp']+vr['slg']:.3f} OPS)"
)
# Step 5: Update batting card ratings
print("\nUpdating BattingCardRatings in database...")
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(" ✓ Updated ratings for vs L and vs R")
# Step 6: Generate new card image
print("\nGenerating new card image...")
now = datetime.now()
release_date = f"{now.year}-{now.month}-{now.day}"
timestamp = int(now.timestamp())
cache_bust_date = f"{now.year}-{now.month}-{now.day}-{timestamp}"
api_image_url = f"https://pd.manticorum.com/api/v2/players/{PLAYER_ID}/battingcard?d={cache_bust_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)")
# Step 7: 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(),
"combined-ops": f"{total_ops:.3f}",
},
)
s3_url = f"{S3_BASE_URL}/{s3_key}?d={release_date}"
print(f" ✓ Uploaded to S3: {s3_key}")
# Step 8: Update player with new S3 URL
await db_patch(
"players", object_id=PLAYER_ID, params=[("image", s3_url)]
)
print(" ✓ Updated player record with new 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}")
import traceback
traceback.print_exc()
# Summary
print("\n" + "=" * 70)
print("✓ LUAN ARROTO UPDATED SUCCESSFULLY!")
print("=" * 70)
print(f"\nPlayer ID: {PLAYER_ID}")
print(f"BattingCard ID: {BATTINGCARD_ID}")
print("\nOLD Offensive Profile:")
print(" vs LHP: .285/.455/.355 (.810 OPS)")
print(" vs RHP: .310/.450/.385 (.835 OPS)")
print(" Combined OPS: 0.818")
print("\nNEW Offensive Profile:")
print(" vs LHP: .296/.473/.369 (.842 OPS)")
print(" vs RHP: .322/.467/.400 (.867 OPS)")
print(f" Combined OPS: {total_ops:.3f}")
print("\nView updated card:")
print(
f" PNG: https://pd.manticorum.com/api/v2/players/{PLAYER_ID}/battingcard?d={cache_bust_date}"
)
print(
f" HTML: https://pd.manticorum.com/api/v2/players/{PLAYER_ID}/battingcard?d={cache_bust_date}&html=true"
)
if "s3_url" in locals():
print(f" S3: {s3_url}")
print("")
if __name__ == "__main__":
asyncio.run(main())