Methodology
How this forecast is built, and how to read it.
The plant
Bhadla Solar Park in Rajasthan is one of the largest operational solar installations in the world — 2,245 MW of nameplate AC capacity across roughly 14,000 acres of the Thar Desert. The site receives more than 300 sunny days per year.
Data sources
Two years of hourly weather data (Jan 2023 – Dec 2024) from Open-Meteo's Historical Archive, sourced from ECMWF's ERA5 reanalysis. Variables include shortwave radiation, direct beam radiation, diffuse radiation, ambient temperature, cloud cover, wind speed, relative humidity, surface pressure.
Real plant-level SCADA data from Adani Green is not public, so ground-truth MW generation is synthesized using pvlib (NREL's industry-standard Python library) configured to the plant's actual specifications: 25° south-facing tilt, 1.2 DC/AC ratio, ~14% system losses (PVWatts default), SAPM cell-temperature model. Stochastic noise (3% multiplicative, 15 MW additive) is layered on top to prevent the model from learning a perfectly clean physics relationship.
Storage and querying
Raw weather and synthetic generation are stored in AWS S3 as Hive-partitioned Parquet (year=YYYY/month=MM/), with schemas auto-discovered by AWS Glue. Queries run on AWS Athena and benefit from partition pruning — a typical month-filtered query scans 4 KB instead of 130 KB (32× reduction).
Modeling
LightGBM gradient-boosted trees trained on 30 features: raw weather (8), calendar (4), cyclical encodings (4), lagged generation at 1/24/48/168 hours (4), lagged weather at 24/168 hours (6), rolling means (2), and solar geometry from pvlib (2 — zenith and elevation angle).
Trained chronologically — Jan 2023 to Sep 2024 for training, Oct–Nov 2024 for validation, Dec 2024 for held-out test. Early-stopping on val RMSE. No data leakage: rolling features use shift(1).rolling(N) so the current row's value is never included in its own rolling stat.
Uncertainty quantification via three additional LightGBM models trained with quantile loss at α = 0.10, 0.50, 0.90 — providing P10, P50, P90 alongside the point forecast. Isotonic post-processing (row-wise sort) prevents quantile crossing. Empirical coverage of the P10-P90 band on val data: 86% (vs 80% nominal — slightly conservative, the preferred direction for grid operators).
Accuracy
| Metric | Persistence | LightGBM |
|---|---|---|
| Test MAE | 13.77 MW | 9.53 MW |
| Test nMAE | 0.61% | 0.42% |
| Val MAE | 23.27 MW | 11.40 MW |
Industry literature reports day-ahead utility-scale solar forecasting at 4–8% nMAE. The 0.42% reported here reflects the synthetic-target setup; real-world performance against actual SCADA would be lower.
DSM penalty economics
India's Central Electricity Regulatory Commission (CERC) Deviation Settlement Mechanism (DSM) penalizes generators for deviating from their scheduled output. Tiered rates apply to the deviation percentage: 0–15% free, 15–25% at ₹0.50/kWh, 25–35% at ₹1.00/kWh, 35%+ at ₹1.50/kWh.
| Period | Persistence | LightGBM | Reduction |
|---|---|---|---|
| Val (Oct-Nov) | ₹89.1 lakh | ₹5.4 lakh | 94% |
| Test (Dec) | ₹3.06 lakh | ₹1.05 lakh | 66% |
| Combined 92d | ₹92.1 lakh | ₹6.5 lakh | 93% |
Annualized extrapolation (×4) ≈ ₹3.43 crore in avoided penalty exposure for a plant of this size. Most of the gap concentrates in high-variability periods — persistence collapses during weather transitions while LightGBM responds to the changing inputs.
Serving
Inference runs on AWS Lambda from a containerized Python 3.11 image. The Lambda pulls the model from S3 on cold start (~17s init, cached afterwards), fetches the next 24 hours of forecasted weather from Open-Meteo's free API, computes 30 features per hour, and returns point + P10/P50/P90 predictions as JSON. API Gateway (HTTP API) provides the public HTTPS endpoint. Memory configured at 3 GB to handle pvlib + LightGBM peak memory ~261 MB with headroom. CloudWatch logs all invocations.
What-if simulator
The cloud cover and temperature sliders on the dashboard apply a first-order sensitivity model on the frontend rather than re-invoking the Lambda. The adjustment factor is (1 - 0.75 × cloud_offset) × (1 - 0.004 × temp_offset), using typical clear-sky transmission and the PVWatts panel temperature coefficient. This is faithful enough for intuition building. A production version would re-run inference server-side with adjusted weather inputs.
Known limitations
- Synthetic target. Generation is modeled from weather + pvlib + noise, not measured. Real plant performance would have inverter clipping, soiling losses, partial shading, and other irreducible noise sources not captured here.
- Lag features zeroed at inference.The model was trained with lagged generation history, but real-time SCADA isn't available, so these features are zeroed when serving. The model degrades gracefully because shortwave radiation dominates feature importance (~40× any other feature).
- No monsoon coverage in test set. Test data is December — clear, predictable. Real Bhadla operates through June–September monsoons where cloud cover is highly variable.
- Open weather data only.Operating teams use proprietary numerical weather prediction with much higher resolution. The forecast here is limited by Open-Meteo's free ERA5-derived data.
Source
Full code, notebooks, deployment Dockerfile, and API documentation at github.com/angelina10504/solar-forecast