football-betting-analysis

>

Skill file

Preview skill file
---
name: football-betting-analysis
description: >
  Análisis pre-partido de fútbol en 8 capas. Recibe una consulta en lenguaje
  natural, descubre el partido en FlashScore MCP, ejecuta build_match_context.py
  para obtener un contexto normalizado en JSON, y produce un informe estructurado
  con lenguaje probabilístico. Solo para partidos notstarted. No inventa datos,
  no suple la predicción con modelos heurísticos.
---

# Football Betting Analysis

## 1. Overview

Análisis pre-partido de fútbol. Recibe una consulta en lenguaje natural, descubre
el partido en FlashScore MCP, ejecuta el script de preprocesamiento
`build_match_context.py` para obtener un contexto JSON normalizado, y produce un
informe estructurado con lenguaje probabilístico.

**Solo para partidos `notstarted` e `inprogress`.** Para partidos `finished` esta skill no aplica.

El modelo no llama endpoints directamente (salvo para match discovery).
Recibe datos ya limpios, normalizados y consolidados en `final_context`.

El análisis es una interpretación de la evidencia disponible. No es un pick.
No es una garantía. Siempre usa lenguaje como "podría", "señal", "sugiere".
Nunca: "va a ganar", "es fijo", "el over entra seguro".

---

## 2. When to Use / When Not to Use

### Usar cuando:

- El usuario pide análisis pre-partido de un partido específico de fútbol.
- El partido está en estado `notstarted`.

### No usar cuando:

- El partido ya está `finished` → esta skill no aplica.
- La consulta es sobre un torneo o equipo sin partido específico → no procede. Se puede ofrecer buscar partidos próximos de ese equipo.
- `final_context` no pudo generarse.

**Nota arquitectónica:** Esta versión no dispone de predicción basada en modelos
de aprendizaje. Por diseño, la capa predictiva es odds-driven — las probabilidades
numéricas SOLO viennent de odds. Los indicadores solo modulan confianza, no generan
porcentajes.

---

## 3. Fuente de Datos — FlashScore MCP

### Endpoints utilizados

| Endpoint                        | Uso                                                                                      |
| ------------------------------- | ---------------------------------------------------------------------------------------- |
| `Get_Match_Details`             | Evento completo + odds                                                                   |
| `Get_Match_H2H`                 | Head-to-head                                                                             |
| `Get_Match_Stats`               | Llamado en paralelo para cada partido del historial (matches[i]) — solo para construir form.advanced agregado |
| `Get_Match_Lineups`             | Alineaciones si existen + `missingPlayers` con nombre y motivo de ausencia               |
| `Get_Team_Results`              | Match discovery (fallback) + historial de resultados del equipo                          |
| `Get_Team_Fixtures`             | Match discovery (primario) — próximos partidos del equipo                                |
| `Get_Tournament_Standings`      | Contexto de forma/posición en torneo                                                     |
| `Get_Match_Standings_OverUnder` | Standings O/U                                                                            |
| `Get_Tournament_Top_Scorers`    | Goleadores del torneo                                                                    |
| `Get_Match_Standings_Form`      | Forma reciente dentro del partido                                                        |

### Lo que NO existe en FlashScore (y nunca se debe inventar)

| Qué no existe                                     | Consecuencia                                                                                                                                            |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Alineaciones confirmadas pre-partido              | El endpoint `Get_Match_Lineups` existe, pero las alineaciones confirmadas pueden no estar disponibles aún. Pueden venir vacías. No inventar alineación. Usar `predictedLineups` si está disponible — el script lo procesa automáticamente. |
| Histórico de lesiones pre-existentes              | No existe en la API. No inventar.                                                                                                                       |
| `missingPlayers` sin datos en `Get_Match_Lineups` | Si el endpoint no lo trae → no inventar motivo ni jugador.                                                                                              |

**Comportamiento de `normalize_lineups` en `build_match_context.py`:**

El script procesa las alineaciones en dos niveles de prioridad:

1. **Si `startingLineups` existe (alineaciones oficiales confirmadas):**
   - `starting_lineups` — alineación titular confirmada [API]
   - `missing_players` — jugadores ausentes con motivo [API]
   - `substitutes` — sustitutos disponibles [API]
   - `predicted_lineups` → vacío
   - `unsure_missing` — vacío

2. **Si `startingLineups` NO existe pero `predictedLineups` sí (alineación predicha/no confirmada):**
   - `unsure_missing` — jugadores cuya ausencia no está confirmada [API]
   - `missing_players` → jugadores ausentes con motivo [API]
   - `predicted_lineups` — alineación predicha [API]
   - `starting_lineups` → vacío
   - `substitutes` → vacío

**Nota:** `Get_Match_Lineups` puede aportar `missingPlayers` (nombre + motivo) incluso cuando las alineacionesConfirmadas no existen. Usar ese dato cuando esté disponible.

**Fuente de cada señal en el análisis:**

| Tag      | Significado                                         |
| -------- | --------------------------------------------------- |
| `[API]`  | Dato directo de un endpoint de FlashScore MCP       |
| `[ODDS]` | Calculado de cuotas de mercado                      |
| `[IND]`  | Indicador calculado a partir de los datos de la API |
| `[N/A]`  | Dato no disponible en la API                        |

---

## 4. Pipeline de Análisis

### 4.1 Parsing de la consulta

De la consulta en lenguaje natural extraer:

```
{
  home_team:  string,      // equipo mencionado primero
  away_team:  string,       // equipo mencionado segundo (o null si solo uno)
  date_from:  ISO 8601,    // fecha inicio del rango
  date_to:    ISO 8601,    // fecha fin del rango
  league:     string|null,  // competición mencionada (o null)
  analysis_mode: "prematch"  // único modo soportado
}
```

**Reglas de parsing:**

| Situación                                         | Regla                                                         |
| ------------------------------------------------- | ------------------------------------------------------------- |
| El usuario dice "hoy"                             | `date_from = hoy`, `date_to = hoy`                            |
| El usuario dice "mañana"                          | `date_from = mañana`, `date_to = mañana`                      |
| El usuario dice "este finde"                      | `date_from = viernes`, `date_to = domingo`                    |
| El usuario dice "esta semana" sin día             | `date_from = lunes`, `date_to = domingo`                      |
| Solo un equipo mencionado                         | Buscar el próximo partido de ese equipo en el rango de 7 días |
| Nombres con acento ("Atletico", "Inter")          | Normalizar quitando acentos antes de buscar                   |
| Nombres parciales ("Barça", "Atleti")             | Fuzzy match contra `home_team`/`away_team` del resultado      |
| Competición mencionada ("la league", "champions") | Filtrar por `league_id` tras buscar                           |

| `status = "notstarted"` | `analysis_mode = "prematch"` → proceed |
| `status = "inprogress"` | → Proceed con análisis en vivo. |
| `status = "finished"` | → No aplica. Notificar al usuario. |

**Si solo un equipo matchea pero hay múltiples candidatos del rival:**
→ Clarificación obligatoria. Preguntar: "¿Buscás el [equipo] vs [candidato A] o vs [candidato B]?"

### 4.2 Match Discovery (Fase 1 — SOLO esta fase usa MCP directamente)

**Paso 1 — Búsqueda por API de búsqueda de equipos:**

```
1. Normalizar nombres de ambos equipos (minúsculas, sin acentos, sin puntos)
2. Usar la API de búsqueda:
   GET https://s.livesport.services/api/v2/search/?q=<query>&lang-id=13&type-ids=1,2,3,4&project-id=202&project-type-id=1
3. Filtrar resultados: sport.id=1 (fútbol), type.id=2 (Team)
4. Retorna: team_id, name, url, country, country_id
   → Si 1 resultado: proceed con ese team_id
   → Si >1 resultado: clarificación obligatoria ("¿Buscás [equipo A] o [equipo B]?")
   → Si 0 resultados: ir a paso 5 (fallback a /data/main_teams.csv)
```

**Normalización para la API de búsqueda:**

- Minúsculas
- Quitar acentos: á→a, é→e, í→i, ó→o, ú→u
- Quitar puntos: "Atl." → "Atl"
- Quitar espacios extra

**Paso 2 — Lookup de equipos en /data/main_teams.csv (fallback):**

```
5. Si no se encontró el equipo en la API, buscar en /data/main_teams.csv:
   Normalizar nombres (mismas reglas que arriba)
6. Buscar match exacto en /data/main_teams.csv después de normalizar
   → Si 1 resultado: proceed
   → Si >1 resultado: clarificación obligatoria
7. Si 0 resultados exactos: intentar substring match
   → Si 1 resultado: proceed
   → Si >1 resultado: clarificación obligatoria
8. Si 0 resultados: buscar coincidencias cercanas (similitud string)
   → Calcular similaridad entre nombre buscado y todos los nombres en /data/main_teams.csv
   → Si hay candidatos con similitud ≥ umbral (ej: Levenshtein ratio ≥ 0.6):
      → Si 1 candidato cercano: "¿Quisiste decir [nombre]?"
      → Si >1 candidatos: mostrar top 3: "¿Buscás alguno de estos? [A], [B], [C]"
   → Si ningún candidato cercano: "No encontré '[equipo]'. Verificá el nombre."
```

**Sugerencia de candidatos cercanos:**

- Umbral de similaridad: ratio ≥ 0.6 (Levenshtein o similar)
- Mostrar máximo 3 candidatos ordenados por similaridad descendente
- Incluir el nombre original del CSV (sin normalizar) en la sugerencia

**Paso 3 — Búsqueda por overlap en fixtures:**

```
 9. Get_Team_Fixtures(team_id_home, page=1)
10. Get_Team_Fixtures(team_id_away, page=1)
11. Filtrar fixtures del rango date_from..date_to
12. Buscar partido donde home_team_id o away_team_id de un fixture
    coincida con el team_id del otro equipo (overlap)
    → Si 1 resultado: proceed
    → Si >1 resultado: priorizar por fecha más cercana al rango solicitado
    → Si 0: ir a paso 4
```

**Paso 4 — Fallback a results:**

```
13. Get_Team_Results(team_id_home, page=1)
14. Get_Team_Results(team_id_away, page=1)
15. Buscar overlap (misma lógica que fixtures)
    → Si 1 resultado: proceed
    → Si >1: priorizar por fecha más cercana
    → Si 0: "No encontré el partido [Home] vs [Away] en las fechas indicadas."
```

**Paso 5 — Verificación y extracción:**

```
16. Verificar estado del evento:
    → status = "notstarted" → proceed con análisis pre-partido
    → status = "inprogress" → proceed con análisis en vivo
    → status = "finished" → "El partido ya finalizó. Esta skill es solo pre-partido y en vivo."
17. Extraer IDs:
    → event_id = del evento encontrado
    → home_team_id = del equipo local
    → away_team_id = del equipo visitante
```

**Priorización cuando hay múltiples candidatos:**

1. Si el usuario mencionó liga → priorizar esa liga.
2. Si el usuario mencionó fecha exacta → solo considerar esa fecha.
3. Si hay exactamente 1 partido de cada equipo combinado → usar ese.
4. **Si múltiples partidos del mismo equipo → priorizar el más cercano en el tiempo al rango de fechas de la consulta.** Esto evita clarificaciones innecesarias.
5. Si no, clarificación obligatoria.

### 4.3 Preprocesamiento (Fase 2 — build_match_context.py)

**Esta es la única manera de obtener datos para el análisis. No usar endpoints MCP directamente para obtener datos del partido.**

```
python scripts/build_match_context.py <event_id> <home_team_id> <away_team_id>
```

**Qué hace el script:**

1. Ejecuta todas las llamadas a endpoints de FlashScore internamente
2. Construye H2H desde el historial de resultados de ambos equipos
3. Resume historial de forma (GF, GC, over, BTTS, home/away split) — `form.basic`
4. Extrae y normaliza ausencias desde `Get_Match_Lineups`
5. Valida qué mercados de odds existen realmente y bloquea los que no existan
6. Integra estadísticas avanzadas de cada partido del historial (tiros, posesión, xG, etc.) — solo para construir `form.advanced` (los datos individuales no se emiten en el JSON final)
7. Agrega player stats históricas por equipo (`player_stats.as_historical_home/away`)
8. Incorpora contexto de torneo (posición, forma, over/under standings)
9. Emite warnings de consistencia
10. Consolida toda la información en un único JSON (`final_context`)

**Output del script — `final_context`:**

```
{
  "meta": {
    event_id, home_team_id, away_team_id, generated_at
  },
  "match": {
    event_id,
    home_team: { id, name, event_participant_id },
    away_team: { id, name, event_participant_id },
    tournament: { id, stage_id, name },
    country, referee, timestamp, datetime, status,
    scores: {
      home, away,
      home_total, away_total,
      home_1st_half, away_1st_half,
      home_2nd_half, away_2nd_half,
      home_extra_time, away_extra_time,
      home_penalties, away_penalties
    },
    "lineups": {
      home: {
        formation,              // formación reportada (de predictedFormation)
        lineup_count,           // cantidad de jugadores en alineación
        // -- Si hay alineaciones oficiales (startingLineups existe): --
        starting_lineups: [...],       // alineación titular oficial [API]
        missing_players: [...],        // jugadores ausentes con motivo [API]
        substitutes: [...],            // sustitutos disponibles [API]
        predicted_lineups: [],        // vacío
        unsure_missing: [],           // vacío
        // -- Si NO hay oficiales pero hay predicción (predictedLineups): --
        starting_lineups: [],          // vacío
        missing_players: [],           // vacío
        substitutes: [],               // vacío
        predicted_lineups: [...],      // alineación predicha [API]
        unsure_missing: [...],         // ausencias no confirmadas [API]
      },
      away: { formation, lineup_count, starting_lineups, missing_players, substitutes, predicted_lineups, unsure_missing },
      warnings: string[] | null
    },
    "preview": string | null,  // texto de previa del partido — web scraping de FlashScore (DOM o contentParsed embebido)
    // --- inprogress or finished only ---
    "summary": object | null,    // eventos clave del partido (goles, tarjetas, etc.) [API]
    "commentary": object | null, // narración minuto a minuto [API]
    "match_stats": object | null, // estadísticas del partido (posesión, tiros, xG, etc.) [API]
    "player_stats": object | null, // stats de jugadores del partido actual [API]
    "live_analysis": {           // solo para status = "inprogress"
      total_goals_over_25: { probability, signal, confidence },
      total_goals_under_25: { ... },
      btts_yes: { ... },
      btts_no: { ... },
      next_goal_home: { ... },
      next_goal_away: { ... },
      next_goal_no_more: { ... },  // explicit class — no more goals in the match
      next_corner: { lean, signal, confidence, mode: "signal_only" },
      total_cards: { lean, signal, confidence, mode: "signal_only" },
      1x2_final: { probabilities: { home, draw, away }, model_version, calibrated, inputs_quality, top_factors, prematch_source, confidence, warnings },
      match_minute: int,
      current_score: { home: int, away: int },
      warnings: []
    } | null,
    warnings: string[] | null
  },
  "odds": {
    available_markets: string[],
    odds_home, odds_draw, odds_away,
    odds_over_25, odds_under_25,
    odds_btts_yes, odds_btts_no,
    warnings: string[]
  },
  "implied_probs": {
    prob_home, prob_draw, prob_away,
    prob_over_25, prob_btts_yes
  },
  "h2h": {
    total_matches,
    home_team_wins,   // partidos ganados por el equipo local del análisis
    away_team_wins,   // partidos ganados por el equipo visitante del análisis
    draws,
    home_team_goals_for,   // goles anotados por el equipo local del análisis en H2H
    away_team_goals_for,   // goles anotados por el equipo visitante del análisis en H2H
    total_goals, both_teams_scored,
    matches: [{
      match_id, timestamp,
      home_score, away_score,
      home_team, away_team,
      tournament_id, tournament_name,
      team_is_home, opponent
    }],
    basic_stats: {               // stats básicos del historial H2H
      // home_team = stats of the team that IS home in the CURRENT match (Bayern)
      // away_team = stats of the team that IS away in the CURRENT match (Real Madrid)
      // form_string/points/home_ppg/away_ppg excluded — not meaningful in h2h context
      home_team: {
        wins, losses, draws,
        gf_avg, gc_avg, total_goals
      },
      away_team: {
        wins, losses, draws,
        gf_avg, gc_avg, total_goals
      },
      both_teams_scored,
      over_25_freq, btts_freq,
      total_matches,
      total_goals
    },
    advanced_stats: {             // stats agregadas del historial (de advanced_stats de cada match)
      // For home_team_results / away_team_results: same flat structure
      // For h2h: split by current-match home/away identity
      // Each sub-dict has: { overall: {...}, first_half: {...}, second_half: {...} }
      home_team: {
        overall: { attack: {...}, defense: {...}, control: {...}, set_pieces_and_territory: {...}, discipline: {...}, duels_and_defending: {...}, efficiency: {...}, derived: {...} },
        first_half: { /* mismo formato que overall */ },
        second_half: { /* mismo formato que overall */ }
      },
      away_team: {
        overall: { attack: {...}, defense: {...}, control: {...}, set_pieces_and_territory: {...}, discipline: {...}, duels_and_defending: {...}, efficiency: {...}, derived: {...} },
        first_half: { /* mismo formato que overall */ },
        second_half: { /* mismo formato que overall */ }
      }
    },
    home_team_player_stats: {
      general: { minutes_total, goals_total, assists_total, shots_total, shots_on_target_total, key_passes_total, tackles_won_total, interceptions_total, ball_recoveries_total, yellow_cards_total, red_cards_total, goals_per_90, assists_per_90, shots_per_90, position_distribution, in_base_lineup_count, substitute_count },
      individual: {
        [player_name]: {
          "position": "Goalkeeper" | "Defender" | "Midfielder" | "Forward" | "Unknown",
          offense:    { GOALS, EXPECTED_GOALS, ASSISTS_GOAL, EXPECTED_ASSISTS, SHOTS_TOTAL, SHOTS_ON_TARGET, BIG_CHANCES_CREATED, BIG_CHANCES_MISSED },
          creation:   { KEY_PASSES, FINAL_THIRD_ENTRIES_TOTAL, BOX_ENTRIES, THROUGH_BALLS },
          possession: { TOUCHES_TOTAL, MATCH_MINUTES_PLAYED, PASSES_TOTAL },
          defense:    { DUELS_WON, DUELS_TOTAL, DUELS_EFFICIENCY, TACKLES_WON, INTERCEPTIONS, BALL_RECOVERIES },
          efficiency: { PASSES_ACCURACY, LONG_BALLS_ACCURACY, CROSSES_ACCURACY, DRIBBLES_EFFICIENCY },
          discipline: { FOULS_COMMITTED, FOULS_SUFFERED, CARDS_YELLOW, CARDS_RED, TURNOVERS, ERRORS_LEAD_TO_SHOT, ERRORS_LEAD_TO_GOAL },
          goalkeeping:{ SAVES_TOTAL, GOALS_CONCEDED, GOALS_PREVENTED, EXPECTED_GOALS_ON_TARGET_FACED, BIG_CHANCES_SAVED },
          derived:    { xg_per_shot, goals_minus_xg, xa_minus_assists, duels_win_pct, actions_per_90 }
        }, ...
      }
    },
    away_team_player_stats: { /* misma estructura */ },
    warnings: string[]
  },
  "home_team_results": {
    matches: [{
      match_id, timestamp,
      goals_for, goals_against, total_goals, both_teams_scored,
      tournament_id, tournament_name,
      team_is_home, opponent
    }],
    basic_stats: {               // stats básicos del historial
      all_matches, form_string, points,
      home_gf_avg, home_gc_avg,
      over_25_freq, btts_freq,
      home_ppg, home_gf_avg, home_gc_avg,
      away_ppg, away_team_gf_avg, away_team_gc_avg,
      total_matches, wins, draws, losses,
      goals_for, goals_against, total_goals, both_teams_scored
    },
    advanced_stats: {            // stats agregadas del historial (de advanced_stats de cada match)
      overall: {
        attack: {
          goals_for_avg, xg_for_avg, xgot_for_avg, xa_for_avg,
          shots_for_avg, shots_on_target_for_avg, shots_off_target_for_avg,
          blocked_shots_for_avg, shots_inside_box_for_avg, shots_outside_box_for_avg,
          big_chances_for_avg, touches_in_opposition_box_avg, hit_woodwork_avg
        },
        defense: {
          goals_against_avg, xg_against_avg, xgot_faced_avg,
          shots_against_avg, shots_on_target_against_avg, shots_off_target_against_avg,
          blocked_shots_against_avg, shots_inside_box_against_avg, shots_outside_box_against_avg,
          big_chances_against_avg, touches_in_opposition_box_against_avg,
          goalkeeper_saves_avg, errors_leading_to_shot_avg, errors_leading_to_goal_avg, goals_prevented_avg
        },
        control: {
          possession_avg, passes_accuracy_avg, passes_completed_avg, passes_attempted_avg,
          long_pass_accuracy_avg, long_passes_completed_avg, long_passes_attempted_avg,
          final_third_pass_accuracy_avg, final_third_passes_completed_avg, final_third_passes_attempted_avg,
          accurate_through_passes_avg
        },
        set_pieces_and_territory: {
          corners_for_avg, corners_against_avg, offsides_for_avg, offsides_against_avg,
          free_kicks_for_avg, free_kicks_against_avg, throw_ins_for_avg, throw_ins_against_avg,
          cross_accuracy_avg, crosses_completed_avg, crosses_attempted_avg
        },
        discipline: {
          yellow_cards_avg, red_cards_avg, cards_total_avg, fouls_committed_avg
        },
        duels_and_defending: {
          tackles_success_pct_avg, tackles_won_avg, tackles_attempted_avg,
          duels_won_avg, clearances_avg, interceptions_avg
        },
        efficiency: {
          shot_accuracy_pct, goal_conversion_pct, big_chance_conversion_pct,
          xg_per_shot, shots_on_target_faced_per_goal_against, save_pct,
          finishing_overperformance, conceding_overperformance
        },
        derived: {
          xg_balance_avg, xg_ratio, shots_share, shots_on_target_share,
          big_chances_balance_avg, corners_balance_avg, discipline_balance_avg
        }
      },
      first_half: { /* mismo formato que overall */ },
      second_half: { /* mismo formato que overall */ },
      warnings: []
    },
    player_stats_as_home: {
      general: { minutes_total, goals_total, assists_total, shots_total, shots_on_target_total, key_passes_total, tackles_won_total, interceptions_total, ball_recoveries_total, yellow_cards_total, red_cards_total, goals_per_90, assists_per_90, shots_per_90, position_distribution, in_base_lineup_count, substitute_count },
      individual: {
        [player_name]: {
          "position": "Goalkeeper" | "Defender" | "Midfielder" | "Forward" | "Unknown",
          offense:    { GOALS, EXPECTED_GOALS, ASSISTS_GOAL, EXPECTED_ASSISTS, SHOTS_TOTAL, SHOTS_ON_TARGET, BIG_CHANCES_CREATED, BIG_CHANCES_MISSED },
          creation:   { KEY_PASSES, FINAL_THIRD_ENTRIES_TOTAL, BOX_ENTRIES, THROUGH_BALLS },
          possession: { TOUCHES_TOTAL, MATCH_MINUTES_PLAYED, PASSES_TOTAL },
          defense:    { DUELS_WON, DUELS_TOTAL, DUELS_EFFICIENCY, TACKLES_WON, INTERCEPTIONS, BALL_RECOVERIES },
          efficiency: { PASSES_ACCURACY, LONG_BALLS_ACCURACY, CROSSES_ACCURACY, DRIBBLES_EFFICIENCY },
          discipline: { FOULS_COMMITTED, FOULS_SUFFERED, CARDS_YELLOW, CARDS_RED, TURNOVERS, ERRORS_LEAD_TO_SHOT, ERRORS_LEAD_TO_GOAL },
          goalkeeping:{ SAVES_TOTAL, GOALS_CONCEDED, GOALS_PREVENTED, EXPECTED_GOALS_ON_TARGET_FACED, BIG_CHANCES_SAVED },
          derived:    { xg_per_shot, goals_minus_xg, xa_minus_assists, duels_win_pct, actions_per_90 }
        }, ...
      }
    },
    player_stats_as_away: { /* misma estructura */ },
    warnings: string[] | null
  },
  "away_team_results": {
    matches: [{
      match_id, timestamp,
      goals_for, goals_against, total_goals, both_teams_scored,
      tournament_id, tournament_name,
      team_is_home, opponent
    }],
    basic_stats: {               // stats básicos del historial
      all_matches, form_string, points,
      home_gf_avg, home_gc_avg,
      over_25_freq, btts_freq,
      home_ppg, home_gf_avg, home_gc_avg,
      away_ppg, away_team_gf_avg, away_team_gc_avg,
      total_matches, wins, draws, losses,
      goals_for, goals_against, total_goals, both_teams_scored
    },
    advanced_stats: {            // stats agregadas del historial (de advanced_stats de cada match)
      overall: { /* mismo formato que en home_team_results */ },
      first_half: { /* mismo formato */ },
      second_half: { /* mismo formato */ },
      warnings: []
    },
    player_stats_as_home: {
      general: { minutes_total, goals_total, assists_total, shots_total, shots_on_target_total, key_passes_total, tackles_won_total, interceptions_total, ball_recoveries_total, yellow_cards_total, red_cards_total, goals_per_90, assists_per_90, shots_per_90, position_distribution, in_base_lineup_count, substitute_count },
      individual: {
        [player_name]: {
          "position": "Goalkeeper" | "Defender" | "Midfielder" | "Forward" | "Unknown",
          offense:    { GOALS, EXPECTED_GOALS, ASSISTS_GOAL, EXPECTED_ASSISTS, SHOTS_TOTAL, SHOTS_ON_TARGET, BIG_CHANCES_CREATED, BIG_CHANCES_MISSED },
          creation:   { KEY_PASSES, FINAL_THIRD_ENTRIES_TOTAL, BOX_ENTRIES, THROUGH_BALLS },
          possession: { TOUCHES_TOTAL, MATCH_MINUTES_PLAYED, PASSES_TOTAL },
          defense:    { DUELS_WON, DUELS_TOTAL, DUELS_EFFICIENCY, TACKLES_WON, INTERCEPTIONS, BALL_RECOVERIES },
          efficiency: { PASSES_ACCURACY, LONG_BALLS_ACCURACY, CROSSES_ACCURACY, DRIBBLES_EFFICIENCY },
          discipline: { FOULS_COMMITTED, FOULS_SUFFERED, CARDS_YELLOW, CARDS_RED, TURNOVERS, ERRORS_LEAD_TO_SHOT, ERRORS_LEAD_TO_GOAL },
          goalkeeping:{ SAVES_TOTAL, GOALS_CONCEDED, GOALS_PREVENTED, EXPECTED_GOALS_ON_TARGET_FACED, BIG_CHANCES_SAVED },
          derived:    { xg_per_shot, goals_minus_xg, xa_minus_assists, duels_win_pct, actions_per_90 }
        }, ...
      }
    },
    player_stats_as_away: { /* misma estructura */ },
    warnings: string[] | null
  },
  "standings": {
    teams: { [team_id]: { position, name, points, wins, draws, losses, goals, goal_difference } },
    warnings: string[] | null
  },
  "overunder_standings": {
    teams: { [team_id]: { over, under, average_goals } },
    warnings: string[] | null
  },
  "form_standings": {
    teams: { [team_id]: { points, form_string } },
    warnings: string[] | null
  },
  "top_scorers": {
    home_scorers: [{ name, player_id, team, goals, assists }],
    away_scorers: [...],
    warnings: string[] | null
  },
  "tournament_top_scorers": {
    home_scorers: [...],
    away_scorers: [...],
    warnings: string[] | null
  }
}
```

**Reglas de uso del script:**

- El modelo NO vuelve a llamar endpoints MCP después de recibir `final_context`.
- Todos los datos del análisis vienen del JSON.
- Si el script emite warnings → el modelo debe tomarlos en cuenta y no contradecirlos.
- Si el JSON marca algo como `[N/A]` → el modelo lo usa como `[N/A]`, no intenta inferir el dato.
- El script es determinista: dada la misma entrada, siempre produce la misma salida.
- El modelo no recalcula ni reinterpreta datos ya normalizados salvo para explicar su significado.

**Si el script falla o no puede ejecutarse:**
→ "No pude generar el contexto del partido. Análisis no viable."

---

## 5. Las 8 Capas — Definición Exacta

Cada capa tiene: inputs, cálculos, output obligatorio, degradación.

---

### Capa 1 — Contexto Base

**Inputs:** `odds_home`, `odds_draw`, `odds_away`, `odds_over_25`,
`odds_btts_yes`.

**Cálculos:**

- Probabilidad implícita de mercado: `1 / odds`
- Probabilidad implícita Over 2.5: `1 / odds_over_25`
- Probabilidad implícita BTTS: `1 / odds_btts_yes`
- Sesgo: favorito claro (>60%), favorito leve (50-60%), equilibrado (<50%)

**Output obligatorio:**

```
## 1. Contexto del Partido
- Partido: [Home] vs [Away]
- Competición: [Liga]
- Fecha/hora: [ISO 8601]
- Estado: [notstarted/inprogress]
---
Mercado dice [ODDS]:
- [Home]: [prob]% | Draw: [prob]% | [Away]: [prob]%
- Over 2.5: [cuota] → prob implícita [prob]%
- BTTS: [cuota] → prob implícita [prob]%
---
Indicadores históricos [IND]:
- Indicadores favorecen: [Home/Away/Equilibrado]
- Soporte histórico: [bajo/medio/alto]
Mercado vs datos: [alineados/parcialmente alineados/en conflicto] — [explicación breve]
```

**Degradación:**

- Si no hay odds → no usar diff mercado vs indicadores. Usar solo odds si disponibles.
- Si neither → capa 1 muy degradada.

**Output para `postmatch`:**

```
Mercado esperaba [ODDS]:
- [Home]: [prob]% | Draw: [prob]% | [Away]: [prob]%
- Over 2.5: [cuota] → prob implícita [prob]%
- BTTS: [cuota] → prob implícita [prob]%

Resultado vs expectativa [ODDS]/[IND]:
- El resultado [fue alineado / parcialmente alineado / sorpresivo] con las probabilidades del mercado
```

---

### Capa 2 — Descriptiva de Equipos

**Inputs:** `Get_Team_Results` (historial), `Get_Match_H2H`, `Get_Tournament_Standings` (si aplica), `final["preview"]` (web scraping — texto de previa del partido en FlashScore).

**Cálculos:**

- Puntos últimos N: de resultados del equipo
- Forma: resultados recientes (W/D/L)
- Goles avg: `goals_scored / matches_played` y `goals_conceded / matches_played`
- Over 2.5 freq: contar partidos con total > 2.5 en últimos N
- BTTS freq: contar partidos con gol de ambos en últimos N
- Perfil: según goles generados vs goles reales (sobre/sub-reperformance)
- H2H: de `Get_Match_H2H` si existe

**Output obligatorio:**

```
## 2. [Qué Viene Pasando / Lo Que Está Pasando]

[Adaptar según estado del partido:]
- status = notstarted → "Qué Viene Pasando"
- status = inprogress → "Lo Que Está Pasando" + marcador actual + minuto
- status = finished → "Lo Que Pasó"

[Home] [IND] [marcador si inprogress]:
  Forma: [form_string] — [pts] pts / [N] pts posibles
  Goles: [home_gf_avg]/[home_gc_avg]
  Over 2.5: [X/N] | BTTS: [X/N] (si hay datos)
  En casa: [home_ppg] ppg | [home_goals]GF / [home_goals]GC
  Perfil: [ofensivo/conservador/equilibrado/inestable]

[ away ] [IND]:
  [mismo formato]

H2H [IND]: [X]PJ — [home_team_wins]V-local [draws]E [away_team_wins]V-visitante | goles local [home_team_goals_for] / visitante [away_team_goals_for]
  [score reciente 1]
  [score reciente 2]

Nota: [si la forma se extrajo solo del evento actual (N=1), indicarlo]

[Para notstarted]: Previa del partido [IND]: [texto de previa de FlashScore — si no disponible: "Previa no disponible [N/A]"]
[Para inprogress]: Eventos recientes: [resumen de últimos eventos del partido — de summary.events]
[Para finished]: Resultado final: [home] [H] - [A] [away] | [marcador final]
[Para postmatch — analysis_mode = "postmatch"]:
  El resultado [confirma/rompe] la tendencia reciente de cada equipo
  Resultado vs forma: [el partido fue consistente/inconsistente con la forma previa]

**Nota:** La previa del partido debe ser concisa. Si excede 3-4 oraciones, resumir los puntos más relevantes para el análisis.
```

**Degradación:**

- Si `home_form` viene vacío → solo listar H2H disponible. Forma = N/A.
- Si no hay H2H → omitir sección H2H.
- Siempre incluir la nota si la muestra es N < 5.
- Si `preview` no disponible → texto "Previa no disponible [N/A]".
- Si status = inprogress → usar "Lo que Está Pasando" con marcador y minuto actual.
- Si status = finished → usar "Lo Que Pasó" con resultado final.
- Si `analysis_mode = "postmatch"`: incluir comparación de resultado vs forma previa y vs odds pre-partido.

---

### Capa 3 — Protagonistas

**Regla transversal (nueva):** Capa 3 = solo hechos + contexto mínimo. NO interpretación, NO peso, NO conclusiones, NO lenguaje causal.

```
Capa 3 NO puede contener:
- conclusiones
- inferencias
- lenguaje causal ("porque", "esto implica", "esto sugiere")

Solo descripción estructurada de datos.
```

**Pregunta única:** ¿qué hay? (NO: ¿qué significa?)

**Lógica de dos carriles (siempre intentar ambos):**

- **Carril A:** `Get_Match_Player_Stats` → stats individuales (gol, asistencia, tiros, etc.)
- **Carril B:** `Get_Match_Lineups` → `missingPlayers` (nombre + motivo de ausencia)

Si Carril A falla pero Carril B devuelve `missingPlayers` → usar Carril B.
Si ambos fallan → capa degradada con `[N/A]`.

---

**Carril A — Inputs directos de `Get_Match_Player_Stats`:**
`goals`, `assists`, `shots`, `shots_on_target`, `key_passes`,
`tackles_won`, `interceptions`, `ball_recoveries`, `yellow_cards`,
`red_cards`, `minutes`.

**Reglas — SIN fórmulas heurísticas:**

- No usar pesos (0.3, 0.5, etc.) ni normalizaciones inventadas.
- No calcular "impact score", "impacto ofensivo", ni ninguna métrica compuesta.
- **Sí se permite:** ranking directo por métrica individual (más goles, más pases clave, etc.).
- **Dependencia:** proporción directa de goles/producción del jugador vs total del equipo.

**Regla (nueva):** Los outputs de Capa 3 deben ser puramente descriptivos. No incluir frases como "esto afecta al equipo", "es una baja clave", "pierde poder ofensivo". Toda interpretación va en Capa 5.

**Outputs por jugador (datos directos, sin fórmulas):**

```
[Jugador X] ([pos]):
  Producción:
  - Goles: X
  - Asistencias: X

  Volumen ofensivo:
  - Tiros: X (X a puerta)

  Creación:
  - Pases clave: X

  Disciplina:
  - Amarillas: X | Rojas: X
```

**Top N por equipo (ranking directo por métrica — sin scores compuestos):**

```
Top generadores de gol [IND]:
1. [Jugador A] — [X] goles
2. [Jugador B] — [X] goles

Top creadores [IND]:
1. [Jugador A] — [X] pases clave
2. [Jugador B] — [X] pases clave
```

**Dependencia ofensiva:**

- Calcular: `(goles jugador / total goles equipo) * 100`
- Umbral: >40% → "Dependencia ofensiva alta [IND]"
- Si un equipo tiene >50% de producción en un solo jugador → alertar.

---

**Carril B — Fuentes desde `Get_Match_Lineups`:**

El script `build_match_context.py` procesa las alineaciones en dos niveles de prioridad (definidos en `normalize_lineups`):

- **Nivel 1 — Alineaciones oficiales (`startingLineups` existe):**
  - `missing_players`: lista de jugadores ausentes con `name`, `player_id`, `reason`, `country` [API]
  - `substitutes`: sustitutos disponibles [API]
  - `starting_lineups`: alineación titular oficial [API]
  - `predicted_lineups`: vacío
  - `unsure_missing`: vacío

- **Nivel 2 — Alineación predicha (`startingLineups` NO existe, `predictedLineups` sí):**
  - `unsure_missing`: jugadores cuya ausencia no está confirmada [API]
  - `predicted_lineups`: alineación predicha [API]
  - `missing_players`: vacío
  - `substitutes`: vacío
  - `starting_lineups`: vacío

Si `missing_players` contiene jugadores ausentes, reportarlos según los niveles de ausencia definidos más abajo.

**Niveles de ausencia (en orden de profundidad):**

1. **Ausencia observada [API]:** Nombre + motivo tal como los devuelve la API (Injury, Inactive, Leg Injury, etc.). Solo reportar lo que la API indica.

2. **Ausencia contextualizada [IND]:** Si además ese jugador aparece en `Get_Tournament_Top_Scorers` o como goleador/generador relevante en `Get_Team_Results` o `Get_Match_Player_Stats` de partidos previos → indicar: "[jugador] ausente — registrado como goleador/top del torneo [IND]." Solo reportar como hecho. No interpretar impacto.

3. **Influencia incierta [N/A/IND]:** Si un jugador está ausente pero no hay evidencia suficiente para estimar su peso → marcar como "influencia no cuantificable con precisión [N/A]". Nunca inventar impacto.

**Regla sobre Carril B solo:**

- Ausencias sin contexto adicional (no aparecen como goleadores relevantes, no hay ranking que los respalde) → reportar como simples hechos observados [API].
- No calcular cuánto baja el equipo por cada ausente.
- No estimar goles perdidos ni probabilidad de gol afectada.
- La acumulación de ausencias en un mismo equipo puede mencionarse como observación contextual (reduce certidumbre sobre el techo de rendimiento del equipo), pero sin cifras inventadas.

**Output obligatorio:**

```
## 3. Protagonistas

[Carril A — Si hay player-stats:]
[Jugador] ([pos]):
  Producción:
  - Goles: X
  - Asistencias: X
  Volumen ofensivo:
  - Tiros: X (X a puerta)
  Creación:
  - Pases clave: X
  Disciplina:
  - Amarillas: X | Rojas: X

[Top del partido]

Dependencia ofensiva:
- [Equipo]: [jugador] → [X]% de los goles del equipo [IND]

[Carril B — Si hay missingPlayers sin player-stats:]
Ausencias observadas [API]:
- [Equipo]: [Jugador] — [motivo de la API]
- [Equipo]: [Jugador] — [motivo de la API]

[Si hay ausencia contextualizada:]
- [Equipo]: [Jugador] — ausente [IND] — [goleador/top scorer del torneo / pieza habitual]

Lectura contextual [IND]:
- [Equipo] llega con [X] bajas registradas en la API.
- La influencia de las ausencias se considera [alta/moderada/baja] solo si coincide con
  otras señales disponibles (producción en排行榜, dependencia ofensiva, ranking histórico, etc.).
- Influencia no cuantificable con precisión [N/A]: [jugador(es)].

[Si commentary disponible Y coincide con indicadores:]
Patrón de estilo observado: [descripción]

[Si commentary disponible pero NO coincide con indicadores:]
Patrón de estilo: [N/A] — dato observacional no respaldado por stats.
```

**Degradación:**

- Si no hay player-stats NI missingPlayers → "Sin datos disponibles de protagonistas [N/A] — capa degradada." No inventar jugadores ni ausencias.
- Si solo hay missingPlayers (Carril B) → capa Carril A = N/A; Carril B según niveles de ausencia definidos arriba.
- Si hay ambos → usar Carril A y añadir Carril B como enriquecimiento.
- Si `analysis_mode = "postmatch"`: `player_stats` como fuente principal, `summary.events` como fuente secundaria, `commentary` como apoyo terciario — solo para describir secuencias, nunca para inventar superioridad estructural. Si no hay `player_stats` pero sí `summary.events` → basarse en eventos para describir el desarrollo.

---

### Capa 4 — Indicadores Compuestos

**Regla (nueva):** Capa 4 puede nombrar indicadores, pero no explicar por qué importan.

| Capa | Qué hace |
|------|----------|
| 4    | **Mide** |
| 5    | **Explica** |

**Pregunta única:** ¿cuánto? (NO: ¿por qué importa?)

```
Capa 4 NO puede contener:
- conclusiones
- inferencias
- lenguaje causal ("porque", "esto implica", "esto sugiere")
- "esto favorece a X"
- "esto indica partido abierto"

Solo descripción estructurada de datos comparables entre equipos.
```

**Inputs:** historial de equipos (`home_team_results`, `away_team_results`, `h2h`), odds.

**Cálculos:**

- Ventaja ofensiva: diferencia de goles avg entre equipos
- Fragilidad defensiva: goles recibidos / partidos jugados del equipo visitante
- Gap forma: puntos últimos N de cada equipo
- Gap casa/fuera: PPG home vs PPG away
- Riesgo Over 2.5: promedio de freq Over de ambos
- Riesgo BTTS: promedio de freq BTTS de ambos
- Disciplina: promedio de tarjetas de ambos equipos (de `Get_Match_Stats`)
- Volatilidad: desv. estándar de goles en últimos 5 de cada equipo
- Estabilidad muestra: N partidos disponibles vs mínimo 5

**Coherencia mercado-datos [IND]:**
- **Alta:** mercado y datos históricos favorecen el mismo lado Y con magnitudes similares.
- **Moderada:** coinciden en el lado, difieren en intensidad.
- **Baja:** favorecen lados distintos, o uno ve equilibrio y el otro no.

*(Medición de alineación — no interpretación de por qué.)*

**Output obligatorio:**

```
## 4. Indicadores Compuestos

Ventaja ofensiva: [Home] por [+/-X.XX] goles [IND]
Fragilidad: [Away] recibe [X.XX] goles/partido [IND]
Gap forma (últimos [N]): [Home] [+/-X pts] sobre [Away] [IND]
Gap casa/fuera: [Home] [+/-X.X] ppg [IND]
Riesgo Over 2.5: [X%] [IND]
Riesgo BTTS: [X%] [IND]
Volatilidad: [baja/media/alta] [IND]
Índice disciplina: [bajo (<1.0 yc/pp) / medio (1.0-1.5) / alto (>1.5)] [IND]
Mercado vs datos: [alta/moderada/baja]
Estabilidad muestra: [N] partidos / mínimo 5 — [suficiente/limitado]
```

**Degradación:**

- Si no hay forma (N<3) → gap forma y volatilidad = N/A.

---

## 4.5 Regla Global de No-Redundancia

> Cada capa debe aportar información **NUEVA**.
> Si una idea ya fue expresada en una capa anterior, no debe repetirse
> salvo que se transforme (ej: de dato → interpretación).

**Impide:**
- Capa 5 re-explicar forma, odds o rachas de capa 2
- Capa 6 re-diagnosticar de capa 5
- Capa 8 resumir señales de capa 6

**Permite:**
- Capa 5 toma datos de capa 2 y los interpreta causalmente
- Capa 6 toma señales de capa 5 y las pondera por peso
- Capa 8 toma la priorización de capa 6 y la traduce a lectura final

**Flujo correcto:**
```
datos (capa 2) → medición (capa 4) → interpretación (capa 5) → priorización (capa 6) → predicción (capa 7) → síntesis (capa 8)
```

---

### Capa 5 — Diagnóstica

**Pregunta única:** ¿Por qué esas señales importan?

**Scope:** Causas + contradicciones + sostenibilidad

**Inputs:** output de capas 1, 2 y 4.

**Regla de causalidad (nueva):** Las causas NO pueden ser una reformulación directa de un dato numérico. Deben explicar el mecanismo, no repetir la métrica.

❌ Ejemplo malo:
"Madrid domina en casa porque tiene 2.25 ppg"

✅ Ejemplo bueno:
"Madrid domina en casa por la intensidad que impone en transiciones y la presión alta — el Bernabéu amplifica esa dinámica, no solo el promedio de puntos"

**Output obligatorio:**

```
## 5. [Por Qué Pasa / Qué Está Pasando]

[Adaptar según estado:]
- notstarted: "Por Qué Pasa" — análisis de tendencias pre-partido
- inprogress: "Qué Está Pasando" — evaluación de lo que ocurre vs lo esperado + comparación con pre-match

Causas [IND]:
- [Causa real derivada de datos — explicar el MECANISMO, no reformular la métrica]
- [Causa 2]

Contradicciones:
- [contradicción 1 — mercado vs stats, forma vs contexto]
- [contradicción 2]

¿Sostenible?
- sí/no — [razón basada en evidencia]

[Para inprogress: usando `live_analysis` y `summary.events`]
Análisis en vivo: El marcador [X-X] [refleja/no refleja] lo que muestran las stats (xG: [xg_home]-[xg_away], posesión: [pos_home]%-[pos_away]%).
[¿El equipo [X] domina aunque no esté ganando?]
[¿Los últimos eventos cambiaron la dinámica?]
[`live_analysis` proporciona probabilidades actualizadas para cada mercado.]
```

**Degradación:**

- Si no hay stats → no se pueden generar causas ni análisis de sostenibilidad.
- Si no hay forma → "Datos insuficientes para diagnóstico de tendencia [N/A]."
- Si `analysis_mode = "postmatch"`: el foco cambia de "qué podría pasar" a "qué pasó y por qué". Incluir contradicciones entre expectativa previa y desarrollo real.

---

### Capa 6 — Ponderación de Señales

**Reglas de peso:**

| Peso         | Qué cuenta                                                                                                                           | Qué NO cuenta                                   |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| **Fuerte**   | Tiros, consistencia (N≥5), coincidencia mercado-indicadores, producción jugadores clave                                              | Rachas cortas sin respaldo, marcadores aislados |
| **Moderada** | Forma con N=3-4, H2H sin contexto de local/visita, diff mercado-historial moderada, múltiples ausencias corroboradas por otras capas |                                                 |
| **Débil**    | N<3, diff mercado-historial alta, mercado sin respaldo en indicadores, ausencias sin corroboración de otras capas                    |                                                 |

**Regla de identidad (nueva):** Capa 6 es la ÚNICA capa de priorización. No se repite en Capa 8.

Capa 6 NO debe:
- Nueva interpretación causal (eso es capa 5)
- Recomendación de mercados (eso es capa 8)
- Repetir señales ya dichas

Solo: priorizar por peso (fuerte/moderada/débil/no utilizable) con fuentes.

**Ausencias múltiples registradas en `Get_Match_Lineups.missingPlayers`:**

- Pueden contar como señal **moderada** solo si coinciden con otras capas (producción del equipo, ranking, dependencia ofensiva, forma).
- Nunca como señal **fuerte** por sí solas — una lista de ausentes, sola, no domina el análisis.
- Si las ausencias no tienen corroboración en stats o rankings → señal **débil**.

**Output obligatorio:**

```
## 6. Qué Señales Pesan Más

Fuertes [fuente]:
- [señal + razón]

Moderadas [fuente]:
- [señal + razón]

Débiles [razón]:
- [señal]

No utilizables [razón]:
- [señal]

Outliers:
- [partido + razón]
```

**Degradación:**

- Si Capa 4 no está disponible → señales no utilizables.
- Si `analysis_mode = "postmatch"`: el foco cambia de "qué podría pasar" a "qué pasó y por qué". Incluir contradicciones entre expectativa previa y desarrollo real.

---

### Capa 7 — Predictiva (odds-driven)

**Reglas de combinación:**

| Condición                            | Acción                                                        |
| ------------------------------------ | ------------------------------------------------------------- |
| Odds disponibles                     | Usar odds como fuente base de probabilidades                  |
| Indicadores disponibles              | Solo para subir/bajar confianza, no para inventar porcentajes |
| Ni odds ni indicadores               | No emitir predictiva. Confianza muy baja.                     |
| Mercado y datos históricos coinciden | Reforzar señal                                                |
| Contradicción mercado vs historial   | Atenuar. Marcar "inseguro".                                   |

**Las probabilidades numéricas SOLO pueden salir de odds. Los indicadores NO crean porcentajes.**

**Output obligatorio:**

```
## 7. [Qué Podría Pasar / Qué Se Espera]

[Adaptar según estado:]
- notstarted: "Qué Podría Pasar" — predicción pre-partido
- inprogress: "Qué Se Espera" — predicción actualizada con marcador actual y stats en vivo

[Para notstarted:]
Resultado: [Home] [X]% | Empate [Y]% | [Away] [Z]%
Goles esperados: [Home] [X.XX] | [Away] [Y.XX] (basado en avg histórico)
Over 1.5: [X]% | Over 2.5: [Y]% | BTTS: [Z]%
Tipo: [cerrado/abierto/defensivo/competido]
Confianza: [muy baja/baja/media/media-alta/alta] — [motivo de la calificación]

[Para inprogress: usando `live_analysis`]
Marcador actual: [Home] [X] - [Y] [Away] — [minuto]'
xG acumulado: [Home] [X.XX] | [Away] [Y.XX]

Señales en vivo [live_analysis]:
- Over 2.5: [probability]% | BTTS: [probability]%
- Próximo gol — Home: [probability]% | Away: [probability]% | No más goles: [probability]%
- Esquinas próximo: [lean] | Señal: [signal]
- 1X2 final — Home: [probability]% | Empate: [probability]% | Away: [probability]%

Resultado esperado al final: [Home] [X] - [Y] [Away] (basado en xG y tiempo restante)
Tipo de partido: [cerrado/abierto/defensivo/competido]
Confianza: [muy baja/baja/media/media-alta/alta] — [motivo de la calificación]
[¿El equipo que está perdiendo merece estar abajo?]

Nota: "Predictiva basada en `live_analysis` + odds históricas."
```

**Degradación:**

- Si ni odds ni indicadores → "Predictiva no disponible [N/A] — datos insuficientes."
- Si contradicción mercado vs historial → añadir: "⚠ Mercado y datos históricos no coinciden.
  Incertidumbre elevada."

---

### Capa 8 — Lectura Final

**Pregunta única:** ¿Con qué lectura final me quedo?

**Scope:** Interpretación humana + 1-2 mercados bien justificados (conceptual, no numérico)

**Regla (nueva):** Capa 8 opera a nivel conceptual — escenarios, riesgos, lectura. NO repite probabilidades ni métricas numéricas de Capa 7, NO re-justifica con datos, NO resume señales de Capa 6.

Capa 8 NO debe:
- Lista de señales más fuertes (ya dichas en Capa 6)
- Lista de alertas (ya dichas en Capa 5)
- Lista de mercados con sustento (ya dichos en Capa 7)
- Probabilidades ni métricas numéricas

Solo traduce a: qué escenario tiene más sentido, qué riesgo oculto existe, qué NO comprar.

**Output obligatorio:**

```
## 8. Lectura Final

- Qué escenario tiene más sentido
- Qué escenario tiene riesgo oculto
- Qué NO comprar del partido

Opcional:
- 1-2 mercados bien justificados (no lista — solo decisión final bien fundamentada)

Nota: "Las lecturas anteriores son las mejor soportadas por la evidencia disponible."
```

**Degradación:**

- Si Capa 7 es muy baja confianza → Capa 8 se reduce a "lectura de escenarios sin recomendación de mercados".
- Si `analysis_mode = "postmatch"`: el foco cambia de "qué podría pasar" a "qué pasó y por qué". Incluir contradicciones entre expectativa previa y desarrollo real.

---

## 9. Política de Confianza

### Confianza global

| Estado de los datos                              | Confianza máxima                 |
| ------------------------------------------------ | -------------------------------- |
| Evento + odds + stats + player-stats + historial | **Alta**                         |
| Evento + odds + stats (sin player-stats)         | **Media-alta**                   |
| Evento + odds (sin stats)                        | **Media**                        |
| Evento solo                                      | **Baja**                         |
| Sin evento                                       | **Muy baja / análisis inviable** |

### Ajuste por capa

| Dato faltante                  | Capas afectadas | Reducción                       |
| ------------------------------ | --------------- | ------------------------------- |
| Sin odds                       | Capa 1, Capa 7  | Máx. media                      |
| Sin stats (tiros, corners, xG) | Capa 4, Capa 5  | Señales de gol debilitadas      |
| Sin player-stats               | Capa 3          | Solo texto N/A                  |
| Sin historial (N<3)            | Capa 2, Capa 4  | Datos históricos no disponibles |
| Sin H2H                        | Capa 2          | H2H = N/A                       |

### Por capa

Cada capa puede marcarse como:

- **Completa** — todos los inputs disponibles.
- **Parcial** — algunos inputs faltantes, se usa texto [N/A] correspondiente.
- **No disponible** — inputs requeridos faltantes, no se puede calcular.

---

## 11. Anti-Racionalizaciones

Esta es la sección de guardrails. Es de cumplimiento **obligatorio**.
Todo lo que sigue **no se puede hacer**, sin excepción:

### No inventar datos

- `[N/A]` → **no inventar.** Si no hay player-stats → capa 3 = "Sin datos suficientes."
- No inventar alineaciones, lesionados, sancionados, técnicos.
- No inventar H2H si `Get_Match_H2H` viene vacío o null.

### No usar fuentes externas

- No consultar Bzzoiro, SofaScore, Transfermarkt, Wikipedia, ni ninguna otra
  fuente durante el análisis.
- La única fuente válida es FlashScore MCP (a través de los endpoints listados
  en sección 3).

### No improvisar modelos

- No construir un modelo heurístico para reemplazar la predicción basada en modelos de aprendizaje.
- Esta versión es odds-driven por diseño. No improvisar modelo propio.
- Las probabilidades numéricas en Capa 7 SOLO viennent de odds. No de cuentas propias.
- No usar pesos (0.3, 0.5, etc.) ni métricas compuestas en Capa 3.

### No vender certeza

- No decir "va a ganar", "es fijo", "el over entra seguro".
- Siempre decir "podría", "señal", "sugiere", "la evidencia apunta a".
- Siempre indicar confianza.

### No omitir contradicciones

- Si mercado e indicadores favorecen lados distintos → decirlo explícitamente.
- Si una señal es ruido y no tendencia → decirlo.

### No rellenar con frases vacías

- Cada sección tiene output definido con campos obligatorios.
- Si no hay dato para un campo → "[N/A]" + razón breve.
- No dejar una sección "blanca" ni con frases genéricas que no dicen nada.

### No interpretar mal FlashScore MCP

- `Get_Team_Fixtures` / `Get_Team_Results` es la vía de match discovery.
  No existe `/api/events/?team=X`.
- Para partidos `inprogress`: análisis en vivo disponible — usar datos actuales del partido.
- Para partidos `finished`: `analysis_mode = "postmatch"` → proceed con análisis post-partido.

**Reglas para `postmatch`:**

- No inventar causas. Commentary sirve para describir secuencias y momentos, no para inventar superioridad estructural.
- Si no hay `match_stats` → limitarse a resultado + eventos + forma + odds previas.
- Si no hay `summary.events` → no hablar de "punto de quiebre" (la output format ya filtra: "solo si existe evidencia en summary.events o commentary").
- Si los datos son insuficientes para una lectura completa → usar la etiqueta "Lectura parcial del desarrollo" y listar qué datos no estuvieron disponibles.
- Commentary es dato observacional, no señal principal. Solo válido si coincide con indicadores de otras capas.

---

## 12. Formato de Salida del Análisis

**Estructura obligatoria (en este orden exacto):**

```
# [Home] vs [Away] — [Competición] ([Fecha])

[8 capas completas]

---
Confianza global: [muy baja/baja/media/media-alta/alta]
```

**Longitud orientativa por sección:**

- Capas 1-2: 8-15 líneas cada una.
- Capas 3-6: 5-12 líneas cada una.
- Capas 7-8: 8-15 líneas cada una.

**Datos no disponibles:**

- Siempre usar `[N/A]` para datos faltantes.
- Siempre explicar brevemente por qué.

**Señales fuertes / moderadas / débiles:**

- Presentar como lista con viñeta, no en prosa.

**Cierre del análisis:**

- Siempre incluir la confianza global.
- Siempre distinguir entre señal fuerte y correlación débil.
- Nunca cerrar con una frase determinista.

### Guardado de datos

1. Guardar este análisis en un txt en ./claude/skills/football-betting-analysis/football-analysis.
2. Crear una carpeta cuyo nombre corresponda con el nombre de la competición, por ejemplo, Champions League 25-26.
3. Guardar el archivo como [DD_MM_AAAA] [EQUIPO LOCAL] vs [EQUIPO VISITANTE] [JORNADA_X].txt donde X en [JORNADA_X] es el # de la jornada que se juega o si es 1/4 de final u 1/8, lo que corresponda.

---

## 13. Quick Reference

```
CONSULTA NL → parsing → { home, away, date_from, date_to, league? }

Fase 1 (MCP directo):
  Match discovery:   livesport search API → /data/main_teams.csv (fallback) → Get_Team_Fixtures → Get_Team_Results
  → analysis_mode = prematch | live | postmatch
  → Extraer: event_id, home_team_id, away_team_id

Fase 2 (build_match_context.py):
  python scripts/build_match_context.py <event_id> <home_team_id> <away_team_id>
  → Devuelve final_context JSON con todos los datos normalizados

NO LLAMAR ENDPOINTS MCP DIRECTAMENTE PARA DATOS DEL PARTIDO.
Solo Get_Team_Fixtures / Get_Team_Results para match discovery.
```

**Umbrales de confianza:**

| Datos                                            | Confianza máx. |
| ------------------------------------------------ | -------------- |
| Evento + odds + stats + player-stats + historial | Alta           |
| Evento + odds + stats (sin player-stats)         | Media-alta     |
| Evento + odds (sin stats)                        | Media          |
| Evento solo                                      | Baja           |
| Sin evento                                       | Inviable       |

**Etiquetas de fuente obligatorias en todo el análisis:**
`[API]` `[ODDS]` `[IND]` `[N/A]`

---

## Reglas de No-Redundancia (Quick Ref)

| Capa | Pregunta única | Qué NO hacer |
|------|---------------|--------------|
| 3    | ¿qué hay?     | No interpretar, no concluir |
| 4    | ¿cuánto?      | No explicar por qué importa |
| 5    | ¿por qué?     | No reformular datos numéricos — explicar mecanismo |
| 6    | ¿cuáles pesan?| No repetir diagnóstico de capa 5 |
| 8    | ¿con qué quedo?| No resumir señales de capa 6, no repetir números de capa 7 |

---

## 14. Agujeros Cerrados

| Racionalización                                                                                | Contramedida                                                                                                                                               |
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Usé Bzzoiro/SofaScore/otra fuente porque FlashScore no tenía"                                 | **Prohibido.** Solo FlashScore MCP. Sin excepciones.                                                                                                       |
| "No había player-stats así que inventé alineación"                                             | **Prohibido.** Capa 3 Carril B: usar `missingPlayers` de `Get_Match_Lineups` si existe. Si no hay nada → [N/A].                                            |
| "Construí un modelo heurístico para reemplazar la predicción basada en modelos de aprendizaje" | **Prohibido.** Capa 7 = odds-driven. No inventar probabilidades.                                                                                           |
| "Usé xG inventado como input"                                                                  | **Prohibido.** Si FlashScore no trae xG en `Get_Match_Stats` → no inventarlo. Usar los datos que la API provee.                                            |
| "El partido ya empezó pero igual quiero hacer el análisis"                                     | **No aplica.** Esta skill es solo para partidos `notstarted`. Para partidos `inprogress` o `finished`, no proceder.                                                   |
| "No había injuries data así que inventé lesionados"                                            | **Prohibido.** Si `missingPlayers` no está disponible → no inventar ausencia ni motivo.                                                                    |
| "Calculé cuánto baja el equipo por cada ausente"                                               | **Prohibido.** No estimar impacto numérico de ausencias. Usar solo lo que la API provee + niveles de ausencia definidos.                                   |
| "Usé missingPlayers como señal fuerte por sí sola"                                             | **Prohibido.** Ausencias sin corroboración en otras capas = señal débil. Nunca señal fuerte.                                                               |
| "El H2H no venía así que puse lo que sabía de memoria"                                         | **Prohibido.** Si Get_Match_H2H = null → "H2H no disponible [N/A]."                                                                                        |
| "Le puse 'Alta' aunque solo tenía el evento"                                                   | **Prohibido.** Evento solo = confianza Baja. Tabla de umbrales es obligatoria.                                                                             |
| "El over entra seguro"                                                                         | **Prohibido.** Lenguaje probabilístico siempre.                                                                                                            |
| "Rellené la capa 3 con nombres de memoria"                                                     | **Prohibido.** Cada capa tiene output definido. Si no hay datos → [N/A].                                                                                   |
| "La muestra de 2 partidos es representativa"                                                   | **Prohibido.** N<5 = "muestra limitada." Indicadores pesan menos.                                                                                          |
| "Calculé un impact score con pesos 0.3/0.5"                                                    | **Prohibido.** Capa 3 usa datos directos y rankings, no fórmulas heurísticas.                                                                              |
| "Llamé a Get_Match_Details/H2H/Stats directamente desde el modelo"                             | **Prohibido.** Toda la recolección de datos del partido pasa por `build_match_context.py`. Solo `Get_Team_Fixtures/Get_Team_Results` para match discovery. |
| "El script emitió un warning pero el dato me parecía correcto"                                 | **Prohibido.** Los warnings del script son instrucciones. Si el script marca un dato como sospechoso → respetarlo y no contradecirlo.                      |
| "El JSON tenía [N/A] pero yo sabía el dato de memoria"                                         | **Prohibido.** El JSON ya normalizó y marcó los datos disponibles. No re-inventar datos marcados como [N/A].                                               |
| "Ejecuté endpoints MCP adicionales después de recibir el JSON"                                 | **Prohibido.** Una vez recibido `final_context`, todos los datos del análisis vienen del JSON. No consultar endpoints adicionales.                         |

Source

Creator's repository · josearroyave/agent-skills

View on GitHub

Security

Security checks in progress
Results will appear here once audits complete
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk