feat: rename genetic optimization API fields to English (#675)
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
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 02:08:25 -04:00
committed by GitHub
parent a0febef6a7
commit 6933b33542
25 changed files with 690 additions and 264 deletions

View File

@@ -148,22 +148,22 @@ async def prepare_optimization_real_parameters() -> GeneticOptimizationParameter
)
print(f"temperature_forecast: {temperature_forecast}")
# Electricity Price (in Euro per Wh)
strompreis_euro_pro_wh = await prediction_eos.key_to_array(
# Electricity price (per Wh)
electricity_price_per_wh = await prediction_eos.key_to_array(
key="elecprice_marketprice_wh",
start_datetime=prediction_eos.ems_start_datetime,
end_datetime=prediction_eos.end_datetime,
fill_method="ffill",
)
print(f"strompreis_euro_pro_wh: {strompreis_euro_pro_wh}")
print(f"electricity_price_per_wh: {electricity_price_per_wh}")
# Overall System Load (in W)
gesamtlast = await prediction_eos.key_to_array(
total_load = await prediction_eos.key_to_array(
key="load_mean",
start_datetime=prediction_eos.ems_start_datetime,
end_datetime=prediction_eos.end_datetime,
)
print(f"gesamtlast: {gesamtlast}")
print(f"total_load: {total_load}")
# Start Solution (binary)
start_solution = None
@@ -173,11 +173,11 @@ async def prepare_optimization_real_parameters() -> GeneticOptimizationParameter
return GeneticOptimizationParameters(
**{
"ems": {
"preis_euro_pro_wh_akku": 0e-05,
"einspeiseverguetung_euro_pro_wh": 7e-05,
"gesamtlast": gesamtlast,
"pv_prognose_wh": pv_forecast,
"strompreis_euro_pro_wh": strompreis_euro_pro_wh,
"price_per_wh_battery": 0e-05,
"feed_in_tariff_per_wh": 7e-05,
"total_load": total_load,
"pv_forecast_wh": pv_forecast,
"electricity_price_per_wh": electricity_price_per_wh,
},
"pv_akku": {
"device_id": "battery 1",
@@ -304,14 +304,14 @@ def prepare_optimization_parameters() -> GeneticOptimizationParameters:
17.4,
]
# Electricity Price (in Euro per Wh)
strompreis_euro_pro_wh = np.full(48, 0.001)
strompreis_euro_pro_wh[0:10] = 0.00001
strompreis_euro_pro_wh[11:15] = 0.00005
strompreis_euro_pro_wh[20] = 0.00001
# Electricity price (per Wh)
electricity_price_per_wh = np.full(48, 0.001)
electricity_price_per_wh[0:10] = 0.00001
electricity_price_per_wh[11:15] = 0.00005
electricity_price_per_wh[20] = 0.00001
# Overall System Load (in W)
gesamtlast = [
total_load = [
676.71,
876.19,
527.13,
@@ -369,11 +369,11 @@ def prepare_optimization_parameters() -> GeneticOptimizationParameters:
return GeneticOptimizationParameters(
**{
"ems": {
"preis_euro_pro_wh_akku": 0e-05,
"einspeiseverguetung_euro_pro_wh": 7e-05,
"gesamtlast": gesamtlast,
"pv_prognose_wh": pv_forecast,
"strompreis_euro_pro_wh": strompreis_euro_pro_wh,
"price_per_wh_battery": 0e-05,
"feed_in_tariff_per_wh": 7e-05,
"total_load": total_load,
"pv_forecast_wh": pv_forecast,
"electricity_price_per_wh": electricity_price_per_wh,
},
"pv_akku": {
"device_id": "battery 1",

View File

@@ -56,7 +56,7 @@ def test_optimize(
config_eos: ConfigEOS,
is_finalize: bool,
):
"""Test optimierung_ems."""
"""Test optimize_ems."""
# Test parameters
fixed_start_hour = 10
fixed_seed = 42
@@ -125,7 +125,7 @@ def test_optimize(
),
) as prepare_visualize_patch:
# Call the optimization function
genetic_solution = genetic_optimization.optimierung_ems(
genetic_solution = genetic_optimization.optimize_ems(
parameters=input_data, start_hour=fixed_start_hour, ngen=ngen
)
# The function creates a visualization result PDF as a side-effect.

View File

@@ -582,7 +582,7 @@ def _run_evaluate_with_mocked_sim(
fitness = optim.evaluate(
fake_individual,
parameters=Mock(
ems=Mock(preis_euro_pro_wh_akku=0.0),
ems=Mock(price_per_wh_battery=0.0),
eauto=None,
),
start_hour=start_hour,

128
tests/test_translations.py Normal file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Quick test to verify API field name translations work correctly."""
import json
from akkudoktoreos.optimization.genetic.geneticparams import (
GeneticEnergyManagementParameters,
)
from akkudoktoreos.optimization.genetic.geneticsolution import GeneticSimulationResult
def test_genetic_params_german_input():
"""Test that German field names are accepted as input."""
data_de = {
"pv_prognose_wh": [100.0, 200.0],
"strompreis_euro_pro_wh": [0.0003, 0.0003],
"einspeiseverguetung_euro_pro_wh": 0.00007,
"preis_euro_pro_wh_akku": 0.0001,
"gesamtlast": [500.0, 600.0],
}
params = GeneticEnergyManagementParameters(**data_de)
assert params.pv_forecast_wh == [100.0, 200.0]
print("✅ German input accepted")
def test_genetic_params_english_input():
"""Test that English field names are accepted as input."""
data_en = {
"pv_forecast_wh": [100.0, 200.0],
"electricity_price_per_wh": [0.0003, 0.0003],
"feed_in_tariff_per_wh": 0.00007,
"price_per_wh_battery": 0.0001,
"total_load": [500.0, 600.0],
}
params = GeneticEnergyManagementParameters(**data_en)
assert params.pv_forecast_wh == [100.0, 200.0]
print("✅ English input accepted")
def test_genetic_params_english_output():
"""Test that both English and German field names are in JSON output (backward compatibility)."""
data_de = {
"pv_prognose_wh": [100.0, 200.0],
"strompreis_euro_pro_wh": [0.0003, 0.0003],
"einspeiseverguetung_euro_pro_wh": 0.00007,
"preis_euro_pro_wh_akku": 0.0001,
"gesamtlast": [500.0, 600.0],
}
params = GeneticEnergyManagementParameters(**data_de)
json_output = json.loads(params.model_dump_json(by_alias=True))
# English names should be in output
assert "pv_forecast_wh" in json_output
assert "electricity_price_per_wh" in json_output
assert "feed_in_tariff_per_wh" in json_output
assert "price_per_wh_battery" in json_output
assert "total_load" in json_output
# German names should ALSO be in output (backward compatibility)
assert "pv_prognose_wh" in json_output
assert "strompreis_euro_pro_wh" in json_output
assert "einspeiseverguetung_euro_pro_wh" in json_output
assert "preis_euro_pro_wh_akku" in json_output
assert "gesamtlast" in json_output
# Both should have same values
assert json_output["pv_forecast_wh"] == json_output["pv_prognose_wh"]
assert json_output["electricity_price_per_wh"] == json_output["strompreis_euro_pro_wh"]
print("✅ Both English and German output generated")
def test_simulation_result_translations():
"""Test simulation result field translations."""
data_de = {
"Last_Wh_pro_Stunde": [100.0, 200.0],
"EAuto_SoC_pro_Stunde": [50.0, 60.0],
"Einnahmen_Euro_pro_Stunde": [0.1, 0.2],
"Gesamt_Verluste": 50.0,
"Gesamtbilanz_Euro": -10.0,
"Gesamteinnahmen_Euro": 5.0,
"Gesamtkosten_Euro": 15.0,
"Home_appliance_wh_per_hour": [0.0, 100.0],
"Kosten_Euro_pro_Stunde": [0.2, 0.3],
"Netzbezug_Wh_pro_Stunde": [50.0, 60.0],
"Netzeinspeisung_Wh_pro_Stunde": [10.0, 20.0],
"Verluste_Pro_Stunde": [5.0, 10.0],
"akku_soc_pro_stunde": [80.0, 90.0],
"Electricity_price": [0.0003, 0.0003],
}
result = GeneticSimulationResult(**data_de)
json_output = json.loads(result.model_dump_json(by_alias=True))
# Check English field names in output
english_names = [
"load_wh_per_hour",
"ev_soc_per_hour",
"revenue_per_hour",
"total_losses",
"total_balance",
"total_revenue",
"total_costs",
"home_appliance_wh_per_hour",
"costs_per_hour",
"grid_consumption_wh_per_hour",
"grid_feed_in_wh_per_hour",
"losses_per_hour",
"battery_soc_per_hour",
"electricity_price",
]
for name in english_names:
assert name in json_output, f"English field {name} missing in output"
# Check ALL deprecated German field names are still in output (backward compatibility)
for name in data_de:
assert name in json_output, f"Deprecated field {name} missing in output"
# Deprecated fields must mirror the values of the renamed fields
assert json_output["Last_Wh_pro_Stunde"] == json_output["load_wh_per_hour"]
assert json_output["Home_appliance_wh_per_hour"] == json_output["home_appliance_wh_per_hour"]
assert json_output["Gesamtbilanz_Euro"] == json_output["total_balance"]
assert json_output["akku_soc_pro_stunde"] == json_output["battery_soc_per_hour"]
assert json_output["Electricity_price"] == json_output["electricity_price"]
print("✅ Simulation result translations work")
if __name__ == "__main__":
test_genetic_params_german_input()
test_genetic_params_english_input()
test_genetic_params_english_output()
test_simulation_result_translations()
print("\n✅✅✅ All translation tests passed! ✅✅✅")

View File

@@ -456,7 +456,7 @@ as a cohesive unit for scheduling and availability checking.
| charging_efficiency | `float` | `rw` | `0.88` | Charging efficiency [0.01 ... 1.00]. |
| device_id | `str` | `rw` | `<unknown>` | ID of device |
| discharging_efficiency | `float` | `rw` | `0.88` | Discharge efficiency [0.01 ... 1.00]. |
| levelized_cost_of_storage_kwh | `float` | `rw` | `0.0` | Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [/kWh]. |
| levelized_cost_of_storage_kwh | `float` | `rw` | `0.0` | Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [amount/kWh]. |
| max_charge_power_w | `Optional[float]` | `rw` | `5000` | Maximum charging power [W]. |
| max_soc_percentage | `int` | `rw` | `100` | Maximum state of charge (SOC) as percentage of capacity [%]. |
| measurement_key_power_3_phase_sym_w | `str` | `ro` | `N/A` | Measurement key for the symmetric 3 phase power the battery is charged or discharged with [W]. |

View File

@@ -7,7 +7,7 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| charges_kwh | `EOS_ELECPRICE__CHARGES_KWH` | `Optional[float]` | `rw` | `None` | Electricity price charges [/kWh]. Will be added to variable market price. |
| charges_kwh | `EOS_ELECPRICE__CHARGES_KWH` | `Optional[float]` | `rw` | `None` | Electricity price charges [amount/kWh]. Will be added to variable market price. |
| elecpricefixed | `EOS_ELECPRICE__ELECPRICEFIXED` | `ElecPriceFixedCommonSettings` | `rw` | `required` | Fixed electricity price provider settings. |
| elecpriceimport | `EOS_ELECPRICE__ELECPRICEIMPORT` | `ElecPriceImportCommonSettings` | `rw` | `required` | Import provider settings. |
| energycharts | `EOS_ELECPRICE__ENERGYCHARTS` | `ElecPriceEnergyChartsCommonSettings` | `rw` | `required` | Energy Charts provider settings. |

View File

@@ -95,7 +95,7 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| feed_in_tariff_kwh | `Optional[float]` | `rw` | `None` | Electricity price feed in tariff [/kWH]. |
| feed_in_tariff_kwh | `Optional[float]` | `rw` | `None` | Electricity price feed in tariff [amount/kWh]. |
:::
<!-- pyml enable line-length -->