Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import os | |
| import random | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| # --- SAFE IMPORT BLOCK --- | |
| # Prevents crash if advanced ML libraries are missing | |
| try: | |
| import joblib | |
| from sentence_transformers import SentenceTransformer | |
| from huggingface_hub import hf_hub_download | |
| LIBRARIES_AVAILABLE = True | |
| except ImportError: | |
| LIBRARIES_AVAILABLE = False | |
| print("β οΈ Warning: Advanced ML libraries missing. Running in Simulation Mode.") | |
| # ========================================== | |
| # 1. LOAD MODELS & DATA (FAIL-SAFE) | |
| # ========================================== | |
| print("π System Startup...") | |
| # Global Variables | |
| MODE = "SIMULATION" | |
| PREDICTION_MODEL = None | |
| EMBEDDINGS = None | |
| EMBEDDING_MODEL = None | |
| DATASET = None | |
| # 1.1 Attempt to load real AI models | |
| if LIBRARIES_AVAILABLE: | |
| try: | |
| print("β³ Attempting to load AI Models...") | |
| # Load Embedder | |
| EMBEDDING_MODEL = SentenceTransformer('all-MiniLM-L6-v2') | |
| # Check and load local files | |
| if os.path.exists("play_embeddings.npy") and os.path.exists("playticker_engine.pkl"): | |
| EMBEDDINGS = np.load("play_embeddings.npy") | |
| PREDICTION_MODEL = joblib.load("playticker_engine.pkl") | |
| MODE = "PRODUCTION (AI-POWERED)" | |
| print("β SUCCESS: Real AI Models Loaded.") | |
| else: | |
| print("β οΈ Files not found on disk. Switching to Logic Fallback.") | |
| except Exception as e: | |
| print(f"β Error loading models: {e}") | |
| print("β οΈ Switching to Logic Fallback.") | |
| # 1.2 Attempt to load dataset (CSV) | |
| try: | |
| if LIBRARIES_AVAILABLE: | |
| # Attempt to download from Repo | |
| dataset_path = hf_hub_download( | |
| repo_id="meirnm13/playticker-nba-momentum", | |
| filename="synthetic_nba_momentum_data.csv", | |
| repo_type="dataset" | |
| ) | |
| DATASET = pd.read_csv(dataset_path) | |
| print(f"β Loaded Dataset: {len(DATASET)} rows.") | |
| else: | |
| raise Exception("No libraries") | |
| except Exception as e: | |
| print(f"β οΈ Could not load CSV ({e}). Generating dummy data.") | |
| DATASET = pd.DataFrame({ | |
| 'play_description': [ | |
| "LeBron James throws down a monster dunk", | |
| "Stephen Curry hits a deep three pointer", | |
| "Timeout called by the Miami Heat", | |
| "Turnover by James Harden", | |
| "Giannis blocks the shot" | |
| ] * 20, | |
| 'player_name': ["LeBron James", "Stephen Curry", "Team", "James Harden", "Giannis Antetokounmpo"] * 20, | |
| 'team': ["Lakers", "Warriors", "Heat", "Clippers", "Bucks"] * 20, | |
| 'momentum_score': [0.85, 0.92, -0.1, -0.6, 0.75] * 20 | |
| }) | |
| # ========================================== | |
| # 2. LOGIC ENGINES (HYBRID) | |
| # ========================================== | |
| def heuristic_logic(text): | |
| text = str(text).lower() | |
| score = 0.0 | |
| if any(x in text for x in ["dunk", "3-pointer", "win", "clutch", "lead", "steal", "block"]): | |
| score = random.uniform(0.6, 0.95) | |
| elif any(x in text for x in ["miss", "turnover", "foul", "lose", "brick", "timeout"]): | |
| score = random.uniform(-0.7, -0.3) | |
| else: | |
| score = random.uniform(-0.2, 0.2) | |
| return score | |
| def predict_momentum(user_input_text): | |
| if not user_input_text: return "", 0.0 | |
| momentum_score = 0.0 | |
| if MODE.startswith("PRODUCTION") and PREDICTION_MODEL is not None: | |
| try: | |
| embedding = EMBEDDING_MODEL.encode([user_input_text]) | |
| momentum_score = PREDICTION_MODEL.predict(embedding.reshape(1, -1))[0] | |
| if isinstance(momentum_score, (str, int, np.integer)): | |
| mapping = {0: -0.8, 1: 0.0, 2: 0.8} | |
| momentum_score = mapping.get(momentum_score, 0.5) | |
| except: | |
| momentum_score = heuristic_logic(user_input_text) | |
| else: | |
| momentum_score = heuristic_logic(user_input_text) | |
| # Visualization | |
| if momentum_score > 0.4: | |
| category, color, icon = "HIGH POSITIVE", "#10b981", "π₯" | |
| elif momentum_score > 0.1: | |
| category, color, icon = "POSITIVE", "#22c55e", "β " | |
| elif momentum_score < -0.4: | |
| category, color, icon = "HIGH NEGATIVE", "#ef4444", "π" | |
| else: | |
| category, color, icon = "NEUTRAL", "#6b7280", "βοΈ" | |
| html_output = f""" | |
| <div style="text-align:center; padding:20px; border:3px solid {color}; | |
| background:{color}10; border-radius:15px;"> | |
| <h1 style="color:{color}; margin:0;">{icon} {category}</h1> | |
| <div style="font-size:40px; color:{color}; font-weight:bold;">{float(momentum_score):.3f}</div> | |
| <div style="font-size:12px; color:#888;">System Mode: {MODE}</div> | |
| </div> | |
| """ | |
| return html_output, float(momentum_score) | |
| # ========================================== | |
| # 3. RECOMMENDATION PIPELINE | |
| # ========================================== | |
| def find_similar_plays(user_input_text, top_k=5): | |
| # Fixed: Always return a DataFrame, even if empty | |
| if not user_input_text: | |
| return pd.DataFrame(columns=['Similarity', 'Play', 'Player', 'Score']) | |
| results = [] | |
| if MODE.startswith("PRODUCTION") and EMBEDDINGS is not None: | |
| try: | |
| input_embedding = EMBEDDING_MODEL.encode([user_input_text]) | |
| similarities = cosine_similarity(input_embedding, EMBEDDINGS)[0] | |
| top_indices = similarities.argsort()[-top_k:][::-1] | |
| for idx in top_indices: | |
| play = DATASET.iloc[idx] | |
| results.append({ | |
| 'Similarity': f"{similarities[idx]:.2f}", | |
| 'Play': str(play['play_description'])[:80] + "...", | |
| 'Player': play['player_name'], | |
| 'Score': f"{play['momentum_score']:.2f}" | |
| }) | |
| return pd.DataFrame(results) | |
| except: | |
| pass | |
| # Fallback | |
| try: | |
| sample = DATASET.sample(top_k) | |
| for _, play in sample.iterrows(): | |
| results.append({ | |
| 'Similarity': "N/A (Sim)", | |
| 'Play': str(play['play_description'])[:80] + "...", | |
| 'Player': play['player_name'], | |
| 'Score': f"{play['momentum_score']:.2f}" | |
| }) | |
| except: | |
| pass | |
| return pd.DataFrame(results) | |
| # ========================================== | |
| # 4. MAIN ANALYZER (UI HANDLER) | |
| # ========================================== | |
| def analyze_play(user_input): | |
| # FIXED: Return valid empty states instead of None to prevent Gradio Crash | |
| if not user_input or len(str(user_input).strip()) < 2: | |
| return "", 0, pd.DataFrame(columns=['Similarity', 'Play', 'Player', 'Score']) | |
| html, score = predict_momentum(user_input) | |
| sim_df = find_similar_plays(user_input) | |
| return html, score, sim_df | |
| # ========================================== | |
| # 5. GRADIO INTERFACE | |
| # ========================================== | |
| custom_css = ".gradio-container {max-width: 1100px !important; margin: auto;}" | |
| with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="orange")) as demo: | |
| gr.HTML(f"""<div style="text-align:center;"> | |
| <h1 style="color:#f59e0b;">π PlayTicker Pro</h1> | |
| <p style="color:#888;">System Status: {MODE}</p> | |
| </div>""") | |
| with gr.Row(): | |
| with gr.Column(): | |
| input_text = gr.Textbox(label="Play Description", placeholder="E.g., LeBron hits a clutch three...", lines=3) | |
| btn = gr.Button("Analyze", variant="primary") | |
| # FIXED: Added cache_examples=False to prevent startup timeout | |
| gr.Examples( | |
| examples=[ | |
| ["LeBron James hits a clutch three-pointer"], | |
| ["Steph Curry turnover leading to a fast break"], | |
| ["Giannis blocks the shot"] | |
| ], | |
| inputs=input_text, | |
| cache_examples=False | |
| ) | |
| with gr.Column(): | |
| out_html = gr.HTML(label="Prediction") | |
| out_score = gr.Number(visible=False) | |
| gr.Markdown("### π Similar Historical Plays") | |
| out_table = gr.Dataframe(headers=['Similarity', 'Play', 'Player', 'Score']) | |
| with gr.Accordion("π Dataset Explorer", open=False): | |
| gr.Dataframe(DATASET.head(50) if DATASET is not None else [], interactive=False) | |
| btn.click(analyze_play, input_text, [out_html, out_score, out_table]) | |
| if __name__ == "__main__": | |
| demo.launch() |