mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-06-28 00:46:53 +00:00
fix: pvforecast fails when there is only a single plane (#569)
* fix: pvforecast fails when there is only a single plane * fix: formatting * fix: formatting * fix: add type annotations * add testdata and validation test for single plane * fix: formatting
This commit is contained in:
parent
058356d1b8
commit
e413543222
@ -78,7 +78,7 @@ Methods:
|
|||||||
from typing import Any, List, Optional, Union
|
from typing import Any, List, Optional, Union
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from pydantic import Field, ValidationError, computed_field
|
from pydantic import Field, ValidationError, computed_field, field_validator
|
||||||
|
|
||||||
from akkudoktoreos.core.cache import cache_in_file
|
from akkudoktoreos.core.cache import cache_in_file
|
||||||
from akkudoktoreos.core.logging import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
@ -114,6 +114,30 @@ class AkkudoktorForecastMeta(PydanticBaseModel):
|
|||||||
horizont: List[List[AkkudoktorForecastHorizon]]
|
horizont: List[List[AkkudoktorForecastHorizon]]
|
||||||
horizontString: List[str]
|
horizontString: List[str]
|
||||||
|
|
||||||
|
@field_validator("power", "azimuth", "tilt", "powerInverter", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def ensure_list(cls, v: Any) -> List[int]:
|
||||||
|
return v if isinstance(v, list) else [v]
|
||||||
|
|
||||||
|
@field_validator("horizont", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def normalize_horizont(cls, v: Any) -> List[List[AkkudoktorForecastHorizon]]:
|
||||||
|
if isinstance(v, list):
|
||||||
|
# Case: flat list of dicts
|
||||||
|
if v and isinstance(v[0], dict):
|
||||||
|
return [v]
|
||||||
|
# Already in correct nested form
|
||||||
|
if v and isinstance(v[0], list):
|
||||||
|
return v
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator("horizontString", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def parse_horizont_string(cls, v: Any) -> List[str]:
|
||||||
|
if isinstance(v, str):
|
||||||
|
return [s.strip() for s in v.split(",")]
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class AkkudoktorForecastValue(PydanticBaseModel):
|
class AkkudoktorForecastValue(PydanticBaseModel):
|
||||||
datetime: str
|
datetime: str
|
||||||
|
@ -19,6 +19,9 @@ from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_
|
|||||||
DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
|
DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
|
||||||
|
|
||||||
FILE_TESTDATA_PV_FORECAST_INPUT_1 = DIR_TESTDATA.joinpath("pv_forecast_input_1.json")
|
FILE_TESTDATA_PV_FORECAST_INPUT_1 = DIR_TESTDATA.joinpath("pv_forecast_input_1.json")
|
||||||
|
FILE_TESTDATA_PV_FORECAST_INPUT_SINGLE_PLANE = DIR_TESTDATA.joinpath(
|
||||||
|
"pv_forecast_input_single_plane.json"
|
||||||
|
)
|
||||||
FILE_TESTDATA_PV_FORECAST_RESULT_1 = DIR_TESTDATA.joinpath("pv_forecast_result_1.txt")
|
FILE_TESTDATA_PV_FORECAST_RESULT_1 = DIR_TESTDATA.joinpath("pv_forecast_result_1.txt")
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@ -93,6 +96,16 @@ def sample_forecast_data_raw():
|
|||||||
return input_data
|
return input_data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_forecast_data_single_plane_raw():
|
||||||
|
"""Fixture that returns raw sample forecast data."""
|
||||||
|
with FILE_TESTDATA_PV_FORECAST_INPUT_SINGLE_PLANE.open(
|
||||||
|
"r", encoding="utf-8", newline=None
|
||||||
|
) as f_in:
|
||||||
|
input_data = f_in.read()
|
||||||
|
return input_data
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sample_forecast_report():
|
def sample_forecast_report():
|
||||||
"""Fixture that returns sample forecast data report."""
|
"""Fixture that returns sample forecast data report."""
|
||||||
@ -236,6 +249,20 @@ def test_pvforecast_akkudoktor_validate_data(provider_empty_instance, sample_for
|
|||||||
# everything worked
|
# everything worked
|
||||||
|
|
||||||
|
|
||||||
|
def test_pvforecast_akkudoktor_validate_data_single_plane(
|
||||||
|
provider_empty_instance, sample_forecast_data_single_plane_raw
|
||||||
|
):
|
||||||
|
"""Test validation of PV forecast data on sample data with a single plane."""
|
||||||
|
logger.info("The following errors are intentional and part of the test.")
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match="Field: meta\nError: Field required\nType: missing\nField: values\nError: Field required\nType: missing\n",
|
||||||
|
):
|
||||||
|
ret = provider_empty_instance._validate_data("{}")
|
||||||
|
data = provider_empty_instance._validate_data(sample_forecast_data_single_plane_raw)
|
||||||
|
# everything worked
|
||||||
|
|
||||||
|
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
def test_pvforecast_akkudoktor_update_with_sample_forecast(
|
def test_pvforecast_akkudoktor_update_with_sample_forecast(
|
||||||
mock_get, sample_settings, sample_forecast_data_raw, sample_forecast_start, provider
|
mock_get, sample_settings, sample_forecast_data_raw, sample_forecast_start, provider
|
||||||
|
2923
tests/testdata/pv_forecast_input_single_plane.json
vendored
Normal file
2923
tests/testdata/pv_forecast_input_single_plane.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user