feat: rename genetic optimization API fields to English (#675)
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
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled

Rename the German field names of the genetic optimization API to
English with full backward compatibility:

- English names are canonical and documented in the OpenAPI schema;
  the German names are still accepted on input via validation aliases
  and re-emitted in responses as deprecated computed fields
- fix visualization receiving the raw German-keyed simulation dict
  (KeyError: load_wh_per_hour)
- rename internal German identifiers (optimize_ems, total_balance,
  battery_residual_value, self_consumption, extra_data keys)
- use amount instead of currency/euro in chart labels and schema
  descriptions; document currency handling (whole currency units,
  never cents) in the API description
- regenerate openapi.json and generated docs

Co-authored-by: Tobias Welz <tobias.wizneteu@gmail.com>
This commit is contained in:
7tobias
2026-07-18 08:08:25 +02:00
committed by GitHub
co-authored by Tobias Welz
parent a0febef6a7
commit 6933b33542
25 changed files with 690 additions and 264 deletions
@@ -11,7 +11,14 @@ forecasts, and fallback defaults, preparing them for optimization runs.
from typing import Optional, Union
from loguru import logger
from pydantic import Field, field_validator, model_validator
from pydantic import (
AliasChoices,
ConfigDict,
Field,
computed_field,
field_validator,
model_validator,
)
from typing_extensions import Self
from akkudoktoreos.core.coreabc import (
@@ -37,32 +44,65 @@ from akkudoktoreos.utils.datetimeutil import to_duration
class GeneticEnergyManagementParameters(GeneticParametersBaseModel):
"""Encapsulates energy-related forecasts and costs used in GENETIC optimization."""
pv_prognose_wh: list[float] = Field(
model_config = ConfigDict(populate_by_name=True, extra="ignore")
pv_forecast_wh: list[float] = Field(
validation_alias=AliasChoices("pv_forecast_wh", "pv_prognose_wh"),
json_schema_extra={
"description": "An array of floats representing the forecasted photovoltaic output in watts for different time intervals."
}
},
)
strompreis_euro_pro_wh: list[float] = Field(
electricity_price_per_wh: list[float] = Field(
validation_alias=AliasChoices("electricity_price_per_wh", "strompreis_euro_pro_wh"),
json_schema_extra={
"description": "An array of floats representing the electricity price in euros per watt-hour for different time intervals."
}
"description": "An array of floats representing the electricity price per watt-hour for different time intervals."
},
)
einspeiseverguetung_euro_pro_wh: Union[list[float], float] = Field(
feed_in_tariff_per_wh: Union[list[float], float] = Field(
validation_alias=AliasChoices("feed_in_tariff_per_wh", "einspeiseverguetung_euro_pro_wh"),
json_schema_extra={
"description": "A float or array of floats representing the feed-in compensation in euros per watt-hour."
}
"description": "A float or array of floats representing the feed-in compensation per watt-hour."
},
)
preis_euro_pro_wh_akku: float = Field(
price_per_wh_battery: float = Field(
validation_alias=AliasChoices("price_per_wh_battery", "preis_euro_pro_wh_akku"),
json_schema_extra={
"description": "A float representing the cost of battery energy per watt-hour."
}
},
)
gesamtlast: list[float] = Field(
total_load: list[float] = Field(
validation_alias=AliasChoices("total_load", "gesamtlast"),
json_schema_extra={
"description": "An array of floats representing the total load (consumption) in watts for different time intervals."
}
},
)
# Computed fields for backward compatibility (deprecated German names)
@computed_field(json_schema_extra={"deprecated": True})
def pv_prognose_wh(self) -> list[float]:
"""Deprecated: Use pv_forecast_wh instead."""
return self.pv_forecast_wh
@computed_field(json_schema_extra={"deprecated": True})
def strompreis_euro_pro_wh(self) -> list[float]:
"""Deprecated: Use electricity_price_per_wh instead."""
return self.electricity_price_per_wh
@computed_field(json_schema_extra={"deprecated": True})
def einspeiseverguetung_euro_pro_wh(self) -> Union[list[float], float]:
"""Deprecated: Use feed_in_tariff_per_wh instead."""
return self.feed_in_tariff_per_wh
@computed_field(json_schema_extra={"deprecated": True})
def preis_euro_pro_wh_akku(self) -> float:
"""Deprecated: Use price_per_wh_battery instead."""
return self.price_per_wh_battery
@computed_field(json_schema_extra={"deprecated": True})
def gesamtlast(self) -> list[float]:
"""Deprecated: Use total_load instead."""
return self.total_load
@model_validator(mode="after")
def validate_list_length(self) -> Self:
"""Validate that all input lists are of the same length.
@@ -70,13 +110,13 @@ class GeneticEnergyManagementParameters(GeneticParametersBaseModel):
Raises:
ValueError: If input list lengths differ.
"""
pv_prognose_length = len(self.pv_prognose_wh)
pv_forecast_length = len(self.pv_forecast_wh)
if (
pv_prognose_length != len(self.strompreis_euro_pro_wh)
or pv_prognose_length != len(self.gesamtlast)
pv_forecast_length != len(self.electricity_price_per_wh)
or pv_forecast_length != len(self.total_load)
or (
isinstance(self.einspeiseverguetung_euro_pro_wh, list)
and pv_prognose_length != len(self.einspeiseverguetung_euro_pro_wh)
isinstance(self.feed_in_tariff_per_wh, list)
and pv_forecast_length != len(self.feed_in_tariff_per_wh)
)
):
raise ValueError("Input lists have different lengths")
@@ -122,7 +162,7 @@ class GeneticOptimizationParameters(
Raises:
ValueError: If list lengths mismatch.
"""
arr_length = len(self.ems.pv_prognose_wh)
arr_length = len(self.ems.pv_forecast_wh)
if self.temperature_forecast is not None and arr_length != len(self.temperature_forecast):
raise ValueError("Input lists have different lengths")
return self
@@ -615,11 +655,11 @@ class GeneticOptimizationParameters(
try:
oparams = GeneticOptimizationParameters(
ems=GeneticEnergyManagementParameters(
pv_prognose_wh=pvforecast_ac_power,
strompreis_euro_pro_wh=elecprice_marketprice_wh,
einspeiseverguetung_euro_pro_wh=feed_in_tariff_wh,
gesamtlast=loadforecast_power_w,
preis_euro_pro_wh_akku=battery_lcos_kwh / 1000,
pv_forecast_wh=pvforecast_ac_power,
electricity_price_per_wh=elecprice_marketprice_wh,
feed_in_tariff_per_wh=feed_in_tariff_wh,
total_load=loadforecast_power_w,
price_per_wh_battery=battery_lcos_kwh / 1000,
),
temperature_forecast=weather_temp_air,
pv_akku=battery_params,