mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-07-20 08:48:11 +00:00
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
This PR adds three native PV power forecast providers, giving operators more
cloud forecast sources to choose from via pvforecast.provider, alongside the
existing PVForecastAkkudoktor, PVForecastVrm and PVForecastImport:
PVForecastPVNode — native 15-minute forecasts from the pvnode.com
V2 API. Saved-site mode (GET /v2/forecast/{site_id}) where the operator enters their API key +
site id, or inline mode (POST /v2/forecast/inline) using the configured planes.
PVForecastForecastSolar — the free Forecast.Solar API (no key
required for the public endpoint). Multi-plane systems issue one request per plane and the
instantaneous powers are summed per timestamp.
PVForecastSolcast — the Solcast rooftop-site API (API key + resource id).
Implementation notes
All three populate the existing pvforecast_ac_power (and mirror pvforecast_dc_power)
prediction keys, so they slot into the optimizer unchanged.
Timezone handling: pvnode and Forecast.Solar return site-local wall-clock timestamps with an
IANA timezone field, which are resolved to absolute instants before resampling; Solcast returns
UTC period_end and is normalised to the period start.
Each provider follows the existing provider pattern (provider_id(), _request_forecast() with
@cache_in_file, _update_data()), is registered in pvforecast.py and prediction.py, and is
upstream-neutral.
Validation
27 unit tests (timezone resolution, null/zero handling, kW→W and period-start conversion,
azimuth conversion, multi-plane summation, request URL/auth, HTTP-error handling).
ruff check (F/D/S/bandit) and ruff format clean.
Each provider was additionally validated against its live API with a real plant, confirming
the response shapes (pvnode: 288 native 15-min slots; Forecast.Solar: instantaneous watts;
Solcast: kW estimates with period_end/PT30M).
Documentation
Provider descriptions and configuration examples added to docs/akkudoktoreos/prediction.md.
CHANGELOG.md entry under Unreleased.
Regenerated docs/_generated/configpvforecast.md and configexample.md.
Notes for reviewers
Forecast.Solar's free endpoint is rate-limited (12 req/hour) and Solcast's free tier limits daily
calls; both providers rely on the standard 1-hour cache_in_file TTL to stay within budget.
Authors:
The code is created by Christin. Only minor adaptions by Bobby.
Signed-off-by: Christin <info@bikinibottom.capital>
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Co-authored-by: Christin <info@bikinibottom.capital>
205 lines
6.8 KiB
Python
205 lines
6.8 KiB
Python
"""Prediction module for weather and photovoltaic forecasts.
|
|
|
|
This module provides a `Prediction` class to manage and update a sequence of
|
|
prediction providers. The `Prediction` class is a subclass of `PredictionContainer`
|
|
and is initialized with a set of forecast providers, such as `WeatherBrightSky`,
|
|
`WeatherClearOutside`, and `PVForecastAkkudoktor`.
|
|
|
|
Usage:
|
|
Instantiate the `Prediction` class with the required providers, maintaining
|
|
the necessary order. Then call the `update` method to refresh forecasts from
|
|
all providers in sequence.
|
|
|
|
Example:
|
|
# Create singleton prediction instance with prediction providers
|
|
from akkudoktoreos.prediction.prediction import prediction
|
|
|
|
await prediction.update_data()
|
|
print("Prediction:", prediction)
|
|
|
|
Classes:
|
|
Prediction: Manages a list of forecast providers to fetch and update predictions.
|
|
|
|
Attributes:
|
|
pvforecast_akkudoktor (PVForecastAkkudoktor): Forecast provider for photovoltaic data.
|
|
weather_brightsky (WeatherBrightSky): Weather forecast provider using BrightSky.
|
|
weather_clearoutside (WeatherClearOutside): Weather forecast provider using ClearOutside.
|
|
"""
|
|
|
|
from typing import Optional, Union
|
|
|
|
from pydantic import Field
|
|
|
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
|
from akkudoktoreos.prediction.elecpriceakkudoktor import ElecPriceAkkudoktor
|
|
from akkudoktoreos.prediction.elecpriceenergycharts import ElecPriceEnergyCharts
|
|
from akkudoktoreos.prediction.elecpricefixed import ElecPriceFixed
|
|
from akkudoktoreos.prediction.elecpriceimport import ElecPriceImport
|
|
from akkudoktoreos.prediction.feedintarifffixed import FeedInTariffFixed
|
|
from akkudoktoreos.prediction.feedintariffimport import FeedInTariffImport
|
|
from akkudoktoreos.prediction.loadakkudoktor import (
|
|
LoadAkkudoktor,
|
|
LoadAkkudoktorAdjusted,
|
|
)
|
|
from akkudoktoreos.prediction.loadimport import LoadImport
|
|
from akkudoktoreos.prediction.loadvrm import LoadVrm
|
|
from akkudoktoreos.prediction.predictionabc import PredictionContainer
|
|
from akkudoktoreos.prediction.pvforecastakkudoktor import PVForecastAkkudoktor
|
|
from akkudoktoreos.prediction.pvforecastforecastsolar import PVForecastForecastSolar
|
|
from akkudoktoreos.prediction.pvforecastimport import PVForecastImport
|
|
from akkudoktoreos.prediction.pvforecastpvnode import PVForecastPVNode
|
|
from akkudoktoreos.prediction.pvforecastsolcast import PVForecastSolcast
|
|
from akkudoktoreos.prediction.pvforecastvrm import PVForecastVrm
|
|
from akkudoktoreos.prediction.weatherbrightsky import WeatherBrightSky
|
|
from akkudoktoreos.prediction.weatherclearoutside import WeatherClearOutside
|
|
from akkudoktoreos.prediction.weatherimport import WeatherImport
|
|
from akkudoktoreos.prediction.weatheropenmeteo import WeatherOpenMeteo
|
|
|
|
|
|
class PredictionCommonSettings(SettingsBaseModel):
|
|
"""General Prediction Configuration."""
|
|
|
|
hours: Optional[int] = Field(
|
|
default=48,
|
|
ge=0,
|
|
json_schema_extra={"description": "Number of hours into the future for predictions"},
|
|
)
|
|
|
|
historic_hours: Optional[int] = Field(
|
|
default=48,
|
|
ge=0,
|
|
json_schema_extra={
|
|
"description": "Number of hours into the past for historical predictions data"
|
|
},
|
|
)
|
|
|
|
|
|
# Initialize forecast providers, all are singletons.
|
|
elecprice_akkudoktor = ElecPriceAkkudoktor()
|
|
elecprice_energy_charts = ElecPriceEnergyCharts()
|
|
elecprice_fixed = ElecPriceFixed()
|
|
elecprice_import = ElecPriceImport()
|
|
feedintariff_fixed = FeedInTariffFixed()
|
|
feedintariff_import = FeedInTariffImport()
|
|
loadforecast_akkudoktor = LoadAkkudoktor()
|
|
loadforecast_akkudoktor_adjusted = LoadAkkudoktorAdjusted()
|
|
loadforecast_vrm = LoadVrm()
|
|
loadforecast_import = LoadImport()
|
|
pvforecast_akkudoktor = PVForecastAkkudoktor()
|
|
pvforecast_vrm = PVForecastVrm()
|
|
pvforecast_pvnode = PVForecastPVNode()
|
|
pvforecast_forecastsolar = PVForecastForecastSolar()
|
|
pvforecast_solcast = PVForecastSolcast()
|
|
pvforecast_import = PVForecastImport()
|
|
weather_brightsky = WeatherBrightSky()
|
|
weather_clearoutside = WeatherClearOutside()
|
|
weather_openmeteo = WeatherOpenMeteo()
|
|
weather_import = WeatherImport()
|
|
|
|
|
|
def prediction_providers() -> list[
|
|
Union[
|
|
ElecPriceAkkudoktor,
|
|
ElecPriceEnergyCharts,
|
|
ElecPriceFixed,
|
|
ElecPriceImport,
|
|
FeedInTariffFixed,
|
|
FeedInTariffImport,
|
|
LoadAkkudoktor,
|
|
LoadAkkudoktorAdjusted,
|
|
LoadVrm,
|
|
LoadImport,
|
|
PVForecastAkkudoktor,
|
|
PVForecastVrm,
|
|
PVForecastPVNode,
|
|
PVForecastForecastSolar,
|
|
PVForecastSolcast,
|
|
PVForecastImport,
|
|
WeatherBrightSky,
|
|
WeatherClearOutside,
|
|
WeatherOpenMeteo,
|
|
WeatherImport,
|
|
]
|
|
]:
|
|
"""Return list of prediction providers.
|
|
|
|
Factory for prediction container.
|
|
"""
|
|
global \
|
|
elecprice_akkudoktor, \
|
|
elecprice_energy_charts, \
|
|
elecprice_fixed, \
|
|
elecprice_import, \
|
|
feedintariff_fixed, \
|
|
feedintariff_import, \
|
|
loadforecast_akkudoktor, \
|
|
loadforecast_akkudoktor_adjusted, \
|
|
loadforecast_vrm, \
|
|
loadforecast_import, \
|
|
pvforecast_akkudoktor, \
|
|
pvforecast_vrm, \
|
|
pvforecast_pvnode, \
|
|
pvforecast_forecastsolar, \
|
|
pvforecast_solcast, \
|
|
pvforecast_import, \
|
|
weather_brightsky, \
|
|
weather_clearoutside, \
|
|
weather_openmeteo, \
|
|
weather_import
|
|
|
|
# Care for provider sequence as providers may rely on others to be updated before.
|
|
return [
|
|
elecprice_akkudoktor,
|
|
elecprice_energy_charts,
|
|
elecprice_fixed,
|
|
elecprice_import,
|
|
feedintariff_fixed,
|
|
feedintariff_import,
|
|
loadforecast_akkudoktor,
|
|
loadforecast_akkudoktor_adjusted,
|
|
loadforecast_vrm,
|
|
loadforecast_import,
|
|
pvforecast_akkudoktor,
|
|
pvforecast_vrm,
|
|
pvforecast_pvnode,
|
|
pvforecast_forecastsolar,
|
|
pvforecast_solcast,
|
|
pvforecast_import,
|
|
weather_brightsky,
|
|
weather_clearoutside,
|
|
weather_openmeteo,
|
|
weather_import,
|
|
]
|
|
|
|
|
|
class Prediction(PredictionContainer):
|
|
"""Prediction container to manage multiple prediction providers."""
|
|
|
|
providers: list[
|
|
Union[
|
|
ElecPriceAkkudoktor,
|
|
ElecPriceEnergyCharts,
|
|
ElecPriceFixed,
|
|
ElecPriceImport,
|
|
FeedInTariffFixed,
|
|
FeedInTariffImport,
|
|
LoadAkkudoktor,
|
|
LoadAkkudoktorAdjusted,
|
|
LoadVrm,
|
|
LoadImport,
|
|
PVForecastAkkudoktor,
|
|
PVForecastVrm,
|
|
PVForecastPVNode,
|
|
PVForecastForecastSolar,
|
|
PVForecastSolcast,
|
|
PVForecastImport,
|
|
WeatherBrightSky,
|
|
WeatherClearOutside,
|
|
WeatherOpenMeteo,
|
|
WeatherImport,
|
|
]
|
|
] = Field(
|
|
default_factory=prediction_providers,
|
|
json_schema_extra={"description": "List of prediction providers"},
|
|
)
|