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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user