""" Preview ratings for "Will the Thrill" custom player. Target: 0.825 total OPS, lots of singles, few XBH, low HR, slightly more power vs L """ from custom_cards.archetype_definitions import BatterArchetype from custom_cards.archetype_calculator import BatterRatingCalculator, calculate_total_ops # Create custom archetype for Will the Thrill # Target total OPS = 0.825 with formula: (OPS_vR + OPS_vL + min) / 3 # Working backwards: 0.825 * 3 = 2.475 # If OPS_vL = 0.865 and OPS_vR = 0.805, then min = 0.805 # 0.865 + 0.805 + 0.805 = 2.475 ✓ will_the_thrill = BatterArchetype( name="Will the Thrill", description="High contact singles hitter with gap power, slightly more pop vs LHP", # VS RHP: Target OPS = 0.805 # High contact, lots of singles, some doubles, few HR avg_vs_r=0.285, # Good average obp_vs_r=0.345, # Decent OBP (AVG + some walks) slg_vs_r=0.460, # Modest slugging (singles-heavy) bb_pct_vs_r=0.07, # Low-moderate walks k_pct_vs_r=0.15, # Good contact (low K) # VS LHP: Target OPS = 0.865 (slightly more power) # Better power vs lefties, more doubles/HR avg_vs_l=0.300, # Better average vs L obp_vs_l=0.360, # Better OBP slg_vs_l=0.505, # More power vs L bb_pct_vs_l=0.07, # Similar walks k_pct_vs_l=0.14, # Slightly less K vs L # Power distribution - LOW HR, few XBH, LOTS of singles hr_per_hit=0.04, # Very low HR rate (4% of hits) triple_per_hit=0.02, # Few triples double_per_hit=0.22, # Moderate doubles (gap power) # Singles = 72% of hits # Batted ball profile - contact-oriented gb_pct=0.45, # Moderate ground balls fb_pct=0.30, # Lower fly balls (less power) ld_pct=0.25, # Good line drive rate # Batted ball quality - contact over power hard_pct=0.33, # Moderate hard contact med_pct=0.50, # Lots of medium contact soft_pct=0.17, # Some soft contact # Spray chart - all fields pull_pct=0.38, # Some pull center_pct=0.36, # Good center % oppo_pct=0.26, # Uses whole field # Infield hits ifh_pct=0.08, # Decent speed for infield hits # Specific power metrics hr_fb_pct=0.08, # Low HR/FB (not a power hitter) # Baserunning - decent speed speed_rating=6, # Above average speed steal_jump=6, # Good reads xbt_pct=0.52, # Takes extra bases # Situational hitting hit_run_skill=8, # Good contact = good hit-and-run # Defensive profile primary_positions=["LF", "2B"], defensive_rating=6, # Above average defender ) # Calculate ratings calc = BatterRatingCalculator(will_the_thrill) ratings = calc.calculate_ratings(battingcard_id=0) # Temp ID baserunning = calc.calculate_baserunning() # Manual correction: Hit-and-Run should be 'A' based on actual BABIP of .428 # (High contact, low K, low HR = very high BABIP) baserunning['hit_and_run'] = 'A' # Manual correction: Remove triples from vs RHP (only keep vs LHP) ratings[1]['triple'] = 0.0 # ratings[1] is vs RHP # Redistribute the triple chances to singles ratings[1]['single_center'] += 0.75 # Adjust flyball distribution # FlyA is rare - defaults to 0.0, only 1.0 for power hitters # Will is NOT a power hitter, so flyA = 0.0 # VS RHP (ratings[1]): More flyballs to RF, flyA = 0 ratings[1]['flyout_a'] = 0.0 # Only for power hitters ratings[1]['flyout_bq'] = 5.75 # Increase to absorb flyA ratings[1]['flyout_lf_b'] = 4.00 # Lower - NOT the emphasis ratings[1]['flyout_rf_b'] = 4.55 # HIGHER - more to RF as requested # VS LHP (ratings[0]): More flyballs to LF, flyA = 0 ratings[0]['flyout_a'] = 0.0 # Only for power hitters ratings[0]['flyout_bq'] = 5.55 # Increase to absorb flyA ratings[0]['flyout_lf_b'] = 4.55 # HIGHER - more to LF as requested ratings[0]['flyout_rf_b'] = 4.00 # Lower - NOT the emphasis # VS RHP: Slightly more strikeouts (take from lineouts) ratings[1]['strikeout'] = 15.50 # Increase from 14.40 ratings[1]['lineout'] = 10.80 # Decrease from 11.90 to balance # Adjust groundball ratio to 3:2:1.5 (gbA:gbB:gbC) # VS RHP: Total groundouts = 21.40 ratings[1]['groundout_a'] = 9.85 # 3 parts ratings[1]['groundout_b'] = 6.60 # 2 parts ratings[1]['groundout_c'] = 4.95 # 1.5 parts # VS LHP: Total groundouts = 21.15 ratings[0]['groundout_a'] = 9.75 # 3 parts ratings[0]['groundout_b'] = 6.50 # 2 parts ratings[0]['groundout_c'] = 4.90 # 1.5 parts # Add light randomization to make the card less homogenous # Small variations (+/- 0.25 to 0.75) to make it feel more natural import random random.seed(42) # Consistent randomization for Will the Thrill 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 singles distribution (keeping BP-SI fixed) ratings[0]['single_two'] = add_variation(ratings[0]['single_two'], 0.75) ratings[0]['single_one'] = add_variation(ratings[0]['single_one'], 0.50) ratings[0]['single_center'] = add_variation(ratings[0]['single_center'], 0.60) ratings[1]['single_two'] = add_variation(ratings[1]['single_two'], 0.60) ratings[1]['single_one'] = add_variation(ratings[1]['single_one'], 0.75) ratings[1]['single_center'] = add_variation(ratings[1]['single_center'], 0.50) # Randomize doubles (keeping total roughly similar) ratings[0]['double_two'] = add_variation(ratings[0]['double_two'], 0.40) ratings[0]['double_pull'] = add_variation(ratings[0]['double_pull'], 0.35) ratings[1]['double_two'] = add_variation(ratings[1]['double_two'], 0.35) ratings[1]['double_pull'] = add_variation(ratings[1]['double_pull'], 0.40) # Randomize flyouts slightly ratings[0]['flyout_bq'] = add_variation(ratings[0]['flyout_bq'], 0.45) ratings[0]['flyout_lf_b'] = add_variation(ratings[0]['flyout_lf_b'], 0.30) ratings[0]['flyout_rf_b'] = add_variation(ratings[0]['flyout_rf_b'], 0.30) ratings[1]['flyout_bq'] = add_variation(ratings[1]['flyout_bq'], 0.40) ratings[1]['flyout_lf_b'] = add_variation(ratings[1]['flyout_lf_b'], 0.35) ratings[1]['flyout_rf_b'] = add_variation(ratings[1]['flyout_rf_b'], 0.35) # Randomize groundouts slightly ratings[0]['groundout_a'] = add_variation(ratings[0]['groundout_a'], 0.50) ratings[0]['groundout_b'] = add_variation(ratings[0]['groundout_b'], 0.40) ratings[0]['groundout_c'] = add_variation(ratings[0]['groundout_c'], 0.35) ratings[1]['groundout_a'] = add_variation(ratings[1]['groundout_a'], 0.45) ratings[1]['groundout_b'] = add_variation(ratings[1]['groundout_b'], 0.50) ratings[1]['groundout_c'] = add_variation(ratings[1]['groundout_c'], 0.40) # Small variation on lineouts ratings[0]['lineout'] = add_variation(ratings[0]['lineout'], 0.40) ratings[1]['lineout'] = add_variation(ratings[1]['lineout'], 0.45) # 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]) # Display results print("="*70) print("WILL THE THRILL - CUSTOM PLAYER PREVIEW") print("="*70) print() print("Player Info:") print(" Name: Will the Thrill") print(" Hand: R (Right-handed batter)") print(" Primary Position: LF") print(" Secondary Position: 2B") print() # Show defensive ratings (will need to be manually created) print("Defensive Ratings (to be set manually):") print(" LF: Range 3 / Error 7 / Arm +2") print(" 2B: Range 4 / Error 12 / Arm (default)") print() 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}%)") # On-base print(f"\n On-Base:") print(f" Walks: {rating['walk']:.1f}") print(f" HBP: {rating['hbp']:.1f}") print(f" Strikeouts: {rating['strikeout']:.1f}") # Outs distribution total_outs = (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']) fly_outs = (rating['flyout_a'] + rating['flyout_bq'] + rating['flyout_lf_b'] + rating['flyout_rf_b']) ground_outs = rating['groundout_a'] + rating['groundout_b'] + rating['groundout_c'] print(f"\n Outs Distribution:") print(f" Strikeouts: {rating['strikeout']:.1f}") print(f" Line outs: {rating['lineout']:.1f}") print(f" Fly outs: {fly_outs:.1f}") print(f" Ground outs: {ground_outs:.1f}") print(f" Pop outs: {rating['popout']:.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.825)") if abs(total_ops - 0.825) <= 0.005: print("✓ Within target range!") elif abs(total_ops - 0.825) <= 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']} (out of 1.0, = {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() 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}") print() print("="*70) print("NEXT STEPS") print("="*70) print() print("1. Review the ratings above") print("2. If you want adjustments, I can:") print(" - Tweak the power distribution (more/less HR)") print(" - Adjust contact rate (K rate)") print(" - Fine-tune OPS to hit exact target") print(" - Modify hit distribution (singles/doubles ratio)") print() print("3. When you approve, I will create:") print(" - MLBPlayer record") print(" - Player record") print(" - BattingCard record") print(" - BattingCardRatings (vs L and vs R)") print(" - CardPosition records (LF, 2B)") print() print("4. Defensive ratings (Range/Error/Arm) will need to be") print(" set via defenders module or manual database update") print() print("⚠️ NOT POSTED TO DATABASE - AWAITING YOUR APPROVAL") print("="*70)