Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import xgboost as xgb | |
| import plotly.graph_objects as go | |
| from scipy.stats import gaussian_kde | |
| from pybaseball import pitching_stats, batting_stats, cache | |
| cache.enable() | |
| # --- THEME: DEEP STEALTH --- | |
| STEALTH_CSS = """ | |
| .gradio-container { background-color: #0d0d0d !important; color: #e0e0e0 !important; font-family: 'Segoe UI', sans-serif !important; } | |
| .player-header { background: #1a1a1a; border-bottom: 3px solid #D50032; padding: 25px; border-radius: 12px 12px 0 0; text-align: center; } | |
| .stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; padding: 20px; background: #1a1a1a; border-radius: 0 0 12px 12px; } | |
| .stat-card { background: #252525; border: 1px solid #333; border-radius: 8px; padding: 15px; text-align: center; } | |
| .metric-name { font-size: 0.7rem; font-weight: 800; color: #888; text-transform: uppercase; letter-spacing: 1px; } | |
| .metric-value { font-size: 1.8rem; font-weight: 900; color: #ffffff; margin: 4px 0; } | |
| .percentile-bar { height: 6px; background: #333; border-radius: 3px; overflow: hidden; margin-top: 8px; } | |
| .percentile-fill { height: 100%; border-radius: 3px; } | |
| """ | |
| class ProIntelligenceEngine: | |
| def __init__(self): | |
| print("📥 Initializing Hardened Engine...") | |
| p_raw = pitching_stats(2015, 2025, ind=1) | |
| h_raw = batting_stats(2015, 2025, ind=1) | |
| # 1. Clean Hitters: Manually calculate to avoid pybaseball NaNs | |
| h_raw['AVG'] = h_raw['H'] / h_raw['AB'].replace(0, np.nan) | |
| h_raw['OBP'] = (h_raw['H'] + h_raw['BB'] + h_raw['HBP']) / (h_raw['PA'].replace(0, np.nan)) | |
| h_raw['SLG'] = (h_raw['H'] + h_raw['2B'] + 2*h_raw['3B'] + 3*h_raw['HR']) / h_raw['AB'].replace(0, np.nan) | |
| h_raw['OPS'] = h_raw['OBP'] + h_raw['SLG'] | |
| h_raw['ISO'] = h_raw['SLG'] - h_raw['AVG'] | |
| self.p_metrics = ["ERA", "SO", "WHIP", "FIP"] | |
| self.h_metrics = ["PA", "H", "AVG", "HR", "OBP", "ISO", "SLG", "OPS", "SB"] | |
| self.p_feats = ['Age', 'IP', 'SO', 'BB'] | |
| self.h_feats = ['Age', 'PA', 'H', 'HR'] # Added HR as a feature for better power-stat training | |
| # Final cleanup | |
| self.p_db = p_raw[self.p_feats + self.p_metrics + ['Name', 'Season']].dropna() | |
| self.h_db = h_raw[self.h_feats + self.h_metrics + ['Name', 'Season']].dropna() | |
| self.models = {} | |
| self._train_models() | |
| def _train_models(self): | |
| for m in self.p_metrics: | |
| self.models[f"P_{m}"] = xgb.train({'objective': 'reg:squarederror'}, xgb.DMatrix(self.p_db[self.p_feats].values, label=self.p_db[m].values)) | |
| for m in self.h_metrics: | |
| self.models[f"H_{m}"] = xgb.train({'objective': 'reg:squarederror'}, xgb.DMatrix(self.h_db[self.h_feats].values, label=self.h_db[m].values)) | |
| def run_sim(self, name, role, metric, n=1500): | |
| db = self.p_db if role == "Pitcher" else self.h_db | |
| feats = self.p_feats if role == "Pitcher" else self.h_feats | |
| player = db[db['Name'] == name].sort_values('Season').iloc[-1:] | |
| if player.empty: return None | |
| base = player[feats].values[0].astype(float) | |
| base[0] += 1 | |
| sim_inputs = base * np.random.normal(1.0, 0.05, (n, len(base))) | |
| return self.models[f"{role[0]}_{metric}"].predict(xgb.DMatrix(sim_inputs)) | |
| engine = ProIntelligenceEngine() | |
| def refresh_ui(role, name): | |
| if not name: return "", go.Figure() | |
| metrics = engine.p_metrics if role == "Pitcher" else engine.h_metrics | |
| html = f"<div class='player-header'><h1>{name.upper()}</h1><p style='color:#777;'>2026 PRO PERFORMANCE REPORT</p></div>" | |
| html += "<div class='stat-grid'>" | |
| for m in metrics: | |
| sims = engine.run_sim(name, role, m) | |
| if sims is None: continue | |
| mu = np.mean(sims) | |
| # Pitchers = 2 decimals | Hitters = 3 decimals | Counts = Int | |
| if role == "Pitcher": | |
| val_str = f"{mu:.2f}" if m in ["ERA", "WHIP", "FIP"] else f"{int(mu)}" | |
| else: | |
| if m in ["AVG", "OBP", "SLG", "OPS", "ISO"]: | |
| val_str = f"{mu:.3f}".replace("0.", ".") | |
| else: | |
| val_str = f"{int(mu)}" | |
| db = engine.p_db if role == "Pitcher" else engine.h_db | |
| pct = min(max((mu / (np.mean(db[m]) * 1.8)) * 100, 5), 95) | |
| color = "#ff4b4b" if pct > 70 else "#4b91ff" if pct < 30 else "#fbc531" | |
| html += f""" | |
| <div class='stat-card'> | |
| <div class='metric-name'>{m}</div> | |
| <div class='metric-value'>{val_str}</div> | |
| <div class='percentile-bar'><div class='percentile-fill' style='width:{pct}%; background:{color}; box-shadow: 0 0 8px {color};'></div></div> | |
| </div>""" | |
| html += "</div>" | |
| # Distribution Plot | |
| primary_sims = engine.run_sim(name, role, metrics[0]) | |
| low, high = np.percentile(primary_sims, [5, 95]) | |
| kde = gaussian_kde(primary_sims) | |
| x = np.linspace(min(primary_sims), max(primary_sims), 200) | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter(x=x, y=kde(x), fill='tozeroy', line_color='#1A73E8', fillcolor='rgba(26, 115, 232, 0.2)')) | |
| fig.add_vrect(x0=low, x1=high, fillcolor="#fff", opacity=0.05, layer="below") | |
| fig.update_layout(title=dict(text=f"2026 {metrics[0]} STRESS TEST", font=dict(color="#fff")), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', height=280, margin=dict(l=10,r=10,t=50,b=10), xaxis=dict(showgrid=False, tickfont=dict(color="#444")), yaxis=dict(visible=False)) | |
| return html, fig | |
| with gr.Blocks(css=STEALTH_CSS) as demo: | |
| with gr.Row(): | |
| role_in = gr.Radio(["Pitcher", "Hitter"], value="Pitcher", label="Category") | |
| player_in = gr.Dropdown(choices=sorted(engine.p_db['Name'].unique().tolist()), label="Search") | |
| out_html = gr.HTML() | |
| out_plot = gr.Plot() | |
| role_in.change(lambda r: gr.update(choices=sorted(engine.p_db['Name'].unique().tolist() if r=="Pitcher" else engine.h_db['Name'].unique().tolist())), inputs=role_in, outputs=player_in) | |
| player_in.change(refresh_ui, inputs=[role_in, player_in], outputs=[out_html, out_plot]) | |
| demo.launch() | |