Gli avvisi di deviazione di GTT (Torino) sono scritti in prosa.
DeviaTo li trasforma in una mappa e risponde a una domanda sola:
la mia fermata Γ¨ ancora servita?
- π΄ Ti dice quali fermate saltano β e dove andare al loro posto. Γ l'informazione che serve davvero, e che GTT non pubblica quasi mai.
- πΊοΈ Disegna la deviazione sulla mappa, ricostruita dal testo dell'avviso: percorso normale, tratto deviato, fermate escluse.
- π Guarda dove sono i mezzi adesso e capisce se la deviazione Γ¨ giΓ finita β cosa che nessun'altra fonte sa dirti, perchΓ© GTT annuncia quando cominciano ma non quando smettono.
- π Separa ciΓ² che Γ¨ in corso da ciΓ² che comincerΓ . Il 19 % delle variazioni pubblicate non Γ¨ ancora in vigore.
- βοΈ Calcola una volta per tutti. Un job su GitHub Actions legge gli avvisi ogni mezz'ora e pubblica i risultati: l'app li scarica in un secondo, senza chiavi nΓ© account. La tua posizione non lascia il dispositivo.
- π€ Non inventa mai. Se non riesce a ricostruire un percorso lo dichiara e ti mostra il testo originale di GTT.
- Il problema
- Scaricala
- Cosa fa
- Come funziona
- Architettura
- Limiti noti
- Sviluppo
- Da dove nasce
- Licenze e attribuzioni
L'avviso tipico di GTT si presenta così:
Linea 65 deviata in direzione corso Bolzano dalle 8:00 di lunedì 3 sino alle 18:00 di venerdì 7 agosto 2026. Da via Asinari di Bernezzo angolo corso Monte Grappa, per via Asinari di Bernezzo, piazza Chironi, via Medici, corso Lecce, via Lessona, segue percorso normale.
Γ un elenco di vie. Ma chi aspetta il bus sta a una fermata, non su una via, e per sapere se quella fermata Γ¨ ancora servita dovrebbe ricostruire il percorso a mente su una mappa che non ha davanti.
L'app fa quel lavoro: geocodifica i toponimi, calcola il percorso deviato sulla rete stradale reale, lo confronta con il percorso ufficiale del GTFS e determina quali fermate restano fuori.
Note
DeviaTo β da Β«deviaΒ» e Β«ToΒ», che a Torino Γ¨ come si scrive Torino. Progetto personale, non affiliato a GTT nΓ© approvato da GTT.
DeviaTo Γ¨ gratuita, senza pubblicitΓ e senza account. Arriva a breve su App Store e Google Play: i link compariranno qui e sul sito.
Aggiungi le linee che usi con +: si cercano per numero o per via. Poi apri una linea, tocca la tua fermata sulla mappa e salvala: la home ti dice subito se Γ¨ servita.
Important
Privacy. Nessun account, nessuna statistica d'uso, nessuna pubblicitΓ . La posizione, se la attivi, serve solo a mostrarti sulla mappa: non viene salvata e non esce dal telefono. Il permesso si chiede quando tocchi il pulsante, non all'apertura. Informativa completa.
L'output più utile, e quello che GTT non fornisce quasi mai. Le fermate chiuse sono riassunte per tratti e non avviso per avviso: quando GTT pubblica un avviso per ogni fermata, sedici avvisi diventano «nove fermate chiuse di fila, sali a Vibò o a Statuto Nord». Le alternative privilegiano le fermate ancora servite dalla stessa linea, così da non richiedere un cambio di mezzo, ed escludono quelle chiuse da qualunque altro avviso della linea.
Percorso normale di entrambe le direzioni con tonalitΓ distinte, tratto deviato in rosso, fermate toccabili per il nome, fermate saltate cerchiate, e la tua posizione su richiesta. Si apre anche a tutto schermo, con una direzione sola se le due si sovrappongono e un pannello che si trascina dal basso: le fermate non servite per tratti, e quella che tocchi.
In tempo reale, finchΓ© non la interrompi, con i mezzi che si aggiornano sulla mappa: ognuno ha una punta verso dove sta andando e il colore della direzione che sta facendo. Continua mentre guardi altre linee β una alla volta, per non raddoppiare le richieste al feed di GTT. Risponde a una domanda che nessun'altra fonte copre β la deviazione Γ¨ giΓ finita? β e a una che il testo non sa rispondere bene: dove escono e dove rientrano davvero. L'app lo dice con i nomi delle fermate, e disegna il tratto realmente percorso. Γ l'unico dato del sistema che non viene da un testo di GTT.
Il 19 % delle variazioni pubblicate non Γ¨ ancora in vigore. L'app le tiene separate β Β«comincia dopodomaniΒ» β invece di segnalarle come attive.
Salvi la fermata che usi β linea, direzione, palo β e la home ti risponde per quella: servita, non servita fino alβ¦, e dove salire invece.
In fondo a ogni scheda, così che il dato grezzo resti disponibile anche quando il sistema sbaglia.
Il passaggio da testo a geometria Γ¨ il punto in cui questi progetti si fermano. Un geocoder interrogato liberamente con Β«via RomaΒ» restituisce decine di risultati in tutto il Piemonte, e la polilinea che ne esce non ha alcun rapporto con il percorso reale della linea.
La soluzione Γ¨ il geocoding vincolato: i toponimi si cercano esclusivamente entro un chilometro dal percorso ufficiale di quella linea. Γ questo vincolo β non il modello linguistico, non l'euristica β a rendere affidabile l'intera catena.
| Grandezza | Valore |
|---|---|
| Toponimi risolti correttamente | 150 / 150 entro 2 km |
| Distanza massima di un toponimo corretto dal percorso | 800 m |
| Distanza minima di una via estranea alla linea | 1342 m |
| Estrazione strutturata dal testo (LLM) | 34 / 34, zero toponimi inventati |
| Copertura della tabella alias dei nomi di linea | 98,4 % (63/64) |
Il margine fra 800 m e 1342 m Γ¨ ciΓ² che rende il filtro possibile: un toponimo corretto e uno estraneo si separano nettamente, e il buffer di 1 km cade in mezzo. Un buffer da 2 km lascia entrare rumore; uno da 500 m scarta vie legittime, perchΓ© una deviazione per definizione si allontana.
avvisi GTT (due fonti)
β
βΌ
unione dei doppioni ......... 31 coppie su 189 avvisi
β
βΌ
estrazione LLM .............. testo β JSON (vie, direzione, tipo)
β
βΌ
geocoding VINCOLATO ......... toponimi β coordinate, entro 1 km dalla linea
β
βΌ
routing bus (Valhalla) ...... coordinate β polilinea sulla rete reale
β
βΌ
cinque validazioni .......... o si dichiara l'incertezza
β
βΌ
impatto sulle fermate ....... quali saltano, dove andare al loro posto
Il percorso ricostruito supera cinque validazioni prima di essere disegnato. Se una fallisce, l'app dichiara l'incertezza e mostra il testo di GTT. La regola Γ¨ esplicita:
β οΈ Meglio nessuna mappa che una mappa sbagliata. Un falso positivo fa camminare l'utente ottocento metri inutilmente, e distrugge la fiducia nello strumento.
Il sistema Γ¨ organizzato attorno a un vincolo strutturale:
app/lib/core/Γ¨ Dart puro. Nessunimport 'package:flutter/β¦'.
Da questo discende il resto: la logica si esegue e si testa in millisecondi
senza simulatore, si puΓ² invocare da riga di comando, e l'interfaccia Γ¨
sostituibile β con un'altra UI, con uno script, con un'implementazione
nativa β senza toccare il calcolo. Se un file di core/ avesse bisogno di
Flutter, quel file sarebbe nel posto sbagliato.
Il calcolo gira in un posto solo: il job su GitHub. L'app legge i risultati.
avvisi GTT ββΊ job su GitHub Actions (ogni 30 min) ββΊ gh-pages: indice.json,
percorsi/, stato/
β
app ββββββββββββββ (qualche KB)
app/tool/pubblica.dart β il giro del job: scarica, calcola, scrive
app/lib/
βββ core/ β Dart puro, zero dipendenze da Flutter
β βββ config.dart β tutte le soglie tarabili, in un posto solo
β βββ models/ β tipi di dominio, senza comportamento di rete
β βββ geo/
β β βββ projection.dart β gradi β metri
β β βββ geometry.dart β distanze, proiezioni, FrΓ©chet, densify
β β βββ polyline.dart β codifica Google polyline
β βββ gtfs/ β scarico e indicizzazione del GTFS statico
β βββ sources/ β una classe per fonte GTT, intercambiabili
β βββ llm/ β client OpenAI-compatibile, con tetto di spesa
β βββ io/formato_pubblicato.dart β il formato dei file fra job e app
β βββ publish/pubblicatore.dart β lo stato di tutte le linee, in un giro
β βββ pipeline/ β un passaggio del calcolo per file
β βββ notice_merge.dart β le due fonti β un avviso solo
β βββ line_resolver.dart β Β«55Β» β 55U (tabella alias)
β βββ extractor.dart β testo β JSON strutturato (LLM)
β βββ geocoder.dart β toponimo β coordinate, VINCOLATO
β βββ route_builder.dart β vie β polilinea (Valhalla) + validazioni
β βββ rejoin_inference.dart β dove rientra, quando GTT non lo dice
β βββ stop_impact.dart β quali fermate saltano, e le alternative
β βββ closure_summary.dart β le fermate chiuse lette per tratti
β βββ vehicle_watch.dart β osservazione dei mezzi in tempo reale
βββ data/ β dati pubblicati con copia locale, preferenze, posizione
βββ ui/ β schermate e mappa
Perché è diviso così. Ogni file di pipeline/ è un passaggio del
ragionamento ed Γ¨ sostituibile isolatamente: se Photon cessa il servizio si
riscrive geocoder.dart; se Valhalla pubblico sparisce, route_builder.dart;
per cambiare modello linguistico, extractor.dart. Nessun altro file se ne
accorge.
config.dart esiste perchΓ© quasi tutte le soglie vanno tarate sul campo. I
valori marcati MISURATO vengono da rilevazioni reali, e in sette casi
contraddicono le stime del progetto originale β fra cui la soglia di
fuori-rotta (50 m misurati contro 80 stimati) e il buffer del geocoding
(1 km contro 2).
| Fonte | Uso | Nota |
|---|---|---|
| GTFS statico GTT | percorsi, fermate, orari | rigenerato ogni giorno alle 04:00, CC-BY |
alerts.aspx (GTFS-RT) |
avvisi | porta il route_id canonico nel 96,7 % dei casi |
/cms/variazioni (HTML) |
avvisi | unica fonte con le date d'inizio reali; spenta nel job finchΓ© GTT non ne autorizza l'uso |
vehicle_position.aspx |
posizioni dei mezzi | si spegne di notte, il servizio no |
| Photon | geocoding | nessuna chiave richiesta |
| Valhalla (FOSSGIS) | routing costing: bus |
polilinee a precisione 6 |
| Overpass (FOSSGIS, riserva VK Maps) | forma intera delle vie degli avvisi, per gli incroci (algoritmo 2) | nessuna chiave; puΓ² essere lento |
| OpenRouter | estrazione dal testo | chiave del job, segreto di GitHub Actions |
L'OTP di GTT β che il progetto originale indicava come fonte primaria β Γ¨
stato scartato dopo verifica: espone un build piΓΉ vecchio, i cui trip_id
non esistono nel feed corrente, e a cui mancano sette linee.
Il job su GitHub calcola ogni mezz'ora, ma l'app non manda notifiche: i dati si vedono aprendola. Le notifiche sono il passo naturale successivo, ora che il calcolo non sta piΓΉ sul telefono.
Quando una fermata salta, l'app ti propone quelle vicine e ti dice quanto distano β ma in linea retta, come vola un uccello, non come cammini tu. A Torino un fiume, una ferrovia o un muro possono raddoppiare il percorso reale. L'app scrive Β«in linea d'ariaΒ» ogni volta, cosΓ¬ il numero non si scambia per una distanza a piedi.
La stessa linea ha spesso piΓΉ percorsi leggermente diversi: alcune corse si fermano prima, altre passano da una via invece che da un'altra. L'app usa quello piΓΉ frequente. Se una deviazione riguardasse soltanto una di quelle corse minori, l'app la calcolerebbe come se valesse per tutte.
GTT pubblica gli avvisi in due posti, e nel feed dei dati aperti la data di inizio non Γ¨ quella vera: Γ¨ l'ora in cui l'avviso Γ¨ stato scritto (verificato su 161 avvisi su 161). Le date vere stanno nella tabella del sito di GTT, che perΓ² non Γ¨ pubblicata come dato aperto: il job non la usa finchΓ© GTT non lo autorizza. Nel frattempo una variazione annunciata in anticipo puΓ² comparire come giΓ in corso.
Serve Flutter con Dart β₯ 3.12.
Il codice Γ¨ lo stesso per iOS e Android: lib/core/ Γ¨ Dart puro e non sa
su cosa sta girando.
cd app && flutter test # 409 test
cd app && flutter analyze
cd app && flutter run # legge i dati pubblicati, nessuna chiavePer provare l'app contro un giro del job lanciato sul computer:
cd app && dart run tool/pubblica.dart --uscita ../sito/v1 --gtfs ../.gtfs
python3 -m http.server 8765 --directory ../sito &
flutter run --dart-define=DATI_URL=http://localhost:8765/v1/.github/workflows/pubblica.yml esegue
app/tool/pubblica.dart ogni mezz'ora di giorno,
ogni ora la sera e una volta di notte, e pubblica il risultato sul ramo
gh-pages. A lanciarlo Γ¨ un servizio esterno (cron-job.org) che chiama
workflow_dispatch: gli orari programmati di GitHub saltano troppi giri, e
restano solo come riserva. Su un fork va configurato una volta:
- Settings βΊ Pages: Β«Deploy from a branchΒ», ramo
gh-pages, cartella/. Il ramo lo crea il primo giro. - Settings βΊ Secrets and variables βΊ Actions: il segreto
OPENROUTER_API_KEY. Senza, si pubblicano solo le fermate sospese che GTT scrive col numero, e gli altri avvisi restano da leggere.
Le richieste al modello dipendono dagli avvisi nuovi di GTT β una ventina al giorno, misurato il 26/09/2026 β e non da quante persone usano l'app: un avviso giΓ letto e non cambiato non si rilegge.
I test sulle fonti girano offline su dati reali: il feed protobuf e la
pagina HTML di GTT del 31 luglio 2026 stanno in app/test/fixtures/. I test
sul GTFS si saltano da soli se i file non ci sono.
Strumenti di misura, che interrogano i servizi veri:
cd app && dart run tool/check_pipeline_live.dart # catena completa
cd app && dart run tool/check_merge_offline.dart # unione delle fontiTip
Prima di modificare qualcosa, leggi CLAUDE.md. Contiene
ciΓ² che non sta nel codice: le misure e il metodo con cui sono state
ottenute, il ragionamento dietro le scelte, i sette punti in cui la
specifica originale Γ¨ stata smentita dai dati, e le trappole giΓ pagate β
per esempio il confronto per sottostringhe che faceva scattare Β«lavori
stradaliΒ» sul capolinea Β«STRADA del DrossoΒ», mandando l'avviso
sulla direzione sbagliata.
Due criteri per i contributi:
- Le soglie si tarano misurando. Gli script in
scripts/eapp/tool/esistono per questo. Un numero rimesso in discussione va rimisurato, non stimato. - Dichiarare l'incertezza Γ¨ un requisito, non un ripiego. Β«Non lo soΒ» Γ¨ una risposta valida e va comunicata come tale.
Altra documentazione: app/ARCHITETTURA.md per lo
stato dei moduli Β· docs/FASE-0-RISULTATI.md per
le misure sulle fonti Β· docs/ per la specifica originale, che va
letta sapendo che sette dei suoi assunti si sono rivelati falsi.
L'idea Γ¨ mia, e nasce da una cosa che mi capitava di continuo: gli avvisi di GTT ci sono, i dati aperti pure, le posizioni dei mezzi anche β ma stanno in posti diversi e in formati diversi, e nessuno li mette insieme per rispondere alla domanda che uno si fa davvero alla fermata. Volevo aggregare quelle fonti e ricavarne delle funzioni utili.
Il passaggio da quell'idea a un'app che funziona l'ho fatto insieme a Claude (Anthropic): l'architettura, il codice, le misure sulle fonti e le verifiche sul campo sono venute da quel lavoro a quattro mani. Le decisioni su cosa dovesse fare e su come dovesse comportarsi sono rimaste mie; il mestiere per realizzarle e il rigore nel misurare invece di supporre li ha messi lui.
Buona parte di quello che c'Γ¨ scritto in CLAUDE.md β le
misure che smentiscono le stime, le trappole trovate provando l'app invece
che leggendo il codice β Γ¨ il residuo di quel processo, ed Γ¨ la parte del
progetto che credo valga di piΓΉ.
| π Dati di trasporto | GTT S.p.A. β Gruppo Torinese Trasporti, dati aperti: CC BY 4.0 sul portale del Comune, solo uso non commerciale secondo la licenza di GTT. L'attribuzione Γ¨ obbligatoria, e l'app non ha pubblicitΓ nΓ© acquisti |
| πΊοΈ Cartografia | OpenStreetMap, ODbL; immagini della cartina di CARTO (Positron e Dark Matter) |
| π§ Routing | Valhalla ospitato da FOSSGIS |
| π Geocoding | Photon di Komoot |
| π£οΈ Vie per nome | Overpass, istanze pubbliche di FOSSGIS e VK Maps, sui dati OpenStreetMap |
Photon, Valhalla e Overpass sono servizi offerti gratuitamente alla comunitΓ . Li
chiama solo il job, per gli avvisi nuovi: qualche decina di richieste al
giorno, con pause fra le chiamate, uno User-Agent riconoscibile e
l'intestazione X-Client-Id che FOSSGIS chiede alle app pubblicate.
DeviaTo non Γ¨ un'app di GTT e non Γ¨ collegata a GTT.
Il codice Γ¨ distribuito con licenza MIT β vedi LICENSE. La
licenza riguarda il codice, non i dati.
