mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-07-21 01:08:12 +00:00
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
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:
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
128
tests/test_translations.py
Normal 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! ✅✅✅")
|
||||
@@ -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]. |
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user