Skip to content

Repository files navigation

VoltaCast

Previsione probabilistica day-ahead del carico elettrico nazionale italiano, in PyTorch.

CI Python PyTorch Licenza

Read this in English.

Chi gestisce la rete elettrica deve stimare ogni giorno quanta energia servirà al Paese l'indomani, ora per ora. VoltaCast affronta questo problema: dato l'andamento dell'ultima settimana di carico, il meteo di cinque città e il calendario, produce le 24 ore successive come quantili P10/P50/P90. Il dataset è costruito da API pubbliche, copre dal 2015 a oggi ed è incluso nel repository insieme ai pesi addestrati, quindi la valutazione e la previsione live funzionano subito, senza chiavi API.

Previsioni day-ahead contro carico reale negli ultimi 14 giorni di test

Risultati

Backtest day-ahead su un periodo di test rigorosamente separato: 379 previsioni, una al giorno alle 00:00 UTC, dall'8 luglio 2025 al 21 luglio 2026. Il periodo di test non è stato usato per nient'altro: niente addestramento, niente selezione del modello, niente tuning.

Modello MAPE RMSE Copertura intervallo 80%
Transformer (questo lavoro) 2,24% 988 MW 79,1% (66,7% senza calibrazione)
Regressione lineare quantilica, stesse feature 2,68% 1349 MW 79,9% (76,8% senza calibrazione)
Naïve stagionale (t − 168 h) 6,52% 3291 MW
Persistenza (t − 24 h) 8,78% 4357 MW

Note sul protocollo:

  • le previsioni usano il meteo osservato del giorno target (valutazione perfect prognosis, standard nella letteratura sul load forecasting); con un vero meteo previsionale gli errori sarebbero un po' più alti;
  • la copertura è riportata dopo la calibrazione conforme, con il valore grezzo tra parentesi; la calibrazione è priva di leakage, perché l'intervallo di ogni giorno usa solo esiti di giorni strettamente precedenti;
  • la baseline lineare condivide feature, loss, budget di addestramento e calibrazione con il Transformer. È lì perché un modello deep che non batte una regressione lineare regolarizzata non merita di andare in produzione; in una prima versione di questo progetto, con target assoluto anziché residuale, vinceva lei.

MAPE per ora del giorno, Transformer contro naïve stagionale

Metodo

Modello. Un Transformer sequence-to-sequence da circa 0,8 M di parametri (3 layer di encoder, 2 di decoder, d_model 128, 8 teste). L'encoder legge 168 ore di storia: carico più covariate. Le query del decoder sono le 24 ore target, ciascuna rappresentata dalle covariate note in anticipo (calendario, previsione meteo). La decodifica non è autoregressiva: le 24 ore escono in un unico forward pass, quindi l'errore non si accumula lungo l'orizzonte.

Target residuale. La rete non predice il livello assoluto ma lo scostamento da un'àncora naïve stagionale (il carico alla stessa ora della settimana precedente). La deriva lenta della domanda (elettrificazione, efficienza, nuovi grandi consumatori) lascia così invariata la distribuzione del target, ed è questo che rende utilizzabile nel 2026 un modello addestrato sul 2015–2024. Questa scelta ha pesato più di qualunque scelta architetturale.

Quantili. Pinball loss a q = 0,1, 0,5, 0,9. La testa di output produce il P10 più incrementi softplus non negativi, quindi l'incrocio dei quantili è impossibile per costruzione.

Calibrazione. Gli intervalli sono calibrati con la Conformalized Quantile Regression (Romano et al., 2019). Nel backtest, i punteggi di non conformità della validazione inizializzano una finestra mobile di 90 giorni, aggiornata giorno per giorno man mano che le previsioni si realizzano, come farebbe un job di ricalibrazione in produzione. Senza, l'intervallo nominale all'80% copre il 67% degli esiti. La previsione live, che parte da un'origine qualsiasi e non da mezzanotte, allarga la banda con i coefficienti calibrati per quella specifica ora di origine UTC: l'errore di previsione dipende fortemente dall'ora del giorno, quindi indicizzare per ora di origine, e non per passo di orizzonte, è ciò che fa reggere la copertura anche fuori da mezzanotte.

Feature. Ora, giorno della settimana e giorno dell'anno come coppie seno/coseno; flag weekend; festività italiane valutate sul calendario Europe/Rome (la serie è in UTC), con flag anche per i giorni adiacenti alle festività (i "ponti"); temperatura pesata sulla popolazione di Milano, Torino, Roma, Napoli e Palermo, con trasformazioni heating/cooling degree.

Split. Addestramento 2015–2024, validazione gennaio–giugno 2025 (early stopping, selezione del modello, seed della calibrazione), test da luglio 2025 in avanti.

Utilizzo

git clone https://github.com/tonytonycoder11/voltacast.git
cd voltacast
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
python scripts/predict.py        # previsione delle prossime 24 ore dai dati più recenti
python scripts/evaluate.py       # riproduce la tabella dei risultati e le figure
python scripts/train.py          # riaddestra entrambi i modelli (minuti su Apple Silicon o CUDA)
python scripts/build_dataset.py  # ricostruisce il dataset fino a ieri
pytest                           # esegue la suite di test

Dati

Carico nazionale orario dalla API di Energy-Charts (Fraunhofer ISE, che aggrega i dati della piattaforma di trasparenza ENTSO-E; CC BY 4.0), ricampionato a medie orarie dove la fonte pubblica valori a 15 minuti. Temperatura da Open-Meteo (CC BY 4.0): endpoint di archivio per lo storico, endpoint di forecast per la previsione live.

Il dataset elaborato copre 101 280 ore da gennaio 2015 a luglio 2026. Dopo l'interpolazione dei buchi fino a tre ore resta mancante lo 0,02% delle ore di carico; le finestre di addestramento non attraversano mai un buco residuo. Il parquet da 1,7 MB è incluso nel repository per riproducibilità.

Profilo settimanale medio del carico

Struttura del progetto

Le dipendenze puntano rigorosamente verso l'interno: l'I/O sta sul bordo del package, il nucleo è puro e coperto da test.

voltacast/
├── config.py             configurazione tipizzata (dataclass); nessuna logica
├── sources/              INFRASTRUTTURA, l'unico layer che parla col mondo:
│   ├── energy_charts.py    client del carico nazionale (retry, backoff, rate limit)
│   ├── open_meteo.py       client meteo, storico + forecast (parametrico sulle città)
│   └── http.py             GET HTTP condiviso e resiliente
├── domain/               DOMINIO, funzioni pure e deterministiche, nessun I/O:
│   ├── features.py         feature di calendario/festività/meteo
│   ├── windows.py          finestre scorrevoli a prova di leakage
│   ├── scaling.py          normalizzazione con statistiche congelate sul train
│   ├── conformal.py        calibrazione conforme (copertura distribution-free)
│   └── metrics.py          MAPE, RMSE, pinball, copertura
├── models/               DOMINIO, architetture (torch puro):
│   ├── forecaster.py       Transformer quantilico seq2seq
│   ├── baselines.py        baseline lineare + naïve classiche
│   └── heads.py            testa a quantili monotoni condivisa
├── dataset.py            APPLICAZIONE, acquisizione e caricamento del dataset
├── matrices.py           matrici di feature, inferenza a batch, calibrazione CQR
├── checkpoints.py        factory dei modelli e persistenza dei checkpoint
├── backtest.py           valutazione day-ahead rolling
├── forecast.py           previsione live delle prossime 24 ore
├── training.py           ciclo di fit: early stopping, scheduling del learning rate
├── cli.py                entry point da riga di comando
└── viz.py                figure

scripts/                  shim sottili verso cli.py (per l'esecuzione senza install)
data/                     dataset elaborato (parquet)
checkpoints/              pesi addestrati
tests/                    vedi sotto

I moduli di dominio sono funzioni pure su array: è questo che permette alla suite di coprirli in millisecondi, senza rete né fixture. Passare dall'Italia a un altro Paese ENTSO-E è un cambio di configurazione, non un refactoring.

Test

La suite fissa le proprietà facili da rompere in silenzio:

  • le finestre non possono vedere valori futuri e non attraversano mai i buchi nei dati;
  • i flag delle festività seguono il calendario Europe/Rome su una serie indicizzata in UTC;
  • i quantili predetti sono monotoni per qualunque input;
  • la calibrazione conforme raggiunge la copertura nominale su dati sintetici, e la variante adattiva insegue un cambio di regime nel rumore;
  • gli scaler fanno il round-trip esatto e un modello a residuo nullo ricostruisce esattamente l'àncora stagionale.

La CI esegue lint (ruff) e suite di test a ogni push su main e su ogni pull request.

Limiti

  • Solo aggregato nazionale; nessuna scomposizione per zona di mercato.
  • Meteo perfect prognosis nel backtest, come indicato sopra.
  • La previsione live applica larghezze conformi calcolate una volta sola sulla validazione e salvate nel checkpoint. Il backtest le riaggiorna ogni giorno sugli esiti realizzati, il percorso live no: le bande restano quelle tarate su gennaio–giugno 2025 finché il modello non viene riaddestrato.
  • Nessun trattamento dedicato degli eventi eccezionali (scioperi, grandi guasti) oltre a ciò che catturano calendario e temperatura.

Possibili estensioni

  • Confronto con la previsione day-ahead ufficiale di Terna.
  • Modelli per zona di mercato (Nord, Centro-Nord, Centro-Sud, Sud, Sicilia, Sardegna).
  • Job di ricalibrazione giornaliera per la previsione live, export ONNX e un endpoint di serving minimale.

Riferimenti

  • Y. Romano, E. Patterson, E. J. Candès. Conformalized Quantile Regression. NeurIPS 2019.
  • Dati di carico: Energy-Charts, Fraunhofer ISE / ENTSO-E (CC BY 4.0). Dati meteo: Open-Meteo (CC BY 4.0).

Licenza

MIT. © 2026 Antonio Sarno

About

Day-ahead probabilistic forecasting of Italy's national electricity demand: a PyTorch Transformer with conformal prediction intervals, benchmarked over a held-out year against a linear baseline

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages