Come calcoliamo la puntualità.
BusZilla è onesta: nessuna formula segreta, nessun algoritmo proprietario. Ecco esattamente come trasformiamo i check-in degli studenti in indicatori di affidabilità per ogni fermata e linea.
1. Il check-in
Ogni check-in è composto da: fermata, linea, orario teorico di passaggio (preso dal GTFS ufficiale dell'azienda) e stato osservato dichiarato dallo studente. Lo stato è uno tra:
- Puntuale - il bus è passato entro ±3 minuti dall'orario teorico.
- Ritardo lieve - tra 3 e 8 minuti di ritardo.
- Ritardo grave - più di 8 minuti.
- Non passato - corsa saltata.
- Sovraffollato - bus passato ma non si è potuti salire.
2. Validazione geofence: il cuore anti-furbetti
Un dato sulla puntualità vale solo se chi lo segnala è davvero alla fermata. Per questo ogni check-in passa da un controllo geografico chiamato geofence: un recinto invisibile attorno alla fermata. Se sei dentro, la segnalazione conta. Se sei fuori, la buttiamo via.
Dentro i 200 m - il check-in è accettato e contribuisce alle statistiche.
Fuori dal cerchio - il check-in è rifiutato. Niente segnalazioni dal divano.
Come funziona, passo per passo
- Al tap, e solo al tap. Quando premi "check-in", l'app legge la tua posizione GPS una volta sola, in quel preciso istante. Nessun tracciamento continuo, nessuna posizione in background.
- Il controllo è server-side. Le coordinate vengono inviate al nostro backend, che non si fida mai del client: è il server a decidere se sei dentro il recinto, non l'app sul tuo telefono. Così nessuno può falsificare l'esito modificando l'app.
- La matematica. Il server calcola la distanza tra il tuo punto e la fermata sulla superficie terrestre - la cosiddetta distanza great-circle, che tiene conto della curvatura del pianeta - e verifica se cade entro un cerchio di 200 metri di raggio centrato sulla fermata. Per farlo in modo veloce le fermate sono organizzate in un indice geospaziale, la stessa struttura dati che le mappe usano per le ricerche "nelle vicinanze": invece di confrontare il tuo punto con ogni fermata, l'indice restringe subito il calcolo a quelle plausibilmente vicine.
- Poi la posizione sparisce. Appena il controllo dà esito (dentro / fuori), le tue coordinate vengono scartate: non finiscono mai nel database. Salviamo solo quale fermata hai segnalato, non dove eri tu.
- Chi prova a barare ci rimette. Un check-in fuori geofence non solo viene rifiutato, ma abbassa anche il punteggio di reputazione dell'account: i recidivi pesano sempre meno nelle statistiche.
In altre parole: non puoi segnalare un ritardo dal divano. E noi non sapremo mai dov'era il tuo divano.
Perché 200 metri e non meno? Il GPS degli smartphone ha un margine di errore tipico di 10–50 metri, che peggiora tra palazzi alti e pensiline coperte. Un raggio di 200 metri lascia passare chi è onestamente alla fermata anche con segnale ballerino, ma resta abbastanza stretto da fermare chi è a casa o dall'altra parte della città.
3. Aggregazione
Per ogni coppia (fermata, linea, giorno_settimana, fascia_oraria)
calcoliamo continuamente:
| Metrica | Formula |
|---|---|
| Tasso di puntualità | (# check-in "puntuale") / (# check-in validi) |
| Ritardo mediano | mediana dei minuti di ritardo dichiarati |
| Tasso di soppressione | (# "non passato") / (# corse teoriche attese) |
L'aggregazione viene ricalcolata ogni 10 minuti da un processo in background. Le statistiche di un bucket vengono pubblicate solo se basate su almeno 15 check-in validi negli ultimi 14 giorni: sotto questa soglia mostriamo "Dati insufficienti".
4. Il modello predittivo: come prevediamo i ritardi di domani
Le statistiche sul passato sono utili. Ma quello che ti serve davvero è sapere stasera se domani mattina alla fermata ti conviene anticipare. Per questo BusZilla ha un suo modello predittivo, e ne raccontiamo qui ogni regola - niente scatola nera.
Cosa prevede il modello, esattamente
Per ogni coppia fermata × linea per cui abbiamo abbastanza dati, il modello produce una previsione del ritardo atteso espressa in minuti, abbinata a un punteggio di confidenza tra 0 e 1. A questa si associa il mood previsto della mascotte (happy / neutral / suspicious / angry / sleeping), che è quello che vedi nell'app la sera prima.
I "bucket": dividiamo il tempo in cassetti
I bus delle 7:30 del lunedì non si comportano come quelli delle 11:00 di
mercoledì. Per questo ogni statistica - e quindi ogni previsione - è
calcolata per il singolo cassetto
(fermata, linea, giorno_della_settimana, ora_del_giorno).
In pratica abbiamo migliaia di micro-modelli, uno per ogni combinazione.
Quando il job notturno gira, ogni cassetto produce la propria previsione
per il giorno dopo.
La formula, passo per passo
- Base storica. Si parte dal ritardo mediano osservato in quel cassetto sulle ultime settimane - la mediana e non la media, perché i giorni storti tirano su la media (ne parliamo al punto sulla distribuzione). È il valore che terremmo come previsione se domani fosse una giornata tipica.
-
Soglia minima e contrazione (shrinkage). Serve un minimo
assoluto di 3 check-in validi: sotto, niente previsione (troppo
rumore). Poi la stima del cassetto viene sempre mescolata con un
riferimento più solido - prima le ore vicine della stessa fermata e
linea (che si somigliano molto), poi la media della linea e infine quella
globale - con un peso
n / (n + 10)che cresce col numero di campioni: con pochi dati domina il riferimento, con tanti dati domina il cassetto. Niente scalino secco: pochi dati danno una stima prudente, non il vuoto, e la confidenza resta bassa di conseguenza. -
Aggiustamento meteo (scostamento dal tipico). Per l'ora
prevista del passaggio interroghiamo le previsioni di Open-Meteo per il
cluster geografico della fermata. La media storica contiene però
già il meteo tipico di quella fascia: sommarci sopra una penalità
piena conterebbe due volte la pioggia "normale". Per questo applichiamo solo
lo scostamento dal meteo tipico - sottraiamo una piccola
baseline già inclusa nello storico, così le giornate serene risultano un
filo migliori della media e quelle brutte peggiori. Le penalità sono
continue nell'intensità, non a gradini secchi:
Sono valori scelti a partire dalla letteratura sul comportamento dei mezzi di superficie nei climi del nord Italia e tarati sui primi dati pilota.Condizione meteo (codice WMO) Minuti aggiunti Pioggia / rovesci (61–67, 80–82) +3 min + 0,8/mm (max 12) Temporale (95–99) come pioggia +4 min Neve (71–77) +12 min + 2/mm (max 25) Nebbia (45–48) +2 min Gelo (temperatura ≤ 0 °C) +4 min Vento > 40 km/h +0,12 per km/h oltre soglia (max 5) Baseline tipica sottratta sempre −1,5 min E sì, conta anche il vento - la voce meteo a cui nessuno pensa mai. Sopra i 40 km/h un bus rallenta davvero: rami e detriti in strada, andatura più prudente, attese più lunghe alle fermate esposte. E alla fermata il vento non rallenta solo il bus - gela anche te: per questo il consiglio vestiario del mattino nell'app non usa la temperatura nominale ma quella percepita col wind-chill. BusZilla non ti dice solo quando arriva il bus, ma anche se ti serve la sciarpa mentre lo aspetti.
- Il carico a monte. Un bus che arriva da te ha già fatto tutte le fermate precedenti: se storicamente sono affollate, accumula ritardo a ogni salita. Per ogni fermata a monte sulla stessa linea (stesso giorno della settimana, stessa ora) sommiamo il suo tasso storico di affollamento e aggiungiamo 0,5 minuti per fermata "piena", con un tetto di 8 minuti: una linea lunga e affollata non può gonfiare la previsione all'infinito. Le fermate a monte con meno di 3 check-in non contano. I disservizi modulano anche questo fattore: una festività svuota le fermate a monte, un grande evento le riempie.
- Disservizi pianificati. Alcune cause di ritardo non stanno nei dati storici perché non sono ancora successe: scioperi, festività scolastiche, eventi/manifestazioni e cantieri. In Italia sono spesso annunciati con giorni di anticipo e pesano moltissimo. Li inseriamo a mano nel pannello operativo indicando finestra temporale, bersaglio (tutta la rete, un'azienda, alcune linee o un'area geografica) e impatto. Il motore, di notte, applica a ogni corsa coperta i minuti previsti, l'eventuale probabilità di soppressione e l'effetto sull'affollamento (una festività, ad esempio, svuota i bus e li rende più puntuali). È anche il modo in cui rientra il traffico eccezionale: la congestione ordinaria è già dentro la media storica, mentre un cantiere o un evento la spostano davvero - e solo allora deve contare.
- Distribuzione, non un numero. Per ogni cassetto teniamo l'intera distribuzione dei ritardi, non solo la media. I ritardi del bus sono asimmetrici (di solito puntuale, ogni tanto +30): la media viene tirata dai giorni storti, quindi la nostra stima centrale è la mediana, più fedele. Dalla distribuzione ti diamo cose azionabili: la probabilità di essere in orario (ritardo entro 2 minuti), il rischio di ritardo > 5 min, e un orario "esci entro" (l'80° percentile: pianifica su quello e prendi il bus circa 4 volte su 5). Sotto il cofano la distribuzione è un istogramma a 7 classi, con i bordi a −5, 0, 2, 5, 10, 15, 30 e 60 minuti. Non sono numeri a caso: 2 e 5 sono esattamente le soglie di "puntuale" e "rischio ritardo", così quelle probabilità si leggono dritte dai confini delle classi, senza approssimazioni. Compatto da salvare, onesto da interrogare.
-
Confidenza e intervallo. Più check-in ha visto quel cassetto,
più ci fidiamo: la confidenza segue una curva di saturazione tipo
1 − e−n/50 · 0,95, e cala quando i ritardi sono molto ballerini (alta dispersione). L'intervallo mostrato (es. "7–12 min") sono i percentili 20–80 della distribuzione: onesto anche quando i ritardi sono sbilanciati. Se manca la previsione meteo, la confidenza è moltiplicata per 0,8. - Tempo reale: il crowd. Per le partenze entro i prossimi 45 minuti guardiamo i check-in arrivati negli ultimi 30 minuti sulla tua tratta: bastano 2 segnalazioni e la previsione si aggiorna adesso, fondendo la mediana di quel segnale dal vivo con la stima statistica. Più segnalazioni arrivano, più il segnale live pesa - fino a un massimo del 70%: il crowd corregge il modello, non lo sostituisce. È il "lo vediamo arrivare prima che arrivi lui" applicato sul serio.
- Auto-correzione e calibrazione. Ogni sera confrontiamo le previsioni coi ritardi realmente arrivati. Misuriamo, per ogni tratta, l'errore sistematico (tendiamo a esagerare o a sottostimare?) e la notte dopo ne sottraiamo la metà - una correzione prudente, che non oscilla da un eccesso all'altro, e che parte solo dopo aver verificato almeno 5 previsioni su quella tratta: il modello impara dai propri sbagli, non dal primo sbaglio. Controlliamo anche la calibrazione dell'intervallo: quante volte il ritardo reale cade davvero nella fascia promessa. Trasparenza, non magia.
- Mood previsto. Sulla base del ritardo previsto e delle soglie di puntualità del cassetto, la stessa funzione che calcola il mood "in diretta" (vedi sezione 7) decide quale mascotte mostrarti la sera prima. Con un'eccezione: se sulla corsa pesa un disservizio serio - rischio di soppressione, o almeno 10 minuti di ritardo aggiunti - BusZilla è angry a prescindere dallo storico.
Quando gira e quanto dura
- Un job notturno alle 02:00 UTC ricalcola da zero le previsioni del giorno successivo per tutti i cassetti eleggibili.
- Ogni 15 minuti, durante la giornata, le previsioni delle prossime 2 ore vengono ri-calibrate col traffico in tempo reale. Conta solo lo scarto dal profilo tipico di quella zona e fascia oraria - la congestione ordinaria è già nella media storica: fino a +3 minuti con traffico moderato, +8 con traffico intenso, +14 se è tutto fermo. In questa passata entra anche il segnale crowd descritto sopra. Gli spostamenti sotto il mezzo minuto vengono ignorati, e il ricalcolo parte sempre dalla stima notturna, quindi rieseguirlo non gonfia i numeri. Il "domani" diventa "fra poco".
- Le previsioni hanno un TTL di 36 ore: dopo non sono più consultabili. Ogni notte si ricomincia: niente accumulo di dati vecchi, niente previsioni stantie.
-
La basis di ogni previsione (etichetta del tipo
statistical_v2:n247+weather_wmo63+disruptions1) viene salvata insieme al risultato: così sappiamo sempre quanti campioni, quale meteo e quali disservizi hanno generato quel numero. Trasparenza forensica.
Niente AI agitata in faccia. Una formula chiara, una soglia minima di dati, un correttore meteo come scostamento dal tipico, il carico delle fermate a monte e i disservizi pianificati, tutti trasparenti. Domani mattina sai cosa aspettarti - e sai perché.
5. Quanto ci azzecchiamo
Un modello che non viene misurato è solo un'opinione. Per questo ogni sera, alle 22:00 UTC - prima che le previsioni del giorno scadano - un job di back-test riprende tutte le previsioni pubblicate per la giornata appena conclusa e le confronta coi check-in realmente arrivati. Ogni check-in validato viene attribuito alla singola previsione più vicina della stessa tratta, entro una finestra di 20 minuti: una segnalazione non viene mai contata due volte su due corse adiacenti.
Da quel confronto calcoliamo e salviamo, ogni giorno:
- Errore medio assoluto (MAE) - di quanti minuti sbagliamo, in media, in una direzione o nell'altra.
- Bias - il segno dell'errore: tendiamo a esagerare i ritardi o a sottostimarli? È il numero che alimenta l'auto-correzione notturna per tratta descritta qui sopra.
- RMSE - come il MAE, ma punisce di più gli errori grossi: sbagliare di 20 minuti è molto peggio che sbagliare di 2, e questa metrica se lo ricorda.
- Copertura dell'intervallo - quante volte il ritardo reale è caduto davvero dentro la fascia "7-12 min" promessa. Per un intervallo ai percentili 20-80 il valore giusto è circa il 60%: se copriamo troppo poco siamo spacconi, se copriamo troppo siamo vaghi.
- Skill score - la metrica che decide tutto. Confrontiamo il nostro errore con quello della previsione più pigra possibile: "il bus è sempre puntuale". Skill positivo = il modello si guadagna da vivere; skill a zero o negativo = tanto valeva non averlo.
Quest'ultimo punto è il nostro patto di onestà: il giorno in cui il modello non batte più il "assumi che sia puntuale", non lo difendiamo - lo cambiamo. Ed è anche il criterio con cui decideremo se e quando passare dalle regole statistiche al machine learning: solo se i numeri dicono che ne vale la pena.
6. Le fasce di affidabilità
I tier visualizzati sulla mappa derivano dal tasso di puntualità sugli ultimi 7 giorni:
- Alta affidabilità: puntualità > 85%.
- Affidabilità media: puntualità tra 70% e 85%.
- Bassa affidabilità: puntualità < 70%.
- Dati insufficienti: meno di 15 check-in nella finestra.
7. Il "mood" di BusZilla
La mascotte cambia espressione in base alla qualità del servizio percepito, con regole esplicite e deterministiche:
- Happy - puntualità > 85% e nessuna segnalazione attiva.
- Neutral - 70-85% oppure dati insufficienti.
- Suspicious - 50-70%.
- Angry - < 50% oppure ritardo segnalato negli ultimi 15 minuti.
- Sleeping - fuori orario di servizio o weekend.
- Celebrate - uno studente ha appena sbloccato un achievement.
8. Anti-abuso
Oltre al geofence, applichiamo un rate-limit di 30 check-in all'ora per utente, penalizziamo i pattern di segnalazione anomali tramite il punteggio di reputazione, e pesiamo meno i check-in di account molto recenti. Tutti questi controlli sono applicati lato server, al momento in cui il check-in viene ricevuto e prima che entri nelle statistiche.
9. Dati che usiamo, dati che non usiamo
- ✔ Orari teorici da GTFS statico ufficiale.
- ✔ Snapshot meteo concomitanti (provider: Open-Meteo) per analisi correlate.
- ✔ Profilo tipico del traffico per cluster geografico e fascia oraria, per spiegabilità (la congestione ordinaria è già nella media storica).
- ✔ Check-in pseudonimi degli studenti.
- ✘ Posizione continuativa: mai.
- ✘ Identità reale dello studente.
- ✘ Dati di terze parti (social, pubblicità).