From 52fe489d4ee5385a14d908405c94cdadaf32b44c Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 29 Jul 2026 22:21:17 +0200 Subject: [PATCH] feat: add dvhubonline feed-in tariff provider (#1193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fetches GET /api/prices?start&end&zone (15-min slots, EUR/MWh) and stores the raw market price as feed_in_tariff_wh (EUR/Wh) — no import charges/VAT. Slots beyond the day-ahead horizon are left to the consumer's forward-fill (FeedInTariffImport behaviour). 5 unit tests + opt-in live smoke (EOS_DVHUB_ONLINE_LIVE=1, passing). Signed-off-by: Christin Signed-off-by: Bobby Noelte --- .env | 2 +- config.yaml | 2 +- docs/_generated/configexample.md | 4 + docs/_generated/configfeedintariff.md | 41 +++++ docs/_generated/openapi.md | 2 +- openapi.json | 35 ++++- src/akkudoktoreos/prediction/feedintariff.py | 9 ++ .../prediction/feedintariffdvhubonline.py | 147 ++++++++++++++++++ src/akkudoktoreos/prediction/prediction.py | 6 + tests/test_feedintariffdvhubonline.py | 71 +++++++++ tests/test_prediction.py | 39 ++--- .../testdata/docs/_generated/configexample.md | 4 + .../docs/_generated/configfeedintariff.md | 41 +++++ 13 files changed, 381 insertions(+), 22 deletions(-) create mode 100644 src/akkudoktoreos/prediction/feedintariffdvhubonline.py create mode 100644 tests/test_feedintariffdvhubonline.py diff --git a/.env b/.env index f19a00b3..9f4a204a 100644 --- a/.env +++ b/.env @@ -11,7 +11,7 @@ DOCKER_COMPOSE_DATA_DIR=${HOME}/.local/share/net.akkudoktor.eos # ----------------------------------------------------------------------------- # Image / build # ----------------------------------------------------------------------------- -VERSION=0.3.0.dev2607290898922686 +VERSION=0.3.0.dev2607290944830210 PYTHON_VERSION=3.13.9 # ----------------------------------------------------------------------------- diff --git a/config.yaml b/config.yaml index 3e44d503..62046de7 100644 --- a/config.yaml +++ b/config.yaml @@ -6,7 +6,7 @@ # the root directory (no add-on folder as usual). name: "Akkudoktor-EOS" -version: "0.3.0.dev2607290898922686" +version: "0.3.0.dev2607290944830210" slug: "eos" description: "Akkudoktor-EOS add-on" url: "https://github.com/Akkudoktor-EOS/EOS" diff --git a/docs/_generated/configexample.md b/docs/_generated/configexample.md index 53c340f3..a9d63b5d 100644 --- a/docs/_generated/configexample.md +++ b/docs/_generated/configexample.md @@ -130,6 +130,10 @@ "import_file_path": null, "import_json": null }, + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + }, "energycharts": { "bidding_zone": "DE-LU" } diff --git a/docs/_generated/configfeedintariff.md b/docs/_generated/configfeedintariff.md index 1af6b33a..8d23b479 100644 --- a/docs/_generated/configfeedintariff.md +++ b/docs/_generated/configfeedintariff.md @@ -7,6 +7,7 @@ | Name | Environment Variable | Type | Read-Only | Default | Description | | ---- | -------------------- | ---- | --------- | ------- | ----------- | +| dvhubonline | `EOS_FEEDINTARIFF__DVHUBONLINE` | `FeedInTariffDvhubOnlineCommonSettings` | `rw` | `required` | DvhubOnline feed in tariff provider settings. | | energycharts | `EOS_FEEDINTARIFF__ENERGYCHARTS` | `FeedInTariffEnergyChartsCommonSettings` | `rw` | `required` | EnergyCharts feed in tariff provider settings. | | feedintarifffixed | `EOS_FEEDINTARIFF__FEEDINTARIFFFIXED` | `FeedInTariffFixedCommonSettings` | `rw` | `required` | Fixed feed in tariff provider settings. | | feedintariffimport | `EOS_FEEDINTARIFF__FEEDINTARIFFIMPORT` | `FeedInTariffImportCommonSettings` | `rw` | `required` | Feed in tarif import provider settings. | @@ -31,6 +32,10 @@ "import_file_path": null, "import_json": null }, + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + }, "energycharts": { "bidding_zone": "DE-LU" } @@ -55,11 +60,16 @@ "import_file_path": null, "import_json": null }, + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + }, "energycharts": { "bidding_zone": "DE-LU" }, "providers": [ "FeedInTariffAkkudoktor", + "FeedInTariffDvhubOnline", "FeedInTariffEnergyCharts", "FeedInTariffFixed", "FeedInTariffImport", @@ -158,3 +168,34 @@ } ``` + +### Common settings for the dvhub.online feed-in tariff provider + + +:::{table} feedintariff::dvhubonline +:widths: 10 10 5 5 30 +:align: left + +| Name | Type | Read-Only | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| base_url | `str` | `rw` | `https://dvhub.online` | Base URL of the dvhub.online price API. | +| zone | `str` | `rw` | `DE-LU` | Bidding zone passed to the dvhub.online price API. | +::: + + + +**Example Input/Output** + + + +```json + { + "feedintariff": { + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + } + } + } +``` + diff --git a/docs/_generated/openapi.md b/docs/_generated/openapi.md index 5cdf6dd7..968eb46c 100644 --- a/docs/_generated/openapi.md +++ b/docs/_generated/openapi.md @@ -1,6 +1,6 @@ # Akkudoktor-EOS -**Version**: `v0.3.0.dev2607290898922686` +**Version**: `v0.3.0.dev2607290944830210` **Description**: This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period. diff --git a/openapi.json b/openapi.json index d27b44f1..8d061f93 100644 --- a/openapi.json +++ b/openapi.json @@ -8,7 +8,7 @@ "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "v0.3.0.dev2607290898922686" + "version": "v0.3.0.dev2607290944830210" }, "paths": { "/v1/admin/cache/clear": { @@ -4322,6 +4322,10 @@ "$ref": "#/components/schemas/FeedInTariffImportCommonSettings", "description": "Feed in tarif import provider settings." }, + "dvhubonline": { + "$ref": "#/components/schemas/FeedInTariffDvhubOnlineCommonSettings", + "description": "DvhubOnline feed in tariff provider settings." + }, "energycharts": { "$ref": "#/components/schemas/FeedInTariffEnergyChartsCommonSettings", "description": "EnergyCharts feed in tariff provider settings." @@ -4357,6 +4361,10 @@ "$ref": "#/components/schemas/FeedInTariffImportCommonSettings", "description": "Feed in tarif import provider settings." }, + "dvhubonline": { + "$ref": "#/components/schemas/FeedInTariffDvhubOnlineCommonSettings", + "description": "DvhubOnline feed in tariff provider settings." + }, "energycharts": { "$ref": "#/components/schemas/FeedInTariffEnergyChartsCommonSettings", "description": "EnergyCharts feed in tariff provider settings." @@ -4378,6 +4386,31 @@ "title": "FeedInTariffCommonSettings", "description": "Feed In Tariff Prediction Configuration." }, + "FeedInTariffDvhubOnlineCommonSettings": { + "properties": { + "base_url": { + "type": "string", + "title": "Base Url", + "description": "Base URL of the dvhub.online price API.", + "default": "https://dvhub.online", + "examples": [ + "https://dvhub.online" + ] + }, + "zone": { + "type": "string", + "title": "Zone", + "description": "Bidding zone passed to the dvhub.online price API.", + "default": "DE-LU", + "examples": [ + "DE-LU" + ] + } + }, + "type": "object", + "title": "FeedInTariffDvhubOnlineCommonSettings", + "description": "Common settings for the dvhub.online feed-in tariff provider." + }, "FeedInTariffEnergyChartsCommonSettings": { "properties": { "bidding_zone": { diff --git a/src/akkudoktoreos/prediction/feedintariff.py b/src/akkudoktoreos/prediction/feedintariff.py index 9bcdcbec..c8700dbc 100644 --- a/src/akkudoktoreos/prediction/feedintariff.py +++ b/src/akkudoktoreos/prediction/feedintariff.py @@ -5,6 +5,9 @@ from pydantic import Field, computed_field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel from akkudoktoreos.core.coreabc import get_prediction from akkudoktoreos.prediction.feedintariffabc import FeedInTariffProvider +from akkudoktoreos.prediction.feedintariffdvhubonline import ( + FeedInTariffDvhubOnlineCommonSettings, +) from akkudoktoreos.prediction.feedintariffenergycharts import ( FeedInTariffEnergyChartsCommonSettings, ) @@ -20,6 +23,7 @@ def feedintariff_provider_ids() -> list[str]: # Prediction may not be initialized. Return static built-in provider ids. return [ "FeedInTariffAkkudoktor", + "FeedInTariffDvhubOnline", "FeedInTariffEnergyCharts", "FeedInTariffFixed", "FeedInTariffImport", @@ -54,6 +58,11 @@ class FeedInTariffCommonSettings(SettingsBaseModel): json_schema_extra={"description": "Feed in tarif import provider settings."}, ) + dvhubonline: FeedInTariffDvhubOnlineCommonSettings = Field( + default_factory=FeedInTariffDvhubOnlineCommonSettings, + json_schema_extra={"description": "DvhubOnline feed in tariff provider settings."}, + ) + energycharts: FeedInTariffEnergyChartsCommonSettings = Field( default_factory=FeedInTariffEnergyChartsCommonSettings, json_schema_extra={"description": "EnergyCharts feed in tariff provider settings."}, diff --git a/src/akkudoktoreos/prediction/feedintariffdvhubonline.py b/src/akkudoktoreos/prediction/feedintariffdvhubonline.py new file mode 100644 index 00000000..7632ba90 --- /dev/null +++ b/src/akkudoktoreos/prediction/feedintariffdvhubonline.py @@ -0,0 +1,147 @@ +"""Provides feed-in tariff data from the dvhub.online price API.""" + +import time +from datetime import datetime +from typing import Any, Optional + +import pandas as pd +import requests +from loguru import logger +from pydantic import Field + +from akkudoktoreos.config.configabc import SettingsBaseModel +from akkudoktoreos.core.cache import cache_in_file +from akkudoktoreos.prediction.feedintariffabc import FeedInTariffProvider +from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration + + +class FeedInTariffDvhubOnlineCommonSettings(SettingsBaseModel): + """Common settings for the dvhub.online feed-in tariff provider.""" + + base_url: str = Field( + default="https://dvhub.online", + json_schema_extra={ + "description": "Base URL of the dvhub.online price API.", + "examples": ["https://dvhub.online"], + }, + ) + zone: str = Field( + default="DE-LU", + json_schema_extra={ + "description": "Bidding zone passed to the dvhub.online price API.", + "examples": ["DE-LU"], + }, + ) + + +class FeedInTariffDvhubOnline(FeedInTariffProvider): + """Fetch dvhub.online day-ahead market prices as feed-in tariff data. + + dvhub.online serves EPEX day-ahead prices (15-minute slots, EUR/MWh) via + ``GET /api/prices?start=YYYY-MM-DD&end=YYYY-MM-DD&zone=DE-LU``. The raw + market price is stored as ``feed_in_tariff_wh`` (EUR/Wh) — like + ``FeedInTariffEnergyCharts`` this intentionally adds no import charges or + VAT, so the series is the direct-marketing revenue the optimizer needs. + Slots beyond the published day-ahead horizon are left to the consumer's + forward-fill (same behaviour as ``FeedInTariffImport``). + """ + + highest_orig_datetime: Optional[datetime] = None + + def historic_hours_min(self) -> int: + """Day-ahead source without seasonal extrapolation — keep two days.""" + return 48 + + @classmethod + def provider_id(cls) -> str: + """Return the unique identifier for the dvhub.online feed-in tariff provider.""" + return "FeedInTariffDvhubOnline" + + def _provider_settings(self) -> FeedInTariffDvhubOnlineCommonSettings: + settings = self.config.feedintariff.dvhubonline + if settings is None: + return FeedInTariffDvhubOnlineCommonSettings() + return settings + + @cache_in_file(with_ttl="1 hour") + def _request_forecast(self, start_date: str, end_date: str) -> dict[str, Any]: + """Fetch market prices from the dvhub.online price API.""" + settings = self._provider_settings() + url = ( + f"{settings.base_url}/api/prices?start={start_date}&end={end_date}&zone={settings.zone}" + ) + # Retry transient network problems with a short backoff (same pattern + # as FeedInTariffEnergyCharts): (connect, read) timeout tuple. + max_attempts = 3 + last_exc: Optional[Exception] = None + for attempt in range(1, max_attempts + 1): + try: + response = requests.get( + url, headers={"accept": "application/json"}, timeout=(5, 60) + ) + logger.debug(f"Response from {url}: {response}") + response.raise_for_status() + data = response.json() + if not isinstance(data, dict) or not isinstance(data.get("data"), list): + raise ValueError(f"Unexpected dvhub.online price API response shape from {url}") + self.update_datetime = to_datetime(in_timezone=self.config.general.timezone) + return data + except ( + requests.exceptions.Timeout, + requests.exceptions.ConnectionError, + ) as exc: + last_exc = exc + logger.warning( + "dvhub.online price request attempt {}/{} failed: {}", + attempt, + max_attempts, + exc, + ) + if attempt < max_attempts: + time.sleep(2 * attempt) + raise last_exc # type: ignore[misc] + + def _parse_data(self, dvhub_data: dict[str, Any]) -> pd.Series: + """dvhub.online entries {ts, price[EUR/MWh]} -> Series of EUR/Wh.""" + series_data = pd.Series(dtype=float) + for entry in dvhub_data.get("data", []): + try: + orig_datetime = to_datetime(entry["ts"], in_timezone=self.config.general.timezone) + price_eur_per_mwh = float(entry["price"]) + except (KeyError, TypeError, ValueError) as exc: + logger.warning("Skipping malformed dvhub.online price entry {}: {}", entry, exc) + continue + series_data.at[orig_datetime] = price_eur_per_mwh / 1_000_000 + return series_data + + async def _update_data(self, force_update: Optional[bool] = False) -> None: + """Update feed-in tariff forecast data from dvhub.online.""" + if not self.ems_start_datetime: + raise ValueError(f"Start DateTime not set: {self.ems_start_datetime}") + + start_date = to_datetime( + self.ems_start_datetime - to_duration("2 days"), as_string="YYYY-MM-DD" + ) + end_date = to_datetime(self.end_datetime, as_string="YYYY-MM-DD") + + try: + dvhub_data = self._request_forecast( + start_date=start_date, end_date=end_date, force_update=force_update + ) # type: ignore[call-arg] + series_data = self._parse_data(dvhub_data) + if series_data.empty: + raise ValueError("No dvhub.online feed-in tariff data available") + self.highest_orig_datetime = series_data.index.max() + await self.key_from_series("feed_in_tariff_wh", series_data) + except Exception as exc: + if self.highest_orig_datetime is None: + # Cold start: nothing to fall back to — a failed fetch is fatal. + raise + # Transient outage with existing history: keep the history and let + # downstream forward-fill cover the remaining slots. + logger.warning( + "dvhub.online feed-in tariff update failed ({}); keeping existing " + "history until {}.", + exc, + self.highest_orig_datetime, + ) diff --git a/src/akkudoktoreos/prediction/prediction.py b/src/akkudoktoreos/prediction/prediction.py index 0a79e6a9..3fe97eab 100644 --- a/src/akkudoktoreos/prediction/prediction.py +++ b/src/akkudoktoreos/prediction/prediction.py @@ -37,6 +37,7 @@ from akkudoktoreos.prediction.elecpricefixed import ElecPriceFixed from akkudoktoreos.prediction.elecpriceimport import ElecPriceImport from akkudoktoreos.prediction.elecpricetibber import ElecPriceTibber from akkudoktoreos.prediction.feedintariffakkudoktor import FeedInTariffAkkudoktor +from akkudoktoreos.prediction.feedintariffdvhubonline import FeedInTariffDvhubOnline from akkudoktoreos.prediction.feedintariffenergycharts import FeedInTariffEnergyCharts from akkudoktoreos.prediction.feedintarifffixed import FeedInTariffFixed from akkudoktoreos.prediction.feedintariffimport import FeedInTariffImport @@ -85,6 +86,7 @@ elecprice_fixed = ElecPriceFixed() elecprice_import = ElecPriceImport() elecprice_tibber = ElecPriceTibber() feedintariff_akkudoktor = FeedInTariffAkkudoktor() +feedintariff_dvhubonline = FeedInTariffDvhubOnline() feedintariff_energy_charts = FeedInTariffEnergyCharts() feedintariff_fixed = FeedInTariffFixed() feedintariff_import = FeedInTariffImport() @@ -113,6 +115,7 @@ def prediction_providers() -> list[ ElecPriceImport, ElecPriceTibber, FeedInTariffAkkudoktor, + FeedInTariffDvhubOnline, FeedInTariffEnergyCharts, FeedInTariffFixed, FeedInTariffImport, @@ -144,6 +147,7 @@ def prediction_providers() -> list[ elecprice_import, \ elecprice_tibber, \ feedintariff_akkudoktor, \ + feedintariff_dvhubonline, \ feedintariff_energy_charts, \ feedintariff_fixed, \ feedintariff_import, \ @@ -171,6 +175,7 @@ def prediction_providers() -> list[ elecprice_import, elecprice_tibber, feedintariff_akkudoktor, + feedintariff_dvhubonline, feedintariff_energy_charts, feedintariff_fixed, feedintariff_import, @@ -203,6 +208,7 @@ class Prediction(PredictionContainer): ElecPriceImport, ElecPriceTibber, FeedInTariffAkkudoktor, + FeedInTariffDvhubOnline, FeedInTariffEnergyCharts, FeedInTariffFixed, FeedInTariffImport, diff --git a/tests/test_feedintariffdvhubonline.py b/tests/test_feedintariffdvhubonline.py new file mode 100644 index 00000000..9cce0aac --- /dev/null +++ b/tests/test_feedintariffdvhubonline.py @@ -0,0 +1,71 @@ +"""Tests for the dvhub.online feed-in tariff provider.""" + +import os +from unittest.mock import MagicMock, patch + +import pytest + +from akkudoktoreos.prediction.feedintariffdvhubonline import ( + FeedInTariffDvhubOnline, + FeedInTariffDvhubOnlineCommonSettings, +) + +SAMPLE = { + "data": [ + {"ts": "2026-07-19T12:00:00.000Z", "price": 42.5}, # EUR/MWh + {"ts": "2026-07-19T12:15:00.000Z", "price": -5.69}, # negative slot + {"ts": "bogus", "price": 10.0}, # malformed -> skipped + {"ts": "2026-07-19T12:30:00.000Z", "price": "x"}, # malformed -> skipped + ] +} + + +@pytest.fixture +def provider(config_eos): + config_eos.merge_settings_from_dict( + {"feedintariff": {"provider": "FeedInTariffDvhubOnline"}} + ) + return FeedInTariffDvhubOnline() + + +class TestFeedInTariffDvhubOnline: + def test_provider_id_registered(self, provider, config_eos): + assert provider.provider_id() == "FeedInTariffDvhubOnline" + assert provider.enabled() + assert "FeedInTariffDvhubOnline" in config_eos.feedintariff.providers + + def test_parse_data_eur_mwh_to_eur_wh(self, provider): + series = provider._parse_data(SAMPLE) + assert len(series) == 2 # malformed entries skipped + assert series.iloc[0] == pytest.approx(42.5 / 1_000_000) + assert series.iloc[1] == pytest.approx(-5.69 / 1_000_000) # negatives kept + + def test_default_settings(self, provider): + settings = provider._provider_settings() + assert settings.base_url == "https://dvhub.online" + assert settings.zone == "DE-LU" + + def test_request_shape_guard(self, provider): + response = MagicMock() + response.raise_for_status.return_value = None + response.json.return_value = {"unexpected": True} + with patch( + "akkudoktoreos.prediction.feedintariffdvhubonline.requests.get", + return_value=response, + ): + with pytest.raises(ValueError, match="response shape"): + provider._request_forecast( + start_date="2026-07-19", end_date="2026-07-21", force_update=True + ) + + +@pytest.mark.skipif( + os.environ.get("EOS_DVHUB_ONLINE_LIVE") != "1", + reason="live API smoke — set EOS_DVHUB_ONLINE_LIVE=1 to run", +) +def test_live_api_smoke(provider): + data = provider._request_forecast( + start_date="2026-07-19", end_date="2026-07-20", force_update=True + ) + series = provider._parse_data(data) + assert not series.empty diff --git a/tests/test_prediction.py b/tests/test_prediction.py index aaa26461..d88d447a 100644 --- a/tests/test_prediction.py +++ b/tests/test_prediction.py @@ -8,6 +8,7 @@ from akkudoktoreos.prediction.elecpricefixed import ElecPriceFixed from akkudoktoreos.prediction.elecpriceimport import ElecPriceImport from akkudoktoreos.prediction.elecpricetibber import ElecPriceTibber from akkudoktoreos.prediction.feedintariffakkudoktor import FeedInTariffAkkudoktor +from akkudoktoreos.prediction.feedintariffdvhubonline import FeedInTariffDvhubOnline from akkudoktoreos.prediction.feedintariffenergycharts import FeedInTariffEnergyCharts from akkudoktoreos.prediction.feedintarifffixed import FeedInTariffFixed from akkudoktoreos.prediction.feedintariffimport import FeedInTariffImport @@ -50,6 +51,7 @@ def forecast_providers(): ElecPriceImport(), ElecPriceTibber(), FeedInTariffAkkudoktor(), + FeedInTariffDvhubOnline(), FeedInTariffEnergyCharts(), FeedInTariffFixed(), FeedInTariffImport(), @@ -106,24 +108,25 @@ def test_provider_sequence(prediction): assert isinstance(prediction.providers[3], ElecPriceImport) assert isinstance(prediction.providers[4], ElecPriceTibber) assert isinstance(prediction.providers[5], FeedInTariffAkkudoktor) - assert isinstance(prediction.providers[6], FeedInTariffEnergyCharts) - assert isinstance(prediction.providers[7], FeedInTariffFixed) - assert isinstance(prediction.providers[8], FeedInTariffImport) - assert isinstance(prediction.providers[9], FeedInTariffTibber) - assert isinstance(prediction.providers[10], LoadAkkudoktor) - assert isinstance(prediction.providers[11], LoadAkkudoktorAdjusted) - assert isinstance(prediction.providers[12], LoadVrm) - assert isinstance(prediction.providers[13], LoadImport) - assert isinstance(prediction.providers[14], PVForecastAkkudoktor) - assert isinstance(prediction.providers[15], PVForecastVrm) - assert isinstance(prediction.providers[16], PVForecastPVNode) - assert isinstance(prediction.providers[17], PVForecastForecastSolar) - assert isinstance(prediction.providers[18], PVForecastSolcast) - assert isinstance(prediction.providers[19], PVForecastImport) - assert isinstance(prediction.providers[20], WeatherBrightSky) - assert isinstance(prediction.providers[21], WeatherClearOutside) - assert isinstance(prediction.providers[22], WeatherOpenMeteo) - assert isinstance(prediction.providers[23], WeatherImport) + assert isinstance(prediction.providers[6], FeedInTariffDvhubOnline) + assert isinstance(prediction.providers[7], FeedInTariffEnergyCharts) + assert isinstance(prediction.providers[8], FeedInTariffFixed) + assert isinstance(prediction.providers[9], FeedInTariffImport) + assert isinstance(prediction.providers[10], FeedInTariffTibber) + assert isinstance(prediction.providers[11], LoadAkkudoktor) + assert isinstance(prediction.providers[12], LoadAkkudoktorAdjusted) + assert isinstance(prediction.providers[13], LoadVrm) + assert isinstance(prediction.providers[14], LoadImport) + assert isinstance(prediction.providers[15], PVForecastAkkudoktor) + assert isinstance(prediction.providers[16], PVForecastVrm) + assert isinstance(prediction.providers[17], PVForecastPVNode) + assert isinstance(prediction.providers[18], PVForecastForecastSolar) + assert isinstance(prediction.providers[19], PVForecastSolcast) + assert isinstance(prediction.providers[20], PVForecastImport) + assert isinstance(prediction.providers[21], WeatherBrightSky) + assert isinstance(prediction.providers[22], WeatherClearOutside) + assert isinstance(prediction.providers[23], WeatherOpenMeteo) + assert isinstance(prediction.providers[24], WeatherImport) def test_provider_by_id(prediction, forecast_providers): diff --git a/tests/testdata/docs/_generated/configexample.md b/tests/testdata/docs/_generated/configexample.md index 53c340f3..a9d63b5d 100644 --- a/tests/testdata/docs/_generated/configexample.md +++ b/tests/testdata/docs/_generated/configexample.md @@ -130,6 +130,10 @@ "import_file_path": null, "import_json": null }, + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + }, "energycharts": { "bidding_zone": "DE-LU" } diff --git a/tests/testdata/docs/_generated/configfeedintariff.md b/tests/testdata/docs/_generated/configfeedintariff.md index 1af6b33a..8d23b479 100644 --- a/tests/testdata/docs/_generated/configfeedintariff.md +++ b/tests/testdata/docs/_generated/configfeedintariff.md @@ -7,6 +7,7 @@ | Name | Environment Variable | Type | Read-Only | Default | Description | | ---- | -------------------- | ---- | --------- | ------- | ----------- | +| dvhubonline | `EOS_FEEDINTARIFF__DVHUBONLINE` | `FeedInTariffDvhubOnlineCommonSettings` | `rw` | `required` | DvhubOnline feed in tariff provider settings. | | energycharts | `EOS_FEEDINTARIFF__ENERGYCHARTS` | `FeedInTariffEnergyChartsCommonSettings` | `rw` | `required` | EnergyCharts feed in tariff provider settings. | | feedintarifffixed | `EOS_FEEDINTARIFF__FEEDINTARIFFFIXED` | `FeedInTariffFixedCommonSettings` | `rw` | `required` | Fixed feed in tariff provider settings. | | feedintariffimport | `EOS_FEEDINTARIFF__FEEDINTARIFFIMPORT` | `FeedInTariffImportCommonSettings` | `rw` | `required` | Feed in tarif import provider settings. | @@ -31,6 +32,10 @@ "import_file_path": null, "import_json": null }, + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + }, "energycharts": { "bidding_zone": "DE-LU" } @@ -55,11 +60,16 @@ "import_file_path": null, "import_json": null }, + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + }, "energycharts": { "bidding_zone": "DE-LU" }, "providers": [ "FeedInTariffAkkudoktor", + "FeedInTariffDvhubOnline", "FeedInTariffEnergyCharts", "FeedInTariffFixed", "FeedInTariffImport", @@ -158,3 +168,34 @@ } ``` + +### Common settings for the dvhub.online feed-in tariff provider + + +:::{table} feedintariff::dvhubonline +:widths: 10 10 5 5 30 +:align: left + +| Name | Type | Read-Only | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| base_url | `str` | `rw` | `https://dvhub.online` | Base URL of the dvhub.online price API. | +| zone | `str` | `rw` | `DE-LU` | Bidding zone passed to the dvhub.online price API. | +::: + + + +**Example Input/Output** + + + +```json + { + "feedintariff": { + "dvhubonline": { + "base_url": "https://dvhub.online", + "zone": "DE-LU" + } + } + } +``` +