mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-07-19 08:18:50 +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
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
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:
@@ -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 -->
|
||||
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
# Akkudoktor-EOS
|
||||
|
||||
**Version**: `v0.3.0.dev2607171544999797`
|
||||
**Version**: `v0.3.0.dev2607171715033828`
|
||||
|
||||
<!-- 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.
|
||||
|
||||
## Currency Information
|
||||
|
||||
All monetary values in this API are expressed in the locally configured currency of the EOS installation. The system is designed to work with any currency (e.g., EUR, SEK, CHF, USD, GBP, etc.). Each installation uses a single, consistent currency throughout all endpoints and responses.
|
||||
|
||||
Values are given in whole currency units, not in hundredth subunits: a value of `0.0003` in an installation configured for Euro means 0.0003 EUR/Wh (i.e. 0.30 EUR/kWh), never cents. The same applies to all totals (e.g. `total_costs` of `1.5` = 1.50 EUR).
|
||||
|
||||
Field names containing cost, price, revenue, tariff, or similar monetary terms (e.g., `total_costs`, `electricity_price_per_wh`, `revenue_per_hour`) represent amounts in the configured currency, without explicit currency designation in the field name to maintain currency-neutrality.
|
||||
|
||||
## Deprecated Field Names
|
||||
|
||||
The genetic optimization API fields were renamed from German to English. For backward compatibility the old German field names (e.g. `gesamtlast`, `pv_prognose_wh`, `Gesamtbilanz_Euro`) are still accepted on input and are emitted in responses alongside the English names, marked as deprecated in the schema. They will be removed in a future release — new clients should use the English field names only.
|
||||
|
||||
<!-- pyml enable line-length -->
|
||||
|
||||
**Base URL**: `No base URL provided.`
|
||||
@@ -184,7 +197,7 @@ Fastapi Strompreis
|
||||
<!-- pyml disable line-length -->
|
||||
```python
|
||||
"""
|
||||
Deprecated: Electricity Market Price Prediction per Wh (€/Wh).
|
||||
Deprecated: Electricity Market Price Prediction per Wh [amount/Wh].
|
||||
|
||||
Electricity prices start at 00.00.00 today and are provided for 48 hours.
|
||||
If no prices are available the missing ones at the start of the series are
|
||||
|
||||
255
openapi.json
255
openapi.json
@@ -3,12 +3,12 @@
|
||||
"info": {
|
||||
"title": "Akkudoktor-EOS",
|
||||
"summary": "Comprehensive solution for simulating and optimizing an energy system based on renewable energy sources",
|
||||
"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.",
|
||||
"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.\n\n## Currency Information\n\nAll monetary values in this API are expressed in the locally configured currency of the EOS installation. The system is designed to work with any currency (e.g., EUR, SEK, CHF, USD, GBP, etc.). Each installation uses a single, consistent currency throughout all endpoints and responses.\n\nValues are given in whole currency units, not in hundredth subunits: a value of `0.0003` in an installation configured for Euro means 0.0003 EUR/Wh (i.e. 0.30 EUR/kWh), never cents. The same applies to all totals (e.g. `total_costs` of `1.5` = 1.50 EUR).\n\nField names containing cost, price, revenue, tariff, or similar monetary terms (e.g., `total_costs`, `electricity_price_per_wh`, `revenue_per_hour`) represent amounts in the configured currency, without explicit currency designation in the field name to maintain currency-neutrality.\n\n## Deprecated Field Names\n\nThe genetic optimization API fields were renamed from German to English. For backward compatibility the old German field names (e.g. `gesamtlast`, `pv_prognose_wh`, `Gesamtbilanz_Euro`) are still accepted on input and are emitted in responses alongside the English names, marked as deprecated in the schema. They will be removed in a future release \u2014 new clients should use the English field names only.\n",
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "v0.3.0.dev2607171544999797"
|
||||
"version": "v0.3.0.dev2607171715033828"
|
||||
},
|
||||
"paths": {
|
||||
"/v1/admin/cache/clear": {
|
||||
@@ -1941,7 +1941,7 @@
|
||||
"prediction"
|
||||
],
|
||||
"summary": "Fastapi Strompreis",
|
||||
"description": "Deprecated: Electricity Market Price Prediction per Wh (\u20ac/Wh).\n\nElectricity prices start at 00.00.00 today and are provided for 48 hours.\nIf no prices are available the missing ones at the start of the series are\nfilled with the first available price.\n\nNote:\n Electricity price charges are added.\n\nNote:\n Set ElecPriceAkkudoktor as provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=elecprice_marketprice_wh' or\n '/v1/prediction/list?key=elecprice_marketprice_kwh' instead.",
|
||||
"description": "Deprecated: Electricity Market Price Prediction per Wh [amount/Wh].\n\nElectricity prices start at 00.00.00 today and are provided for 48 hours.\nIf no prices are available the missing ones at the start of the series are\nfilled with the first available price.\n\nNote:\n Electricity price charges are added.\n\nNote:\n Set ElecPriceAkkudoktor as provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=elecprice_marketprice_wh' or\n '/v1/prediction/list?key=elecprice_marketprice_kwh' instead.",
|
||||
"operationId": "fastapi_strompreis_strompreis_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -2319,7 +2319,7 @@
|
||||
"levelized_cost_of_storage_kwh": {
|
||||
"type": "number",
|
||||
"title": "Levelized Cost Of Storage Kwh",
|
||||
"description": "Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [\u20ac/kWh].",
|
||||
"description": "Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [amount/kWh].",
|
||||
"default": 0.0,
|
||||
"examples": [
|
||||
0.12
|
||||
@@ -2473,7 +2473,7 @@
|
||||
"levelized_cost_of_storage_kwh": {
|
||||
"type": "number",
|
||||
"title": "Levelized Cost Of Storage Kwh",
|
||||
"description": "Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [\u20ac/kWh].",
|
||||
"description": "Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [amount/kWh].",
|
||||
"default": 0.0,
|
||||
"examples": [
|
||||
0.12
|
||||
@@ -3462,7 +3462,7 @@
|
||||
}
|
||||
],
|
||||
"title": "Charges Kwh",
|
||||
"description": "Electricity price charges [\u20ac/kWh]. Will be added to variable market price.",
|
||||
"description": "Electricity price charges [amount/kWh]. Will be added to variable market price.",
|
||||
"examples": [
|
||||
0.21
|
||||
]
|
||||
@@ -3529,7 +3529,7 @@
|
||||
}
|
||||
],
|
||||
"title": "Charges Kwh",
|
||||
"description": "Electricity price charges [\u20ac/kWh]. Will be added to variable market price.",
|
||||
"description": "Electricity price charges [amount/kWh]. Will be added to variable market price.",
|
||||
"examples": [
|
||||
0.21
|
||||
]
|
||||
@@ -4447,7 +4447,7 @@
|
||||
}
|
||||
],
|
||||
"title": "Feed In Tariff Kwh",
|
||||
"description": "Electricity price feed in tariff [\u20ac/kWH].",
|
||||
"description": "Electricity price feed in tariff [amount/kWh].",
|
||||
"examples": [
|
||||
0.078
|
||||
]
|
||||
@@ -4861,23 +4861,23 @@
|
||||
},
|
||||
"GeneticEnergyManagementParameters": {
|
||||
"properties": {
|
||||
"pv_prognose_wh": {
|
||||
"pv_forecast_wh": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Pv Prognose Wh",
|
||||
"title": "Pv Forecast Wh",
|
||||
"description": "An array of floats representing the forecasted photovoltaic output in watts for different time intervals."
|
||||
},
|
||||
"strompreis_euro_pro_wh": {
|
||||
"electricity_price_per_wh": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Strompreis Euro Pro Wh",
|
||||
"description": "An array of floats representing the electricity price in euros per watt-hour for different time intervals."
|
||||
"title": "Electricity Price Per Wh",
|
||||
"description": "An array of floats representing the electricity price per watt-hour for different time intervals."
|
||||
},
|
||||
"einspeiseverguetung_euro_pro_wh": {
|
||||
"feed_in_tariff_per_wh": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
@@ -4889,31 +4889,30 @@
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"title": "Einspeiseverguetung Euro Pro Wh",
|
||||
"description": "A float or array of floats representing the feed-in compensation in euros per watt-hour."
|
||||
"title": "Feed In Tariff Per Wh",
|
||||
"description": "A float or array of floats representing the feed-in compensation per watt-hour."
|
||||
},
|
||||
"preis_euro_pro_wh_akku": {
|
||||
"price_per_wh_battery": {
|
||||
"type": "number",
|
||||
"title": "Preis Euro Pro Wh Akku",
|
||||
"title": "Price Per Wh Battery",
|
||||
"description": "A float representing the cost of battery energy per watt-hour."
|
||||
},
|
||||
"gesamtlast": {
|
||||
"total_load": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Gesamtlast",
|
||||
"title": "Total Load",
|
||||
"description": "An array of floats representing the total load (consumption) in watts for different time intervals."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pv_prognose_wh",
|
||||
"strompreis_euro_pro_wh",
|
||||
"einspeiseverguetung_euro_pro_wh",
|
||||
"preis_euro_pro_wh_akku",
|
||||
"gesamtlast"
|
||||
"pv_forecast_wh",
|
||||
"electricity_price_per_wh",
|
||||
"feed_in_tariff_per_wh",
|
||||
"price_per_wh_battery",
|
||||
"total_load"
|
||||
],
|
||||
"title": "GeneticEnergyManagementParameters",
|
||||
"description": "Encapsulates energy-related forecasts and costs used in GENETIC optimization."
|
||||
@@ -5014,51 +5013,51 @@
|
||||
},
|
||||
"GeneticSimulationResult": {
|
||||
"properties": {
|
||||
"Last_Wh_pro_Stunde": {
|
||||
"load_wh_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Last Wh Pro Stunde",
|
||||
"description": "TBD"
|
||||
"title": "Load Wh Per Hour",
|
||||
"description": "The load in watt-hours per hour."
|
||||
},
|
||||
"EAuto_SoC_pro_Stunde": {
|
||||
"ev_soc_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Eauto Soc Pro Stunde",
|
||||
"title": "Ev Soc Per Hour",
|
||||
"description": "The state of charge of the EV for each hour."
|
||||
},
|
||||
"Einnahmen_Euro_pro_Stunde": {
|
||||
"revenue_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Einnahmen Euro Pro Stunde",
|
||||
"description": "The revenue from grid feed-in or other sources in euros per hour."
|
||||
"title": "Revenue Per Hour",
|
||||
"description": "The revenue from grid feed-in or other sources per hour."
|
||||
},
|
||||
"Gesamt_Verluste": {
|
||||
"total_losses": {
|
||||
"type": "number",
|
||||
"title": "Gesamt Verluste",
|
||||
"title": "Total Losses",
|
||||
"description": "The total losses in watt-hours over the entire period."
|
||||
},
|
||||
"Gesamtbilanz_Euro": {
|
||||
"total_balance": {
|
||||
"type": "number",
|
||||
"title": "Gesamtbilanz Euro",
|
||||
"description": "The total balance of revenues minus costs in euros."
|
||||
"title": "Total Balance",
|
||||
"description": "The total balance of revenues minus costs."
|
||||
},
|
||||
"Gesamteinnahmen_Euro": {
|
||||
"total_revenue": {
|
||||
"type": "number",
|
||||
"title": "Gesamteinnahmen Euro",
|
||||
"description": "The total revenues in euros."
|
||||
"title": "Total Revenue",
|
||||
"description": "The total revenues."
|
||||
},
|
||||
"Gesamtkosten_Euro": {
|
||||
"total_costs": {
|
||||
"type": "number",
|
||||
"title": "Gesamtkosten Euro",
|
||||
"description": "The total costs in euros."
|
||||
"title": "Total Costs",
|
||||
"description": "The total costs."
|
||||
},
|
||||
"Home_appliance_wh_per_hour": {
|
||||
"home_appliance_wh_per_hour": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -5073,13 +5072,138 @@
|
||||
"title": "Home Appliance Wh Per Hour",
|
||||
"description": "The energy consumption of a household appliance in watt-hours per hour."
|
||||
},
|
||||
"costs_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Costs Per Hour",
|
||||
"description": "The costs per hour."
|
||||
},
|
||||
"grid_consumption_wh_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Grid Consumption Wh Per Hour",
|
||||
"description": "The grid energy drawn in watt-hours per hour."
|
||||
},
|
||||
"grid_feed_in_wh_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Grid Feed In Wh Per Hour",
|
||||
"description": "The energy fed into the grid in watt-hours per hour."
|
||||
},
|
||||
"losses_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Losses Per Hour",
|
||||
"description": "The losses in watt-hours per hour."
|
||||
},
|
||||
"battery_soc_per_hour": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Battery Soc Per Hour",
|
||||
"description": "The state of charge of the battery (not the EV) in percentage per hour."
|
||||
},
|
||||
"electricity_price": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Electricity Price",
|
||||
"description": "Used Electricity Price, including predictions"
|
||||
},
|
||||
"Last_Wh_pro_Stunde": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Last Wh Pro Stunde",
|
||||
"description": "Deprecated: Use load_wh_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"EAuto_SoC_pro_Stunde": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Eauto Soc Pro Stunde",
|
||||
"description": "Deprecated: Use ev_soc_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Einnahmen_Euro_pro_Stunde": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Einnahmen Euro Pro Stunde",
|
||||
"description": "Deprecated: Use revenue_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Gesamt_Verluste": {
|
||||
"type": "number",
|
||||
"title": "Gesamt Verluste",
|
||||
"description": "Deprecated: Use total_losses instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Gesamtbilanz_Euro": {
|
||||
"type": "number",
|
||||
"title": "Gesamtbilanz Euro",
|
||||
"description": "Deprecated: Use total_balance instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Gesamteinnahmen_Euro": {
|
||||
"type": "number",
|
||||
"title": "Gesamteinnahmen Euro",
|
||||
"description": "Deprecated: Use total_revenue instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Gesamtkosten_Euro": {
|
||||
"type": "number",
|
||||
"title": "Gesamtkosten Euro",
|
||||
"description": "Deprecated: Use total_costs instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Home_appliance_wh_per_hour": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Home Appliance Wh Per Hour",
|
||||
"description": "Deprecated: Use home_appliance_wh_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Kosten_Euro_pro_Stunde": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Kosten Euro Pro Stunde",
|
||||
"description": "The costs in euros per hour."
|
||||
"description": "Deprecated: Use costs_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Netzbezug_Wh_pro_Stunde": {
|
||||
"items": {
|
||||
@@ -5087,7 +5211,9 @@
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Netzbezug Wh Pro Stunde",
|
||||
"description": "The grid energy drawn in watt-hours per hour."
|
||||
"description": "Deprecated: Use grid_consumption_wh_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Netzeinspeisung_Wh_pro_Stunde": {
|
||||
"items": {
|
||||
@@ -5095,7 +5221,9 @@
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Netzeinspeisung Wh Pro Stunde",
|
||||
"description": "The energy fed into the grid in watt-hours per hour."
|
||||
"description": "Deprecated: Use grid_feed_in_wh_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Verluste_Pro_Stunde": {
|
||||
"items": {
|
||||
@@ -5103,7 +5231,9 @@
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Verluste Pro Stunde",
|
||||
"description": "The losses in watt-hours per hour."
|
||||
"description": "Deprecated: Use losses_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"akku_soc_pro_stunde": {
|
||||
"items": {
|
||||
@@ -5111,7 +5241,9 @@
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Akku Soc Pro Stunde",
|
||||
"description": "The state of charge of the battery (not the EV) in percentage per hour."
|
||||
"description": "Deprecated: Use battery_soc_per_hour instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"Electricity_price": {
|
||||
"items": {
|
||||
@@ -5119,12 +5251,27 @@
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Electricity Price",
|
||||
"description": "Used Electricity Price, including predictions"
|
||||
"description": "Deprecated: Use electricity_price instead.",
|
||||
"deprecated": true,
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"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",
|
||||
"Last_Wh_pro_Stunde",
|
||||
"EAuto_SoC_pro_Stunde",
|
||||
"Einnahmen_Euro_pro_Stunde",
|
||||
@@ -5236,7 +5383,7 @@
|
||||
"eauto_obj"
|
||||
],
|
||||
"title": "GeneticSolution",
|
||||
"description": "**Note**: The first value of \"Last_Wh_per_hour\", \"Netzeinspeisung_Wh_per_hour\", and \"Netzbezug_Wh_per_hour\", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged."
|
||||
"description": "**Note**: The first value of \"load_wh_per_hour\", \"grid_feed_in_wh_per_hour\", and \"grid_consumption_wh_per_hour\", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged."
|
||||
},
|
||||
"GesamtlastRequest": {
|
||||
"properties": {
|
||||
|
||||
@@ -278,7 +278,7 @@ class EnergyManagement(
|
||||
start_hour = EnergyManagement._start_datetime.hour
|
||||
solution = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: optimization.optimierung_ems(
|
||||
lambda: optimization.optimize_ems(
|
||||
start_hour=start_hour,
|
||||
parameters=cast(
|
||||
GeneticOptimizationParameters, genetic_parameters
|
||||
@@ -355,7 +355,7 @@ class EnergyManagement(
|
||||
start_hour = EnergyManagement._start_datetime.hour
|
||||
solution = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: optimization.optimierung_ems(
|
||||
lambda: optimization.optimize_ems(
|
||||
start_hour=start_hour,
|
||||
parameters=genetic_parameters,
|
||||
ngen=genetic_individuals,
|
||||
|
||||
@@ -51,7 +51,7 @@ class BatteriesCommonSettings(DevicesBaseSettings):
|
||||
levelized_cost_of_storage_kwh: float = Field(
|
||||
default=0.0,
|
||||
json_schema_extra={
|
||||
"description": "Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [€/kWh].",
|
||||
"description": "Levelized cost of storage (LCOS), the average lifetime cost of delivering one kWh [amount/kWh].",
|
||||
"examples": [0.12],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -67,13 +67,13 @@ class GeneticSimulation(PydanticBaseModel):
|
||||
elect_price_hourly: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None,
|
||||
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."
|
||||
},
|
||||
)
|
||||
elect_revenue_per_hour_arr: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"description": "An array of floats representing the feed-in compensation in euros per watt-hour."
|
||||
"description": "An array of floats representing the feed-in compensation per watt-hour."
|
||||
},
|
||||
)
|
||||
|
||||
@@ -121,15 +121,13 @@ class GeneticSimulation(PydanticBaseModel):
|
||||
self.prediction_hours = prediction_hours
|
||||
|
||||
# Load arrays from provided EMS parameters
|
||||
self.load_energy_array = np.array(parameters.gesamtlast, float)
|
||||
self.pv_prediction_wh = np.array(parameters.pv_prognose_wh, float)
|
||||
self.elect_price_hourly = np.array(parameters.strompreis_euro_pro_wh, float)
|
||||
self.load_energy_array = np.array(parameters.total_load, float)
|
||||
self.pv_prediction_wh = np.array(parameters.pv_forecast_wh, float)
|
||||
self.elect_price_hourly = np.array(parameters.electricity_price_per_wh, float)
|
||||
self.elect_revenue_per_hour_arr = (
|
||||
parameters.einspeiseverguetung_euro_pro_wh
|
||||
if isinstance(parameters.einspeiseverguetung_euro_pro_wh, list)
|
||||
else np.full(
|
||||
len(self.load_energy_array), parameters.einspeiseverguetung_euro_pro_wh, float
|
||||
)
|
||||
parameters.feed_in_tariff_per_wh
|
||||
if isinstance(parameters.feed_in_tariff_per_wh, list)
|
||||
else np.full(len(self.load_energy_array), parameters.feed_in_tariff_per_wh, float)
|
||||
)
|
||||
|
||||
# Associate devices
|
||||
@@ -159,8 +157,8 @@ class GeneticSimulation(PydanticBaseModel):
|
||||
def simulate(self, start_hour: int) -> dict[str, Any]:
|
||||
"""Simulate energy usage and costs for the given start hour.
|
||||
|
||||
akku_soc_pro_stunde begin of the hour, initial hour state!
|
||||
last_wh_pro_stunde integral of last hour (end state)
|
||||
battery_soc_per_hour begin of the hour, initial hour state!
|
||||
load_wh_per_hour integral of last hour (end state)
|
||||
"""
|
||||
# Remember start hour
|
||||
self.start_hour = start_hour
|
||||
@@ -328,11 +326,11 @@ class GeneticSimulation(PydanticBaseModel):
|
||||
if ev_fast:
|
||||
soc_ev_per_hour[hour_idx] = ev_fast.current_soc_percentage() # save begin state
|
||||
if ev_charge_hours_fast[hour] > 0:
|
||||
loaded_energy_ev, verluste_eauto = ev_fast.charge_energy(
|
||||
loaded_energy_ev, ev_charge_losses = ev_fast.charge_energy(
|
||||
wh=None, hour=hour, charge_factor=ev_charge_hours_fast[hour]
|
||||
)
|
||||
consumption += loaded_energy_ev
|
||||
losses_wh_per_hour[hour_idx] += verluste_eauto
|
||||
losses_wh_per_hour[hour_idx] += ev_charge_losses
|
||||
|
||||
# Save battery SOC before inverter processing = true begin-of-interval state.
|
||||
# Must be recorded here (before DC charge/discharge) so the displayed SOC at
|
||||
@@ -342,9 +340,9 @@ class GeneticSimulation(PydanticBaseModel):
|
||||
soc_per_hour[hour_idx] = battery_fast.current_soc_percentage()
|
||||
|
||||
# Process inverter logic
|
||||
energy_feedin_grid_actual = energy_consumption_grid_actual = losses = eigenverbrauch = (
|
||||
0.0
|
||||
)
|
||||
energy_feedin_grid_actual = energy_consumption_grid_actual = losses = (
|
||||
self_consumption
|
||||
) = 0.0
|
||||
|
||||
if inverter_fast:
|
||||
energy_produced = pv_prediction_wh_fast[hour]
|
||||
@@ -352,7 +350,7 @@ class GeneticSimulation(PydanticBaseModel):
|
||||
energy_feedin_grid_actual,
|
||||
energy_consumption_grid_actual,
|
||||
losses,
|
||||
eigenverbrauch,
|
||||
self_consumption,
|
||||
) = inverter_fast.process_energy(energy_produced, consumption, hour)
|
||||
|
||||
# AC PV Battery Charge
|
||||
@@ -442,7 +440,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
)
|
||||
self.ev_possible_charge_values: list[float] = [1.0]
|
||||
# Separate charge-level list for battery AC charging (independent of EV rates).
|
||||
# Populated from parameters.pv_akku.charge_rates in optimierung_ems.
|
||||
# Populated from parameters.pv_akku.charge_rates in optimize_ems.
|
||||
self.bat_possible_charge_values: list[float] = [1.0]
|
||||
self.verbose = verbose
|
||||
self.fix_seed = fixed_seed
|
||||
@@ -585,14 +583,14 @@ class GeneticOptimization(OptimizationBase):
|
||||
if self.optimize_ev and eautocharge_hours_index is not None:
|
||||
individual.extend(eautocharge_hours_index.tolist())
|
||||
elif self.optimize_ev:
|
||||
# Falls optimize_ev aktiv ist, aber keine EV-Daten vorhanden sind, fügen wir Nullen hinzu
|
||||
# If optimize_ev is active but no EV data is available, append zeros
|
||||
individual.extend([0] * self.config.prediction.hours)
|
||||
|
||||
# Add dishwasher start time if applicable
|
||||
if self.opti_param.get("home_appliance", 0) > 0 and washingstart_int is not None:
|
||||
individual.append(washingstart_int)
|
||||
elif self.opti_param.get("home_appliance", 0) > 0:
|
||||
# Falls ein Haushaltsgerät optimiert wird, aber kein Startzeitpunkt vorhanden ist
|
||||
# If a home appliance is optimized but no start time is available
|
||||
individual.append(0)
|
||||
|
||||
return individual
|
||||
@@ -784,7 +782,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
# Return bad fitness score ("FitnessMin") in case of an exception
|
||||
return (100000.0,)
|
||||
|
||||
gesamtbilanz = simulation_result["Gesamtbilanz_Euro"] * (-1.0 if worst_case else 1.0)
|
||||
total_balance = simulation_result["Gesamtbilanz_Euro"] * (-1.0 if worst_case else 1.0)
|
||||
|
||||
# EV 100% & charge not allowed
|
||||
if self.optimize_ev:
|
||||
@@ -846,7 +844,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
# # discharge_hours_bin_tail[zero_soc_mask] = (
|
||||
# # len_ac + 2
|
||||
# # ) # Activate discharge for these hours
|
||||
# set_to_len_ac_plus_2 = np.random.rand() < 0.5 # True mit 50% Wahrscheinlichkeit
|
||||
# set_to_len_ac_plus_2 = np.random.rand() < 0.5 # True with 50% probability
|
||||
|
||||
# # Werte setzen basierend auf der zufälligen Entscheidung
|
||||
# value_to_set = len_ac + 2 if set_to_len_ac_plus_2 else 0
|
||||
@@ -874,8 +872,8 @@ class GeneticOptimization(OptimizationBase):
|
||||
# (stored DC energy must pass through inverter to be usable as AC)
|
||||
if self.simulation.inverter:
|
||||
battery_energy_content *= self.simulation.inverter.dc_to_ac_efficiency
|
||||
restwert_akku = battery_energy_content * parameters.ems.preis_euro_pro_wh_akku
|
||||
gesamtbilanz += -restwert_akku
|
||||
battery_residual_value = battery_energy_content * parameters.ems.price_per_wh_battery
|
||||
total_balance += -battery_residual_value
|
||||
|
||||
# --- AC charging break-even penalty ---
|
||||
# Penalise AC charging decisions that cannot be economically justified given the
|
||||
@@ -924,7 +922,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
* inv.dc_to_ac_efficiency
|
||||
)
|
||||
|
||||
# Configurable penalty multiplier (default 1 = economic loss in €)
|
||||
# Configurable penalty multiplier (default 1 = economic loss in currency units)
|
||||
try:
|
||||
ac_penalty_factor = float(
|
||||
self.config.optimization.genetic.penalties["ac_charge_break_even"]
|
||||
@@ -971,7 +969,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
dc_wh = bat.max_charge_power_w * ac_factor
|
||||
ac_wh = dc_wh / max(inv.ac_to_dc_efficiency, 1e-9)
|
||||
excess_cost_per_wh = break_even_price - best_uncovered_price
|
||||
gesamtbilanz += ac_wh * excess_cost_per_wh * ac_penalty_factor
|
||||
total_balance += ac_wh * excess_cost_per_wh * ac_penalty_factor
|
||||
|
||||
if self.optimize_ev and parameters.eauto and self.simulation.ev:
|
||||
try:
|
||||
@@ -987,11 +985,11 @@ class GeneticOptimization(OptimizationBase):
|
||||
ev_soc_percentage < parameters.eauto.min_soc_percentage
|
||||
or ev_soc_percentage > parameters.eauto.max_soc_percentage
|
||||
):
|
||||
gesamtbilanz += (
|
||||
total_balance += (
|
||||
abs(parameters.eauto.min_soc_percentage - ev_soc_percentage) * penalty
|
||||
)
|
||||
|
||||
return (gesamtbilanz,)
|
||||
return (total_balance,)
|
||||
|
||||
def optimize(
|
||||
self,
|
||||
@@ -1000,7 +998,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
) -> tuple[Any, dict[str, list[Any]]]:
|
||||
"""Run the optimization process using a genetic algorithm.
|
||||
|
||||
@TODO: optimize() ngen default (200) is different from optimierung_ems() ngen default (400).
|
||||
@TODO: optimize() ngen default (200) is different from optimize_ems() ngen default (400).
|
||||
"""
|
||||
# Set the number of inviduals in a generation
|
||||
try:
|
||||
@@ -1047,17 +1045,17 @@ class GeneticOptimization(OptimizationBase):
|
||||
"min": log.select("min"), # Minimum fitness for each generation (Y-axis)
|
||||
}
|
||||
|
||||
member: dict[str, list[float]] = {"bilanz": [], "verluste": [], "nebenbedingung": []}
|
||||
member: dict[str, list[float]] = {"balance": [], "losses": [], "constraints": []}
|
||||
for ind in population:
|
||||
if hasattr(ind, "extra_data"):
|
||||
extra_value1, extra_value2, extra_value3 = ind.extra_data
|
||||
member["bilanz"].append(extra_value1)
|
||||
member["verluste"].append(extra_value2)
|
||||
member["nebenbedingung"].append(extra_value3)
|
||||
member["balance"].append(extra_value1)
|
||||
member["losses"].append(extra_value2)
|
||||
member["constraints"].append(extra_value3)
|
||||
|
||||
return hof[0], member
|
||||
|
||||
def optimierung_ems(
|
||||
def optimize_ems(
|
||||
self,
|
||||
parameters: GeneticOptimizationParameters,
|
||||
start_hour: Optional[int] = None,
|
||||
@@ -1082,28 +1080,24 @@ class GeneticOptimization(OptimizationBase):
|
||||
generations = 400
|
||||
logger.error("Generations not configured. Using {}.", generations)
|
||||
|
||||
einspeiseverguetung_euro_pro_wh = np.full(
|
||||
self.config.prediction.hours, parameters.ems.einspeiseverguetung_euro_pro_wh
|
||||
)
|
||||
|
||||
self.simulation.reset()
|
||||
|
||||
# Initialize PV and EV batteries
|
||||
akku: Optional[Battery] = None
|
||||
battery: Optional[Battery] = None
|
||||
if parameters.pv_akku:
|
||||
akku = Battery(
|
||||
battery = Battery(
|
||||
parameters.pv_akku,
|
||||
prediction_hours=self.config.prediction.hours,
|
||||
)
|
||||
akku.set_charge_per_hour(np.full(self.config.prediction.hours, 0))
|
||||
battery.set_charge_per_hour(np.full(self.config.prediction.hours, 0))
|
||||
|
||||
eauto: Optional[Battery] = None
|
||||
ev: Optional[Battery] = None
|
||||
if parameters.eauto:
|
||||
eauto = Battery(
|
||||
ev = Battery(
|
||||
parameters.eauto,
|
||||
prediction_hours=self.config.prediction.hours,
|
||||
)
|
||||
eauto.set_charge_per_hour(np.full(self.config.prediction.hours, 1))
|
||||
ev.set_charge_per_hour(np.full(self.config.prediction.hours, 1))
|
||||
self.optimize_ev = (
|
||||
parameters.eauto.min_soc_percentage - parameters.eauto.initial_soc_percentage >= 0
|
||||
)
|
||||
@@ -1172,7 +1166,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
if parameters.inverter:
|
||||
inverter = Inverter(
|
||||
parameters.inverter,
|
||||
battery=akku,
|
||||
battery=battery,
|
||||
)
|
||||
|
||||
# Prepare device simulation
|
||||
@@ -1181,7 +1175,7 @@ class GeneticOptimization(OptimizationBase):
|
||||
optimization_hours=self.config.optimization.horizon_hours,
|
||||
prediction_hours=self.config.prediction.hours,
|
||||
inverter=inverter, # battery is part of inverter
|
||||
ev=eauto,
|
||||
ev=ev,
|
||||
home_appliance=dishwasher,
|
||||
)
|
||||
|
||||
@@ -1240,10 +1234,10 @@ class GeneticOptimization(OptimizationBase):
|
||||
"dc_charge": dc_charge_hours,
|
||||
"discharge_allowed": discharge,
|
||||
"eautocharge_hours_float": eautocharge_hours_float,
|
||||
"result": simulation_result,
|
||||
"result": GeneticSimulationResult(**simulation_result).model_dump(),
|
||||
"eauto_obj": self.simulation.ev.to_dict() if self.simulation.ev else None,
|
||||
"start_solution": start_solution,
|
||||
"spuelstart": washingstart_int,
|
||||
"washingstart": washingstart_int,
|
||||
"extra_data": extra_data,
|
||||
"fitness_history": self.fitness_history,
|
||||
"fixed_seed": self.fix_seed,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any, Optional
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from loguru import logger
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic import AliasChoices, ConfigDict, Field, computed_field, field_validator
|
||||
|
||||
from akkudoktoreos.core.coreabc import (
|
||||
ConfigMixin,
|
||||
@@ -86,62 +86,153 @@ class ElectricVehicleResult(DeviceOptimizeResult):
|
||||
class GeneticSimulationResult(GeneticParametersBaseModel):
|
||||
"""This object contains the results of the simulation and provides insights into various parameters over the entire forecast period."""
|
||||
|
||||
Last_Wh_pro_Stunde: list[float] = Field(json_schema_extra={"description": "TBD"})
|
||||
EAuto_SoC_pro_Stunde: list[float] = Field(
|
||||
json_schema_extra={"description": "The state of charge of the EV for each hour."}
|
||||
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
||||
|
||||
load_wh_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("load_wh_per_hour", "Last_Wh_pro_Stunde"),
|
||||
json_schema_extra={"description": "The load in watt-hours per hour."},
|
||||
)
|
||||
Einnahmen_Euro_pro_Stunde: list[float] = Field(
|
||||
ev_soc_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("ev_soc_per_hour", "EAuto_SoC_pro_Stunde"),
|
||||
json_schema_extra={"description": "The state of charge of the EV for each hour."},
|
||||
)
|
||||
revenue_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("revenue_per_hour", "Einnahmen_Euro_pro_Stunde"),
|
||||
json_schema_extra={
|
||||
"description": "The revenue from grid feed-in or other sources in euros per hour."
|
||||
}
|
||||
"description": "The revenue from grid feed-in or other sources per hour."
|
||||
},
|
||||
)
|
||||
Gesamt_Verluste: float = Field(
|
||||
json_schema_extra={"description": "The total losses in watt-hours over the entire period."}
|
||||
total_losses: float = Field(
|
||||
validation_alias=AliasChoices("total_losses", "Gesamt_Verluste"),
|
||||
json_schema_extra={"description": "The total losses in watt-hours over the entire period."},
|
||||
)
|
||||
Gesamtbilanz_Euro: float = Field(
|
||||
json_schema_extra={"description": "The total balance of revenues minus costs in euros."}
|
||||
total_balance: float = Field(
|
||||
validation_alias=AliasChoices("total_balance", "Gesamtbilanz_Euro"),
|
||||
json_schema_extra={"description": "The total balance of revenues minus costs."},
|
||||
)
|
||||
Gesamteinnahmen_Euro: float = Field(
|
||||
json_schema_extra={"description": "The total revenues in euros."}
|
||||
total_revenue: float = Field(
|
||||
validation_alias=AliasChoices("total_revenue", "Gesamteinnahmen_Euro"),
|
||||
json_schema_extra={"description": "The total revenues."},
|
||||
)
|
||||
Gesamtkosten_Euro: float = Field(json_schema_extra={"description": "The total costs in euros."})
|
||||
Home_appliance_wh_per_hour: list[Optional[float]] = Field(
|
||||
total_costs: float = Field(
|
||||
validation_alias=AliasChoices("total_costs", "Gesamtkosten_Euro"),
|
||||
json_schema_extra={"description": "The total costs."},
|
||||
)
|
||||
home_appliance_wh_per_hour: list[Optional[float]] = Field(
|
||||
validation_alias=AliasChoices("home_appliance_wh_per_hour", "Home_appliance_wh_per_hour"),
|
||||
json_schema_extra={
|
||||
"description": "The energy consumption of a household appliance in watt-hours per hour."
|
||||
}
|
||||
},
|
||||
)
|
||||
Kosten_Euro_pro_Stunde: list[float] = Field(
|
||||
json_schema_extra={"description": "The costs in euros per hour."}
|
||||
costs_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("costs_per_hour", "Kosten_Euro_pro_Stunde"),
|
||||
json_schema_extra={"description": "The costs per hour."},
|
||||
)
|
||||
Netzbezug_Wh_pro_Stunde: list[float] = Field(
|
||||
json_schema_extra={"description": "The grid energy drawn in watt-hours per hour."}
|
||||
grid_consumption_wh_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("grid_consumption_wh_per_hour", "Netzbezug_Wh_pro_Stunde"),
|
||||
json_schema_extra={"description": "The grid energy drawn in watt-hours per hour."},
|
||||
)
|
||||
Netzeinspeisung_Wh_pro_Stunde: list[float] = Field(
|
||||
json_schema_extra={"description": "The energy fed into the grid in watt-hours per hour."}
|
||||
grid_feed_in_wh_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("grid_feed_in_wh_per_hour", "Netzeinspeisung_Wh_pro_Stunde"),
|
||||
json_schema_extra={"description": "The energy fed into the grid in watt-hours per hour."},
|
||||
)
|
||||
Verluste_Pro_Stunde: list[float] = Field(
|
||||
json_schema_extra={"description": "The losses in watt-hours per hour."}
|
||||
losses_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("losses_per_hour", "Verluste_Pro_Stunde"),
|
||||
json_schema_extra={"description": "The losses in watt-hours per hour."},
|
||||
)
|
||||
akku_soc_pro_stunde: list[float] = Field(
|
||||
battery_soc_per_hour: list[float] = Field(
|
||||
validation_alias=AliasChoices("battery_soc_per_hour", "akku_soc_pro_stunde"),
|
||||
json_schema_extra={
|
||||
"description": "The state of charge of the battery (not the EV) in percentage per hour."
|
||||
}
|
||||
},
|
||||
)
|
||||
Electricity_price: list[float] = Field(
|
||||
json_schema_extra={"description": "Used Electricity Price, including predictions"}
|
||||
electricity_price: list[float] = Field(
|
||||
validation_alias=AliasChoices("electricity_price", "Electricity_price"),
|
||||
json_schema_extra={"description": "Used Electricity Price, including predictions"},
|
||||
)
|
||||
|
||||
# Computed fields for backward compatibility (deprecated German names)
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Last_Wh_pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use load_wh_per_hour instead."""
|
||||
return self.load_wh_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def EAuto_SoC_pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use ev_soc_per_hour instead."""
|
||||
return self.ev_soc_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Einnahmen_Euro_pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use revenue_per_hour instead."""
|
||||
return self.revenue_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Gesamt_Verluste(self) -> float:
|
||||
"""Deprecated: Use total_losses instead."""
|
||||
return self.total_losses
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Gesamtbilanz_Euro(self) -> float:
|
||||
"""Deprecated: Use total_balance instead."""
|
||||
return self.total_balance
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Gesamteinnahmen_Euro(self) -> float:
|
||||
"""Deprecated: Use total_revenue instead."""
|
||||
return self.total_revenue
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Gesamtkosten_Euro(self) -> float:
|
||||
"""Deprecated: Use total_costs instead."""
|
||||
return self.total_costs
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Home_appliance_wh_per_hour(self) -> list[Optional[float]]:
|
||||
"""Deprecated: Use home_appliance_wh_per_hour instead."""
|
||||
return self.home_appliance_wh_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Kosten_Euro_pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use costs_per_hour instead."""
|
||||
return self.costs_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Netzbezug_Wh_pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use grid_consumption_wh_per_hour instead."""
|
||||
return self.grid_consumption_wh_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Netzeinspeisung_Wh_pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use grid_feed_in_wh_per_hour instead."""
|
||||
return self.grid_feed_in_wh_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Verluste_Pro_Stunde(self) -> list[float]:
|
||||
"""Deprecated: Use losses_per_hour instead."""
|
||||
return self.losses_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def akku_soc_pro_stunde(self) -> list[float]:
|
||||
"""Deprecated: Use battery_soc_per_hour instead."""
|
||||
return self.battery_soc_per_hour
|
||||
|
||||
@computed_field(json_schema_extra={"deprecated": True})
|
||||
def Electricity_price(self) -> list[float]:
|
||||
"""Deprecated: Use electricity_price instead."""
|
||||
return self.electricity_price
|
||||
|
||||
@field_validator(
|
||||
"Last_Wh_pro_Stunde",
|
||||
"Netzeinspeisung_Wh_pro_Stunde",
|
||||
"akku_soc_pro_stunde",
|
||||
"Netzbezug_Wh_pro_Stunde",
|
||||
"Kosten_Euro_pro_Stunde",
|
||||
"Einnahmen_Euro_pro_Stunde",
|
||||
"EAuto_SoC_pro_Stunde",
|
||||
"Verluste_Pro_Stunde",
|
||||
"Home_appliance_wh_per_hour",
|
||||
"Electricity_price",
|
||||
"load_wh_per_hour",
|
||||
"grid_feed_in_wh_per_hour",
|
||||
"battery_soc_per_hour",
|
||||
"grid_consumption_wh_per_hour",
|
||||
"costs_per_hour",
|
||||
"revenue_per_hour",
|
||||
"ev_soc_per_hour",
|
||||
"losses_per_hour",
|
||||
"home_appliance_wh_per_hour",
|
||||
"electricity_price",
|
||||
mode="before",
|
||||
)
|
||||
def convert_numpy(cls, field: Any) -> Any:
|
||||
@@ -149,7 +240,7 @@ class GeneticSimulationResult(GeneticParametersBaseModel):
|
||||
|
||||
|
||||
class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
"""**Note**: The first value of "Last_Wh_per_hour", "Netzeinspeisung_Wh_per_hour", and "Netzbezug_Wh_per_hour", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged."""
|
||||
"""**Note**: The first value of "load_wh_per_hour", "grid_feed_in_wh_per_hour", and "grid_consumption_wh_per_hour", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged."""
|
||||
|
||||
ac_charge: list[float] = Field(
|
||||
json_schema_extra={
|
||||
@@ -363,7 +454,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
|
||||
# --- Create index based on list length and interval ---
|
||||
# Ensure we only use the minimum of results and commands if differing
|
||||
periods = min(len(self.result.Kosten_Euro_pro_Stunde), len(self.ac_charge) - start_day_hour)
|
||||
periods = min(len(self.result.costs_per_hour), len(self.ac_charge) - start_day_hour)
|
||||
time_index = pd.date_range(
|
||||
start=start_datetime,
|
||||
periods=periods,
|
||||
@@ -387,12 +478,12 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
{
|
||||
"date_time": time_index,
|
||||
# result starts at start_day_hour
|
||||
"load_energy_wh": self.result.Last_Wh_pro_Stunde[:n_points],
|
||||
"grid_feedin_energy_wh": self.result.Netzeinspeisung_Wh_pro_Stunde[:n_points],
|
||||
"grid_consumption_energy_wh": self.result.Netzbezug_Wh_pro_Stunde[:n_points],
|
||||
"costs_amt": self.result.Kosten_Euro_pro_Stunde[:n_points],
|
||||
"revenue_amt": self.result.Einnahmen_Euro_pro_Stunde[:n_points],
|
||||
"losses_energy_wh": self.result.Verluste_Pro_Stunde[:n_points],
|
||||
"load_energy_wh": self.result.load_wh_per_hour[:n_points],
|
||||
"grid_feedin_energy_wh": self.result.grid_feed_in_wh_per_hour[:n_points],
|
||||
"grid_consumption_energy_wh": self.result.grid_consumption_wh_per_hour[:n_points],
|
||||
"costs_amt": self.result.costs_per_hour[:n_points],
|
||||
"revenue_amt": self.result.revenue_per_hour[:n_points],
|
||||
"losses_energy_wh": self.result.losses_per_hour[:n_points],
|
||||
},
|
||||
index=time_index,
|
||||
)
|
||||
@@ -401,7 +492,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
battery_device_id = self._battery_device_id()
|
||||
solution[f"{battery_device_id}_soc_factor"] = [
|
||||
v / 100
|
||||
for v in self.result.akku_soc_pro_stunde[:n_points] # result starts at start_day_hour
|
||||
for v in self.result.battery_soc_per_hour[:n_points] # result starts at start_day_hour
|
||||
]
|
||||
operation: dict[str, list[float]] = {
|
||||
"genetic_ac_charge_factor": [],
|
||||
@@ -427,8 +518,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
# this hour given the expected battery state of charge.
|
||||
result_idx = hour_idx - start_day_hour
|
||||
soc_h_pct = (
|
||||
self.result.akku_soc_pro_stunde[result_idx]
|
||||
if result_idx < len(self.result.akku_soc_pro_stunde)
|
||||
self.result.battery_soc_per_hour[result_idx]
|
||||
if result_idx < len(self.result.battery_soc_per_hour)
|
||||
else 0.0
|
||||
)
|
||||
eff_ac, eff_dc, eff_dis = self._soc_clamped_operation_factors(
|
||||
@@ -458,7 +549,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
|
||||
# Add EV battery solution
|
||||
# eautocharge_hours_float start at hour 0 of start day
|
||||
# result.EAuto_SoC_pro_Stunde start at start_datetime.hour
|
||||
# result.ev_soc_per_hour start at start_datetime.hour
|
||||
if self.eauto_obj:
|
||||
ev_device_id = self._ev_device_id()
|
||||
if self.eautocharge_hours_float is None:
|
||||
@@ -480,7 +571,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
solution[factor_key] = [0.0] * n_points
|
||||
else:
|
||||
solution[f"{ev_device_id}_soc_factor"] = [
|
||||
v / 100 for v in self.result.EAuto_SoC_pro_Stunde[:n_points]
|
||||
v / 100 for v in self.result.ev_soc_per_hour[:n_points]
|
||||
]
|
||||
operation = {
|
||||
"genetic_ev_charge_factor": [],
|
||||
@@ -520,7 +611,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
homeappliance_device_id = self._homeappliance_device_id()
|
||||
# result starts at start_day_hour
|
||||
solution[f"{homeappliance_device_id}_energy_wh"] = (
|
||||
self.result.Home_appliance_wh_per_hour[:n_points]
|
||||
self.result.home_appliance_wh_per_hour[:n_points]
|
||||
)
|
||||
operation = {
|
||||
f"{homeappliance_device_id}_run_op_mode": [],
|
||||
@@ -629,11 +720,11 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
comment="Optimization solution derived from GeneticSolution.",
|
||||
valid_from=start_datetime,
|
||||
valid_until=start_datetime.add(hours=self.config.optimization.horizon_hours),
|
||||
total_losses_energy_wh=self.result.Gesamt_Verluste,
|
||||
total_revenues_amt=self.result.Gesamteinnahmen_Euro,
|
||||
total_costs_amt=self.result.Gesamtkosten_Euro,
|
||||
total_losses_energy_wh=self.result.total_losses,
|
||||
total_revenues_amt=self.result.total_revenue,
|
||||
total_costs_amt=self.result.total_costs,
|
||||
fitness_score={
|
||||
self.result.Gesamtkosten_Euro,
|
||||
self.result.total_costs,
|
||||
},
|
||||
prediction=PydanticDateTimeDataFrame.from_dataframe(prediction),
|
||||
solution=PydanticDateTimeDataFrame.from_dataframe(solution),
|
||||
@@ -667,8 +758,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
# dataframe (genetic_*_factor columns).
|
||||
result_idx = hour_idx - start_day_hour
|
||||
soc_h_pct = (
|
||||
self.result.akku_soc_pro_stunde[result_idx]
|
||||
if result_idx < len(self.result.akku_soc_pro_stunde)
|
||||
self.result.battery_soc_per_hour[result_idx]
|
||||
if result_idx < len(self.result.battery_soc_per_hour)
|
||||
else 0.0
|
||||
)
|
||||
eff_ac, eff_dc, eff_dis = self._soc_clamped_operation_factors(
|
||||
@@ -752,11 +843,11 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
# even if configured to be started.
|
||||
resource_id = self._homeappliance_device_id()
|
||||
last_energy: Optional[float] = None
|
||||
for hours, energy in enumerate(self.result.Home_appliance_wh_per_hour):
|
||||
for hours, energy in enumerate(self.result.home_appliance_wh_per_hour):
|
||||
# hours starts at start_datetime with 0
|
||||
if energy is None:
|
||||
raise ValueError(
|
||||
f"Unexpected value {energy} in {self.result.Home_appliance_wh_per_hour}"
|
||||
f"Unexpected value {energy} in {self.result.home_appliance_wh_per_hour}"
|
||||
)
|
||||
if last_energy is None or energy != last_energy:
|
||||
if energy > 0.0:
|
||||
|
||||
@@ -43,7 +43,7 @@ class ElecPriceCommonSettings(SettingsBaseModel):
|
||||
default=None,
|
||||
ge=0,
|
||||
json_schema_extra={
|
||||
"description": "Electricity price charges [€/kWh]. Will be added to variable market price.",
|
||||
"description": "Electricity price charges [amount/kWh]. Will be added to variable market price.",
|
||||
"examples": [0.21],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -21,14 +21,14 @@ class ElecPriceDataRecord(PredictionRecord):
|
||||
"""
|
||||
|
||||
elecprice_marketprice_wh: Optional[float] = Field(
|
||||
None, json_schema_extra={"description": "Electricity market price per Wh (€/Wh)"}
|
||||
None, json_schema_extra={"description": "Electricity market price per Wh [amount/Wh]"}
|
||||
)
|
||||
|
||||
# Computed fields
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def elecprice_marketprice_kwh(self) -> Optional[float]:
|
||||
"""Electricity market price per kWh (€/kWh).
|
||||
"""Electricity market price per kWh [amount/kWh].
|
||||
|
||||
Convenience attribute calculated from `elecprice_marketprice_wh`.
|
||||
"""
|
||||
|
||||
@@ -21,14 +21,14 @@ class FeedInTariffDataRecord(PredictionRecord):
|
||||
"""
|
||||
|
||||
feed_in_tariff_wh: Optional[float] = Field(
|
||||
None, json_schema_extra={"description": "Feed in tariff per Wh (€/Wh)"}
|
||||
None, json_schema_extra={"description": "Feed in tariff per Wh [amount/Wh]"}
|
||||
)
|
||||
|
||||
# Computed fields
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def feed_in_tariff_kwh(self) -> Optional[float]:
|
||||
"""Feed in tariff per kWh (€/kWh).
|
||||
"""Feed in tariff per kWh [amount/kWh].
|
||||
|
||||
Convenience attribute calculated from `feed_in_tariff_wh`.
|
||||
"""
|
||||
|
||||
@@ -17,7 +17,7 @@ class FeedInTariffFixedCommonSettings(SettingsBaseModel):
|
||||
default=None,
|
||||
ge=0,
|
||||
json_schema_extra={
|
||||
"description": "Electricity price feed in tariff [€/kWH].",
|
||||
"description": "Electricity price feed in tariff [amount/kWh].",
|
||||
"examples": [0.078],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -265,11 +265,11 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
y2_axis = LinearAxis(y_range_name="factor", axis_label="Factor [0.0..1.0]")
|
||||
plot.add_layout(y2_axis, "left")
|
||||
# y3 axis
|
||||
y3_axis = LinearAxis(y_range_name="amt_kwh", axis_label="Electricty Price [currency/kWh]")
|
||||
y3_axis = LinearAxis(y_range_name="amt_kwh", axis_label="Electricity Price [amount/kWh]")
|
||||
y3_axis.axis_label_text_color = "red"
|
||||
plot.add_layout(y3_axis, "right")
|
||||
# y4 axis
|
||||
y4_axis = LinearAxis(y_range_name="amt", axis_label="Amount [currency]")
|
||||
y4_axis = LinearAxis(y_range_name="amt", axis_label="Amount")
|
||||
plot.add_layout(y4_axis, "right")
|
||||
|
||||
plot.toolbar.autohide = True
|
||||
|
||||
@@ -54,7 +54,7 @@ def ElectricityPriceForecast(
|
||||
),
|
||||
title=f"Electricity Price Prediction ({provider})",
|
||||
x_axis_label=f"Datetime [localtime {date_time_tz}]",
|
||||
y_axis_label="Price [€/kWh]",
|
||||
y_axis_label="Price [amount/kWh]",
|
||||
sizing_mode="stretch_width",
|
||||
height=400,
|
||||
)
|
||||
|
||||
@@ -204,7 +204,20 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
|
||||
app = FastAPI(
|
||||
title="Akkudoktor-EOS",
|
||||
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.",
|
||||
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.
|
||||
|
||||
## Currency Information
|
||||
|
||||
All monetary values in this API are expressed in the locally configured currency of the EOS installation. The system is designed to work with any currency (e.g., EUR, SEK, CHF, USD, GBP, etc.). Each installation uses a single, consistent currency throughout all endpoints and responses.
|
||||
|
||||
Values are given in whole currency units, not in hundredth subunits: a value of `0.0003` in an installation configured for Euro means 0.0003 EUR/Wh (i.e. 0.30 EUR/kWh), never cents. The same applies to all totals (e.g. `total_costs` of `1.5` = 1.50 EUR).
|
||||
|
||||
Field names containing cost, price, revenue, tariff, or similar monetary terms (e.g., `total_costs`, `electricity_price_per_wh`, `revenue_per_hour`) represent amounts in the configured currency, without explicit currency designation in the field name to maintain currency-neutrality.
|
||||
|
||||
## Deprecated Field Names
|
||||
|
||||
The genetic optimization API fields were renamed from German to English. For backward compatibility the old German field names (e.g. `gesamtlast`, `pv_prognose_wh`, `Gesamtbilanz_Euro`) are still accepted on input and are emitted in responses alongside the English names, marked as deprecated in the schema. They will be removed in a future release — new clients should use the English field names only.
|
||||
""",
|
||||
summary="Comprehensive solution for simulating and optimizing an energy system based on renewable energy sources",
|
||||
version=f"v{__version__}",
|
||||
license_info={
|
||||
@@ -1160,7 +1173,7 @@ def fastapi_energy_management_plan_get() -> EnergyManagementPlan:
|
||||
|
||||
@app.get("/strompreis", tags=["prediction"])
|
||||
async def fastapi_strompreis() -> list[float]:
|
||||
"""Deprecated: Electricity Market Price Prediction per Wh (€/Wh).
|
||||
"""Deprecated: Electricity Market Price Prediction per Wh [amount/Wh].
|
||||
|
||||
Electricity prices start at 00.00.00 today and are provided for 48 hours.
|
||||
If no prices are available the missing ones at the start of the series are
|
||||
|
||||
@@ -448,7 +448,7 @@ def prepare_visualize(
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date,
|
||||
[
|
||||
parameters.ems.gesamtlast[start_hour:],
|
||||
parameters.ems.total_load[start_hour:],
|
||||
],
|
||||
title="Load Profile",
|
||||
# xlabel="Hours", # not enough space
|
||||
@@ -458,7 +458,7 @@ def prepare_visualize(
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date,
|
||||
[
|
||||
parameters.ems.pv_prognose_wh[start_hour:],
|
||||
parameters.ems.pv_forecast_wh[start_hour:],
|
||||
],
|
||||
title="PV Forecast",
|
||||
# xlabel="Hours", # not enough space
|
||||
@@ -469,15 +469,15 @@ def prepare_visualize(
|
||||
next_full_hour_date,
|
||||
[
|
||||
np.full(
|
||||
len(parameters.ems.gesamtlast) - start_hour,
|
||||
parameters.ems.einspeiseverguetung_euro_pro_wh[start_hour:]
|
||||
if isinstance(parameters.ems.einspeiseverguetung_euro_pro_wh, list)
|
||||
else parameters.ems.einspeiseverguetung_euro_pro_wh,
|
||||
len(parameters.ems.total_load) - start_hour,
|
||||
parameters.ems.feed_in_tariff_per_wh[start_hour:]
|
||||
if isinstance(parameters.ems.feed_in_tariff_per_wh, list)
|
||||
else parameters.ems.feed_in_tariff_per_wh,
|
||||
)
|
||||
],
|
||||
title="Remuneration",
|
||||
# xlabel="Hours", # not enough space
|
||||
ylabel="€/Wh",
|
||||
ylabel="amount/Wh",
|
||||
x2label=None, # not enough space
|
||||
)
|
||||
if parameters.temperature_forecast:
|
||||
@@ -497,11 +497,11 @@ def prepare_visualize(
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
[
|
||||
results["result"]["Last_Wh_pro_Stunde"],
|
||||
results["result"]["Home_appliance_wh_per_hour"],
|
||||
results["result"]["Netzeinspeisung_Wh_pro_Stunde"],
|
||||
results["result"]["Netzbezug_Wh_pro_Stunde"],
|
||||
results["result"]["Verluste_Pro_Stunde"],
|
||||
results["result"]["load_wh_per_hour"],
|
||||
results["result"]["home_appliance_wh_per_hour"],
|
||||
results["result"]["grid_feed_in_wh_per_hour"],
|
||||
results["result"]["grid_consumption_wh_per_hour"],
|
||||
results["result"]["losses_per_hour"],
|
||||
],
|
||||
title="Energy Flow per Hour",
|
||||
# xlabel="Date", # not enough space
|
||||
@@ -521,7 +521,7 @@ def prepare_visualize(
|
||||
# Group 3:
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
[results["result"]["akku_soc_pro_stunde"], results["result"]["EAuto_SoC_pro_Stunde"]],
|
||||
[results["result"]["battery_soc_per_hour"], results["result"]["ev_soc_per_hour"]],
|
||||
title="Battery SOC",
|
||||
# xlabel="Date", # not enough space
|
||||
ylabel="%",
|
||||
@@ -533,10 +533,10 @@ def prepare_visualize(
|
||||
)
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
[parameters.ems.strompreis_euro_pro_wh[start_hour:]],
|
||||
[parameters.ems.electricity_price_per_wh[start_hour:]],
|
||||
# title="Electricity Price", # not enough space
|
||||
# xlabel="Date", # not enough space
|
||||
ylabel="Electricity Price (€/Wh)",
|
||||
ylabel="Electricity Price (amount/Wh)",
|
||||
x2label=None, # not enough space
|
||||
)
|
||||
|
||||
@@ -570,50 +570,50 @@ def prepare_visualize(
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
[
|
||||
results["result"]["Kosten_Euro_pro_Stunde"],
|
||||
results["result"]["Einnahmen_Euro_pro_Stunde"],
|
||||
results["result"]["costs_per_hour"],
|
||||
results["result"]["revenue_per_hour"],
|
||||
],
|
||||
title="Financial Balance per Hour",
|
||||
# xlabel="Date", # not enough space
|
||||
ylabel="Euro",
|
||||
ylabel="Amount",
|
||||
labels=["Costs", "Revenue"],
|
||||
)
|
||||
|
||||
extra_data = results["extra_data"]
|
||||
report.create_scatter_plot(
|
||||
extra_data["verluste"],
|
||||
extra_data["bilanz"],
|
||||
extra_data["losses"],
|
||||
extra_data["balance"],
|
||||
title="Scatter Plot",
|
||||
xlabel="losses",
|
||||
ylabel="balance",
|
||||
c=extra_data["nebenbedingung"],
|
||||
c=extra_data["constraints"],
|
||||
)
|
||||
|
||||
values_list = [
|
||||
[
|
||||
results["result"]["Gesamtkosten_Euro"],
|
||||
results["result"]["Gesamteinnahmen_Euro"],
|
||||
results["result"]["Gesamtbilanz_Euro"],
|
||||
results["result"]["total_costs"],
|
||||
results["result"]["total_revenue"],
|
||||
results["result"]["total_balance"],
|
||||
]
|
||||
]
|
||||
labels = ["Total Costs [€]", "Total Revenue [€]", "Total Balance [€]"]
|
||||
labels = ["Total Costs [amount]", "Total Revenue [amount]", "Total Balance [amount]"]
|
||||
|
||||
report.create_bar_chart(
|
||||
labels=labels,
|
||||
values_list=values_list,
|
||||
title="Financial Overview",
|
||||
ylabel="Euro",
|
||||
xlabels=["Total Costs [€]", "Total Revenue [€]", "Total Balance [€]"],
|
||||
ylabel="Amount",
|
||||
xlabels=["Total Costs [amount]", "Total Revenue [amount]", "Total Balance [amount]"],
|
||||
)
|
||||
|
||||
report.finalize_group()
|
||||
|
||||
# Group 1: Scatter plot of losses vs balance with color-coded constraints
|
||||
f1 = np.array(extra_data["verluste"]) # Losses
|
||||
f2 = np.array(extra_data["bilanz"]) # Balance
|
||||
n1 = np.array(extra_data["nebenbedingung"]) # Constraints
|
||||
f1 = np.array(extra_data["losses"]) # Losses
|
||||
f2 = np.array(extra_data["balance"]) # Balance
|
||||
n1 = np.array(extra_data["constraints"]) # Constraints
|
||||
|
||||
# Filter data where 'nebenbedingung' < 0.01
|
||||
# Filter data where 'constraints' < 0.01
|
||||
filtered_indices = n1 < 0.01
|
||||
filtered_losses = f1[filtered_indices]
|
||||
filtered_balance = f2[filtered_indices]
|
||||
|
||||
@@ -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