daniel-saed commited on
Commit
7b16da5
·
verified ·
1 Parent(s): 71537af

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +70 -38
streamlit_app.py CHANGED
@@ -10,7 +10,7 @@ from dotenv import load_dotenv
10
  import os
11
 
12
  load_dotenv()
13
- API_KEY = os.getenv("API_KEY") # ⚠️ CÁMBIALA POR UNA SEGURA
14
  # --- CONFIGURACIÓN INICIAL ---
15
  st.set_page_config(layout="wide", page_title="Corners Forecast", page_icon="⚽")
16
 
@@ -30,7 +30,7 @@ st.markdown("""
30
  MSE_MODELO = 1.9
31
  RMSE_MODELO = 2.42
32
  R2_MODELO = 0.39
33
- N_SIMULACIONES = 5000 # 👈 REDUCIDO A 5000
34
 
35
  # --- FUNCIONES AUXILIARES ---
36
  def probabilidad_a_momio(probabilidad):
@@ -48,14 +48,14 @@ def clasificar_valor_apuesta(momio_real, momio_modelo):
48
  else:
49
  return "🔴 SIN VALOR"
50
 
51
- @st.cache_data(ttl=3600) # 👈 CACHE 1 HORA
52
  def simular_lambda_montecarlo(lambda_pred, sigma=RMSE_MODELO, n_sims=N_SIMULACIONES):
53
  """Genera simulaciones Monte Carlo con CACHE"""
54
  lambdas = np.random.normal(lambda_pred, sigma, n_sims)
55
  lambdas = np.maximum(lambdas, 0.1)
56
  return lambdas
57
 
58
- @st.cache_data(ttl=3600) # 👈 CACHE 1 HORA
59
  def calcular_probabilidades_con_incertidumbre(lambda_pred, linea, tipo='over', sigma=RMSE_MODELO, n_sims=N_SIMULACIONES):
60
  """Calcula probabilidades con CACHE"""
61
  lambdas_sim = simular_lambda_montecarlo(lambda_pred, sigma, n_sims)
@@ -159,9 +159,8 @@ LEAGUES_DICT = {
159
  # --- HEADER ---
160
  st.markdown("<h1 style='text-align: center;'>Corners Forecast</h1>", unsafe_allow_html=True)
161
 
162
-
163
  # --- CARGAR DATOS ---
164
- @st.cache_data # 👈 CACHE PERMANENTE
165
  def cargar_datos():
166
  df = pd.read_csv(r"https://raw.githubusercontent.com/danielsaed/futbol_corners_forecast/refs/heads/main/dataset/cleaned/dataset_cleaned.csv")
167
  return df[['local','league']].drop_duplicates()
@@ -174,38 +173,76 @@ if 'prediccion_realizada' not in st.session_state:
174
  if 'resultado_api' not in st.session_state:
175
  st.session_state.resultado_api = None
176
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  st.markdown("")
178
 
179
  # --- SELECCIÓN DE PARÁMETROS ---
180
  col1, col2, col3 = st.columns([1, 1, 1])
181
 
182
-
183
-
184
  with col2:
185
  option = st.selectbox(
186
  "🏆 Liga",
187
  ["La Liga", "Premier League", "Ligue 1", "Serie A", "Eredivisie", "Liga NOS", "Pro League", "Bundesliga"],
188
  index=None,
189
  placeholder="Selecciona liga",
 
190
  )
191
 
 
 
 
 
 
 
192
  st.write("")
193
 
194
  col_jornada1, col_jornada2, col_jornada3, col_jornada4 = st.columns([2, 1, 1, 2])
 
 
 
 
195
  with col_jornada2:
196
  if option:
197
- jornada = st.number_input("📅 Jornada", min_value=5, max_value=42, value=15, step=1)
 
 
 
 
 
 
 
198
  with col_jornada3:
199
  if option:
200
  temporada = st.selectbox(
201
  "Temporada",
202
  [2526, 2425, 2324, 2223, 2122],
203
- index=0
 
204
  )
 
 
 
 
 
 
205
 
206
  st.write("")
207
 
208
- cl2, cl3, cl4 = st.columns([ 4, 1, 4])
 
 
 
209
 
210
  with cl2:
211
  if option:
@@ -215,7 +252,14 @@ with cl2:
215
  list(df["local"][df["league"] == LEAGUES_DICT[option]]),
216
  index=None,
217
  placeholder="Equipo local",
 
218
  )
 
 
 
 
 
 
219
 
220
  with cl3:
221
  if option:
@@ -231,7 +275,14 @@ with cl4:
231
  list(df["local"][df["league"] == LEAGUES_DICT[option]]),
232
  index=None,
233
  placeholder="Equipo visitante",
 
234
  )
 
 
 
 
 
 
235
 
236
  # --- BOTÓN PARA GENERAR PREDICCIÓN ---
237
  if option and option_local and option_away:
@@ -241,10 +292,9 @@ if option and option_local and option_away:
241
  col_btn1, col_btn2, col_btn3 = st.columns([1, 1, 1])
242
 
243
  with col_btn2:
244
- # 👈 BOTÓN PARA EJECUTAR PREDICCIÓN
245
  if st.button("Generar Predicción", type="secondary", use_container_width=True):
246
  st.session_state.prediccion_realizada = True
247
- st.session_state.resultado_api = None # Reset resultado
248
 
249
  st.write("")
250
  st.write("")
@@ -252,13 +302,11 @@ if option and option_local and option_away:
252
  # --- REALIZAR PREDICCIÓN (SOLO SI SE PRESIONÓ EL BOTÓN) ---
253
  if option and option_local and option_away and st.session_state.prediccion_realizada:
254
 
255
- # Si no hay resultado en cache, hacer petición
256
  if st.session_state.resultado_api is None:
257
 
258
  with st.spinner('🔮 Generando predicción con análisis de incertidumbre...'):
259
 
260
  url = "https://daniel-saed-futbol-corners-forecast-api.hf.space/items/"
261
- #url = "http://localhost:7860//items/"
262
  headers = {"X-API-Key": API_KEY}
263
  params = {
264
  "local": option_local,
@@ -272,7 +320,7 @@ if option and option_local and option_away and st.session_state.prediccion_reali
272
  response = requests.get(url, headers=headers, params=params, timeout=30)
273
 
274
  if response.status_code == 200:
275
- st.session_state.resultado_api = response.json() # 👈 GUARDAR EN SESSION
276
  st.success("✅ Predicción generada")
277
  elif response.status_code == 401:
278
  st.error("❌ Error de Autenticación - API Key inválida")
@@ -296,7 +344,7 @@ if option and option_local and option_away and st.session_state.prediccion_reali
296
  st.code(traceback.format_exc())
297
  st.stop()
298
 
299
- # --- MOSTRAR RESULTADOS (DESDE SESSION STATE) ---
300
  if st.session_state.resultado_api:
301
  resultado = st.session_state.resultado_api
302
  lambda_pred = resultado['prediccion']
@@ -315,7 +363,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
315
 
316
  st.write("")
317
 
318
- # Métricas principales con Streamlit nativo
319
  col_pred1, col_pred2, col_pred3 = st.columns(3)
320
 
321
  with col_pred1:
@@ -341,10 +388,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
341
  help="Intervalo de confianza 95% (superior)"
342
  )
343
 
344
- st.write("")
345
-
346
-
347
-
348
  st.write("")
349
  st.write("")
350
  st.markdown("---")
@@ -352,7 +395,7 @@ if option and option_local and option_away and st.session_state.prediccion_reali
352
  st.write("")
353
 
354
  # ============================================
355
- # 2. ANÁLISIS DE EQUIPOS (CON TABLAS)
356
  # ============================================
357
 
358
  stats_data = resultado['stats']
@@ -365,7 +408,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
365
 
366
  riesgo = resultado['riesgo']
367
 
368
- # 👈 TABLA DE CORNERS GENERADOS Y CONCEDIDOS
369
  st.markdown("### Análisis de Corners")
370
 
371
  df_corners = pd.DataFrame({
@@ -394,7 +436,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
394
  st.write("")
395
  st.write("")
396
 
397
- # --- FIABILIDAD ---
398
  st.markdown("### Fiabilidad")
399
 
400
  col_fiab1, col_fiab2, col_fiab3 = st.columns(3)
@@ -435,14 +476,13 @@ if option and option_local and option_away and st.session_state.prediccion_reali
435
  st.write("")
436
 
437
  # ============================================
438
- # 3. PROBABILIDADES CON MONTE CARLO
439
  # ============================================
440
 
441
  st.info(f"🔬 **Análisis con {N_SIMULACIONES:,} simulaciones Monte Carlo** considerando RMSE={RMSE_MODELO}")
442
 
443
  tab_over, tab_under = st.tabs(["⬆️ OVER", "⬇️ UNDER"])
444
 
445
- # TAB OVER
446
  with tab_over:
447
  probs_over = resultado['probabilidades_over']
448
 
@@ -494,7 +534,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
494
 
495
  st.write("")
496
 
497
- # Gráfico
498
  fig_over = go.Figure()
499
 
500
  lineas_sorted = sorted([x['linea_num'] for x in df_over_incertidumbre])
@@ -532,7 +571,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
532
 
533
  st.plotly_chart(fig_over, use_container_width=True)
534
 
535
- # TAB UNDER
536
  with tab_under:
537
  probs_under = resultado['probabilidades_under']
538
 
@@ -586,7 +624,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
586
 
587
  st.write("")
588
 
589
- # Gráfico
590
  fig_under = go.Figure()
591
 
592
  lineas_sorted_under = sorted([x['linea_num'] for x in df_under_incertidumbre])
@@ -631,13 +668,12 @@ if option and option_local and option_away and st.session_state.prediccion_reali
631
  st.write("")
632
 
633
  # ============================================
634
- # 4. CALCULADORA AVANZADA
635
  # ============================================
636
  st.markdown("## 💰 Calculadora de Valor")
637
 
638
  st.write("")
639
 
640
- # Combinar datos
641
  todas_lineas_datos = {}
642
 
643
  for item in df_over_incertidumbre:
@@ -765,7 +801,6 @@ if option and option_local and option_away and st.session_state.prediccion_reali
765
  else:
766
  st.error("🔴 EV negativo")
767
 
768
- # Footer
769
  st.write("")
770
  st.write("")
771
  st.markdown("---")
@@ -774,7 +809,7 @@ if option and option_local and option_away and st.session_state.prediccion_reali
774
  else:
775
  if option:
776
  if option_local and option_away:
777
- pass # Esperando botón
778
  else:
779
  st.info("👆 Selecciona ambos equipos")
780
  else:
@@ -799,9 +834,6 @@ with st.sidebar:
799
  for league in LEAGUES_DICT.keys():
800
  st.write(f"• {league}")
801
 
802
-
803
-
804
- # 👈 BOTÓN PARA LIMPIAR CACHE
805
  if st.button("🗑️ Limpiar Cache", use_container_width=True):
806
  st.cache_data.clear()
807
  st.session_state.prediccion_realizada = False
@@ -809,4 +841,4 @@ with st.sidebar:
809
  st.success("✅ Cache limpiado")
810
  st.rerun()
811
 
812
- st.markdown("---")
 
10
  import os
11
 
12
  load_dotenv()
13
+ API_KEY = os.getenv("API_KEY")
14
  # --- CONFIGURACIÓN INICIAL ---
15
  st.set_page_config(layout="wide", page_title="Corners Forecast", page_icon="⚽")
16
 
 
30
  MSE_MODELO = 1.9
31
  RMSE_MODELO = 2.42
32
  R2_MODELO = 0.39
33
+ N_SIMULACIONES = 5000
34
 
35
  # --- FUNCIONES AUXILIARES ---
36
  def probabilidad_a_momio(probabilidad):
 
48
  else:
49
  return "🔴 SIN VALOR"
50
 
51
+ @st.cache_data(ttl=3600)
52
  def simular_lambda_montecarlo(lambda_pred, sigma=RMSE_MODELO, n_sims=N_SIMULACIONES):
53
  """Genera simulaciones Monte Carlo con CACHE"""
54
  lambdas = np.random.normal(lambda_pred, sigma, n_sims)
55
  lambdas = np.maximum(lambdas, 0.1)
56
  return lambdas
57
 
58
+ @st.cache_data(ttl=3600)
59
  def calcular_probabilidades_con_incertidumbre(lambda_pred, linea, tipo='over', sigma=RMSE_MODELO, n_sims=N_SIMULACIONES):
60
  """Calcula probabilidades con CACHE"""
61
  lambdas_sim = simular_lambda_montecarlo(lambda_pred, sigma, n_sims)
 
159
  # --- HEADER ---
160
  st.markdown("<h1 style='text-align: center;'>Corners Forecast</h1>", unsafe_allow_html=True)
161
 
 
162
  # --- CARGAR DATOS ---
163
+ @st.cache_data
164
  def cargar_datos():
165
  df = pd.read_csv(r"https://raw.githubusercontent.com/danielsaed/futbol_corners_forecast/refs/heads/main/dataset/cleaned/dataset_cleaned.csv")
166
  return df[['local','league']].drop_duplicates()
 
173
  if 'resultado_api' not in st.session_state:
174
  st.session_state.resultado_api = None
175
 
176
+ # 👇 NUEVO: Guardar valores anteriores para detectar cambios
177
+ if 'prev_liga' not in st.session_state:
178
+ st.session_state.prev_liga = None
179
+ if 'prev_jornada' not in st.session_state:
180
+ st.session_state.prev_jornada = None
181
+ if 'prev_temporada' not in st.session_state:
182
+ st.session_state.prev_temporada = None
183
+ if 'prev_local' not in st.session_state:
184
+ st.session_state.prev_local = None
185
+ if 'prev_away' not in st.session_state:
186
+ st.session_state.prev_away = None
187
+
188
  st.markdown("")
189
 
190
  # --- SELECCIÓN DE PARÁMETROS ---
191
  col1, col2, col3 = st.columns([1, 1, 1])
192
 
 
 
193
  with col2:
194
  option = st.selectbox(
195
  "🏆 Liga",
196
  ["La Liga", "Premier League", "Ligue 1", "Serie A", "Eredivisie", "Liga NOS", "Pro League", "Bundesliga"],
197
  index=None,
198
  placeholder="Selecciona liga",
199
+ key="liga_select"
200
  )
201
 
202
+ # 👇 DETECTAR CAMBIO EN LIGA
203
+ if option != st.session_state.prev_liga:
204
+ st.session_state.prediccion_realizada = False
205
+ st.session_state.resultado_api = None
206
+ st.session_state.prev_liga = option
207
+
208
  st.write("")
209
 
210
  col_jornada1, col_jornada2, col_jornada3, col_jornada4 = st.columns([2, 1, 1, 2])
211
+
212
+ jornada = None
213
+ temporada = None
214
+
215
  with col_jornada2:
216
  if option:
217
+ jornada = st.number_input("📅 Jornada", min_value=5, max_value=42, value=15, step=1, key="jornada_input")
218
+
219
+ # 👇 DETECTAR CAMBIO EN JORNADA
220
+ if jornada != st.session_state.prev_jornada:
221
+ st.session_state.prediccion_realizada = False
222
+ st.session_state.resultado_api = None
223
+ st.session_state.prev_jornada = jornada
224
+
225
  with col_jornada3:
226
  if option:
227
  temporada = st.selectbox(
228
  "Temporada",
229
  [2526, 2425, 2324, 2223, 2122],
230
+ index=0,
231
+ key="temporada_select"
232
  )
233
+
234
+ # 👇 DETECTAR CAMBIO EN TEMPORADA
235
+ if temporada != st.session_state.prev_temporada:
236
+ st.session_state.prediccion_realizada = False
237
+ st.session_state.resultado_api = None
238
+ st.session_state.prev_temporada = temporada
239
 
240
  st.write("")
241
 
242
+ cl2, cl3, cl4 = st.columns([4, 1, 4])
243
+
244
+ option_local = None
245
+ option_away = None
246
 
247
  with cl2:
248
  if option:
 
252
  list(df["local"][df["league"] == LEAGUES_DICT[option]]),
253
  index=None,
254
  placeholder="Equipo local",
255
+ key="local_select"
256
  )
257
+
258
+ # 👇 DETECTAR CAMBIO EN EQUIPO LOCAL
259
+ if option_local != st.session_state.prev_local:
260
+ st.session_state.prediccion_realizada = False
261
+ st.session_state.resultado_api = None
262
+ st.session_state.prev_local = option_local
263
 
264
  with cl3:
265
  if option:
 
275
  list(df["local"][df["league"] == LEAGUES_DICT[option]]),
276
  index=None,
277
  placeholder="Equipo visitante",
278
+ key="away_select"
279
  )
280
+
281
+ # 👇 DETECTAR CAMBIO EN EQUIPO VISITANTE
282
+ if option_away != st.session_state.prev_away:
283
+ st.session_state.prediccion_realizada = False
284
+ st.session_state.resultado_api = None
285
+ st.session_state.prev_away = option_away
286
 
287
  # --- BOTÓN PARA GENERAR PREDICCIÓN ---
288
  if option and option_local and option_away:
 
292
  col_btn1, col_btn2, col_btn3 = st.columns([1, 1, 1])
293
 
294
  with col_btn2:
 
295
  if st.button("Generar Predicción", type="secondary", use_container_width=True):
296
  st.session_state.prediccion_realizada = True
297
+ st.session_state.resultado_api = None
298
 
299
  st.write("")
300
  st.write("")
 
302
  # --- REALIZAR PREDICCIÓN (SOLO SI SE PRESIONÓ EL BOTÓN) ---
303
  if option and option_local and option_away and st.session_state.prediccion_realizada:
304
 
 
305
  if st.session_state.resultado_api is None:
306
 
307
  with st.spinner('🔮 Generando predicción con análisis de incertidumbre...'):
308
 
309
  url = "https://daniel-saed-futbol-corners-forecast-api.hf.space/items/"
 
310
  headers = {"X-API-Key": API_KEY}
311
  params = {
312
  "local": option_local,
 
320
  response = requests.get(url, headers=headers, params=params, timeout=30)
321
 
322
  if response.status_code == 200:
323
+ st.session_state.resultado_api = response.json()
324
  st.success("✅ Predicción generada")
325
  elif response.status_code == 401:
326
  st.error("❌ Error de Autenticación - API Key inválida")
 
344
  st.code(traceback.format_exc())
345
  st.stop()
346
 
347
+ # --- MOSTRAR RESULTADOS ---
348
  if st.session_state.resultado_api:
349
  resultado = st.session_state.resultado_api
350
  lambda_pred = resultado['prediccion']
 
363
 
364
  st.write("")
365
 
 
366
  col_pred1, col_pred2, col_pred3 = st.columns(3)
367
 
368
  with col_pred1:
 
388
  help="Intervalo de confianza 95% (superior)"
389
  )
390
 
 
 
 
 
391
  st.write("")
392
  st.write("")
393
  st.markdown("---")
 
395
  st.write("")
396
 
397
  # ============================================
398
+ # 2. ANÁLISIS DE EQUIPOS
399
  # ============================================
400
 
401
  stats_data = resultado['stats']
 
408
 
409
  riesgo = resultado['riesgo']
410
 
 
411
  st.markdown("### Análisis de Corners")
412
 
413
  df_corners = pd.DataFrame({
 
436
  st.write("")
437
  st.write("")
438
 
 
439
  st.markdown("### Fiabilidad")
440
 
441
  col_fiab1, col_fiab2, col_fiab3 = st.columns(3)
 
476
  st.write("")
477
 
478
  # ============================================
479
+ # 3. PROBABILIDADES
480
  # ============================================
481
 
482
  st.info(f"🔬 **Análisis con {N_SIMULACIONES:,} simulaciones Monte Carlo** considerando RMSE={RMSE_MODELO}")
483
 
484
  tab_over, tab_under = st.tabs(["⬆️ OVER", "⬇️ UNDER"])
485
 
 
486
  with tab_over:
487
  probs_over = resultado['probabilidades_over']
488
 
 
534
 
535
  st.write("")
536
 
 
537
  fig_over = go.Figure()
538
 
539
  lineas_sorted = sorted([x['linea_num'] for x in df_over_incertidumbre])
 
571
 
572
  st.plotly_chart(fig_over, use_container_width=True)
573
 
 
574
  with tab_under:
575
  probs_under = resultado['probabilidades_under']
576
 
 
624
 
625
  st.write("")
626
 
 
627
  fig_under = go.Figure()
628
 
629
  lineas_sorted_under = sorted([x['linea_num'] for x in df_under_incertidumbre])
 
668
  st.write("")
669
 
670
  # ============================================
671
+ # 4. CALCULADORA
672
  # ============================================
673
  st.markdown("## 💰 Calculadora de Valor")
674
 
675
  st.write("")
676
 
 
677
  todas_lineas_datos = {}
678
 
679
  for item in df_over_incertidumbre:
 
801
  else:
802
  st.error("🔴 EV negativo")
803
 
 
804
  st.write("")
805
  st.write("")
806
  st.markdown("---")
 
809
  else:
810
  if option:
811
  if option_local and option_away:
812
+ pass
813
  else:
814
  st.info("👆 Selecciona ambos equipos")
815
  else:
 
834
  for league in LEAGUES_DICT.keys():
835
  st.write(f"• {league}")
836
 
 
 
 
837
  if st.button("🗑️ Limpiar Cache", use_container_width=True):
838
  st.cache_data.clear()
839
  st.session_state.prediccion_realizada = False
 
841
  st.success("✅ Cache limpiado")
842
  st.rerun()
843
 
844
+ st.markdown("---")