Spaces:
Running
Running
Upload streamlit_app.py
Browse files- 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")
|
| 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
|
| 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)
|
| 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,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
|
| 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([
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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()
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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("---")
|