fix: prediction import with pydantic-typed bodies (#1152)
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
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled

Respect pydantic model in serializing json data for import.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2026-07-16 17:15:10 +02:00
committed by GitHub
parent 4848280f9c
commit 0306e7e4ed
9 changed files with 73 additions and 14 deletions

2
.env
View File

@@ -11,7 +11,7 @@ DOCKER_COMPOSE_DATA_DIR=${HOME}/.local/share/net.akkudoktor.eos
# -----------------------------------------------------------------------------
# Image / build
# -----------------------------------------------------------------------------
VERSION=0.3.0.dev2607150960221650
VERSION=0.3.0.dev2607161418098328
PYTHON_VERSION=3.13.9
# -----------------------------------------------------------------------------

View File

@@ -6,7 +6,7 @@
# the root directory (no add-on folder as usual).
name: "Akkudoktor-EOS"
version: "0.3.0.dev2607150960221650"
version: "0.3.0.dev2607161418098328"
slug: "eos"
description: "Akkudoktor-EOS add-on"
url: "https://github.com/Akkudoktor-EOS/EOS"

View File

@@ -1,6 +1,6 @@
# Akkudoktor-EOS
**Version**: `v0.3.0.dev2607150960221650`
**Version**: `v0.3.0.dev2607161418098328`
<!-- pyml disable line-length -->
**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.

View File

@@ -8,7 +8,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "v0.3.0.dev2607150960221650"
"version": "v0.3.0.dev2607161418098328"
},
"paths": {
"/v1/admin/cache/clear": {

View File

@@ -9,7 +9,7 @@ from akkudoktoreos.prediction.feedintarifffixed import FeedInTariffFixedCommonSe
from akkudoktoreos.prediction.feedintariffimport import FeedInTariffImportCommonSettings
def elecprice_provider_ids() -> list[str]:
def feedintariff_provider_ids() -> list[str]:
"""Valid feedintariff provider ids."""
try:
prediction_eos = get_prediction()
@@ -67,14 +67,14 @@ class FeedInTariffCommonSettings(SettingsBaseModel):
@property
def providers(self) -> list[str]:
"""Available feed in tariff provider ids."""
return elecprice_provider_ids()
return feedintariff_provider_ids()
# Validators
@field_validator("provider", mode="after")
@classmethod
def validate_provider(cls, value: Optional[str]) -> Optional[str]:
if value is None or value in elecprice_provider_ids():
if value is None or value in feedintariff_provider_ids():
return value
raise ValueError(
f"Provider '{value}' is not a valid feed in tariff provider: {elecprice_provider_ids()}."
f"Provider '{value}' is not a valid feed in tariff provider: {feedintariff_provider_ids()}."
)

View File

@@ -40,6 +40,7 @@ from akkudoktoreos.core.ems import ems_manage_energy
from akkudoktoreos.core.emsettings import EnergyManagementMode
from akkudoktoreos.core.logging import logging_track_config, read_file_log
from akkudoktoreos.core.pydantic import (
BaseModel,
PydanticBaseModel,
PydanticDateTimeData,
PydanticDateTimeDataFrame,
@@ -1036,7 +1037,7 @@ async def fastapi_prediction_list_get(
@app.put("/v1/prediction/import/{provider_id}", tags=["prediction"])
def fastapi_prediction_import_provider(
async def fastapi_prediction_import_provider(
provider_id: str = FastapiPath(..., description="Provider ID."),
data: Optional[Union[PydanticDateTimeDataFrame, PydanticDateTimeData, dict]] = None,
force_enable: Optional[bool] = None,
@@ -1056,7 +1057,11 @@ def fastapi_prediction_import_provider(
if not provider.enabled() and not force_enable:
raise HTTPException(status_code=404, detail=f"Provider '{provider_id}' not enabled.")
try:
provider.import_from_json(json_str=json.dumps(data))
if isinstance(data, BaseModel):
json_str = data.model_dump_json()
else:
json_str = json.dumps(data)
await provider.import_from_json(json_str=json_str)
provider.update_datetime = to_datetime(in_timezone=get_config().general.timezone)
except Exception as e:
raise HTTPException(

View File

@@ -170,6 +170,57 @@ class TestSystem:
else:
pass
def test_prediction_import(self, server_setup_for_class, is_system_test):
"""Test eprediction import."""
server = server_setup_for_class["server"]
eos_dir = server_setup_for_class["eos_dir"]
# Reset config
with FILE_TESTDATA_EOSSERVER_CONFIG_1.open("r", encoding="utf-8", newline=None) as fd:
config = json.load(fd)
config["elecprice"]["provider"] = "ElecPriceImport"
result = requests.put(f"{server}/v1/config", json=config)
assert result.status_code == HTTPStatus.OK
# Assure prediction is enabled
result = requests.get(f"{server}/v1/prediction/providers?enabled=true")
assert result.status_code == HTTPStatus.OK
providers = result.json()
assert "ElecPriceImport" in providers
# Elex price payload
payload = {
"start_datetime": "2026-05-27 00:00:00",
"interval": "1 hour",
"elecprice_marketprice_wh": [0.000234, 0.000228, 0.000231],
}
result = requests.put(
f"{server}/v1/prediction/import/ElecPriceImport",
params={"force_enable": True},
json=payload,
)
assert result.status_code == HTTPStatus.OK
# Import should work with any provider
config["feedintariff"]["provider"] = "FeedInTariffImport"
result = requests.put(f"{server}/v1/config", json=config)
assert result.status_code == HTTPStatus.OK
# Feed In Tariff payload
payload = {
"start_datetime": "2026-05-27 00:00:00",
"interval": "1 hour",
"feed_in_tariff_wh": [0.000071, 0.000072, 0.000073],
}
result = requests.put(
f"{server}/v1/prediction/import/FeedInTariffImport",
params={"force_enable": True},
json=payload,
)
assert result.status_code == HTTPStatus.OK
def test_measurement(self, server_setup_for_class, is_system_test):
"""Test measurement endpoints comprehensively."""
server = server_setup_for_class["server"]

View File

@@ -3,6 +3,9 @@
"charges_kwh": 0.21,
"provider": "ElecPriceImport"
},
"feedintariff": {
"provider": "FeedInTariffFixed"
},
"general": {
"latitude": 52.5,
"longitude": 13.4

8
uv.lock generated
View File

@@ -116,7 +116,7 @@ requires-dist = [
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = "==1.4.0" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = "==7.1.0" },
{ name = "pytest-xprocess", marker = "extra == 'dev'", specifier = "==1.0.2" },
{ name = "python-fasthtml", specifier = "==0.14.5" },
{ name = "python-fasthtml", specifier = "==0.14.6" },
{ name = "requests", specifier = "==2.34.2" },
{ name = "rich-toolkit", specifier = "==0.20.3" },
{ name = "scipy", specifier = "==1.17.1" },
@@ -2697,7 +2697,7 @@ wheels = [
[[package]]
name = "python-fasthtml"
version = "0.14.5"
version = "0.14.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
@@ -2711,9 +2711,9 @@ dependencies = [
{ name = "starlette" },
{ name = "uvicorn", extra = ["standard"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/9b/e442133c5d950d9ccc0a879357a3c108e1dcfd80a19bd601459a615c1a7e/python_fasthtml-0.14.5.tar.gz", hash = "sha256:d0181c36415f9bbe242e1d05de1fb5279a14aa9c605c244e76ae95a32650eda4", size = 76817, upload-time = "2026-07-09T05:46:10.028Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/db/1efed9c205c9f65a1e9715f91aff1510fdc42e41f8c032718274969b35a7/python_fasthtml-0.14.6.tar.gz", hash = "sha256:11b535e752b4f79e8da7eb6d55928d2ced0ed7a085619ac89a6e943a76915773", size = 77102, upload-time = "2026-07-11T04:03:42.889Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/63/5685ed7eff6b581bcfd0db4e239c8d07d0e1e59c016b409a1c01c10aab64/python_fasthtml-0.14.5-py3-none-any.whl", hash = "sha256:e5ce3cc43af056ac5d48b1cdcb9346111fdb8a21fa0fcf788d2490c8ea12dc9f", size = 80441, upload-time = "2026-07-09T05:46:08.427Z" },
{ url = "https://files.pythonhosted.org/packages/69/d6/70783f684b6c367466c4288005e0f95cdf9ffdd1d8f397fad921f9567d10/python_fasthtml-0.14.6-py3-none-any.whl", hash = "sha256:38a93cfb87911ae34aaa088e3b53784a5ed72c575bc9656df7599d82428ab41c", size = 80779, upload-time = "2026-07-11T04:03:41.222Z" },
]
[[package]]