🚨 Módulo detector¶
Identifica partidos con posibles sorpresas basándose en inconsistencias entre probabilidades LAE y factores contextuales.
Clase Principal: SurpriseDetector¶
Detecta posibles sorpresas en una jornada completa.
Parámetro |
Tipo |
Descripción |
|---|---|---|
|
int |
Número de jornada |
|
int |
Año de la temporada |
|
float |
Umbral de divergencia (0-100, default 30.0) |
return: dict con lista de alertas de sorpresas (ver ejemplo de estructura más abajo).
Ejemplo de Uso Programático¶
Nota
Los valores de jornada y temporada en los siguientes ejemplos son ilustrativos. Para probar su funcionamiento, actualiza estos valores con datos actuales, ya que las APIs no proporcionan datos históricos.
import json
from kinielagpt.detector import SurpriseDetector
detector = SurpriseDetector()
detect = detector.detect(jornada=32, temporada=2026, threshold=30.0)
print(json.dumps(detect, indent=2, ensure_ascii=False))
Resultado
{
"jornada": 32,
"temporada": 2026,
"threshold": 30.0,
"total_surprises": 2,
"surprises": [
{
"match_id": 3,
"match": "ELCHE | VILLARREAL",
"alert_level": "⚠️ ALERTA MEDIA",
"inconsistency_type": "historical_inconsistency",
"description": "Probabilidad LAE de victoria visitante (58%) muy superior al histórico de enfrentamientos (11% en 9 partidos)",
"probabilities": {
"1": 16.1,
"X": 25.6,
"2": 58.3
},
"context_factors": {
"historical_rate": "11%",
"lae_probability": "58%",
"total_matches": 9
}
},
{
"match_id": 6,
"match": "R.MADRID | BETIS",
"alert_level": "⚠️ ALERTA MEDIA",
"inconsistency_type": "historical_inconsistency",
"description": "Probabilidad LAE de victoria local (77%) muy superior al histórico de enfrentamientos (40% en 10 partidos)",
"probabilities": {
"1": 77.4,
"X": 15.9,
"2": 6.7
},
"context_factors": {
"historical_rate": "40%",
"lae_probability": "77%",
"total_matches": 10
}
}
]
}
Algoritmo de Detección de Sorpresas¶
El algoritmo de detección de sorpresas evalúa cada partido identificando hasta cuatro tipos de inconsistencias que pueden indicar un resultado inesperado:
Inconsistencia base: Se detecta cuando existe un favorito claro (probabilidad superior al 50%).
Inconsistencia por racha: Analiza y compara las rachas recientes de ambos equipos, asignando una puntuación según su tendencia positiva o negativa.
Inconsistencia por resultados históricos: Compara la probabilidad asignada al favorito con la probabilidad real observada en los enfrentamientos previos entre ambos equipos.
Inconsistencia por clasificación: Considera la diferencia de posiciones en la tabla; solo se aplica si la distancia es mayor a 8 puestos.
Para cada inconsistencia detectada se calcula un score de divergencia, que mide la magnitud de la contradicción y permite clasificar la sorpresa en tres niveles:
🔴 ALERTA ROJA: Divergencia ≥ 50 puntos (contradicción crítica)
🟠 ALERTA MEDIA: Divergencia ≥ 35 puntos (contradicción notable)
🟡 ALERTA: Divergencia ≥ threshold (contradicción detectable)
El parámetro threshold ajusta la sensibilidad del sistema:
threshold=20: Detección muy sensible (mayor número de alertas)
threshold=30: Configuración equilibrada (recomendada)
threshold=40: Solo se reportan inconsistencias muy marcadas
🔍 Ejemplo de detección de sorpresas¶
A continuación se explica el proceso completo de detección de sorpresas desde los datos en bruto hasta el resultado final, usando ejemplo (ficticio) el partido VILLARREAL - GETAFE con los siguientes datos:
Campo |
Valor |
|---|---|
Partido |
VILLARREAL - GETAFE |
Threshold |
25.0 |
Prob 1 |
72.5% |
Prob X |
18.3% |
Prob 2 |
9.2% |
Racha Local |
D-D-D-E-D |
Racha Visitante |
V-V-V-V-E |
Posición Local |
12º (28 pts) |
Posición Visitante |
16º (22 pts) |
Histórico 1 |
8 victorias locales |
Histórico X |
5 empates |
Histórico 2 |
3 victorias visitante |
Total Histórico |
16 partidos |
1. Cálculo de Divergencia Base¶
Cuando hay un favorito claro (probabilidad > 50%), se calcula una divergencia base que presenta cuánto la probabilidad supera el umbral del 50%:
base_divergence = max_prob - 50
= 72.5 - 50
= 22.5 puntos
Interpretación: Este valor (22.5) será la base para calcular la divergencia total. Representa qué tan favorito es el resultado y se sumará a otros factores (rachas, histórico, clasificación) para determinar si hay inconsistencia.
Significado:
base_divergence = 0-10: Favorito débil (50-60% prob)base_divergence = 10-20: Favorito moderado (60-70% prob)base_divergence = 20-30: Favorito fuerte (70-80% prob) ← Caso actualbase_divergence = 30-50: Favorito muy fuerte (80-100% prob)
2.- Cálculo de Divergencia por Rachas¶
Se aplica el siguiente Sistema de puntuación:
Victoria (V) = +3 puntos
Empate (E) = +1 punto
Derrota (D) = -2 puntos
Cálculo para VILLARREAL (local):
local_streak = __calculate_streak_value(["D", "D", "D", "E", "D"])
# = -2 + (-2) + (-2) + 1 + (-2)
# = -7 puntos
Cálculo para GETAFE (visitante):
visitor_streak = __calculate_streak_value(["V", "V", "V", "V", "E"])
# = 3 + 3 + 3 + 3 + 1
# = 13 puntos
Rangos de interpretación de rachas (en 5 partidos):
streak >= 10: Racha excelente (mayoría victorias)streak 6-9: Buena rachastreak 1-5: Racha moderadastreak -3 a 0: Racha pobrestreak <= -6: Racha crítica (mayoría derrotas)
Resultado:
VILLARREAL: -7 puntos → Racha crítica (4 derrotas en 5 partidos)
GETAFE: +13 puntos → Racha excelente (4 victorias en 5 partidos)
Evaluación de Escenarios¶
Escenario evaluado: Alta probabilidad de victoria local.
El análisis verifica si se cumple la condición de inconsistencia por rachas, aplicable cuando hay un favorito claro (probabilidad > 60%).
if max_prob >= 60:
if local_streak < -6 and visitor_streak > 6:
# ¡INCONSISTENCIA DETECTADA!
Verificación de condiciones:
✅
max_prob >= 60→ Verdadero (72.5% >= 60%) - Favorito fuerte✅
local_streak < -6→ Verdadero (-7 < -6) - Mala racha significativa✅
visitor_streak > 6→ Verdadero (13 > 6) - Buena racha significativa
✅ INCONSISTENCIA CONFIRMADA
Explicación de umbrales:
60% de probabilidad: Umbral que distingue favoritos fuertes (>=60%) de favoritos débiles (<60%)
-6 puntos en racha: Umbral de mala forma (típicamente 3-4 derrotas recientes)
+6 puntos en racha: Umbral de buena forma (típicamente 2-3 victorias recientes)
Estos umbrales aseguran que solo se detecten inconsistencias cuando hay una contradicción real entre ser favorito fuerte y tener forma muy pobre, mientras el rival tiene forma muy buena.
Cálculo de Score de Divergencia¶
Una vez confirmada la inconsistencia, se calcula el score de divergencia total que mide la magnitud de la contradicción:
divergence = min((max_prob - 50) + abs(local_streak) + abs(visitor_streak), 100)
= min((72.5 - 50) + abs(-7) + 13, 100)
= min(22.5 + 7 + 13, 100)
= min(42.5, 100)
= 42.5 puntos
Divergencia base =
(max_prob - 50)= 22.5Magnitud racha local =
abs(local_streak)= 7Usa valor absoluto porque queremos la magnitud, no el signo
Penaliza por tener mala racha siendo favorito
Magnitud racha visitante =
abs(visitor_streak)= 13Penaliza por ignorar la buena racha del rival
Suma total = 22.5 + 7 + 13 = 42.5 puntos
Límite máximo =
min(42.5, 100)= 42.5El score se limita a 100 para evitar valores extremos
En este caso, 42.5 < 100, así que no se aplica el límite
Interpretación del score 42.5:
Es un valor alto-moderado que indica inconsistencia notable
Supera claramente el threshold de 25.0 configurado
Se acerca al umbral de 50 para ALERTA ROJA
Refleja una contradicción significativa pero no extrema
Generación del Reporte¶
return {
"type": "streak_inconsistency",
"description": "Probabilidad alta de victoria local (72%) pero el local está en mala racha y el visitante en buena forma",
"factors": {
"local_recent_form": "Mala racha",
"visitor_recent_form": "Buena racha"
},
"divergence_score": 42.5
}
3.- Cálculo de Divergencia por Historico¶
Tenemos un históricos de 16 encuentros entre el VILLARREAL - GETAFE con los siguientes resultados:
veces1 = 8 # Victorias locales
vecesX = 5 # Empates
veces2 = 3 # Victorias visitante
total_historic = 16 # Total de enfrentamientos
Validación de Muestra¶
Este tipo de inconsistencia se calcula si se han jugamos a lo largo de la historia mas de 5 partidos:
if total_historic < 5:
return None # Muestra insuficiente
✅ Cumple: 16 >= 5 → Muestra estadísticamente significativa
Cálculo de Tasas Históricas¶
historical_rates = {
"1": (8 / 16) * 100 = 50.0%,
"X": (5 / 16) * 100 = 31.25%,
"2": (3 / 16) * 100 = 18.75%
}
Cálculo de Divergencia¶
La divergencia por histórico mide cuánto se aleja la probabilidad del encuentro del patrón histórico real de enfrentamientos directos:
if max_prob >= 50:
divergencie = max_prob - historical_rate
# divergence = 72.5 - 50 = 22.5
Interpretación:
Probabilidad: 72.5% de victoria local
Histórico real: 50.0% de victorias locales (8 de 16 partidos)
Divergencia: 22.5 puntos más al local de lo que indica su histórico
Interpretación de rangos:
Rango de Divergencia |
Interpretación |
Acción |
|---|---|---|
|
Discrepancia extrema |
Las probabilidades ignoran el patrón histórico claro, sorpresa muy probable |
|
Discrepancia alta |
Histórico sugiere resultado diferente, analizar con detalle |
|
Discrepancia moderada |
Diferencia notable pero no crítica |
|
Sin inconsistencia |
Diferencia explicable por otros factores, no reportar |
Evaluación del Umbral¶
Verificación:
❌
divergencia_historico > 25→ Falso (22.5 < 25)
❌ NO SE DETECTA INCONSISTENCIA HISTÓRICA
Explicación del umbral de 25 puntos:
Se requiere una diferencia de más de 25 puntos porcentuales para considerar inconsistencia
En este caso: 22.5 < 25 → La diferencia no es suficientemente grande
Interpretación: Aunque la probabilidad (72.5%) es mayor que la del histórico (50%), la diferencia es aceptable
Factores como ventaja de local, forma actual o clasificación pueden justificar esta diferencia de 22.5 puntos
Ejemplo de inconsistencia histórica que SÍ se detectaría:
Si histórico fuera 35% y la probabilidad diera 72.5%:
divergencia = 72.5 - 35.0 = 37.5 > 25 ✅ ALERTA DETECTADA -> DISCREPANCIA ALTA
Interpretación: Las probabilidades ignoran que históricamente este partido tiende al empate/derrota local
4.- Cálculo de Divergencia por Clasificación¶
Escenario evaluado: Victoria local sobrestimada (probabilidad > 65%) con visitante mejor clasificado
if max_prob >= 65 and pos_visitor < pos_local - 8:
# Detectar inconsistencia
Verificación:
✅
max_prob >= 65→ Verdadero (72.5% >= 65%)❌
pos_visitor < pos_local - 8→ Falso (16 no es < 12 - 8 = 4)
Interpretación: Getafe está peor clasificado (16º vs 12º) pero no hay una diferencia de más de 8 posiciones. No hay contradicción con la clasificación.
❌ NO SE DETECTA INCONSISTENCIA DE CLASIFICACIÓN
Interpretación por diferencia de posiciones:
Posiciones de Diferencia |
Divergencia Resultante |
Interpretación |
|---|---|---|
<8 posiciones |
0 puntos |
Diferencia no reportable |
8 posiciones |
20 puntos |
Diferencia notable (umbral mínimo) |
10 posiciones |
25 puntos |
Diferencia significativa |
12 posiciones |
30 puntos |
Diferencia crítica |
15+ posiciones |
37.5+ puntos |
Diferencia extrema (favorito muy mal clasificado) |
Nota: Diferencias < 8 posiciones no se reportan (justificables por ventaja de campo u otros factores).
Ejemplo con 10 posiciones de diferencia:
Supongamos un partido SEVILLA (14º) vs REAL MADRID (4º) con las siguientes características:
# Datos del partido
max_prob = 68.0 # 68% probabilidad victoria local (victoria del SEVILLA)
pos_local = 14 # Sevilla en 14º posición
pos_visitor = 4 # Real Madrid en 4º posición
Verificación de condiciones:
if max_prob >= 65 and pos_visitor < pos_local - 8:
# Calcular divergencia
✅
max_prob >= 65→ Verdadero (68% >= 65%)✅
pos_visitor < pos_local - 8→ Verdadero (4 < 14 - 8 = 6)
✅ INCONSISTENCIA DE CLASIFICACIÓN DETECTADA
Cálculo de divergencia:
divergence = (pos_local - pos_visitor) * 2.5
= (14 - 4) * 2.5
= 10 * 2.5
= 25.0 puntos
Interpretación:
Sevilla está 10 posiciones por debajo de Real Madrid en la tabla (14º vs 4º)
Las probabilidades dan a Sevilla 68% de victoria local
Esta es una diferencia significativa: ¿por qué un equipo 10 puestos peor es tan favorito?
Posibles factores: ventaja de campo muy fuerte, mala racha del Madrid, lesiones clave del visitante
Divergencia de 25 puntos → Si se combina con divergencia base (18 pts), score total = 43 pts → ⚠️ ALERTA MEDIA
🎚️ Niveles de Alerta¶
Una vez detectadas las inconsistencia más significativa, se aplican estos umbrales globales:
Nivel de Alerta |
Rango de Divergencia |
Interpretación |
Recomendación |
|---|---|---|---|
🔴 ALERTA ROJA |
|
Contradicción crítica entre Probabilidad y contexto |
Alta probabilidad de sorpresa, analizar con detalle |
🟠 ALERTA MEDIA |
|
Contradicción notable y significativa |
Sorpresa posible, considerar factores adicionales |
🟡 ALERTA |
|
Contradicción detectable pero moderada |
Monitorear, puede haber valor oculto |
🟢 Sin alerta |
|
Sin inconsistencia significativa |
No se reporta |
Ajuste de sensibilidad con el parámetro threshold: en función del umbral (threshold) definido se pueden detectar más o menos alertas:
Threshold |
Tipo de Detección |
Alertas Esperadas |
Uso Recomendado |
|---|---|---|---|
|
Sensible |
Muchas alertas |
Exploración amplia, análisis exhaustivo |
|
Estándar |
Balance óptimo |
Recomendado (default) |
|
Conservador |
Menos alertas |
Solo inconsistencias claras |
|
Restrictivo |
Pocas alertas |
Solo casos muy críticos |
📊 Resultado Final¶
A continuación se muestra el resultado que devolveria el proceso de detección de sorpresas:
return {
"jornada": 19,
"temporada": 2026,
"threshold": 25.0,
"total_surprises": 3, # Este partido + 2 más
"surprises": [
# ... otros partidos ...
{
"match": "VILLARREAL - GETAFE",
"alert_level": "🟠 ALERTA MEDIA",
"inconsistency_type": "streak_inconsistency",
"description": "Probabilidad alta de victoria local (72%) pero el local está en mala racha y el visitante en buena forma",
"probabilities": {
"1": 72.5,
"X": 18.3,
"2": 9.2
},
"context_factors": {
"local_recent_form": "Mala racha",
"visitor_recent_form": "Buena racha"
},
"divergence_analysis": {
"divergence_base": 22.5,
"divergence_streak": 20.0,
"divergence_historical": 22.5,
"divergence_classification": null,
"total_score": 42.5,
"selected_inconsistency": "streak_inconsistency",
}
}
]
}
Desglose del análisis de divergencias:
divergence_base(22.5): Calculada en punto 1 comomax_prob - 50 = 72.5 - 50divergence_streak(20.0): Calculada en punto 2 comoabs(-7) + 13 = 7 + 13Esta se suma a la base porque cumplió todas las condiciones (prob≥60%, racha<-6, racha>6)
divergence_historical(22.5): Calculada en punto 3 como72.5 - 50.0NO se usó porque 22.5 < 25 (umbral de inconsistencia histórica)
divergence_classification(null): punto 4 no detectó inconsistenciaGetafe está peor clasificado (16º vs 12º), consistente con favorito local
total_score(42.5):divergence_base + divergence_streak = 22.5 + 20.0Solo incluye las divergencias que se activaron
selected_inconsistency(“streak_inconsistency”): La única que cumplió todos los criteriosthreshold_used(25.0): Umbral configurado en la llamada al detectorexceeded_threshold(true): 42.5 ≥ 25.0, por eso se reporta la alerta
🔑 Puntos Clave del Análisis¶
¿Por qué se detectó esta sorpresa?¶
La detección se activó porque se cumplieron todas las condiciones necesarias:
Favorito claro identificado:
Probabilidad del “1” (72.5%) > 50% → Hay un resultado favorito
Divergencia base: 72.5 - 50 = 22.5 puntos
Favorito fuerte con mala forma:
Probabilidad >= 60% (72.5%) → Favorito fuerte
Racha local = -7 puntos (< -6) → Mala racha significativa (4D-1E)
Rival con excelente forma:
Racha visitante = +13 puntos (> +6) → Buena racha significativa (4V-1E)
Score de divergencia alto:
Cálculo: (72.5 - 50) + |-7| + 13 = 42.5 puntos
Supera el threshold de 25.0 configurado
Alcanza nivel de ALERTA MEDIA (35 <= 42.5 < 50)
Magnitud extrema de rachas:
Diferencia total: -7 (local) vs +13 (visitante) = 20 puntos de separación
Esto representa una contradicción muy marcada en el momentum actual
¿Qué no activó alertas?¶
Inconsistencia histórica (no detectada):
Tasa histórica local: 8/16 = 50%
Probabilidad: 72.5%
Diferencia: 22.5 puntos porcentuales
❌ No supera el umbral de 25 puntos requerido
Conclusión: El histórico sugiere leve favorito local, consistente con probabilidades
Inconsistencia de clasificación (no detectada):
Villarreal: 12º posición
Getafe: 16º posición (4 puestos peor)
❌ No cumple condición: visitante NO está 8+ posiciones mejor clasificado
Conclusión: La clasificación respalda a Villarreal como favorito
Interpretación práctica¶
Esta alerta sugiere que:
Sobrevalorización del factor campo:
Las probabilidades podrían estar dando demasiado peso al hecho de que Villarreal juega en casa
El 72.5% parece no considerar suficientemente el pésimo momento de Villarreal
Momento actual vs datos históricos:
El momentum reciente de Getafe (4 victorias) contradice su posición de tabla (16º)
La mala racha de Villarreal (4 derrotas) contradice su probabilidad de favorito
Oportunidad de apuesta de valor:
La victoria del Getafe o el empate podrían tener más probabilidad real
El mercado podría estar infravalorando la forma actual de Getafe
Posible partido para apostar contra el favorito (Villarreal)
Contexto relevante:
La forma reciente (últimos 5 partidos) pesa más que la posición en tabla
4 derrotas consecutivas indican problemas actuales graves (lesiones, crisis, cambio técnico)
4 victorias consecutivas indican equipo en racha ascendente
📝 Conclusión¶
El proceso de detección de sorpresas es un análisis multi-dimensional que:
✅ Recopila datos de múltiples fuentes (probabilidades + detalles de partidos)
✅ Calcula métricas cuantitativas (rachas, tasas históricas, posiciones)
✅ Evalúa múltiples dimensiones de inconsistencia (forma, histórico, clasificación)
✅ Pondera la magnitud de las contradicciones (divergence_score)
✅ Filtra por relevancia (threshold configurable)
✅ Clasifica la gravedad (ALERTA ROJA/MEDIA/NORMAL)
Este enfoque sistemático permite identificar oportunidades de valor donde el contexto actual sugiere resultados diferentes a las probabilidades publicadas.