mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-11-13 09:13:30 +01:00
feat(pvforecast): model snow on the modules in the local backend (#1484)
No PV forecast modelled snow lying on the modules, so after a snowfall the optimizer planned with PV that never came. The local Open-Meteo/pvlib backend can now reduce the forecast by the snow coverage (opt-in via pvforecast.akkudoktor.snow_enabled): - Request hourly snowfall and snow depth with the weather and run the NREL snow coverage model (pvlib.snow) per plane. - Correct the coverage with measured PV production: production above the model clears it at once; low production raises it only while snow is plausible, so curtailment and inverter outages are not learned as snow. Persisting snow damps the slide rate, fading with snow_feedback_memory_hours. - Rebuild the coverage from snow_lookback_days on every update instead of storing state. - Exclude intervals with possible snow from the calibration fit. - Warn when PV meter readings are missing or stale; ignore impossible meter jumps. _plane_power keeps its two-value signature (used by the adaptive provider in #1422); the irradiance comes from the new _plane_power_and_poa. Tests replay real February 2026 data from one plant (snow on 02-19/20). Signed-off-by: SchaafAlexander <86607129+SchaafAlexander@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a06473b6d6
commit
628226d3d9
@@ -336,7 +336,16 @@
|
||||
"calibration_azimuth_bin_degrees": 45,
|
||||
"calibration_prior_kwh": 5.0,
|
||||
"calibration_min_factor": 0.5,
|
||||
"calibration_max_factor": 1.5
|
||||
"calibration_max_factor": 1.5,
|
||||
"snow_enabled": false,
|
||||
"snow_lookback_days": 7,
|
||||
"snow_threshold_snowfall_cm_per_hour": 0.5,
|
||||
"snow_threshold_depth_cm": 1.0,
|
||||
"snow_feedback_enabled": true,
|
||||
"snow_feedback_gain": 0.8,
|
||||
"snow_feedback_min_fraction": 0.05,
|
||||
"snow_feedback_memory_hours": 12.0,
|
||||
"snow_detection_ratio": 0.5
|
||||
},
|
||||
"adaptive": {
|
||||
"backend": "local",
|
||||
|
||||
@@ -60,7 +60,16 @@
|
||||
"calibration_azimuth_bin_degrees": 45,
|
||||
"calibration_prior_kwh": 5.0,
|
||||
"calibration_min_factor": 0.5,
|
||||
"calibration_max_factor": 1.5
|
||||
"calibration_max_factor": 1.5,
|
||||
"snow_enabled": false,
|
||||
"snow_lookback_days": 7,
|
||||
"snow_threshold_snowfall_cm_per_hour": 0.5,
|
||||
"snow_threshold_depth_cm": 1.0,
|
||||
"snow_feedback_enabled": true,
|
||||
"snow_feedback_gain": 0.8,
|
||||
"snow_feedback_min_fraction": 0.05,
|
||||
"snow_feedback_memory_hours": 12.0,
|
||||
"snow_detection_ratio": 0.5
|
||||
},
|
||||
"adaptive": {
|
||||
"backend": "local",
|
||||
@@ -246,7 +255,16 @@
|
||||
"calibration_azimuth_bin_degrees": 45,
|
||||
"calibration_prior_kwh": 5.0,
|
||||
"calibration_min_factor": 0.5,
|
||||
"calibration_max_factor": 1.5
|
||||
"calibration_max_factor": 1.5,
|
||||
"snow_enabled": false,
|
||||
"snow_lookback_days": 7,
|
||||
"snow_threshold_snowfall_cm_per_hour": 0.5,
|
||||
"snow_threshold_depth_cm": 1.0,
|
||||
"snow_feedback_enabled": true,
|
||||
"snow_feedback_gain": 0.8,
|
||||
"snow_feedback_min_fraction": 0.05,
|
||||
"snow_feedback_memory_hours": 12.0,
|
||||
"snow_detection_ratio": 0.5
|
||||
},
|
||||
"adaptive": {
|
||||
"backend": "local",
|
||||
@@ -773,6 +791,15 @@
|
||||
| past_days | `Optional[int]` | `rw` | `None` | Days of past data to request (0-92). Leave empty to derive it from `prediction.historic_hours`. |
|
||||
| resolution_minutes | `int` | `rw` | `15` | Forecast resolution in minutes. 15 requests Open-Meteo's `minutely_15` block (natively resolved over Central Europe and North America, interpolated from hourly elsewhere); 60 requests the `hourly` block. |
|
||||
| shift_to_interval_start | `bool` | `rw` | `True` | Open-Meteo stamps an interval mean with the interval END. EOS labels an interval by its START, so records are shifted back by one interval. Disable only to compare like-for-like against a provider that does not. |
|
||||
| snow_detection_ratio | `float` | `rw` | `0.5` | Measured/clear-model production ratio below which an interval counts as evidence of snow the weather model missed. |
|
||||
| snow_enabled | `bool` | `rw` | `False` | Model snow lying on the modules. Requests snowfall and snow depth from Open-Meteo and runs the NREL snow coverage model (pvlib.snow) per plane, which covers the modules on heavy snowfall and lets the snow slide off with warmth and sunshine. Without this, the forecast after a snowfall assumes clean modules. |
|
||||
| snow_feedback_enabled | `bool` | `rw` | `True` | Correct the modelled snow coverage with measured PV production from `measurement.pv_production_emr_keys`. Production well above the snow model clears the coverage at once; production far below the clear-module model raises it, but only while snow is plausible (snow on the ground or already modelled on the modules). Needs cumulative meter readings. |
|
||||
| snow_feedback_gain | `float` | `rw` | `0.8` | How far one measured interval pulls the coverage towards the observed value (1.0 = adopt the observation). |
|
||||
| snow_feedback_memory_hours | `float` | `rw` | `12.0` | When measured production shows more snow than modelled, the snow is assumed to slide off slower than the NREL model says. This damping fades back to the model's rate with this time constant after the last measurement. |
|
||||
| snow_feedback_min_fraction | `float` | `rw` | `0.05` | A measured interval is only used when the clear-module model expects at least this fraction of the installed AC capacity. Dawn, dusk and dark overcast carry no usable evidence about snow. |
|
||||
| snow_lookback_days | `int` | `rw` | `7` | Days of past weather (and PV measurements) replayed on every update to rebuild the current snow coverage. No state is stored between runs, so this must reach back before the last relevant snowfall. |
|
||||
| snow_threshold_depth_cm | `float` | `rw` | `1.0` | Ground snow depth below which the modules are assumed clear (pvlib `threshold_depth`). |
|
||||
| snow_threshold_snowfall_cm_per_hour | `float` | `rw` | `0.5` | Snowfall rate that fully covers the modules (pvlib `threshold_snowfall`). pvlib's default of 1.0 cm/h was tuned on US station data; Open-Meteo's hourly model snowfall is smoother, so a lower rate fits better. |
|
||||
| temperature_coefficient | `float` | `rw` | `-0.36` | Module power temperature coefficient in %/degC (negative). Matches the `cellCoEff` the akkudoktor.net forecast uses. |
|
||||
| transposition_model | `str` | `rw` | `perez` | pvlib sky-diffuse transposition model: isotropic, klucher, haydavies, reindl, king or perez. |
|
||||
| weather_models | `list[str]` | `rw` | `['best_match']` | Open-Meteo weather models to request. Listing more than one turns the input into a poor-man's ensemble: the members are averaged per variable, which is the cheapest reliable way to cut irradiance forecast error. Costs no extra API calls. |
|
||||
@@ -810,7 +837,16 @@
|
||||
"calibration_azimuth_bin_degrees": 45,
|
||||
"calibration_prior_kwh": 5.0,
|
||||
"calibration_min_factor": 0.5,
|
||||
"calibration_max_factor": 1.5
|
||||
"calibration_max_factor": 1.5,
|
||||
"snow_enabled": false,
|
||||
"snow_lookback_days": 7,
|
||||
"snow_threshold_snowfall_cm_per_hour": 0.5,
|
||||
"snow_threshold_depth_cm": 1.0,
|
||||
"snow_feedback_enabled": true,
|
||||
"snow_feedback_gain": 0.8,
|
||||
"snow_feedback_min_fraction": 0.05,
|
||||
"snow_feedback_memory_hours": 12.0,
|
||||
"snow_detection_ratio": 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,6 +908,50 @@ Hourly meters are fitted hourly; native quarter-hour meters retain their cadence
|
||||
Probable outage or curtailment days are excluded by default. Without sufficient
|
||||
usable measurements the uncalibrated physical model is used.
|
||||
|
||||
##### Snow on the modules
|
||||
|
||||
Set `snow_enabled` to true to model snow lying on the modules. The local backend
|
||||
then also requests hourly `snowfall` and `snow_depth` from Open-Meteo and runs the
|
||||
NREL snow coverage model (`pvlib.snow.coverage_nrel`) per plane: heavy snowfall
|
||||
covers the modules, warmth and sunshine let the snow slide off, and the modules
|
||||
count as clear once the ground snow depth drops below `snow_threshold_depth_cm`.
|
||||
The forecast is reduced by the covered fraction. No state is stored between runs;
|
||||
every update replays the last `snow_lookback_days` of weather to rebuild the
|
||||
current coverage.
|
||||
|
||||
The weather model alone often misjudges how long snow stays on the modules. With
|
||||
`measurement.pv_production_emr_keys` configured (cumulative meter readings in
|
||||
kWh), `snow_feedback_enabled` corrects the coverage with the measured production
|
||||
of the past hours:
|
||||
|
||||
- Production above the snow model clears the coverage at once.
|
||||
- Production far below the clean-module model (less than `snow_detection_ratio`)
|
||||
raises the coverage, but only while snow is plausible: snow lies on the ground
|
||||
or the model already holds some coverage. Low production on a snow-free day
|
||||
(curtailment, an inverter fault) is ignored.
|
||||
- Persisting snow also slows the modelled slide rate. This fades back with
|
||||
`snow_feedback_memory_hours`, so a warm night after a covered day still clears
|
||||
the modules in the forecast.
|
||||
|
||||
Intervals with possible snow are excluded from the calibration fit, so snow is
|
||||
never learned as a permanent model loss. Meter jumps larger than the plant can
|
||||
produce are ignored.
|
||||
|
||||
```json
|
||||
{
|
||||
"pvforecast": {
|
||||
"provider": "PVForecastAkkudoktor",
|
||||
"akkudoktor": {
|
||||
"backend": "local",
|
||||
"snow_enabled": true
|
||||
}
|
||||
},
|
||||
"measurement": {
|
||||
"pv_production_emr_keys": ["pv_production_emr"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Transient weather errors are retried. If an update still fails, existing usable
|
||||
AC forecasts are retained; a cold start or expired-only forecast reports the
|
||||
error. Retention does not extend the available forecast horizon. Calibration and
|
||||
|
||||
Reference in New Issue
Block a user