spjasper commited on
Commit
4595b6d
·
verified ·
1 Parent(s): f4af79d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +429 -373
app.py CHANGED
@@ -1,374 +1,430 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Aplicación Gradio para Análisis de Partidos de Fútbol
4
- Basada en datos de la Premier League
5
- """
6
-
7
- import gradio as gr
8
- import pandas as pd
9
- import numpy as np
10
- import anthropic
11
- import os
12
- import tempfile
13
- from io import StringIO
14
-
15
- # Variables globales para almacenar resultados
16
- analisis_partido_resultado = ""
17
- analisis_partido_volatilidad = ""
18
- df = None
19
-
20
- def cargar_datos(archivo_csv):
21
- """Carga el archivo CSV con los datos de partidos"""
22
- global df
23
- try:
24
- if archivo_csv is None:
25
- return "⚠️ Por favor, carga un archivo CSV primero.", None, None
26
-
27
- df = pd.read_csv(archivo_csv.name)
28
-
29
- # Obtener lista de equipos únicos
30
- equipos_local = df['HomeTeam'].unique().tolist()
31
- equipos_visitante = df['AwayTeam'].unique().tolist()
32
- equipos = sorted(list(set(equipos_local + equipos_visitante)))
33
-
34
- info = f"""✅ **Archivo cargado exitosamente**
35
-
36
- 📊 **Información del dataset:**
37
- - Total de partidos: {len(df)}
38
- - Total de equipos: {len(equipos)}
39
- - Columnas disponibles: {len(df.columns)}
40
-
41
- 📋 **Primeras filas del dataset:**
42
- """
43
- preview = df.head(10).to_string()
44
-
45
- return info + "\n" + preview, gr.update(choices=equipos, value=None), gr.update(choices=equipos, value=None)
46
-
47
- except Exception as e:
48
- return f"❌ Error al cargar el archivo: {str(e)}", None, None
49
-
50
-
51
- def interpretar_volatilidad(media, moda):
52
- """Compara la media con la moda para determinar la consistencia"""
53
- diferencia = abs(media - moda)
54
-
55
- if diferencia < 0.5:
56
- return "Alta Consistencia"
57
- elif media > moda:
58
- return "Volatilidad Positiva (Picos altos)"
59
- else:
60
- return "Volatilidad Negativa (Bajones marcados)"
61
-
62
-
63
- def analizar_partido(equipo_a, equipo_b):
64
- """Análisis cruzado de rendimiento entre dos equipos"""
65
- global analisis_partido_resultado, df
66
-
67
- if df is None or df.empty:
68
- return "⚠️ No hay datos disponibles. Por favor, carga un archivo CSV primero."
69
-
70
- if not equipo_a or not equipo_b:
71
- return "⚠️ Por favor, selecciona ambos equipos."
72
-
73
- # Definir las métricas de interés
74
- metrics = {
75
- 'Goles': ('FTHG', 'FTAG'),
76
- 'Goles MT': ('HTHG', 'HTAG'),
77
- 'Tiros': ('HS', 'AS'),
78
- 'Tiros a Puerta': ('HST', 'AST'),
79
- 'Corners': ('HC', 'AC')
80
- }
81
-
82
- # Filtrar datos
83
- df_home_a = df[df['HomeTeam'] == equipo_a]
84
- df_away_b = df[df['AwayTeam'] == equipo_b]
85
-
86
- # Validación
87
- if df_home_a.empty or df_away_b.empty:
88
- return f"❌ Error: No hay suficientes datos para {equipo_a} o {equipo_b}"
89
-
90
- def get_stats(series):
91
- if series.empty:
92
- return (0, 0, 0)
93
- mode_val = series.mode().iloc[0] if not series.mode().empty else 0
94
- return series.mean(), series.median(), mode_val
95
-
96
- results = []
97
- output_text = f"\n## 📊 ANÁLISIS CRUZADO: {equipo_a} (Local) vs {equipo_b} (Visitante)\n\n"
98
-
99
- for nombre, (col_home, col_away) in metrics.items():
100
- # Cruce 1: Ataque Local A vs Defensa Visitante B
101
- mean_atq_a, _, _ = get_stats(df_home_a[col_home])
102
- mean_def_b, _, _ = get_stats(df_away_b[col_home])
103
-
104
- # Cruce 2: Ataque Visitante B vs Defensa Local A
105
- mean_atq_b, _, _ = get_stats(df_away_b[col_away])
106
- mean_def_a, _, _ = get_stats(df_home_a[col_away])
107
-
108
- results.append({
109
- 'Categoría': nombre,
110
- f'Atq {equipo_a} (L)': round(mean_atq_a, 2),
111
- f'Def {equipo_b} (V)': round(mean_def_b, 2),
112
- f'Atq {equipo_b} (V)': round(mean_atq_b, 2),
113
- f'Def {equipo_a} (L)': round(mean_def_a, 2)
114
- })
115
-
116
- tabla = pd.DataFrame(results)
117
- analisis_partido_resultado = tabla.to_string(index=False)
118
- output_text += tabla.to_markdown(index=False)
119
-
120
- return output_text
121
-
122
-
123
- def analizar_partido_volatilidad_func(equipo_local, equipo_visitante):
124
- """Análisis de volatilidad y consistencia"""
125
- global analisis_partido_volatilidad, df
126
-
127
- if df is None or df.empty:
128
- return "⚠️ No hay datos disponibles. Por favor, carga un archivo CSV primero."
129
-
130
- if not equipo_local or not equipo_visitante:
131
- return "⚠️ Por favor, selecciona ambos equipos."
132
-
133
- # Categorías a analizar
134
- metricas = {
135
- 'Goles Final': ('FTHG', 'FTAG'),
136
- 'Goles Medio Tiempo': ('HTHG', 'HTAG'),
137
- 'Tiros Totales': ('HS', 'AS'),
138
- 'Tiros a Puerta': ('HST', 'AST'),
139
- 'Corners': ('HC', 'AC')
140
- }
141
-
142
- df_local = df[df['HomeTeam'] == equipo_local]
143
- df_visitante = df[df['AwayTeam'] == equipo_visitante]
144
-
145
- if df_local.empty or df_visitante.empty:
146
- return "❌ Uno de los equipos no se encuentra en el dataset."
147
-
148
- resultados = []
149
-
150
- for nombre, (col_L, col_V) in metricas.items():
151
- # CRUCE 1: ATAQUE LOCAL VS DEFENSA VISITANTE
152
- m_atq_l = df_local[col_L].mean()
153
- mod_atq_l = df_local[col_L].mode().iloc[0] if not df_local[col_L].empty else 0
154
-
155
- m_def_v = df_visitante[col_L].mean()
156
- mod_def_v = df_visitante[col_L].mode().iloc[0] if not df_visitante[col_L].empty else 0
157
-
158
- # CRUCE 2: ATAQUE VISITANTE VS DEFENSA LOCAL
159
- m_atq_v = df_visitante[col_V].mean()
160
- mod_atq_v = df_visitante[col_V].mode().iloc[0] if not df_visitante[col_V].empty else 0
161
-
162
- m_def_l = df_local[col_V].mean()
163
- mod_def_l = df_local[col_V].mode().iloc[0] if not df_local[col_V].empty else 0
164
-
165
- # Agregar resultados
166
- resultados.append({
167
- 'Métrica': f"{nombre} - Atq {equipo_local}",
168
- 'Media': round(m_atq_l, 2),
169
- 'Moda': mod_atq_l,
170
- 'Volatilidad': interpretar_volatilidad(m_atq_l, mod_atq_l)
171
- })
172
- resultados.append({
173
- 'Métrica': f"{nombre} - Def {equipo_visitante}",
174
- 'Media': round(m_def_v, 2),
175
- 'Moda': mod_def_v,
176
- 'Volatilidad': interpretar_volatilidad(m_def_v, mod_def_v)
177
- })
178
- resultados.append({
179
- 'Métrica': f"{nombre} - Atq {equipo_visitante}",
180
- 'Media': round(m_atq_v, 2),
181
- 'Moda': mod_atq_v,
182
- 'Volatilidad': interpretar_volatilidad(m_atq_v, mod_atq_v)
183
- })
184
- resultados.append({
185
- 'Métrica': f"{nombre} - Def {equipo_local}",
186
- 'Media': round(m_def_l, 2),
187
- 'Moda': mod_def_l,
188
- 'Volatilidad': interpretar_volatilidad(m_def_l, mod_def_l)
189
- })
190
-
191
- df_final = pd.DataFrame(resultados)
192
- analisis_partido_volatilidad = df_final.to_string(index=False)
193
-
194
- output_text = f"\n## 📈 ANÁLISIS DE VOLATILIDAD: {equipo_local} vs {equipo_visitante}\n\n"
195
- output_text += df_final.to_markdown(index=False)
196
-
197
- return output_text
198
-
199
-
200
- def generar_analisis_completo(equipo_local, equipo_visitante, api_key):
201
- """Genera análisis completo usando Claude AI"""
202
- global analisis_partido_resultado, analisis_partido_volatilidad
203
-
204
- if not api_key:
205
- return "⚠️ Por favor, ingresa tu API Key de Anthropic."
206
-
207
- # Ejecutar ambos análisis
208
- resultado_rendimiento = analizar_partido(equipo_local, equipo_visitante)
209
- resultado_volatilidad = analizar_partido_volatilidad_func(equipo_local, equipo_visitante)
210
-
211
- if "Error" in resultado_rendimiento or "Error" in resultado_volatilidad:
212
- return resultado_rendimiento + "\n\n" + resultado_volatilidad
213
-
214
- # Generar análisis con Claude
215
- SYSTEM_PROMPT = """
216
- Actúa como un experto analista estadístico de fútbol y apostador profesional.
217
-
218
- REGLAS DE PROCESAMIENTO:
219
- 1. Cruce de Variables: Calcula la proyección (Media Local + Media Visitante) / 2 para Goles, Corners y Tiros.
220
- 2. Evaluación de Riesgo: Cruza la 'Media' con la 'Moda' y la 'Volatilidad'.
221
- - Si Media > Moda y Volatilidad es "Picos Altos", advierte sobre la inconsistencia.
222
- - Si Media ≈ Moda y hay "Alta Consistencia", aumenta la confianza del pronóstico.
223
- 3. Formato: Genera un informe con Resumen Estadístico, Porcentajes de Probabilidad y Sugerencias de Apuesta.
224
- """
225
-
226
- USER_PROMPT = f"""
227
- Analiza el siguiente partido utilizando estas dos fuentes de datos:
228
-
229
- 1. DATOS DE RENDIMIENTO (Ataque vs Defensa):
230
- {analisis_partido_resultado}
231
-
232
- 2. DATOS DE VOLATILIDAD Y CONSISTENCIA:
233
- {analisis_partido_volatilidad}
234
-
235
- Proporciona el análisis siguiendo el tono profesional y la estructura de valor solicitada.
236
- """
237
-
238
- try:
239
- client = anthropic.Anthropic(api_key=api_key)
240
- response = client.messages.create(
241
- model="claude-haiku-4-5",
242
- max_tokens=2500,
243
- temperature=0,
244
- system=SYSTEM_PROMPT,
245
- messages=[{"role": "user", "content": USER_PROMPT}]
246
- )
247
-
248
- analisis_ia = response.content[0].text
249
-
250
- # Combinar todos los resultados
251
- output_completo = f"""# ANÁLISIS COMPLETO DEL PARTIDO
252
-
253
- {resultado_rendimiento}
254
-
255
- ---
256
-
257
- {resultado_volatilidad}
258
-
259
- ---
260
-
261
- ## 🤖 ANÁLISIS PROFESIONAL CON IA
262
-
263
- {analisis_ia}
264
- """
265
-
266
- return output_completo
267
-
268
- except Exception as e:
269
- return f"❌ Error en la API de Anthropic: {str(e)}\n\nAsegúrate de que tu API Key sea válida."
270
-
271
-
272
- # Crear la interfaz de Gradio
273
- with gr.Blocks(theme=gr.themes.Soft(), title=" Análisis de Partidos - Premier League") as demo:
274
-
275
- gr.Markdown("""
276
- # Sistema de Análisis de Partidos de Fútbol
277
- ### Análisis estadístico profesional para apuestas deportivas
278
-
279
- Esta aplicación analiza partidos de fútbol usando datos históricos de la Premier League.
280
- Proporciona análisis de rendimiento, volatilidad y predicciones profesionales con IA.
281
- """)
282
-
283
- with gr.Row():
284
- with gr.Column(scale=1):
285
- gr.Markdown("### 📁 Paso 1: Cargar Datos")
286
- archivo_input = gr.File(
287
- label="Cargar archivo CSV",
288
- file_types=[".csv"],
289
- type="filepath"
290
- )
291
- btn_cargar = gr.Button("📊 Cargar y Procesar Datos", variant="primary")
292
- info_datos = gr.Textbox(
293
- label="Información del Dataset",
294
- lines=15,
295
- interactive=False
296
- )
297
-
298
- gr.Markdown("---")
299
-
300
- with gr.Row():
301
- with gr.Column():
302
- gr.Markdown("### 🏟️ Paso 2: Seleccionar Equipos")
303
- equipo_local = gr.Dropdown(
304
- label="Equipo Local",
305
- choices=[],
306
- interactive=True
307
- )
308
- equipo_visitante = gr.Dropdown(
309
- label="Equipo Visitante",
310
- choices=[],
311
- interactive=True
312
- )
313
-
314
- with gr.Column():
315
- gr.Markdown("### 🔑 Paso 3: API Key (Opcional)")
316
- api_key = gr.Textbox(
317
- label="Anthropic API Key",
318
- placeholder="sk-ant-...",
319
- type="password",
320
- info="Solo necesaria para análisis con IA"
321
- )
322
-
323
- gr.Markdown("---")
324
-
325
- with gr.Row():
326
- btn_rendimiento = gr.Button("📊 Análisis de Rendimiento", size="lg")
327
- btn_volatilidad = gr.Button("📈 Análisis de Volatilidad", size="lg")
328
- btn_completo = gr.Button("🤖 Análisis Completo con IA", variant="primary", size="lg")
329
-
330
- gr.Markdown("---")
331
-
332
- output_analisis = gr.Markdown(label="Resultados del Análisis")
333
-
334
- # Eventos
335
- btn_cargar.click(
336
- fn=cargar_datos,
337
- inputs=[archivo_input],
338
- outputs=[info_datos, equipo_local, equipo_visitante]
339
- )
340
-
341
- btn_rendimiento.click(
342
- fn=analizar_partido,
343
- inputs=[equipo_local, equipo_visitante],
344
- outputs=[output_analisis]
345
- )
346
-
347
- btn_volatilidad.click(
348
- fn=analizar_partido_volatilidad_func,
349
- inputs=[equipo_local, equipo_visitante],
350
- outputs=[output_analisis]
351
- )
352
-
353
- btn_completo.click(
354
- fn=generar_analisis_completo,
355
- inputs=[equipo_local, equipo_visitante, api_key],
356
- outputs=[output_analisis]
357
- )
358
-
359
- gr.Markdown("""
360
- ---
361
- ### 📖 Instrucciones de Uso:
362
- 1. **Cargar datos**: Sube un archivo CSV con datos de partidos (formato Football-Data.co.uk)
363
- 2. **Seleccionar equipos**: Elige el equipo local y visitante
364
- 3. **Elegir análisis**:
365
- - **Rendimiento**: Compara ataque vs defensa
366
- - **Volatilidad**: Analiza consistencia estadística
367
- - **Completo con IA**: Genera reporte profesional (requiere API Key)
368
-
369
- 💡 **Tip**: Puedes descargar datos gratuitos desde [Football-Data.co.uk](https://www.football-data.co.uk/englandm.php)
370
- """)
371
-
372
- # Lanzar la aplicación
373
- if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  demo.launch(share=True)
 
1
+ # -*- coding: utf-8 -*-
2
+ """bet-inglaterra con Gradio
3
+
4
+ Análisis estadístico de partidos de fútbol con interfaz Gradio
5
+ """
6
+
7
+ # Instalaciones necesarias
8
+ # !pip install selenium webdriver-manager pandas anthropic gradio
9
+
10
+ import os
11
+ import shutil
12
+ import pandas as pd
13
+ import numpy as np
14
+ import gradio as gr
15
+ import anthropic
16
+
17
+ # Variables globales para almacenar resultados
18
+ analisis_partido_resultado = ""
19
+ analisis_partido_volatilidad = ""
20
+ df = pd.DataFrame()
21
+
22
+ # =============================================================================
23
+ # FUNCIONES ORIGINALES (SIN CAMBIOS)
24
+ # =============================================================================
25
+
26
+ def limpiar_carpeta():
27
+ """Limpia la carpeta de trabajo"""
28
+ folder = '/content'
29
+ mensajes = []
30
+ for filename in os.listdir(folder):
31
+ file_path = os.path.join(folder, filename)
32
+ try:
33
+ if filename != 'sample_data' and not filename.startswith('.'):
34
+ if os.path.isfile(file_path) or os.path.islink(file_path):
35
+ os.unlink(file_path)
36
+ elif os.path.isdir(file_path):
37
+ shutil.rmtree(file_path)
38
+ mensajes.append(f"Eliminado: {filename}")
39
+ except Exception as e:
40
+ mensajes.append(f'No se pudo borrar {file_path}. Razón: {e}')
41
+ return "\n".join(mensajes)
42
+
43
+ def descargar_datos():
44
+ """Descarga los datos de football-data.co.uk"""
45
+ import urllib.request
46
+ url = "https://www.football-data.co.uk/mmz4281/2526/E0.csv"
47
+ try:
48
+ urllib.request.urlretrieve(url, 'E0.csv')
49
+ return "✓ Datos descargados exitosamente"
50
+ except Exception as e:
51
+ return f"Error al descargar: {e}"
52
+
53
+ def cargar_datos():
54
+ """Carga los datos del CSV"""
55
+ global df
56
+ try:
57
+ df = pd.read_csv('E0.csv')
58
+ return f" Datos cargados: {df.shape[0]} partidos, {df.shape[1]} columnas"
59
+ except FileNotFoundError:
60
+ return "Error: No se encontró el archivo 'E0.csv'. Descarga los datos primero."
61
+ except Exception as e:
62
+ return f"Error al cargar datos: {e}"
63
+
64
+ def renombrar_columnas():
65
+ """Renombra y filtra columnas del dataset"""
66
+ global df
67
+
68
+ column_mapping = {
69
+ 'Div': 'League_Division',
70
+ 'Date': 'Match_Date',
71
+ 'Time': 'Kick_Off_Time',
72
+ 'HomeTeam': 'Home_Team',
73
+ 'AwayTeam': 'Away_Team',
74
+ 'FTHG': 'Full_Time_Home_Goals',
75
+ 'HG': 'Full_Time_Home_Goals',
76
+ 'FTAG': 'Full_Time_Away_Goals',
77
+ 'AG': 'Full_Time_Away_Goals',
78
+ 'FTR': 'Full_Time_Result',
79
+ 'Res': 'Full_Time_Result',
80
+ 'HTHG': 'Half_Time_Home_Goals',
81
+ 'HTAG': 'Half_Time_Away_Goals',
82
+ 'HTR': 'Half_Time_Result',
83
+ 'Attendance': 'Crowd_Attendance',
84
+ 'Referee': 'Match_Referee',
85
+ 'HS': 'Home_Shots',
86
+ 'AS': 'Away_Shots',
87
+ 'HST': 'Home_Shots_On_Target',
88
+ 'AST': 'Away_Shots_On_Target',
89
+ 'HHW': 'Home_Hit_Woodwork',
90
+ 'AHW': 'Away_Hit_Woodwork',
91
+ 'HC': 'Home_Corners',
92
+ 'AC': 'Away_Corners',
93
+ 'HF': 'Home_Fouls',
94
+ 'AF': 'Away_Fouls',
95
+ 'HFKC': 'Home_Free_Kicks_Conceded',
96
+ 'AFKC': 'Away_Free_Kicks_Conceded',
97
+ 'HO': 'Home_Offsides',
98
+ 'AO': 'Away_Offsides',
99
+ 'HY': 'Home_Yellow_Cards',
100
+ 'AY': 'Away_Yellow_Cards',
101
+ 'HR': 'Home_Red_Cards',
102
+ 'AR': 'Away_Red_Cards',
103
+ 'HBP': 'Home_Booking_Points',
104
+ 'ABP': 'Away_Booking_Points'
105
+ }
106
+
107
+ columns_to_keep = [col for col in df.columns if col in column_mapping.keys()]
108
+ df_filtered = df[columns_to_keep]
109
+ df_renamed = df_filtered.rename(columns=column_mapping)
110
+ df_renamed.to_csv('football_data_renamed.csv', index=False)
111
+
112
+ return f"✓ Columnas renombradas: {len(columns_to_keep)} columnas conservadas"
113
+
114
+ def generar_estadisticas():
115
+ """Genera estadísticas por equipo"""
116
+ global df
117
+
118
+ numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
119
+
120
+ def get_mode(x):
121
+ m = x.mode()
122
+ if not m.empty:
123
+ return m.iloc[0]
124
+ return np.nan
125
+
126
+ home_stats = df.groupby('HomeTeam')[numeric_cols].agg(['mean', 'median', get_mode])
127
+ home_stats.columns = [f'Home_{col}_{stat}' for col, stat in home_stats.columns]
128
+ home_stats.index.name = 'Team'
129
+
130
+ away_stats = df.groupby('AwayTeam')[numeric_cols].agg(['mean', 'median', get_mode])
131
+ away_stats.columns = [f'Away_{col}_{stat}' for col, stat in away_stats.columns]
132
+ away_stats.index.name = 'Team'
133
+
134
+ final_stats = pd.concat([home_stats, away_stats], axis=1)
135
+ final_stats.columns = [col.replace('get_mode', 'mode') for col in final_stats.columns]
136
+ final_stats.to_csv('estadisticas_equipos.csv')
137
+
138
+ return f"✓ Estadísticas generadas: {len(final_stats)} equipos procesados"
139
+
140
+ def analizar_partido(equipo_a, equipo_b):
141
+ """Análisis cruzado de un partido"""
142
+ global analisis_partido_resultado, df
143
+
144
+ if df.empty:
145
+ return "No hay datos disponibles. Carga los datos primero."
146
+
147
+ metrics = {
148
+ 'Goles': ('FTHG', 'FTAG'),
149
+ 'Goles MT': ('HTHG', 'HTAG'),
150
+ 'Tiros': ('HS', 'AS'),
151
+ 'Tiros a Puerta': ('HST', 'AST'),
152
+ 'Corners': ('HC', 'AC')
153
+ }
154
+
155
+ df_home_a = df[df['HomeTeam'] == equipo_a]
156
+ df_away_b = df[df['AwayTeam'] == equipo_b]
157
+
158
+ if df_home_a.empty or df_away_b.empty:
159
+ return f"Error: No hay suficientes datos para {equipo_a} o {equipo_b}"
160
+
161
+ def get_stats(series):
162
+ if series.empty: return (0, 0, 0)
163
+ mode_val = series.mode().iloc[0] if not series.mode().empty else 0
164
+ return series.mean(), series.median(), mode_val
165
+
166
+ results = []
167
+ for nombre, (col_home, col_away) in metrics.items():
168
+ mean_atq_a, _, _ = get_stats(df_home_a[col_home])
169
+ mean_def_b, _, _ = get_stats(df_away_b[col_home])
170
+ mean_atq_b, _, _ = get_stats(df_away_b[col_away])
171
+ mean_def_a, _, _ = get_stats(df_home_a[col_away])
172
+
173
+ results.append({
174
+ 'Categoría': nombre,
175
+ f'Atq {equipo_a} (L)': round(mean_atq_a, 2),
176
+ f'Def {equipo_b} (V)': round(mean_def_b, 2),
177
+ f'Atq {equipo_b} (V)': round(mean_atq_b, 2),
178
+ f'Def {equipo_a} (L)': round(mean_def_a, 2)
179
+ })
180
+
181
+ tabla = pd.DataFrame(results)
182
+ analisis_partido_resultado = tabla.to_string(index=False)
183
+ return analisis_partido_resultado
184
+
185
+ def interpretar_volatilidad(media, moda):
186
+ """Compara la media con la moda para determinar la consistencia"""
187
+ diferencia = abs(media - moda)
188
+
189
+ if diferencia < 0.5:
190
+ return "Alta Consistencia"
191
+ elif media > moda:
192
+ return "Volatilidad Positiva (Picos altos)"
193
+ else:
194
+ return "Volatilidad Negativa (Bajones marcados)"
195
+
196
+ def analizar_partido_volatilidad_func(equipo_local, equipo_visitante):
197
+ """Análisis de volatilidad del partido"""
198
+ global analisis_partido_volatilidad, df
199
+
200
+ if df.empty:
201
+ return "No hay datos disponibles. Carga los datos primero."
202
+
203
+ metricas = {
204
+ 'Goles Final': ('FTHG', 'FTAG'),
205
+ 'Goles Medio Tiempo': ('HTHG', 'HTAG'),
206
+ 'Tiros Totales': ('HS', 'AS'),
207
+ 'Tiros a Puerta': ('HST', 'AST'),
208
+ 'Corners': ('HC', 'AC')
209
+ }
210
+
211
+ df_local = df[df['HomeTeam'] == equipo_local]
212
+ df_visitante = df[df['AwayTeam'] == equipo_visitante]
213
+
214
+ if df_local.empty or df_visitante.empty:
215
+ return "Uno de los equipos no se encuentra en el dataset."
216
+
217
+ resultados = []
218
+
219
+ for nombre, (col_L, col_V) in metricas.items():
220
+ m_atq_l = df_local[col_L].mean()
221
+ mod_atq_l = df_local[col_L].mode().iloc[0] if not df_local[col_L].empty else 0
222
+
223
+ m_def_v = df_visitante[col_L].mean()
224
+ mod_def_v = df_visitante[col_L].mode().iloc[0] if not df_visitante[col_L].empty else 0
225
+
226
+ m_atq_v = df_visitante[col_V].mean()
227
+ mod_atq_v = df_visitante[col_V].mode().iloc[0] if not df_visitante[col_V].empty else 0
228
+
229
+ m_def_l = df_local[col_V].mean()
230
+ mod_def_l = df_local[col_V].mode().iloc[0] if not df_local[col_V].empty else 0
231
+
232
+ resultados.append({'Métrica': f"{nombre} - Atq {equipo_local}", 'Media': round(m_atq_l, 2), 'Moda': mod_atq_l, 'Volatilidad': interpretar_volatilidad(m_atq_l, mod_atq_l)})
233
+ resultados.append({'Métrica': f"{nombre} - Def {equipo_visitante}", 'Media': round(m_def_v, 2), 'Moda': mod_def_v, 'Volatilidad': interpretar_volatilidad(m_def_v, mod_def_v)})
234
+ resultados.append({'Métrica': f"{nombre} - Atq {equipo_visitante}", 'Media': round(m_atq_v, 2), 'Moda': mod_atq_v, 'Volatilidad': interpretar_volatilidad(m_atq_v, mod_atq_v)})
235
+ resultados.append({'Métrica': f"{nombre} - Def {equipo_local}", 'Media': round(m_def_l, 2), 'Moda': mod_def_l, 'Volatilidad': interpretar_volatilidad(m_def_l, mod_def_l)})
236
+
237
+ df_final = pd.DataFrame(resultados)
238
+ analisis_partido_volatilidad = df_final.to_string(index=False)
239
+ return analisis_partido_volatilidad
240
+
241
+ def generar_analisis_profesional(api_key, datos_rendimiento, datos_volatilidad):
242
+ """Genera análisis profesional usando Claude API"""
243
+ SYSTEM_PROMPT = """
244
+ Actúa como un experto analista estadístico de fútbol y apostador profesional.
245
+
246
+ REGLAS DE PROCESAMIENTO:
247
+ 1. Cruce de Variables: Calcula la proyección (Media Local + Media Visitante) / 2 para Goles, Corners y Tiros.
248
+ 2. Evaluación de Riesgo: Cruza la 'Media' con la 'Moda' y la 'Volatilidad'.
249
+ - Si Media > Moda y Volatilidad es "Picos Altos", advierte sobre la inconsistencia.
250
+ - Si Media Moda y hay "Alta Consistencia", aumenta la confianza del pronóstico.
251
+ 3. Formato: Genera un informe con Resumen Estadístico, Porcentajes de Probabilidad y Sugerencias de Apuesta.
252
+ """
253
+
254
+ USER_PROMPT = f"""
255
+ Analiza el siguiente partido utilizando estas dos fuentes de datos:
256
+
257
+ 1. DATOS DE RENDIMIENTO (Ataque vs Defensa):
258
+ {datos_rendimiento}
259
+
260
+ 2. DATOS DE VOLATILIDAD Y CONSISTENCIA:
261
+ {datos_volatilidad}
262
+
263
+ Proporciona el análisis siguiendo el tono profesional y la estructura de valor solicitada.
264
+ """
265
+
266
+ try:
267
+ client = anthropic.Anthropic(api_key=api_key)
268
+ response = client.messages.create(
269
+ model="claude-haiku-4-5-20251001",
270
+ max_tokens=2500,
271
+ temperature=0,
272
+ system=SYSTEM_PROMPT,
273
+ messages=[{"role": "user", "content": USER_PROMPT}]
274
+ )
275
+ return response.content[0].text
276
+ except Exception as e:
277
+ return f"Error en la API: {e}"
278
+
279
+ # =============================================================================
280
+ # FUNCIONES PARA GRADIO
281
+ # =============================================================================
282
+
283
+ def obtener_equipos():
284
+ """Obtiene la lista de equipos disponibles"""
285
+ global df
286
+ if df.empty:
287
+ return []
288
+ equipos = sorted(list(set(df['HomeTeam'].unique().tolist() + df['AwayTeam'].unique().tolist())))
289
+ return equipos
290
+
291
+ def inicializar_datos():
292
+ """Función para inicializar todos los datos"""
293
+ resultado = []
294
+ resultado.append(descargar_datos())
295
+ resultado.append(cargar_datos())
296
+ resultado.append(renombrar_columnas())
297
+ resultado.append(generar_estadisticas())
298
+ return "\n".join(resultado)
299
+
300
+ def analisis_completo(equipo_local, equipo_visitante, api_key):
301
+ """Realiza el análisis completo del partido"""
302
+ global analisis_partido_resultado, analisis_partido_volatilidad
303
+
304
+ # Análisis de rendimiento
305
+ rendimiento = analizar_partido(equipo_local, equipo_visitante)
306
+
307
+ # Análisis de volatilidad
308
+ volatilidad = analizar_partido_volatilidad_func(equipo_local, equipo_visitante)
309
+
310
+ # Análisis con IA
311
+ if api_key:
312
+ analisis_ia = generar_analisis_profesional(api_key, rendimiento, volatilidad)
313
+ else:
314
+ analisis_ia = "Proporciona tu API Key de Anthropic para obtener el análisis profesional con IA."
315
+
316
+ return rendimiento, volatilidad, analisis_ia
317
+
318
+ # =============================================================================
319
+ # INTERFAZ GRADIO
320
+ # =============================================================================
321
+
322
+ with gr.Blocks(title="Análisis de Partidos de Fútbol", theme=gr.themes.Soft()) as demo:
323
+ gr.Markdown("""
324
+ # ⚽ Análisis Estadístico de Partidos - Premier League
325
+ ### Sistema de análisis avanzado para apuestas deportivas
326
+ """)
327
+
328
+ with gr.Tab("1️⃣ Configuración"):
329
+ gr.Markdown("### Paso 1: Inicializar datos")
330
+ btn_inicializar = gr.Button("🔄 Descargar y Procesar Datos", variant="primary")
331
+ output_inicializacion = gr.Textbox(label="Estado", lines=5)
332
+
333
+ btn_inicializar.click(
334
+ fn=inicializar_datos,
335
+ outputs=output_inicializacion
336
+ )
337
+
338
+ with gr.Tab("2️⃣ Análisis de Partido"):
339
+ gr.Markdown("### Selecciona los equipos y obtén el análisis completo")
340
+
341
+ with gr.Row():
342
+ with gr.Column():
343
+ equipo_local = gr.Dropdown(
344
+ label="Equipo Local",
345
+ choices=obtener_equipos(),
346
+ interactive=True
347
+ )
348
+ btn_actualizar = gr.Button("🔄 Actualizar lista de equipos")
349
+
350
+ with gr.Column():
351
+ equipo_visitante = gr.Dropdown(
352
+ label="Equipo Visitante",
353
+ choices=obtener_equipos(),
354
+ interactive=True
355
+ )
356
+
357
+ api_key_input = gr.Textbox(
358
+ label="API Key de Anthropic (opcional)",
359
+ type="password",
360
+ placeholder="sk-ant-..."
361
+ )
362
+
363
+ btn_analizar = gr.Button("📊 Analizar Partido", variant="primary", size="lg")
364
+
365
+ with gr.Row():
366
+ with gr.Column():
367
+ gr.Markdown("### 📈 Análisis de Rendimiento")
368
+ output_rendimiento = gr.Textbox(label="Ataque vs Defensa", lines=10)
369
+
370
+ with gr.Column():
371
+ gr.Markdown("### 📉 Análisis de Volatilidad")
372
+ output_volatilidad = gr.Textbox(label="Consistencia y Riesgo", lines=10)
373
+
374
+ gr.Markdown("### 🤖 Análisis Profesional con IA")
375
+ output_ia = gr.Textbox(label="Recomendaciones", lines=15)
376
+
377
+ # Eventos
378
+ btn_actualizar.click(
379
+ fn=obtener_equipos,
380
+ outputs=[equipo_local, equipo_visitante]
381
+ )
382
+
383
+ btn_analizar.click(
384
+ fn=analisis_completo,
385
+ inputs=[equipo_local, equipo_visitante, api_key_input],
386
+ outputs=[output_rendimiento, output_volatilidad, output_ia]
387
+ )
388
+
389
+ with gr.Tab("ℹ️ Información"):
390
+ gr.Markdown("""
391
+ ## Cómo usar esta aplicación
392
+
393
+ ### Paso 1: Configuración
394
+ 1. Ve a la pestaña "Configuración"
395
+ 2. Haz clic en "Descargar y Procesar Datos"
396
+ 3. Espera a que se complete el procesamiento
397
+
398
+ ### Paso 2: Análisis
399
+ 1. Ve a la pestaña "Análisis de Partido"
400
+ 2. Actualiza la lista de equipos si es necesario
401
+ 3. Selecciona el equipo local y visitante
402
+ 4. (Opcional) Ingresa tu API Key de Anthropic para análisis con IA
403
+ 5. Haz clic en "Analizar Partido"
404
+
405
+ ### Interpretación de Resultados
406
+
407
+ **Análisis de Rendimiento:**
408
+ - Muestra el promedio de goles, tiros, corners, etc.
409
+ - Compara el ataque de cada equipo vs la defensa del rival
410
+
411
+ **Análisis de Volatilidad:**
412
+ - Alta Consistencia: El equipo es predecible
413
+ - Volatilidad Positiva: Tiene picos de buen rendimiento
414
+ - Volatilidad Negativa: Tiene caídas en rendimiento
415
+
416
+ **Análisis con IA:**
417
+ - Combina ambos análisis
418
+ - Proporciona recomendaciones de apuesta
419
+ - Calcula probabilidades
420
+
421
+ ### Obtener API Key de Anthropic
422
+ 1. Visita https://console.anthropic.com/
423
+ 2. Crea una cuenta o inicia sesión
424
+ 3. Ve a "API Keys"
425
+ 4. Genera una nueva clave
426
+ """)
427
+
428
+ # Lanzar la aplicación
429
+ if __name__ == "__main__":
430
  demo.launch(share=True)