""" Preview ratings for Kalin Young custom player. Target: 0.855 total OPS (UPGRADED from 0.820) Pull rate: vL=33%, vR=25% Steal rate: 0.22222 """ from custom_cards.archetype_definitions import BatterArchetype from custom_cards.archetype_calculator import BatterRatingCalculator, calculate_total_ops import random # Create custom archetype for Kalin Young # Target: Combined OPS = 0.855 with formula: (OPS_vR + OPS_vL + min) / 3 # UPGRADE: Increased OBP via HBP (+1.0) and walks kalin_young = BatterArchetype( name="Kalin Young", description="All-fields outfielder with low pull rate, balanced contact and power", # VS RHP: Target OPS ~0.815 (OBP ~0.350 + SLG ~0.465) - aiming for 0.855 total avg_vs_r=0.240, # Good average obp_vs_r=0.350, # UPGRADED OBP (+0.035) slg_vs_r=0.435, # Decent power (unchanged) bb_pct_vs_r=0.115, # INCREASED walks (0.09 → 0.115) k_pct_vs_r=0.21, # Decent contact # VS LHP: Target OPS ~0.830 (OBP ~0.365 + SLG ~0.465) - aiming for 0.855 total avg_vs_l=0.250, # Better average vs L obp_vs_l=0.360, # UPGRADED OBP (+0.035) slg_vs_l=0.445, # More power vs L (unchanged) bb_pct_vs_l=0.115, # INCREASED walks (0.09 → 0.115) k_pct_vs_l=0.20, # Slightly better contact vs L # Power distribution - moderate power, balanced hr_per_hit=0.09, # Moderate HR rate triple_per_hit=0.03, # Some triples (outfield speed) double_per_hit=0.26, # Good gap power # Singles = 62% of hits # Batted ball profile - balanced gb_pct=0.43, # Moderate ground balls fb_pct=0.34, # Moderate fly balls ld_pct=0.23, # Line drives # Batted ball quality hard_pct=0.36, # Good hard contact med_pct=0.46, # Lots of medium contact soft_pct=0.18, # Some soft contact # Spray chart - LOW PULL RATE (25%) pull_pct=0.25, # Low pull (per requirements) center_pct=0.40, # High center usage oppo_pct=0.35, # Good opposite field usage # Infield hits ifh_pct=0.07, # Moderate speed for infield hits # Specific power metrics hr_fb_pct=0.11, # Moderate HR/FB # Baserunning - good speed speed_rating=6, # Above average speed steal_jump=6, # Good reads xbt_pct=0.53, # Takes extra bases # Situational hitting hit_run_skill=7, # Good contact = good hit-and-run # Defensive profile primary_positions=["RF", "LF"], defensive_rating=6, # Above average defender ) print("="*70) print("KALIN YOUNG - CUSTOM PLAYER PREVIEW") print("="*70) print() print("Player Info:") print(" Name: Kalin Young") print(" Hand: R (Right-handed batter)") print(" Primary Position: RF") print(" Secondary Position: LF") print() # Show defensive ratings print("Defensive Ratings (per requirements):") print(" RF: Range 3 / Error 7 / Arm 0") print(" LF: Range 4 / Error 7 / Arm 0") print() print("Target Statistics (UPGRADED):") print(f" Target Total OPS: 0.855 (was 0.820)") print(f" Target Pull Rate: vL=33%, vR=25%") print(f" Running: 13") print(f" Stealing: 15-7 (0.22222)") print(f" HBP: 2.0 both splits (+1.0)") print() # Calculate ratings calc = BatterRatingCalculator(kalin_young) ratings = calc.calculate_ratings(battingcard_id=0) # Temp ID baserunning = calc.calculate_baserunning() # Override steal rate to 0.22222 - UPGRADED steal success baserunning['steal_jump'] = 0.22222 baserunning['steal_high'] = 15 baserunning['steal_low'] = 7 # Override running to 13 baserunning['running'] = 13 # Set bunting to C (average) baserunning['bunting'] = 'C' # Apply randomization to make results look more natural random.seed(42) # For reproducibility def round_to_05(value): """Round to nearest 0.05 (Stratomatic standard).""" return round(value * 20) / 20 def add_variation(base_value, max_variation=0.5): """Add small random variation to a value, then round to 0.05.""" if base_value == 0.0: return 0.0 # Don't randomize zeros variation = random.uniform(-max_variation, max_variation) result = max(0.0, base_value + variation) return round_to_05(result) # Randomize to make card look natural for rating in ratings: 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: rating[field] = add_variation(rating[field], 0.5) # Fix BP-HR and HBP to whole numbers (CRITICAL RULE) # UPGRADE: HBP increased from 1 to 2 both splits print("\n✓ Setting BP-HR to whole numbers (2 vL, 1 vR)...") print("✓ Setting HBP to whole numbers (2 both splits) - UPGRADED +1.0...") print("✓ Removing Triple vL...") # vs LHP (ratings[0]) old_bphr_vl = ratings[0]['bp_homerun'] ratings[0]['bp_homerun'] = 2.0 # Redistribute difference to single_center ratings[0]['single_center'] += (old_bphr_vl - 2.0) old_hbp_vl = ratings[0]['hbp'] ratings[0]['hbp'] = 2.0 # UPGRADED from 1.0 # Redistribute difference to outs (since we're adding OBP) ratings[0]['groundout_b'] += (old_hbp_vl - 2.0) # Remove triple vL old_triple_vl = ratings[0]['triple'] ratings[0]['triple'] = 0.0 # Redistribute to singles ratings[0]['single_center'] += old_triple_vl # vs RHP (ratings[1]) old_bphr_vr = ratings[1]['bp_homerun'] ratings[1]['bp_homerun'] = 1.0 # Redistribute difference to single_center ratings[1]['single_center'] += (old_bphr_vr - 1.0) old_hbp_vr = ratings[1]['hbp'] ratings[1]['hbp'] = 2.0 # UPGRADED from 1.0 # Redistribute difference to outs (since we're adding OBP) ratings[1]['groundout_b'] += (old_hbp_vr - 2.0) # Adjust pull rate: vL to 33%, vR stays at 25% print("✓ Adjusting pull rate vL to 33% (vR stays 25%)...") ratings[0]['pull_rate'] = 0.33 ratings[0]['center_rate'] = 0.37 # Reduce center to compensate ratings[0]['slap_rate'] = 0.30 # Reduce oppo slightly # vR stays at archetype defaults (~25%) # Manual adjustments for vL print("✓ Manual vL adjustments: HBP -1.0 → BB, K -5.0 → GB-B +3.0, GB-C +2.0...") ratings[0]['hbp'] -= 1.0 # 2.0 → 1.0 ratings[0]['walk'] += 1.0 # Add to BB ratings[0]['strikeout'] -= 5.0 # Remove 5.0 K ratings[0]['groundout_b'] += 3.0 # Add 3.0 to GB-B ratings[0]['groundout_c'] += 2.0 # Add 2.0 to GB-C # Rebalance each split to exactly 108.00 def rebalance_to_108(rating_dict): """Adjust to ensure total = 108.00 by tweaking single_center (most common result).""" current_total = sum([ rating_dict['homerun'], rating_dict['bp_homerun'], rating_dict['triple'], rating_dict['double_three'], rating_dict['double_two'], rating_dict['double_pull'], rating_dict['single_two'], rating_dict['single_one'], rating_dict['single_center'], rating_dict['bp_single'], rating_dict['walk'], rating_dict['hbp'], rating_dict['strikeout'], rating_dict['lineout'], rating_dict['popout'], rating_dict['flyout_a'], rating_dict['flyout_bq'], rating_dict['flyout_lf_b'], rating_dict['flyout_rf_b'], rating_dict['groundout_a'], rating_dict['groundout_b'], rating_dict['groundout_c'] ]) diff = 108.0 - current_total rating_dict['single_center'] = round_to_05(rating_dict['single_center'] + diff) return rating_dict ratings[0] = rebalance_to_108(ratings[0]) ratings[1] = rebalance_to_108(ratings[1]) # Recalculate rate stats after randomization 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 results print("-"*70) print("CALCULATED BATTING RATINGS") print("-"*70) for rating in ratings: vs_hand = rating['vs_hand'] print(f"\nVS {vs_hand}HP:") print(f" AVG: {rating['avg']:.3f} OBP: {rating['obp']:.3f} SLG: {rating['slg']:.3f} OPS: {rating['obp']+rating['slg']:.3f}") # Hit breakdown total_hits = (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']) total_doubles = rating['double_pull'] + rating['double_two'] + rating['double_three'] total_singles = (rating['single_two'] + rating['single_one'] + rating['single_center'] + rating['bp_single']) print(f"\n Hit Distribution (out of {total_hits:.1f} total hits):") print(f" Singles: {total_singles:.1f} ({100*total_singles/total_hits:.1f}%)") print(f" Doubles: {total_doubles:.1f} ({100*total_doubles/total_hits:.1f}%)") print(f" Triples: {rating['triple']:.1f} ({100*rating['triple']/total_hits:.1f}%)") print(f" HR: {rating['homerun']+rating['bp_homerun']:.1f} ({100*(rating['homerun']+rating['bp_homerun'])/total_hits:.1f}%)") # Verify total = 108 total_chances = 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['hbp'], rating['walk'], 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'] ]) print(f"\n Total Chances: {total_chances:.2f} (must be 108.0)") # Calculate and display total OPS total_ops = calculate_total_ops(ratings[0], ratings[1], is_pitcher=False) print() print("="*70) print(f"TOTAL OPS: {total_ops:.3f} (Target: 0.855)") if abs(total_ops - 0.855) <= 0.005: print("✓ Within target range!") elif abs(total_ops - 0.855) <= 0.015: print("~ Close to target (can tweak if needed)") else: print("⚠ Outside target range - needs adjustment") print("="*70) # Show baserunning print() print("Baserunning Ratings (Stratomatic Format):") print(f" Steal Range: {baserunning['steal_low']}-{baserunning['steal_high']} (on 2d10)") print(f" Steal Auto: {baserunning['steal_auto']} (0=No, 1=Yes)") print(f" Steal Jump: {baserunning['steal_jump']:.5f} ({int(baserunning['steal_jump']*36)}/36 chances)") print(f" Running: {baserunning['running']} (scale 8-17)") print(f" Hit-and-Run: {baserunning['hit_and_run']} (letter grade A/B/C/D)") print(f" Bunting: {baserunning['bunting']} (letter grade A/B/C/D)") print() print("="*70) print("DETAILED D20 RATINGS (Side-by-Side Comparison)") print("="*70) print() # Create table header print(f"{'Rating':<25} {'vs LHP':>10} {'vs RHP':>10}") print("-" * 70) # Extract ratings for easier access vl = ratings[0] # vs LHP vr = ratings[1] # vs RHP # Display all ratings in table format rating_pairs = [ ("Homerun", 'homerun'), ("BP Homerun", 'bp_homerun'), ("Triple", 'triple'), ("Double (3-zone)", 'double_three'), ("Double (2-zone)", 'double_two'), ("Double (Pull)", 'double_pull'), ("Single (2-zone)", 'single_two'), ("Single (1-zone)", 'single_one'), ("Single (Center)", 'single_center'), ("BP Single", 'bp_single'), ("Walk", 'walk'), ("HBP", 'hbp'), ("Strikeout", 'strikeout'), ("Lineout", 'lineout'), ("Popout", 'popout'), ("Flyout A", 'flyout_a'), ("Flyout BQ", 'flyout_bq'), ("Flyout LF-B", 'flyout_lf_b'), ("Flyout RF-B", 'flyout_rf_b'), ("Groundout A", 'groundout_a'), ("Groundout B", 'groundout_b'), ("Groundout C", 'groundout_c'), ] for label, key in rating_pairs: print(f"{label:<25} {vl[key]:>10.2f} {vr[key]:>10.2f}") # Show totals vl_total = sum([vl[key] for _, key in rating_pairs]) vr_total = sum([vr[key] for _, key in rating_pairs]) print("-" * 70) print(f"{'TOTAL CHANCES':<25} {vl_total:>10.2f} {vr_total:>10.2f}") print(f"{'(must be 108.0)':<25} {'':>10} {'':>10}") # Show spray chart percentages print() print("="*70) print("SPRAY CHART VERIFICATION") print("="*70) print(f"vs LHP - Pull: {vl['pull_rate']*100:.1f}% Center: {vl['center_rate']*100:.1f}% Opposite: {vl['slap_rate']*100:.1f}%") print(f"vs RHP - Pull: {vr['pull_rate']*100:.1f}% Center: {vr['center_rate']*100:.1f}% Opposite: {vr['slap_rate']*100:.1f}%") print(f"\nTarget Pull Rate: 25% ({'✓ MATCH' if abs(vl['pull_rate'] - 0.25) < 0.03 else '⚠ ADJUST'})") print() print("="*70) print("SUMMARY (UPGRADED)") print("="*70) print() print("✓ Ratings calculated successfully") print(f"✓ Total OPS: {total_ops:.3f} (Target: 0.855)") print(f"✓ Pull Rate: vL={vl['pull_rate']*100:.0f}% (target 33%) / vR={vr['pull_rate']*100:.0f}% (target 25%)") print(f"✓ Running: {baserunning['running']} (Target: 13)") print(f"✓ Stealing: {baserunning['steal_low']}-{baserunning['steal_high']} (Target: 15-7, rate: 0.22222)") print(f"✓ HBP: 2.0 both splits (upgraded +1.0)") print() print("Next Steps:") print("1. Review the ratings above") print("2. If approved, run create_kalin_young.py to UPDATE the database records") print("3. Card will be UPDATED with:") print(" - Cardset: 29 (Custom Characters)") print(" - Positions: RF (Range 3, Error 7, Arm 0), LF (Range 4, Error 7, Arm 0) - UNCHANGED") print(" - Rarity: Starter - UNCHANGED") print(" - Cost: ~85 - UNCHANGED") print() print("⚠️ NOT POSTED TO DATABASE - PREVIEW ONLY") print("="*70)