Come funziona — Dati & statistiche¶
Come un payload di un provider diventa una statistica sul sito: la pipeline è a 3 strati persistenti e ri-proiettabili, con un principio unico — nessun dato inventato, ogni numero tracciabile fino alla fonte.
La pipeline in una riga¶
provider → ingest (raw, dedup sha256) → projection (fatti tipizzati) → feature store (agg_*) → 64 statistiche → widget
Le fonti¶
| Provider | Copertura | Badge nel sito |
|---|---|---|
| api-football (piano Pro) | backbone di tutte le leghe di club: fixture, statistiche match, formazioni, classifiche, quote multi-book, xG dove la lega li fornisce | blu |
| Sportmonks (WC Special) | solo Mondiale 2026 (torneo concluso, oggi archivio): live, eventi, voti, cronaca, trends, meteo | arancio |
| modello BAP | statistiche derivate in-house dallo storico Postgres proprietario | verde |
Ogni chiamata provider passa da un budget-guard (limiti giornalieri e al minuto del piano) e da un provider-registry con routing per-dominio validato al boot: i provider sono sostituibili senza toccare il resto del sistema.

Strato 1 — Raw store ri-proiettabile¶
Ogni payload provider viene persistito grezzo in ingest_payloads con dedup sha256,
nella stessa transazione della projection. Conseguenza pratica: se un mapper aveva un
bug o una formula cambia, lo storico si ri-proietta dal raw con zero nuove chiamate
provider — il dato grezzo non si perde mai.
Strato 2 — Fatti tipizzati¶
Il punto d'ingresso dell'ingest è unico (ingest/service.py): fine-match live, endpoint
admin e backfill convergono tutti lì, così budget e idempotenza valgono ovunque. La
projection trasforma il raw in dimensioni (leagues, teams, players, …) e fatti
per-match (matches, match_events, match_team_stats, player_match_stats,
formazioni). I campi provider non mappati finiscono in colonne JSONB extra: niente si
butta.
Strato 3 — Feature store¶
Le tabelle agg_* (forma squadra, stagione giocatore, H2H, arbitro, Elo, fasce gol) sono
aggregati pre-calcolati con refresh-on-ingest mirato: quando arriva un match si
aggiornano solo le chiavi toccate (2 squadre, ~40 giocatori, 1 coppia H2H, 1 arbitro).
Dalle tabelle alle 64 statistiche¶
Le statistiche derivate vivono in un registry (stats_registry/registry.yaml): oltre
60 voci, ognuna con formula dichiarata, funzione di calcolo registrata (fail-fast al boot
se manca), parametri regolabili a caldo dal pannello admin e possibilità di
accensione/spegnimento per lega. Le statistiche sono scope-aware: badge di
scope e toggle competizione dove una squadra gioca più tornei.
Le principali sono spiegate una per una in Le statistiche BAP spiegate.
Il live¶
Un solo poller per processo interroga il feed live (cadenza 20s con match in corso, 60s a riposo), scrive lo snapshot in cache e fa fan-out via SSE a tutti i client connessi: il sito mantiene una sola connessione al flusso e ticker, liste partite, header match e classifica live si aggiornano da soli. A fine match il sistema ingerisce automaticamente statistiche e formazioni definitive; uno sweep periodico recupera i match persi durante eventuali restart.
Le garanzie di onestà¶
- Lineage visibile: il bottone "i" accanto a ogni statistica mostra fonte, formula in italiano e frequenza di aggiornamento.
- Honest-empty: se lo storico non basta (poche partite, lega non ingerita, xG assente), il widget sparisce o dichiara il limite — mai numeri "riempitivi".
- Solo osservato: le serie live (es. momentum) esistono solo per i minuti realmente osservati; non vengono mai ricostruite a posteriori.
I termini ricorrenti (honest-empty, lineage…) sono nel Glossario; cosa il sito NON promette è in Limiti & disclaimer.