Gestione completa di un'asta del fantacalcio: catalogo listone versionato, progetti di tag personali, aste con regole configurabili e una home page live aggiornata via WebSocket.
- Backend: Python + FastAPI, SQLAlchemy 2 async, PostgreSQL, Alembic, WebSocket nativi Starlette.
- Frontend: React + TypeScript + Vite, responsive (PC, tablet, smartphone).
- Infrastruttura: Docker Compose (postgres + api + web + scheduler).
- Scraping automatico: listone ufficiale e storico statistiche giocatori da fantacalcio.it, senza credenziali (vedi sotto).
docker compose up --build| Servizio | URL |
|---|---|
| Frontend | http://localhost:5173 |
| API | http://localhost:8010 |
| Swagger | http://localhost:8010/docs |
| Postgres | localhost:5433 (utente/password/db: fantasta) |
L'API è esposta sulla 8010 e non sulla 8000, che su molte macchine è già occupata. Le migrazioni Alembic vengono applicate automaticamente all'avvio del container
api.
Funziona senza configurazione: apri http://<ip-del-server>:5173 da telefono, tablet o un altro
PC. Il frontend deriva l'indirizzo dell'API dall'host da cui è stata servita la pagina (stesso
host, porta 8010), e il backend accetta in CORS qualunque origine sulla porta 5173.
Serve solo che le porte 5173 e 8010 siano raggiungibili dal dispositivo. Imposta
VITE_API_URL unicamente se metti l'API su un host o una porta diversi.
Prima di andare in produzione:
- sostituire
JWT_SECRETcon un valore casuale di almeno 32 byte; - svuotare
CORS_ORIGIN_REGEXe pinnareCORS_ORIGINSsul dominio reale; - allineare
APP_BASE_URL, usato per i link di invito restituiti dall'API (l'interfaccia web costruisce comunque il link sull'origine corrente).
Un servizio separato (scheduler, stessa immagine di api) scarica in automatico:
- Listone ufficiale — ogni giorno alle 2:00, dalla pagina pubblica delle quotazioni.
Scrive sulla
list_versionfissaPREDEFINITO(configurabile viaOFFICIAL_LIST_VERSION), sempre la stessa: non va rinominata di anno in anno. L'upload manuale CSV resta disponibile per qualunque altra versione (mercati di riparazione, listoni di test), e qualunque versione — inclusaPREDEFINITO— si può eliminare da Listone se non più usata da progetti o aste. - Statistiche storiche dei giocatori — mercoledì e sabato alle 1:30 (la notte tra martedì/mercoledì e tra venerdì/sabato). Per un giocatore mai visto scarica lo storico fino a un massimo di 5 stagioni; per un giocatore già noto aggiorna solo la stagione corrente, senza mai ri-scaricare le stagioni passate.
Nessuna delle due pagine richiede login: sono pubbliche e permesse da robots.txt. Il client
HTTP resta comunque a concorrenza limitata e con un piccolo delay fra le richieste, per non
generare carico sul sito.
Per ogni giocatore viene salvata anche l'immagine ("campioncino") — mostrata a fianco del nome
nel listone di tagging progetto e in quello d'asta, con fallback alle iniziali finché il
giocatore non è ancora stato scrapato — e le statistiche di stagione (presenze, presenze da
titolare, gol, gol subiti per i portieri, assist, media voto, fantamedia), scelte con il
selettore di stagione in alto a sinistra e attivate come colonne con i bottoni in alto a destra.
Le statistiche per singola giornata (voto, fantavoto, minuti di entrata/uscita, bonus/malus) sono
salvate in Postgres ma non ancora esposte in UI — solo un endpoint di lettura minimale
(GET /catalog/players/{external_id}/stats), in attesa di specificarne il layout.
Trigger manuali (utili senza aspettare lo scheduler):
curl -X POST http://localhost:8010/api/v1/scraping/listone/refresh -H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:8010/api/v1/scraping/players/{external_id}/refresh-stats -H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:8010/api/v1/scraping/players/refresh-stats -H "Authorization: Bearer $TOKEN"-
Registrati dalla schermata di accesso.
-
Listone → carica il file delle quotazioni scaricato da fantacalcio.it (
.xlsxo.csv) indicando una versione, es.2025-26. Il caricamento è idempotente: ripeterlo aggiorna le quotazioni senza duplicare i giocatori.Vengono importate le colonne
Id,R,RM,Nome,Squadra,Qt.A(quotazione attuale),Qt.I(quotazione iniziale) eFVM(fantavalore di mercato). Le varianti Mantra (Qt.A M,Qt.I M,FVM M) e le colonneDiff.vengono ignorate. -
Progetti → crea un progetto sulla stessa versione di listone e assegna i tuoi tag
slot(raggruppamento) einfo(nota libera). Nell'editor: ricerca per sottostringa, filtri per ruolo e per squadra, colonne ordinabili (default FVM decrescente) e click sulla riga per aprire l'editor dei tag del giocatore. -
Aste → crea l'asta indicando nome squadra, modalità, crediti e ordine di chiamata.
-
Configura → imposta gli slot per ruolo, poi copia il link di invito e distribuiscilo. Il pulsante Riallinea listone riporta nell'asta le novità del catalogo: aggiunge i giocatori mancanti e aggiorna anagrafica, quotazioni e FVM di quelli già presenti, lasciando intatti assegnazioni, stato dei giocatori e override di ruolo.
-
Gli altri partecipanti aprono il link, scelgono nome squadra e quale proprio progetto portare.
-
Avvia asta e si opera dalla pagina live. Il listone d'asta ha gli stessi filtri dell'editor tag (ricerca per sottostringa, bottoni ruolo, squadre, colonne ordinabili con default FVM decrescente), più il filtro "solo disponibili" e il raggruppamento per tag slot. I tag qui si vedono ma non si modificano: esistono solo se importati da un progetto.
accounts ──┬─ projects ── project_tags ──▶ players_catalog (listone master, versionato)
│
└─ auction_participants ──┬── auction_player_tags ──▶ auction_players
├── roster (storico: ordine di chiamata + prezzo)
└── call_order
auctions ──┬── auction_role_config
├── auction_players (copia del listone per l'asta, con override di ruolo)
└── auction_state (stato live, non storicizzato)
Concetti chiave:
- Progetto: spazio personale con i tag su una versione di listone, indipendente da ogni asta.
- Ingresso in asta: i
project_tagsvengono copiati una tantum inauction_player_tags. Da quel momento vivono dentro l'asta: modificare il progetto non tocca le aste già avviate. Un progetto è importabile solo seproject.list_version == auction.list_version. - Tag privati: ogni partecipante vede solo i tag che ha portato con sé.
- Storico: si registra solo l'ordine di chiamata e il prezzo finale (
roster). I rilanci intermedi e le chiamate annullate non lasciano traccia.
offerta_massima = crediti_residui − (slot_ancora_da_riempire − 1)
Ogni altro slot va coperto da almeno un credito. La regola è applicata sia sui rilanci sia sull'assegnazione finale, insieme alla verifica che lo slot del ruolo scelto sia ancora libero.
Canale: ws://<host>/ws/{auction_id}?token=<jwt>. Alla connessione il server invia uno
snapshot completo; poi arrivano gli eventi in broadcast.
| Evento | Quando |
|---|---|
snapshot |
all'apertura della connessione |
player_called |
un giocatore è stato messo in asta |
bid_update |
rilancio: prezzo/leader correnti |
call_cleared |
chiamata annullata, il giocatore torna disponibile |
player_assigned |
assegnazione: roster, crediti e disponibilità aggiornati |
role_closed |
una squadra ha completato gli slot di un ruolo |
participant_joined |
nuova squadra entrata |
auction_started |
l'admin ha avviato l'asta |
auction_closed |
l'admin ha chiuso l'asta |
player_updated |
override di ruolo applicato dall'admin |
call_order_updated |
ordine di chiamata modificato |
Il client invia ping come keepalive; tutte le azioni passano dalle REST API, che poi fanno
il broadcast.
docker compose run --rm --no-deps api python -m pytest -qCoprono: formula dell'offerta massima e validazioni di assegnazione, ruoli Classic/Mantra e override, parser del listone (csv e xlsx), formato di export, e il flusso API completo (listone → progetto → asta → ingresso con import tag → live → export → chiusura).
Type-check e build del frontend:
docker compose run --rm --no-deps web npm run builddocker compose run --rm api alembic revision --autogenerate -m "descrizione"
docker compose run --rm api alembic upgrade head- Formato di export Leghe Fantacalcio: CSV senza intestazione, una riga per calciatore nel
formato
<Squadra>,<Id>,<Costo>(id = quello di fantacalcio.it, lo stesso usato per lo scraping) — così come richiesto dal modale "Importa rose" dell'interfaccia attuale. Il formato può cambiare tra una stagione e l'altra e non è ancora stato verificato con un vero import: va controllato sul modale corrente di Leghe Fantacalcio. Tutta la conoscenza del formato è in export_leghe.py. - Ordine di chiamata: la turnazione
fixed_listè esposta come suggerimento (GET /auctions/{id}/live/next-caller) e non blocca le chiamate, perché le chiamate annullate non vengono storicizzate e il turno non sarebbe ricostruibile in modo affidabile. - Scalabilità WebSocket: il registro delle connessioni è in-process. Con più worker uvicorn serve un pub/sub (es. Redis) per propagare i broadcast tra processi.
- PWA: non ancora configurata; l'interfaccia è responsive ma non installabile.