Back
PyTorchTransformers (from scratch)MLflowDagsHubEvidently AIPanderaAWS S3 (boto3)GitHub ActionsNext.jsVerceluvPython

VoltCast:24-Hour US Electricity Demand Forecasting

Serverless MLOps system forecasting the next 24 hours of electricity demand for 4 US grid regions (CAL, ERCOT, PJM, MISO) using a Transformer built from scratch in PyTorch. No always-on backend: scheduled GitHub Actions ingest, validate, train, evaluate, register, serve, and drift-gate retraining; a Next.js dashboard reads forecasts directly from S3.

Core Impact

From-scratch PyTorch Transformer that beats a naive baseline by 32.8% (1,050 MW MAE) and serves as champion in all 4 regions, retrained automatically through a drift-gated, serverless GitHub Actions pipeline.

VoltCast: 24-Hour US Electricity Demand Forecasting

1,050 MW

CAL test MAE, 3.24% MAPE

+32.8%

Better than naive baseline

$0

Compute cost, serverless CI

The Problem

Grid operators must match electricity supply to demand every hour: too little means blackouts, too much means wasted money. Forecasting demand 24 hours ahead is the same problem real ISOs solve, but most demos stop at a model in a notebook. I wanted the full production loop, ingest to drift-gated retraining, with no always-on server to pay for and every layer built explicitly so the engineering is visible.

The Approach

01

Why a Transformer from scratch

Every layer is written by hand with a raw AdamW training loop, cosine annealing, gradient clipping, and early stopping. An LSTM and a naive baseline run alongside it for a real ablation, so the 32.8% win is measured, not assumed. Building it explicitly keeps the engineering visible instead of hidden behind a `Trainer`.

02

Why one-shot 168 to 24

The model sees 168 real hours (one week) and outputs all 24 future hours at once. No recursive feeding of predictions back as inputs, which compounds error step by step. One forward pass, full day forecast.

03

Why drift-gated hybrid retraining

Evidently runs a K-S test against the champion's pinned training distribution, snapshotted at crowning time so drift always measures against what the live model actually learned. Retraining fires on drift OR a 30-day age cap, catching slow shifts the statistical test alone would miss.

04

Why serverless on Actions

Scheduled GitHub Actions handle hourly forecasts and weekly retrains on free CPU runners; models live in a DagsHub registry, forecasts in S3, and the Next.js frontend reads S3 JSON directly. No backend stays running, so the whole system costs roughly nothing to operate.

Technical Deep Dive

01

Built a TemporalTransformer layer by layer in PyTorch (no HuggingFace, no `Trainer`): linear projection, sin/cos positional encoding, 2 encoder layers (nhead=4, dim_feedforward=256), one-shot 168h-in to 24h-out head. Beats a strong naive baseline by 32.8% (CAL test MAE 1,050 MW, 3.24% MAPE) and crowns champion across all 4 regions.

02

Engineered a Champion/Challenger registry on DagsHub MLflow: `registry.py` evaluates Transformer vs LSTM on an untouched test set and only promotes a challenger that beats the live champion by >1% test MAE. No worse model can silently ship.

03

Designed a drift-gated hybrid retraining loop with Evidently: K-S test compares fresh EIA pulls against the champion's pinned training distribution (snapshot saved at crowning time). Retrain fires on drift OR a 30-day champion age cap to catch slow shifts the statistical test misses.

04

Ran the entire ingest to serve loop serverless on GitHub Actions (hourly forecast, weekly retrain, manual full rebuild) on free public-repo CPU runners, holding compute cost at ~$0. Raw data is re-pulled from EIA each run (rolling 5-year window), nothing stored stale.

05

Enforced leak-free time-series discipline: 70/15/15 chronological splits never shuffled, scalers fit on training data only, test set never touched during training or tuning. Pandera data contract rejects nulls, spikes, and gaps before any row reaches the model.

Systems Analysis Concluded

© 2026 · Marian Glen Louis

Engineered with Next.js, Tailwind v4 & Framer Motion