import gradio as gr import pandas as pd import requests import numpy as np from datetime import datetime import json from collections import Counter import os from io import StringIO # Configuración de OpenAI OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '') # Mapeo de códigos de liga LEAGUE_CODES = { 'E0': 'Premier League', 'E1': 'Championship', 'E2': 'League One', 'E3': 'League Two', 'SP1': 'La Liga', 'SP2': 'La Liga 2', 'I1': 'Serie A', 'I2': 'Serie B', 'D1': 'Bundesliga', 'D2': 'Bundesliga 2', 'F1': 'Ligue 1', 'F2': 'Ligue 2', 'N1': 'Eredivisie', 'B1': 'Jupiler League', 'P1': 'Liga Portugal', 'T1': 'Super Lig', 'G1': 'Super League' } class FootballAnalyzer: def __init__(self): self.fixtures_df = None self.league_data = None self.selected_match = None self.analysis_data = None def download_fixtures(self): """Descarga el archivo fixtures.csv""" try: url = 'https://www.football-data.co.uk/fixtures.csv' response = requests.get(url, timeout=10) response.raise_for_status() with open('fixtures.csv', 'wb') as f: f.write(response.content) self.fixtures_df = pd.read_csv('fixtures.csv') matches = [] for idx, row in self.fixtures_df.iterrows(): league_name = LEAGUE_CODES.get(row['Div'], row['Div']) match_str = f"{league_name} | {row['Date']} {row['Time']} | {row['HomeTeam']} vs {row['AwayTeam']}" matches.append(match_str) return gr.update(choices=matches, value=None), "✅ Fixtures descargados exitosamente" except Exception as e: return gr.update(choices=[]), f"❌ Error: {str(e)}" def show_bet365_odds(self, selected_match_str): """Muestra las cuotas de Bet365""" if not selected_match_str or self.fixtures_df is None: return "Selecciona un partido primero" try: parts = selected_match_str.split('|') teams = parts[2].strip().split(' vs ') home_team = teams[0].strip() away_team = teams[1].strip() match_row = self.fixtures_df[ (self.fixtures_df['HomeTeam'] == home_team) & (self.fixtures_df['AwayTeam'] == away_team) ].iloc[0] self.selected_match = match_row odds_info = f""" ### 📊 Cuotas Bet365 **{home_team} vs {away_team}** - 🏠 **Local ({home_team}):** {match_row.get('B365H', 'N/A')} - 🤝 **Empate:** {match_row.get('B365D', 'N/A')} - ✈️ **Visitante ({away_team}):** {match_row.get('B365A', 'N/A')} **Liga:** {LEAGUE_CODES.get(match_row['Div'], match_row['Div'])} **Fecha:** {match_row['Date']} {match_row['Time']} """ return odds_info except Exception as e: return f"❌ Error: {str(e)}" def download_league_data(self, selected_match_str): """Descarga datos históricos de la liga""" if not selected_match_str or self.selected_match is None: return "Primero selecciona un partido y visualiza las cuotas", None, None try: league_code = self.selected_match['Div'] url = f'https://www.football-data.co.uk/mmz4281/2526/{league_code}.csv' response = requests.get(url, timeout=10) response.raise_for_status() filename = f'league_{league_code}.csv' with open(filename, 'wb') as f: f.write(response.content) self.league_data = pd.read_csv(filename) message = f"✅ Datos descargados: {len(self.league_data)} partidos" # Generar análisis analysis_md, tables_html = self.analyze_teams() return message, analysis_md, tables_html except Exception as e: return f"❌ Error: {str(e)}", None, None def calculate_statistics(self, values): """Calcula todas las estadísticas relevantes""" if len(values) == 0: return { 'media': 0, 'mediana': 0, 'moda': 0, 'volatilidad': 0, 'desv_std': 0, 'min': 0, 'max': 0, 'total': 0, 'p25': 0, 'p75': 0, 'coef_var': 0 } values_clean = [v for v in values if pd.notna(v) and str(v).strip() != ''] if len(values_clean) == 0: return { 'media': 0, 'mediana': 0, 'moda': 0, 'volatilidad': 0, 'desv_std': 0, 'min': 0, 'max': 0, 'total': 0, 'p25': 0, 'p75': 0, 'coef_var': 0 } values_clean = [float(v) for v in values_clean] media = np.mean(values_clean) mediana = np.median(values_clean) counter = Counter(values_clean) moda = counter.most_common(1)[0][0] if counter else 0 desv_std = np.std(values_clean) volatilidad = (desv_std / media * 100) if media != 0 else 0 p25 = np.percentile(values_clean, 25) p75 = np.percentile(values_clean, 75) return { 'media': round(media, 2), 'mediana': round(mediana, 2), 'moda': round(moda, 2), 'volatilidad': round(volatilidad, 2), 'desv_std': round(desv_std, 2), 'min': round(min(values_clean), 2), 'max': round(max(values_clean), 2), 'total': round(sum(values_clean), 2), 'p25': round(p25, 2), 'p75': round(p75, 2), 'coef_var': round(volatilidad, 2) } def analyze_team_comprehensive(self, team_data, team_name, is_home): """Análisis comprehensivo de un equipo con TODAS las categorías""" if len(team_data) == 0: return None # Filtrar partidos como local o visitante if is_home: team_matches = team_data[team_data['HomeTeam'] == team_name].copy() else: team_matches = team_data[team_data['AwayTeam'] == team_name].copy() if len(team_matches) == 0: return None # Obtener últimos 5 partidos last_5 = team_matches.tail(5).copy() # Definir todas las categorías a analizar categories = {} # GOLES if is_home: categories['Goles Favor'] = { 'season': team_matches['FTHG'].astype(float), 'last5': last_5['FTHG'].astype(float) } categories['Goles Contra'] = { 'season': team_matches['FTAG'].astype(float), 'last5': last_5['FTAG'].astype(float) } categories['Goles Medio Tiempo Favor'] = { 'season': team_matches['HTHG'].astype(float) if 'HTHG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTHG'].astype(float) if 'HTHG' in last_5.columns else pd.Series([0]) } categories['Goles Medio Tiempo Contra'] = { 'season': team_matches['HTAG'].astype(float) if 'HTAG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTAG'].astype(float) if 'HTAG' in last_5.columns else pd.Series([0]) } else: categories['Goles Favor'] = { 'season': team_matches['FTAG'].astype(float), 'last5': last_5['FTAG'].astype(float) } categories['Goles Contra'] = { 'season': team_matches['FTHG'].astype(float), 'last5': last_5['FTHG'].astype(float) } categories['Goles Medio Tiempo Favor'] = { 'season': team_matches['HTAG'].astype(float) if 'HTAG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTAG'].astype(float) if 'HTAG' in last_5.columns else pd.Series([0]) } categories['Goles Medio Tiempo Contra'] = { 'season': team_matches['HTHG'].astype(float) if 'HTHG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTHG'].astype(float) if 'HTHG' in last_5.columns else pd.Series([0]) } # TIROS if is_home: categories['Tiros Totales'] = { 'season': team_matches['HS'].astype(float) if 'HS' in team_matches.columns else pd.Series([0]), 'last5': last_5['HS'].astype(float) if 'HS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco'] = { 'season': team_matches['HST'].astype(float) if 'HST' in team_matches.columns else pd.Series([0]), 'last5': last_5['HST'].astype(float) if 'HST' in last_5.columns else pd.Series([0]) } categories['Tiros Contra'] = { 'season': team_matches['AS'].astype(float) if 'AS' in team_matches.columns else pd.Series([0]), 'last5': last_5['AS'].astype(float) if 'AS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco Contra'] = { 'season': team_matches['AST'].astype(float) if 'AST' in team_matches.columns else pd.Series([0]), 'last5': last_5['AST'].astype(float) if 'AST' in last_5.columns else pd.Series([0]) } else: categories['Tiros Totales'] = { 'season': team_matches['AS'].astype(float) if 'AS' in team_matches.columns else pd.Series([0]), 'last5': last_5['AS'].astype(float) if 'AS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco'] = { 'season': team_matches['AST'].astype(float) if 'AST' in team_matches.columns else pd.Series([0]), 'last5': last_5['AST'].astype(float) if 'AST' in last_5.columns else pd.Series([0]) } categories['Tiros Contra'] = { 'season': team_matches['HS'].astype(float) if 'HS' in team_matches.columns else pd.Series([0]), 'last5': last_5['HS'].astype(float) if 'HS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco Contra'] = { 'season': team_matches['HST'].astype(float) if 'HST' in team_matches.columns else pd.Series([0]), 'last5': last_5['HST'].astype(float) if 'HST' in last_5.columns else pd.Series([0]) } # CORNERS if is_home: categories['Corners Favor'] = { 'season': team_matches['HC'].astype(float) if 'HC' in team_matches.columns else pd.Series([0]), 'last5': last_5['HC'].astype(float) if 'HC' in last_5.columns else pd.Series([0]) } categories['Corners Contra'] = { 'season': team_matches['AC'].astype(float) if 'AC' in team_matches.columns else pd.Series([0]), 'last5': last_5['AC'].astype(float) if 'AC' in last_5.columns else pd.Series([0]) } else: categories['Corners Favor'] = { 'season': team_matches['AC'].astype(float) if 'AC' in team_matches.columns else pd.Series([0]), 'last5': last_5['AC'].astype(float) if 'AC' in last_5.columns else pd.Series([0]) } categories['Corners Contra'] = { 'season': team_matches['HC'].astype(float) if 'HC' in team_matches.columns else pd.Series([0]), 'last5': last_5['HC'].astype(float) if 'HC' in last_5.columns else pd.Series([0]) } # FALTAS if is_home: categories['Faltas Cometidas'] = { 'season': team_matches['HF'].astype(float) if 'HF' in team_matches.columns else pd.Series([0]), 'last5': last_5['HF'].astype(float) if 'HF' in last_5.columns else pd.Series([0]) } categories['Faltas Recibidas'] = { 'season': team_matches['AF'].astype(float) if 'AF' in team_matches.columns else pd.Series([0]), 'last5': last_5['AF'].astype(float) if 'AF' in last_5.columns else pd.Series([0]) } else: categories['Faltas Cometidas'] = { 'season': team_matches['AF'].astype(float) if 'AF' in team_matches.columns else pd.Series([0]), 'last5': last_5['AF'].astype(float) if 'AF' in last_5.columns else pd.Series([0]) } categories['Faltas Recibidas'] = { 'season': team_matches['HF'].astype(float) if 'HF' in team_matches.columns else pd.Series([0]), 'last5': last_5['HF'].astype(float) if 'HF' in last_5.columns else pd.Series([0]) } # TARJETAS if is_home: categories['Tarjetas Amarillas'] = { 'season': team_matches['HY'].astype(float) if 'HY' in team_matches.columns else pd.Series([0]), 'last5': last_5['HY'].astype(float) if 'HY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas'] = { 'season': team_matches['HR'].astype(float) if 'HR' in team_matches.columns else pd.Series([0]), 'last5': last_5['HR'].astype(float) if 'HR' in last_5.columns else pd.Series([0]) } categories['Tarjetas Amarillas Contra'] = { 'season': team_matches['AY'].astype(float) if 'AY' in team_matches.columns else pd.Series([0]), 'last5': last_5['AY'].astype(float) if 'AY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas Contra'] = { 'season': team_matches['AR'].astype(float) if 'AR' in team_matches.columns else pd.Series([0]), 'last5': last_5['AR'].astype(float) if 'AR' in last_5.columns else pd.Series([0]) } else: categories['Tarjetas Amarillas'] = { 'season': team_matches['AY'].astype(float) if 'AY' in team_matches.columns else pd.Series([0]), 'last5': last_5['AY'].astype(float) if 'AY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas'] = { 'season': team_matches['AR'].astype(float) if 'AR' in team_matches.columns else pd.Series([0]), 'last5': last_5['AR'].astype(float) if 'AR' in last_5.columns else pd.Series([0]) } categories['Tarjetas Amarillas Contra'] = { 'season': team_matches['HY'].astype(float) if 'HY' in team_matches.columns else pd.Series([0]), 'last5': last_5['HY'].astype(float) if 'HY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas Contra'] = { 'season': team_matches['HR'].astype(float) if 'HR' in team_matches.columns else pd.Series([0]), 'last5': last_5['HR'].astype(float) if 'HR' in last_5.columns else pd.Series([0]) } # Calcular estadísticas para cada categoría stats_dict = {} for cat_name, cat_data in categories.items(): stats_dict[cat_name] = { 'temporada': self.calculate_statistics(cat_data['season'].tolist()), 'ultimos_5': self.calculate_statistics(cat_data['last5'].tolist()) } # Calcular resultados (W/D/L) results = [] for _, match in team_matches.iterrows(): if is_home: if match['FTHG'] > match['FTAG']: results.append('W') elif match['FTHG'] < match['FTAG']: results.append('L') else: results.append('D') else: if match['FTAG'] > match['FTHG']: results.append('W') elif match['FTAG'] < match['FTHG']: results.append('L') else: results.append('D') last_5_results = results[-5:] if len(results) >= 5 else results return { 'partidos_totales': len(team_matches), 'victorias': results.count('W'), 'empates': results.count('D'), 'derrotas': results.count('L'), 'forma_reciente': ''.join(last_5_results), 'estadisticas': stats_dict, 'partidos_last5': len(last_5) } def create_stats_tables(self, home_analysis, away_analysis, home_team, away_team): """Crea tablas HTML con todas las estadísticas""" html = f""" """ def format_volatility(vol): if vol < 30: return f'{vol}%' elif vol < 60: return f'{vol}%' else: return f'{vol}%' # Tabla para cada equipo for team_name, analysis, is_home in [(home_team, home_analysis, True), (away_team, away_analysis, False)]: position = "Local" if is_home else "Visitante" html += f'
Récord: {analysis["victorias"]}V - {analysis["empates"]}E - {analysis["derrotas"]}D | ' html += f'Forma Reciente: {analysis["forma_reciente"]}
' # Tabla de todas las categorías html += '| Categoría | Período | Media | Mediana | Moda | Min-Max | Volatilidad | Desv.Std | Total |
|---|---|---|---|---|---|---|---|---|
| {cat_name} | Temporada ({analysis["partidos_totales"]} PJ) | {s["media"]} | {s["mediana"]} | {s["moda"]} | {s["min"]} - {s["max"]} | {format_volatility(s["volatilidad"])} | {s["desv_std"]} | {s["total"]} |
| Últimos 5 | {l5["media"]} | {l5["mediana"]} | {l5["moda"]} | {l5["min"]} - {l5["max"]} | {format_volatility(l5["volatilidad"])} | {l5["desv_std"]} | {l5["total"]} |