""" Create custom card for Sphealthamus Sphealy Spheal Sr Power hitter vs RHP (singles/HRs), patient hitter vs LHP (walks/singles) Extreme pull tendency vs RHP (62%), all-fields vs LHP (28%) """ import asyncio from custom_cards.archetype_definitions import BatterArchetype from custom_cards.archetype_calculator import ( BatterRatingCalculator, calculate_total_ops, ) from creation_helpers import mlbteam_and_franchise from db_calls import db_get, db_post, db_put, db_patch from datetime import datetime import random # 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" async def create_sphealthamus_spheal(): """Create Sphealthamus Sphealy Spheal Sr custom card.""" print("=" * 70) print("CREATING SPHEALTHAMUS SPHEALY SPHEAL SR") print("=" * 70) # Player details name_first = "Sphealthamus Sphealy" name_last = "Spheal Sr" hand = "L" # Left-handed batter team_abbrev = "SEA" # Placeholder team positions = ["1B"] # First baseman # Get team info mlb_team_id, franchise_id = mlbteam_and_franchise(team_abbrev) # Cardset setup - Always use cardset 29 for custom characters cardset_id = 29 cardset = {"id": cardset_id, "name": "Custom Characters"} season = 2005 player_description = "05 Custom" print( f"✓ Using cardset ID: {cardset['id']} - Player description: '{player_description}'" ) # Create custom archetype for Sphealthamus Sphealy Spheal Sr # Target: Combined OPS = 0.850 # vs RHP: Power (singles + HRs with 3:2 BP-HR:HR ratio), high pull (62%) # vs LHP: Patient (walks + singles), low pull (28%) spheal = BatterArchetype( name="Sphealthamus Sphealy Spheal Sr", description="Power vs RHP, patience vs LHP, extreme splits", # VS RHP: Target OPS = ~0.850 (power profile - singles + HRs) avg_vs_r=0.260, # Boosted to hit OPS target obp_vs_r=0.340, # Boosted to hit OPS target slg_vs_r=0.495, # Boosted to hit OPS target bb_pct_vs_r=0.09, # Moderate walks k_pct_vs_r=0.24, # Higher strikeouts (power hitter) # VS LHP: Target OPS = ~0.830 (patient profile - walks + singles) avg_vs_l=0.260, # Boosted to hit OPS target obp_vs_l=0.375, # Boosted to hit OPS target slg_vs_l=0.420, # Boosted to hit OPS target bb_pct_vs_l=0.15, # Very high walks k_pct_vs_l=0.20, # Fewer strikeouts # Power distribution - high HR rate vs RHP, moderate vs LHP hr_per_hit=0.15, # High HR rate triple_per_hit=0.00, # NO TRIPLES double_per_hit=0.20, # Moderate doubles # Batted ball profile gb_pct=0.40, # Some ground balls fb_pct=0.38, # Lots of fly balls (power) ld_pct=0.22, # Line drives # Batted ball quality - power hitter hard_pct=0.40, # Lots of hard contact med_pct=0.42, # Medium contact soft_pct=0.18, # Some soft contact # Spray chart - EXTREME PULL vs RHP, all-fields vs LHP pull_pct=0.45, # Will adjust per split center_pct=0.32, # Will adjust per split oppo_pct=0.23, # Will adjust per split # Infield hits ifh_pct=0.04, # Low (slow runner) # Specific power metrics hr_fb_pct=0.16, # Good HR/FB # Baserunning - poor speed speed_rating=3, # Poor speed steal_jump=5, # Below average xbt_pct=0.42, # Below average # Situational hitting - poor hit_run_skill=3, # Poor hit-and-run # Defensive profile primary_positions=["1B"], defensive_rating=5, # Average defender ) print(f"\n✓ Created custom archetype: {spheal.name}") print( f" Base stats vR: {spheal.avg_vs_r:.3f}/{spheal.obp_vs_r:.3f}/{spheal.slg_vs_r:.3f} (OPS: {spheal.obp_vs_r + spheal.slg_vs_r:.3f})" ) print( f" Base stats vL: {spheal.avg_vs_l:.3f}/{spheal.obp_vs_l:.3f}/{spheal.slg_vs_l:.3f} (OPS: {spheal.obp_vs_l + spheal.slg_vs_l:.3f})" ) # Calculate ratings calc = BatterRatingCalculator(spheal) ratings = calc.calculate_ratings(battingcard_id=0) # Temp ID baserunning = calc.calculate_baserunning() # Override steal rate to 0.416666 (18-13) print("\n✓ Setting steal rate to 0.416666 (18-13)...") baserunning["steal_jump"] = 0.416666 baserunning["steal_high"] = 18 baserunning["steal_low"] = 13 # Override running to 9 print("✓ Setting running to 9...") baserunning["running"] = 9 # Override bunting to D print("✓ Setting bunting to D...") baserunning["bunting"] = "D" # Override hit-and-run to D print("✓ Setting hit-and-run to D...") baserunning["hit_and_run"] = "D" # Apply randomization to make results look more natural print("\n✓ Applying randomization (±0.5) and rounding to 0.05...") random.seed(43) # Different seed for different character for rating in ratings: # Fields to randomize randomize_fields = [ "homerun", "bp_homerun", "triple", "double_three", "double_two", "double_pull", "single_two", "single_one", "single_center", "walk", "hbp", "strikeout", "lineout", "popout", "flyout_a", "flyout_bq", "flyout_lf_b", "flyout_rf_b", "groundout_a", "groundout_b", "groundout_c", ] for field in randomize_fields: if rating[field] > 0: randomization = random.uniform(-0.5, 0.5) new_value = rating[field] + randomization rating[field] = round(new_value * 20) / 20 rating[field] = max(0.05, rating[field]) # Fix BP-HR and HBP to whole numbers, remove triples, adjust pull rates print("\n✓ Applying custom adjustments...") print(" - Setting BP-HR to whole numbers") print(" - Setting HBP to whole numbers") print(" - Removing all triples") print(" - Adjusting pull rates (62% vR, 28% vL)") print(" - Setting BP-HR:HR ratio to 3:2 vs RHP") # vs LHP (ratings[0]) - Patient profile old_bphr_vl = ratings[0]["bp_homerun"] ratings[0]["bp_homerun"] = 1.0 # Fewer BP-HR vs LHP ratings[0]["single_center"] += old_bphr_vl - 1.0 old_hbp_vl = ratings[0]["hbp"] ratings[0]["hbp"] = 1.0 ratings[0]["single_center"] += old_hbp_vl - 1.0 # Remove triples vL old_triple_vl = ratings[0]["triple"] ratings[0]["triple"] = 0.0 ratings[0]["single_center"] += old_triple_vl # Remove Double2 vL and move to Walks old_double2_vl = ratings[0]["double_two"] ratings[0]["double_two"] = 0.0 ratings[0]["walk"] += old_double2_vl # Set pull rate to 28% vL ratings[0]["pull_rate"] = 0.28 ratings[0]["center_rate"] = 0.40 ratings[0]["slap_rate"] = 0.32 # vs RHP (ratings[1]) - Power profile # Add 2 BP-HR and 1.4 HR total_hr_chances = ratings[1]["homerun"] + ratings[1]["bp_homerun"] ratings[1]["homerun"] = 3.4 # Add 1.4 HR ratings[1]["bp_homerun"] = 5.0 # Add 2 BP-HR # Redistribute excess excess = total_hr_chances - 8.4 ratings[1]["single_center"] += excess old_hbp_vr = ratings[1]["hbp"] ratings[1]["hbp"] = 1.0 ratings[1]["single_center"] += old_hbp_vr - 1.0 # Remove triples vR old_triple_vr = ratings[1]["triple"] ratings[1]["triple"] = 0.0 ratings[1]["single_center"] += old_triple_vr # Remove Double2 vR and move to singles old_double2_vr = ratings[1]["double_two"] ratings[1]["double_two"] = 0.0 ratings[1]["single_center"] += old_double2_vr # Remove Flyout LF-B vR and move to strikeouts old_flyout_lf_vr = ratings[1]["flyout_lf_b"] ratings[1]["flyout_lf_b"] = 0.0 ratings[1]["strikeout"] += old_flyout_lf_vr # Set pull rate to 62% vR ratings[1]["pull_rate"] = 0.62 ratings[1]["center_rate"] = 0.25 ratings[1]["slap_rate"] = 0.13 # Fix total chances to exactly 108.0 for rating in ratings: total = sum( [ rating["homerun"], rating["bp_homerun"], rating["triple"], rating["double_three"], rating["double_two"], rating["double_pull"], rating["single_two"], rating["single_one"], rating["single_center"], rating["bp_single"], rating["walk"], rating["hbp"], rating["strikeout"], rating["lineout"], rating["popout"], rating["flyout_a"], rating["flyout_bq"], rating["flyout_lf_b"], rating["flyout_rf_b"], rating["groundout_a"], rating["groundout_b"], rating["groundout_c"], ] ) diff = 108.0 - total if abs(diff) > 0.01: rating["groundout_b"] += diff rating["groundout_b"] = round(rating["groundout_b"] * 20) / 20 # Adjust groundout ratios to 4:2:1 (A:B:C) print("\n✓ Adjusting groundout ratios to 4:2:1 (A:B:C)...") for rating in ratings: total_groundouts = ( rating["groundout_a"] + rating["groundout_b"] + rating["groundout_c"] ) rating["groundout_a"] = round(total_groundouts * (4 / 7) * 20) / 20 rating["groundout_b"] = round(total_groundouts * (2 / 7) * 20) / 20 rating["groundout_c"] = round(total_groundouts * (1 / 7) * 20) / 20 # Set Flyout A to 1.0 and move removed chances to strikeouts print("✓ Setting Flyout A to 1.0 both sides...") for rating in ratings: old_flyout_a = rating["flyout_a"] rating["flyout_a"] = 1.0 rating["strikeout"] += old_flyout_a - 1.0 # Rebalance again after adjustments for rating in ratings: total = sum( [ rating["homerun"], rating["bp_homerun"], rating["triple"], rating["double_three"], rating["double_two"], rating["double_pull"], rating["single_two"], rating["single_one"], rating["single_center"], rating["bp_single"], rating["walk"], rating["hbp"], rating["strikeout"], rating["lineout"], rating["popout"], rating["flyout_a"], rating["flyout_bq"], rating["flyout_lf_b"], rating["flyout_rf_b"], rating["groundout_a"], rating["groundout_b"], rating["groundout_c"], ] ) diff = 108.0 - total if abs(diff) > 0.01: rating["groundout_b"] += diff rating["groundout_b"] = round(rating["groundout_b"] * 20) / 20 # Recalculate rate stats for rating in ratings: total_hits = ( rating["homerun"] + rating["bp_homerun"] * 0.5 + rating["triple"] + rating["double_three"] + rating["double_two"] + rating["double_pull"] + rating["single_two"] + rating["single_one"] + rating["single_center"] + rating["bp_single"] * 0.5 ) rating["avg"] = round(total_hits / 108, 5) rating["obp"] = round((total_hits + rating["hbp"] + rating["walk"]) / 108, 5) rating["slg"] = round( ( rating["homerun"] * 4 + rating["bp_homerun"] * 2 + rating["triple"] * 3 + ( rating["double_two"] + rating["double_three"] + rating["double_pull"] ) * 2 + rating["single_center"] + rating["single_two"] + rating["single_one"] + rating["bp_single"] ) / 108, 5, ) # Display adjusted ratings print("\n" + "=" * 70) print("FINAL RATINGS (TWO-COLUMN TABLE)") print("=" * 70) vl = ratings[0] vr = ratings[1] print(f"\n{'RATING':<25} {'VS LHP':>12} {'VS RHP':>12}") print("-" * 50) print(f"{'AVG':<25} {vl['avg']:>12.3f} {vr['avg']:>12.3f}") print(f"{'OBP':<25} {vl['obp']:>12.3f} {vr['obp']:>12.3f}") print(f"{'SLG':<25} {vl['slg']:>12.3f} {vr['slg']:>12.3f}") print(f"{'OPS':<25} {vl['obp']+vl['slg']:>12.3f} {vr['obp']+vr['slg']:>12.3f}") print() print(f"{'HITS':<25}") print(f"{' Homerun':<25} {vl['homerun']:>12.1f} {vr['homerun']:>12.1f}") print(f"{' BP Homerun':<25} {vl['bp_homerun']:>12.1f} {vr['bp_homerun']:>12.1f}") print(f"{' Triple':<25} {vl['triple']:>12.1f} {vr['triple']:>12.1f}") print( f"{' Double (3B)':<25} {vl['double_three']:>12.1f} {vr['double_three']:>12.1f}" ) print(f"{' Double (2B)':<25} {vl['double_two']:>12.1f} {vr['double_two']:>12.1f}") print( f"{' Double (Pull)':<25} {vl['double_pull']:>12.1f} {vr['double_pull']:>12.1f}" ) print(f"{' Single (2B)':<25} {vl['single_two']:>12.1f} {vr['single_two']:>12.1f}") print(f"{' Single (1B)':<25} {vl['single_one']:>12.1f} {vr['single_one']:>12.1f}") print( f"{' Single (Center)':<25} {vl['single_center']:>12.1f} {vr['single_center']:>12.1f}" ) print(f"{' BP Single':<25} {vl['bp_single']:>12.1f} {vr['bp_single']:>12.1f}") print() print(f"{'ON-BASE':<25}") print(f"{' Walk':<25} {vl['walk']:>12.1f} {vr['walk']:>12.1f}") print(f"{' HBP':<25} {vl['hbp']:>12.1f} {vr['hbp']:>12.1f}") print() print(f"{'SPRAY CHART':<25}") print(f"{' Pull %':<25} {vl['pull_rate']:>11.1%} {vr['pull_rate']:>11.1%}") print(f"{' Center %':<25} {vl['center_rate']:>11.1%} {vr['center_rate']:>11.1%}") print(f"{' Opposite %':<25} {vl['slap_rate']:>11.1%} {vr['slap_rate']:>11.1%}") print() # Calculate and display total OPS total_ops = calculate_total_ops(ratings[0], ratings[1], is_pitcher=False) print(f"\nTotal OPS: {total_ops:.3f} (Target: 0.850)") print("\nBaserunning:") print( f" Steal: {baserunning['steal_low']}-{baserunning['steal_high']} (Jump: {baserunning['steal_jump']})" ) print( f" Running: {baserunning['running']} Hit-and-Run: {baserunning['hit_and_run']} Bunting: {baserunning['bunting']}" ) # Summary print("\n" + "=" * 70) print("SUMMARY") print("=" * 70) print(f"\nPlayer: Sphealthamus Sphealy Spheal Sr ({hand})") print("Position: 1B (Range 5, Error 3)") print("Cardset: 29 (Custom Characters)") print("Description: 05 Custom") print(f"Total OPS: {total_ops:.3f} / 0.850 target") print( f"Pull Rate: {vl['pull_rate']*100:.0f}% vL / {vr['pull_rate']*100:.0f}% vR (Target: 28% vL, 62% vR)" ) print( f"BP-HR:HR ratio vR: {vr['bp_homerun']:.0f}:{vr['homerun']:.0f} (Target: 3:2)" ) print("\n" + "=" * 70) print("DATABASE CREATION") print("=" * 70) # Create database records bbref_id = f"custom_{name_last.lower().replace(' ', '')}{name_first[0].lower()}01" # Step 1: Create/verify MLBPlayer record print("\n✓ Checking for existing MLBPlayer record...") mlb_query = await db_get( "mlbplayers", params=[("first_name", name_first), ("last_name", name_last)] ) if mlb_query and mlb_query.get("count", 0) > 0: mlbplayer_id = mlb_query["players"][0]["id"] print(f" Using existing MLBPlayer ID: {mlbplayer_id}") else: try: mlbplayer_payload = { "key_bbref": bbref_id, "key_fangraphs": 0, "key_mlbam": 0, "key_retro": "", "first_name": name_first, "last_name": name_last, } new_mlbplayer = await db_post("mlbplayers/one", payload=mlbplayer_payload) mlbplayer_id = new_mlbplayer["id"] print(f" Created MLBPlayer ID: {mlbplayer_id}") except ValueError as e: print(f" MLBPlayer creation failed: {e}") print(" Proceeding without MLBPlayer linkage...") mlbplayer_id = None # Step 2: Create or update Player record print("\n✓ Checking for existing Player record...") now = datetime.now() release_date = f"{now.year}-{now.month}-{now.day}" p_query = await db_get( "players", params=[("bbref_id", bbref_id), ("cardset_id", cardset["id"])] ) if p_query and p_query.get("count", 0) > 0: player_id = p_query["players"][0]["player_id"] print(f" Using existing Player ID: {player_id}") image_url = f"https://pd.manticorum.com/api/v2/players/{player_id}/battingcard?d={release_date}" await db_patch("players", object_id=player_id, params=[("image", image_url)]) print(" Updated image URL") else: print(" Creating new Player record...") temp_image_url = ( f"https://pd.manticorum.com/api/v2/players/0/battingcard?d={release_date}" ) player_payload = { "p_name": f"{name_first} {name_last}", "bbref_id": bbref_id, "fangr_id": 0, "strat_code": 0, "hand": hand, "mlbclub": "Custom Ballplayers", "franchise": "Custom Ballplayers", "cardset_id": cardset["id"], "description": player_description, "is_custom": True, "cost": 100, "rarity_id": 5, "image": temp_image_url, "set_num": 9999, "pos_1": "1B", } if mlbplayer_id: player_payload["mlbplayer_id"] = mlbplayer_id new_player = await db_post("players", payload=player_payload) player_id = new_player["player_id"] print(f" Created Player ID: {player_id}") image_url = f"https://pd.manticorum.com/api/v2/players/{player_id}/battingcard?d={release_date}" await db_patch("players", object_id=player_id, params=[("image", image_url)]) print(" Updated with correct image URL") # Step 3: Create BattingCard print("\n✓ Creating BattingCard...") 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["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": hand, "bunting": baserunning["bunting"], } ] } await db_put("battingcards", payload=batting_card_payload, timeout=10) print(" BattingCard created") bc_query = await db_get("battingcards", params=[("player_id", player_id)]) battingcard_id = bc_query["cards"][0]["id"] print(f" BattingCard ID: {battingcard_id}") # Step 4: Create BattingCardRatings print("\n✓ Creating BattingCardRatings...") for rating in ratings: rating["battingcard_id"] = battingcard_id ratings_payload = {"ratings": ratings} await db_put("battingcardratings", payload=ratings_payload, timeout=10) print(" Ratings created (vL and vR)") # Step 5: Create CardPositions print("\n✓ Creating CardPosition (1B)...") positions_payload = { "positions": [ { "player_id": player_id, "variant": 0, "position": "1B", "innings": 1, "range": 5, "error": 3, } ] } await db_put("cardpositions", payload=positions_payload, timeout=10) print(" Position created: 1B (Range 5, Error 3)") # Step 6: Update rarity and cost print("\n✓ Updating rarity and cost...") target_rarity_id = 3 # Starter target_cost = 188 await db_patch( "players", object_id=player_id, params=[("rarity_id", target_rarity_id)] ) await db_patch("players", object_id=player_id, params=[("cost", target_cost)]) print(" Rarity set to: Starter (ID 3)") print(f" Cost set to: {target_cost}") # Step 7: Skip image generation for now print("\n✓ Skipping card image generation and S3 upload (will do after review)...") api_image_url = f"https://pd.manticorum.com/api/v2/players/{player_id}/battingcard?d={release_date}" s3_url = f"https://{AWS_BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com/cards/cardset-{cardset['id']:03d}/player-{player_id}/battingcard.png?d={release_date}" print(f" Card preview URL: {api_image_url}") print(f" Future S3 URL: {s3_url}") print("\n" + "=" * 70) print("✅ SUCCESS!") print("=" * 70) print("\nSphealthamus Sphealy Spheal Sr created successfully!") print(f" Player ID: {player_id}") print(f" BattingCard ID: {battingcard_id}") print(" Position: 1B (Range 5, Error 3)") print(" Bunting: D Hit-and-Run: D") print(f" Running: {baserunning['running']}") print( f" Stealing: {baserunning['steal_low']}-{baserunning['steal_high']} ({baserunning['steal_jump']})" ) print(f" Pull Rates: {vl['pull_rate']*100:.0f}% vL, {vr['pull_rate']*100:.0f}% vR") print(" Rarity: Starter") print(f" Cost: {target_cost}") print(f" Total OPS: {total_ops:.3f}") print(f"\n Card Preview URL: {api_image_url}") print(" (Image not yet uploaded to S3 - awaiting review)") print("\n" + "=" * 70) if __name__ == "__main__": asyncio.run(create_sphealthamus_spheal())