# Agente 01 — "Formazioni & Assenze" (design)

## Perché questo agente, e perché è l'unico che ha senso ora

Cinque test di backtest hanno stabilito che tutto ciò che è calcolabile da dati
storici pubblici (risultati, xG) è già prezzato nelle quote di chiusura
(peso ottimale nel blend = 0 in ogni variante). L'unico spazio residuo è
l'informazione **fresca**: la finestra tra il momento in cui una notizia esiste
(formazione ufficiale, infortunio dell'ultima ora, turnover) e il momento in
cui il mercato l'ha completamente digerita.

Questo agente lavora esclusivamente in quella finestra. Non è backtestabile
(non esistono archivi di "cosa si sapeva alle 14:03"): va misurato **in
avanti**, con un protocollo definito qui sotto prima di scrivere una riga di
codice — così non potremo barare nemmeno per sbaglio.

## Principio architetturale: l'LLM raccoglie fatti, la matematica resta deterministica

L'errore classico è chiedere all'LLM "quanto sposta le probabilità l'assenza di
Lautaro?". Non lo sa, e risponderà con sicurezza comunque. Divisione dei ruoli:

- **LLM (con web search)**: trova e verifica i FATTI — chi manca, chi rientra,
  quanto è certa la notizia, c'è turnover in vista. Output: JSON di fatti con
  fonti. Mai numeri di probabilità.
- **Layer deterministico (Python)**: converte i fatti in aggiustamenti dei gol
  attesi usando i dati giocatore di Understat (quota di xG+xA della squadra
  prodotta dal giocatore), applica gli aggiustamenti al modello DC, produce
  le probabilità.
- **Aggregatore**: combina probabilità aggiustate e snapshot delle quote con un
  peso `w` appreso dai dati live raccolti (parte in shadow mode, vedi sotto).

## Le due finestre temporali

| Stage | Quando | Cosa cerca | Perché lì |
|-------|--------|-----------|-----------|
| **S1** | T–24h | Formazioni probabili, infortuni/squalifiche note, turnover pre-coppa, notizie societarie | Il mercato ha già digerito molto, ma non tutto; ampio tempo di calcolo |
| **S2** | T–75min → T–40min | **Formazione ufficiale** (esce ~T–60min) confrontata con le attese di S1 | La finestra più preziosa: il mercato impiega minuti a riprezzare le sorprese |

Prima di T–24h non si corre: i backtest dicono che a quell'orizzonte il
mercato sa già tutto quello che sappiamo noi.

## Contratto di output dell'LLM (per ogni run)

```json
{
  "match_id": 12345,
  "stage": "S1",
  "checked_at": "2026-08-22T14:00:00Z",
  "home": {
    "absent":    [{"player": "Lautaro Martinez", "status": "confirmed_out",
                   "reason": "lesione al flessore", "source_url": "..."}],
    "doubtful":  [{"player": "...", "status": "doubtful", "prob_out": 0.5, "source_url": "..."}],
    "returning": [{"player": "...", "source_url": "..."}]
  },
  "away": { "...": "..." },
  "turnover_risk": {"home": 0.0, "away": 0.6,
                    "reason": "Champions League martedì, 4 titolari a rischio riposo"},
  "surprise_vs_s1": null,
  "confidence": 0.8,
  "notes": "testo libero SOLO per log umano, mai usato dal calcolo"
}
```

Regole: ogni fatto deve avere una fonte; se non trova nulla di rilevante
l'output è vuoto e va bene così (un agente che "trova sempre qualcosa" è
rumore); `status` ammessi: `confirmed_out`, `doubtful`, `rested`, `suspended`.

## Conversione deterministica fatti → probabilità

1. Per ogni giocatore assente: quota di contributo offensivo
   `s_i = (xG90 + xA90 del giocatore) / totale squadra` dagli ultimi 12 mesi
   Understat (endpoint `getLeagueData` → `players`, già usato per gli xG).
2. Impatto sul gol atteso della squadra:
   `delta_att = -k * Σ s_i * certezza_i` con `k = 0.4` (il sostituto rende
   ~60% del titolare — parametro rivedibile coi dati live, non a sensazione).
   I `doubtful` pesano `prob_out`. Difensori: effetto sul `dfn` con lo stesso
   schema sui contributi difensivi (proxy: minuti nella linea difensiva).
3. `lambda_home_adj = lambda_home * exp(delta_att_home + turnover_home * -0.10)`
   e simmetrico per l'ospite; poi si ricalcolano H/D/A con la griglia DC.
4. Cap di sicurezza: `|delta totale| ≤ 0.25` per squadra (nessuna assenza vale
   più del 25% dei gol attesi — contro le allucinazioni).

## Misurazione (definita ORA, prima di partire)

Ad ogni run l'agente salva **tre cose contemporaneamente**: le sue probabilità
aggiustate, le probabilità del modello non aggiustato e lo **snapshot delle
quote in quel momento**. A risultato acquisito si aggiungono le closing.

Due KPI, in ordine di velocità di risposta:

1. **Direzione del movimento (CLV proxy)** — quando l'agente sposta le
   probabilità rispetto allo snapshot, la closing si muove nella stessa
   direzione? Se l'informazione è reale, sì. Si misura in settimane:
   `hit rate > 55%` su segnali non nulli = l'agente vede cose vere prima del
   mercato. `~50%` = rumore.
2. **Log loss vs snapshot** (lento, serve per il peso `w`): il blend
   agente+snapshot batte lo snapshot da solo? Stesso protocollo di blend.py.

**Shadow mode**: per i primi 2-3 mesi l'agente logga e basta, non pubblica
nulla sul sito. **Kill criterion**: dopo 150 segnali non nulli, se il CLV hit
rate è ≤ 52% l'agente si archivia e si scrive cosa si è imparato. Deciso ora,
a freddo, perché a caldo non lo deciderà nessuno.

## Fonti dati live

| Dato | Fonte | Costo |
|------|-------|-------|
| Calendario + quote correnti | football-data.co.uk `fixtures.csv` (stesso formato dello storico) | gratis |
| Snapshot quote (S2, serve freschezza) | The Odds API (free tier 500 req/mese) o fixtures.csv se basta S1 | gratis/€ |
| Formazioni ufficiali e notizie | LLM con web search (Sky Sport, Gazzetta, siti club, account ufficiali) | API Claude |
| Statistiche giocatore per gli impatti | Understat `getLeagueData` → `players` (già integrato) | gratis |

Stima costo LLM: 2 run/partita (S1+S2), ~10 ricerche/run con Haiku 4.5 per la
raccolta e un passaggio di validazione: ~0,03-0,06 €/partita → ~2-4 €/weekend
per la sola Serie A.

## Perimetro MVP

- **Solo Serie A** (10 partite/settimana): notizie in italiano facilmente
  verificabili, dati giocatore Understat disponibili. Serie B esclusa (niente
  xG giocatore). Estensione alle altre leghe solo dopo un CLV positivo.
- Scheduling su Windows Task Scheduler (il server WAMP è già sempre acceso):
  - ogni giorno 09:00 — sync fixtures + quote, run S1 per le partite a T–24h
  - nei giorni di partita, ogni 5 min in [T–90, T–40] — check formazioni
    ufficiali, run S2 se pubblicate
  - ogni giorno 03:00 — aggiornamento risultati/closing e scoring dei segnali

## Schema DB (tabelle nuove)

```
odds_snapshots   id, match_id→matches, taken_at, stage, source,
                 odds_home/draw/away, prob_home/draw/away (normalizzate)
agent_signals    id, match_id→matches, agent, stage, checked_at,
                 payload (JSON grezzo dell'LLM), delta_lambda_home/away,
                 confidence, model_version
predictions      id, match_id→matches, made_at, stage,
                 source ('model'|'market'|'blend'|'agent_adj'),
                 prob_home/draw/away, details (JSON)
```

Le partite future entrano in `matches` con `result NULL` (lo schema lo
permette già); lo scoring le completa a risultato noto.

## Cosa NON fa questo agente

Niente meteo, niente arbitri, niente tipster (decisi fuori perimetro),
niente sentiment social, niente "analisi tattica" dell'LLM. Un solo tipo di
informazione, la più vicina al gol atteso: chi gioca e chi no.
