Files
EOS/docs/_generated/configpvforecast.md
T
Andreas f976335122 feat(pvforecast): local pvlib provider with measurement calibration
Add PVForecastAkkudoktorLocal, which runs the modelling chain inside EOS on
raw Open-Meteo irradiance instead of calling a forecast service: solar
position, horizon shading, plane transposition, incidence-angle modifier,
cell temperature, PVWatts DC and inverter AC.

It needs no API key and serves up to 16 days at 15-minute resolution from a
single hourly request, which is what keeps `optimization.tail_horizon_hours`
fed - services wrapping Open-Meteo cut the horizon much shorter. Several
Open-Meteo models can be listed in `weather_models` and are averaged per
variable at no extra request cost.

With `calibration_enabled` the provider fits itself against
`measurement.pv_production_emr_keys` over the past `calibration_days`: a
global scale factor plus optional per-solar-azimuth factors, each weighted by
modelled energy, shrunk toward the global factor by `calibration_prior_kwh`
and clamped to `[calibration_min_factor, calibration_max_factor]`. The
comparison runs on past intervals, where Open-Meteo serves analysed rather
than forecast weather, so it corrects the error of the PV model and not that
of the weather forecast.

Calibration is a scale factor on the output and never touches `userhorizon`,
`surface_tilt`, `surface_azimuth` or `peakpower`. The docs say so, and say
why a short window and a plant fault inside it are the two ways to end up
with a misleading factor.

Also add `Measurement.pv_production_total_kwh()` alongside the existing load
total, and `scripts/pvforecast_backtest.py`, which scores configuration
variants against the stored meter readings without waiting for new forecasts
to come true.
2026-09-06 18:21:20 +02:00

21 KiB

PV Forecast Configuration

:::{table} pvforecast :widths: 10 20 10 5 5 30 :align: left

Name Environment Variable Type Read-Only Default Description
max_planes EOS_PVFORECAST__MAX_PLANES Optional[int] rw 0 Maximum number of planes that can be set
planes EOS_PVFORECAST__PLANES Optional[list[akkudoktoreos.prediction.pvforecast.PVForecastPlaneSetting]] rw None Plane configuration.
planes_azimuth List[float] ro N/A Compute a list of the azimuths per active planes.
planes_inverter_paco Any ro N/A Compute a list of the maximum power rating of the inverter per active planes.
planes_peakpower List[float] ro N/A Compute a list of the peak power per active planes.
planes_tilt List[float] ro N/A Compute a list of the tilts per active planes.
planes_userhorizon Any ro N/A Compute a list of the user horizon per active planes.
provider EOS_PVFORECAST__PROVIDER Optional[str] rw None PVForecast provider id of provider to be used.
provider_settings EOS_PVFORECAST__PROVIDER_SETTINGS PVForecastCommonProviderSettings rw required Provider settings
providers list[str] ro N/A Available PVForecast provider ids.
:::

Example Input

   {
       "pvforecast": {
           "provider": "PVForecastAkkudoktor",
           "provider_settings": {
               "PVForecastImport": null,
               "PVForecastVrm": null,
               "PVForecastPVNode": null,
               "PVForecastForecastSolar": null,
               "PVForecastSolcast": null,
               "PVForecastAkkudoktorLocal": null
           },
           "planes": [
               {
                   "surface_tilt": 10.0,
                   "surface_azimuth": 180.0,
                   "userhorizon": [
                       10.0,
                       20.0,
                       30.0
                   ],
                   "peakpower": 5.0,
                   "pvtechchoice": "crystSi",
                   "mountingplace": "free",
                   "loss": 14.0,
                   "trackingtype": 0,
                   "optimal_surface_tilt": false,
                   "optimalangles": false,
                   "albedo": null,
                   "module_model": null,
                   "inverter_model": null,
                   "inverter_paco": 6000,
                   "modules_per_string": 20,
                   "strings_per_inverter": 2
               },
               {
                   "surface_tilt": 20.0,
                   "surface_azimuth": 90.0,
                   "userhorizon": [
                       5.0,
                       15.0,
                       25.0
                   ],
                   "peakpower": 3.5,
                   "pvtechchoice": "crystSi",
                   "mountingplace": "free",
                   "loss": 14.0,
                   "trackingtype": 1,
                   "optimal_surface_tilt": false,
                   "optimalangles": false,
                   "albedo": null,
                   "module_model": null,
                   "inverter_model": null,
                   "inverter_paco": 4000,
                   "modules_per_string": 20,
                   "strings_per_inverter": 2
               }
           ],
           "max_planes": 1
       }
   }

Example Output

   {
       "pvforecast": {
           "provider": "PVForecastAkkudoktor",
           "provider_settings": {
               "PVForecastImport": null,
               "PVForecastVrm": null,
               "PVForecastPVNode": null,
               "PVForecastForecastSolar": null,
               "PVForecastSolcast": null,
               "PVForecastAkkudoktorLocal": null
           },
           "planes": [
               {
                   "surface_tilt": 10.0,
                   "surface_azimuth": 180.0,
                   "userhorizon": [
                       10.0,
                       20.0,
                       30.0
                   ],
                   "peakpower": 5.0,
                   "pvtechchoice": "crystSi",
                   "mountingplace": "free",
                   "loss": 14.0,
                   "trackingtype": 0,
                   "optimal_surface_tilt": false,
                   "optimalangles": false,
                   "albedo": null,
                   "module_model": null,
                   "inverter_model": null,
                   "inverter_paco": 6000,
                   "modules_per_string": 20,
                   "strings_per_inverter": 2
               },
               {
                   "surface_tilt": 20.0,
                   "surface_azimuth": 90.0,
                   "userhorizon": [
                       5.0,
                       15.0,
                       25.0
                   ],
                   "peakpower": 3.5,
                   "pvtechchoice": "crystSi",
                   "mountingplace": "free",
                   "loss": 14.0,
                   "trackingtype": 1,
                   "optimal_surface_tilt": false,
                   "optimalangles": false,
                   "albedo": null,
                   "module_model": null,
                   "inverter_model": null,
                   "inverter_paco": 4000,
                   "modules_per_string": 20,
                   "strings_per_inverter": 2
               }
           ],
           "max_planes": 1,
           "providers": [
               "PVForecastAkkudoktor",
               "PVForecastVrm",
               "PVForecastPVNode",
               "PVForecastForecastSolar",
               "PVForecastSolcast",
               "PVForecastImport",
               "PVForecastAkkudoktorLocal"
           ],
           "planes_peakpower": [
               5.0,
               3.5
           ],
           "planes_azimuth": [
               180.0,
               90.0
           ],
           "planes_tilt": [
               10.0,
               20.0
           ],
           "planes_userhorizon": [
               [
                   10.0,
                   20.0,
                   30.0
               ],
               [
                   5.0,
                   15.0,
                   25.0
               ]
           ],
           "planes_inverter_paco": [
               6000.0,
               4000.0
           ]
       }
   }

Common settings for the local (pvlib) PV forecast provider

:::{table} pvforecast::provider_settings::PVForecastAkkudoktorLocal :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
albedo float rw 0.25 Ground albedo used for planes that do not set their own.
apply_iam bool rw True Apply the ASHRAE incidence-angle modifier to the beam component.
calibration_azimuth_bin_degrees int rw 15 Width of the solar-azimuth bins for the correction. 0 fits a single global factor only.
calibration_days int rw 30 Length of the measurement window used to fit the correction.
calibration_enabled bool rw False Correct systematic model error against measured PV production. Requires measurement.pv_production_emr_keys to be configured and fed. Fits a global scale factor plus per-solar-azimuth factors, which is what catches near-field shading the horizon profile misses.
calibration_max_factor float rw 1.5 Upper clamp on any fitted correction factor.
calibration_min_factor float rw 0.5 Lower clamp on any fitted correction factor.
calibration_prior_kwh float rw 5.0 Shrinkage strength: a bin needs this much modelled energy before its own factor outweighs the global one. Higher is more conservative.
forecast_days Optional[int] rw None Forecast horizon in days (1-16). Leave empty to derive it from prediction.hours, which is what keeps the optimizer's tail horizon fed.
inverter_efficiency float rw 0.96 Nominal inverter efficiency (PVWatts eta_inv_nom).
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.
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.
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastAkkudoktorLocal": {
                   "resolution_minutes": 15,
                   "forecast_days": null,
                   "past_days": null,
                   "weather_models": [
                       "best_match"
                   ],
                   "transposition_model": "perez",
                   "albedo": 0.25,
                   "inverter_efficiency": 0.96,
                   "temperature_coefficient": -0.36,
                   "apply_iam": true,
                   "shift_to_interval_start": true,
                   "calibration_enabled": true,
                   "calibration_days": 30,
                   "calibration_azimuth_bin_degrees": 15,
                   "calibration_prior_kwh": 5.0,
                   "calibration_min_factor": 0.5,
                   "calibration_max_factor": 1.5
               }
           }
       }
   }

Common settings for the Solcast PV forecast provider

:::{table} pvforecast::provider_settings::PVForecastSolcast :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
api_key str rw `` Solcast API key (Bearer auth). Required.
site_id str rw `` Solcast rooftop site (resource) id. Required.
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastSolcast": {
                   "api_key": "your-solcast-key",
                   "site_id": "abcd-1234-efgh-5678"
               }
           }
       }
   }

Common settings for the Forecast.Solar PV forecast provider

:::{table} pvforecast::provider_settings::PVForecastForecastSolar :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
api_key Optional[str] rw None Forecast.Solar API key. Optional — the public endpoint works without a key (lower rate limit).
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastForecastSolar": {
                   "api_key": null
               }
           }
       }
   }

Common settings for the pvnode.com PV forecast provider

:::{table} pvforecast::provider_settings::PVForecastPVNode :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
api_key str rw `` pvnode.com API key (Bearer auth). Required.
forecast_days int rw 2 Forecast horizon in days (1-7, capped by the pvnode plan).
site_id Optional[str] rw None pvnode.com site id of the saved plant ('Anlagen-ID'). When set, the saved (possibly calibrated) site is used. Leave empty to send the configured pvforecast.planes inline instead.
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastPVNode": {
                   "api_key": "pvn_live_xxxxxxxxxxxxxxxx",
                   "site_id": "abcd-1234",
                   "forecast_days": 2
               }
           }
       }
   }

Common settings for PV forecast VRM API

:::{table} pvforecast::provider_settings::PVForecastVrm :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
pvforecast_vrm_idsite int rw 12345 VRM-Installation-ID
pvforecast_vrm_token str rw your-token Token for Connecting VRM API
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastVrm": {
                   "pvforecast_vrm_token": "your-token",
                   "pvforecast_vrm_idsite": 12345
               }
           }
       }
   }

Common settings for pvforecast data import from file or JSON string

:::{table} pvforecast::provider_settings::PVForecastImport :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
import_file_path Union[str, pathlib.Path, NoneType] rw None Path to the file to import PV forecast data from.
import_json Optional[str] rw None JSON string, dictionary of PV forecast value lists.
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastImport": {
                   "import_file_path": null,
                   "import_json": "{\"pvforecast_ac_power\": [0, 8.05, 352.91]}"
               }
           }
       }
   }

PV Forecast Provider Configuration

:::{table} pvforecast::provider_settings :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
PVForecastForecastSolar Optional[akkudoktoreos.prediction.pvforecastforecastsolar.PVForecastForecastSolarCommonSettings] rw None PVForecastForecastSolar settings
PVForecastImport Optional[akkudoktoreos.prediction.pvforecastimport.PVForecastImportCommonSettings] rw None PVForecastImport settings
PVForecastAkkudoktorLocal Optional[akkudoktoreos.prediction.pvforecastlocal.PVForecastAkkudoktorLocalCommonSettings] rw None PVForecastAkkudoktorLocal settings
PVForecastPVNode Optional[akkudoktoreos.prediction.pvforecastpvnode.PVForecastPVNodeCommonSettings] rw None PVForecastPVNode settings
PVForecastSolcast Optional[akkudoktoreos.prediction.pvforecastsolcast.PVForecastSolcastCommonSettings] rw None PVForecastSolcast settings
PVForecastVrm Optional[akkudoktoreos.prediction.pvforecastvrm.PVForecastVrmCommonSettings] rw None PVForecastVrm settings
:::

Example Input/Output

   {
       "pvforecast": {
           "provider_settings": {
               "PVForecastImport": null,
               "PVForecastVrm": null,
               "PVForecastPVNode": null,
               "PVForecastForecastSolar": null,
               "PVForecastSolcast": null,
               "PVForecastAkkudoktorLocal": null
           }
       }
   }

PV Forecast Plane Configuration

:::{table} pvforecast::planes::list :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
albedo Optional[float] rw None Proportion of the light hitting the ground that it reflects back.
inverter_model Optional[str] rw None Model of the inverter of this plane.
inverter_paco Optional[int] rw None AC power rating of the inverter [W].
loss Optional[float] rw 14.0 Sum of PV system losses in percent
module_model Optional[str] rw None Model of the PV modules of this plane.
modules_per_string Optional[int] rw None Number of the PV modules of the strings of this plane.
mountingplace Optional[str] rw free Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
optimal_surface_tilt Optional[bool] rw False Calculate the optimum tilt angle. Ignored for two-axis tracking.
optimalangles Optional[bool] rw False Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
peakpower Optional[float] rw None Nominal power of PV system in kW.
pvtechchoice Optional[str] rw crystSi PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
strings_per_inverter Optional[int] rw None Number of the strings of the inverter of this plane.
surface_azimuth Optional[float] rw 180.0 Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
surface_tilt Optional[float] rw 30.0 Tilt angle from horizontal plane. Ignored for two-axis tracking.
trackingtype Optional[int] rw None Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
userhorizon Optional[List[float]] rw None Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
:::

Example Input/Output

   {
       "pvforecast": {
           "planes": [
               {
                   "surface_tilt": 10.0,
                   "surface_azimuth": 180.0,
                   "userhorizon": [
                       10.0,
                       20.0,
                       30.0
                   ],
                   "peakpower": 5.0,
                   "pvtechchoice": "crystSi",
                   "mountingplace": "free",
                   "loss": 14.0,
                   "trackingtype": 0,
                   "optimal_surface_tilt": false,
                   "optimalangles": false,
                   "albedo": null,
                   "module_model": null,
                   "inverter_model": null,
                   "inverter_paco": 6000,
                   "modules_per_string": 20,
                   "strings_per_inverter": 2
               }
           ]
       }
   }