Leakage-safe probabilistic forecasting, daily load-shape clustering, residual anomaly monitoring, and drift analysis for Türkiye's national electricity consumption.
This is deliberately an analysis and modeling project. It does not include an API, dashboard, or cloud deployment. Its focus is the part that matters most in a time-series portfolio: honest backtesting, reproducible data contracts, interpretable failure modes, and results generated from real data.
- How accurately can national demand be forecast 24 hours and 7 days ahead?
- How much does a nonlinear quantile model improve over last-week and linear baselines?
- Does an 80% prediction interval remain calibrated during the 2020 regime shift?
- Which recurring 24-hour load shapes appear without customer-level smart-meter data?
- When do forecast residuals become unusually persistent, and when does the load distribution drift?
Training ends on 31 December 2018, 2019 is used only for interval/alarm calibration, and 2020 is a locked test period. No random split is used.
| Horizon | Model | MAE (MWh) | RMSE (MWh) | sMAPE | MASE |
|---|---|---|---|---|---|
| 24 hours | Seasonal naive | 1,883 | 3,004 | 6.13% | 1.261 |
| 24 hours | Ridge | 1,990 | 2,696 | 6.41% | 1.332 |
| 24 hours | HistGB median | 1,627 | 2,271 | 5.19% | 1.089 |
| 7 days | Seasonal naive | 1,883 | 3,004 | 6.13% | 1.261 |
| 7 days | Ridge | 2,002 | 2,702 | 6.44% | 1.340 |
| 7 days | HistGB median | 1,685 | 2,378 | 5.39% | 1.128 |
The median HistGradientBoosting model reduces MAE by 13.6% at 24 hours and 10.5% at 7 days versus the last-week baseline. The 2019-conformalized nominal 80% interval covers only 71.7% / 71.0% of 2020 observations. That shortfall is an important result: calibration deteriorates under structural change and should be monitored, not hidden behind a single point score.
The primary target is 41,232 hourly national-consumption records from 31 December 2015 through 12 September 2020, published on Kaggle by H. Gültekin and derived from EPİAŞ Transparency Platform data. Four zero/missing structural hours are marked unobserved and excluded from target metrics; they are never backfilled.
Weather is an equal-weighted proxy across Istanbul, Ankara, Izmir, Antalya, and Diyarbakır from the Open-Meteo Historical Weather API. Realized future weather is not used. Target-hour weather features are train-only month/hour climatologies, so the primary backtest remains usable at a real forecast origin.
An optional CC BY 4.0 Mendeley snapshot contains EPİAŞ market forecasts, production mix, and prices. It is auxiliary only: total is market supply and must not be mislabeled as actual consumption. If Mendeley's anti-bot layer rejects the public zip URL, the pipeline warns and continues with the primary target.
See Data Card for provenance, licenses, hashes, and field definitions.
flowchart LR
A["Hourly national load"] --> B["Chronological data contract"]
W["Five-city weather"] --> C["Origin-known features"]
H["Turkish holidays"] --> C
B --> C
C --> D["Seasonal naive"]
C --> E["Ridge"]
C --> F["Quantile HistGB"]
F --> G["2019 conformal calibration"]
G --> T["Locked 2020 test"]
T --> R["Forecast metrics"]
T --> X["Residual alerts"]
B --> K["Daily-shape clustering"]
B --> P["Seasonal PSI drift"]
- Direct global forecasting model with horizon
1..168as a feature. - Demand lags at 24/48/168/336 hours and causal rolling statistics at 24/168/336 hours.
- Calendar, Turkish public holidays, and cyclic hour/weekday/day-of-year terms.
- Seasonal-naive value at target minus 168 hours, which is known for every supported horizon.
- Quantile HistGradientBoosting at q10/q50/q90 plus split-conformal interval expansion on 2019.
- Bottom-up hourly-to-daily/weekly temporal aggregation for coherent totals.
- K-Means on normalized complete 24-hour profiles;
k=2selected on train-only silhouette score. - Median/MAD residual score, causal EWMA, calibration q=0.995, and 3-of-4 persistence.
- Monthly PSI against prior data from the same calendar month to separate drift from seasonality.
PowerShell (Python 3.12):
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.lock
.\.venv\Scripts\python.exe -m pip install -e .
.\.venv\Scripts\gridwatch.exe reproduceThe last command verifies snapshot SHA-256 values, downloads weather once, prepares Parquet data, trains all models, evaluates the locked test, and regenerates artifacts, manifests, tables, and figures. Raw downloads and the 41k-row prepared table stay out of Git.
Useful commands:
gridwatch data download
gridwatch data prepare
gridwatch data refresh-epias --start "2026-08-01T00:00:00+03:00" --end "2026-08-02T00:00:00+03:00"
gridwatch reproduce
pytest
ruff check src tests
mypy srcThe optional refresh command uses the current official EPİAŞ real-time-consumption endpoint and requires EPIAS_TGT in the environment. It writes a separate raw CSV and never silently replaces the pinned historical benchmark.
configs/ experiment contract
src/gridwatch/ data, features, models, metrics, monitoring
notebooks/ thin reproducible EDA and evaluation narratives
reports/figures/ generated portfolio figures
reports/experiments/ metrics, predictions, scores, and manifest
artifacts/v0.1.0/ fitted forecasting bundle
tests/ leakage, determinism, schema, and metric tests
- This is national aggregate demand, not a 370-customer Turkish smart-meter panel. “Customer clustering” is therefore replaced with honest daily load-regime clustering.
- The weather proxy is equal-weighted and not population- or demand-weighted.
- The snapshot is historical (2015–2020) and does not establish current production accuracy.
- 2020 contains COVID-19 and other structural changes. It is intentionally retained as a difficult locked test.
- There are no labeled outage, fraud, or incident events. Anomaly outputs identify unusual forecast residuals, not root cause.
- Prediction intervals are not fully calibrated on the 2020 test; the reported coverage gap is a model-risk finding.
Code is MIT licensed. Data retains its original terms: the Kaggle consumption database page states Open Database/Database Contents terms; the Mendeley auxiliary dataset and Open-Meteo weather are CC BY 4.0. No raw dataset is redistributed in this repository.

