Adds inverter AC/DC efficiency and break-even penalty (#888)
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled

* feat: add inverter AC/DC efficiency and break-even penalty

* test: update tests/test_geneticoptimize.py with new ac_charge_break_even parameter

* docs: update documentation

* chore: update version numbers in configuration files to v0.2.0.dev2602272006923535
This commit is contained in:
Christopher Nadler
2026-02-27 23:12:08 +01:00
committed by GitHub
parent 04420e66ab
commit 3ccc25d731
30 changed files with 3043 additions and 152 deletions

View File

@@ -9,7 +9,7 @@
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| homeassistant | `EOS_ADAPTER__HOMEASSISTANT` | `HomeAssistantAdapterCommonSettings` | `rw` | `required` | Home Assistant adapter settings. |
| nodered | `EOS_ADAPTER__NODERED` | `NodeREDAdapterCommonSettings` | `rw` | `required` | NodeRED adapter settings. |
| provider | `EOS_ADAPTER__PROVIDER` | `Optional[list[str]]` | `rw` | `None` | List of adapter provider id(s) of provider(s) to be used. |
| provider | `EOS_ADAPTER__PROVIDER` | `list[str] | None` | `rw` | `None` | List of adapter provider id(s) of provider(s) to be used. |
| providers | | `list[str]` | `ro` | `N/A` | Available adapter provider ids. |
:::
<!-- pyml enable line-length -->
@@ -103,8 +103,8 @@ There are two URLs that are used:
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| host | `Optional[str]` | `rw` | `127.0.0.1` | Node-RED server IP address. Defaults to 127.0.0.1. |
| port | `Optional[int]` | `rw` | `1880` | Node-RED server IP port number. Defaults to 1880. |
| host | `str | None` | `rw` | `127.0.0.1` | Node-RED server IP address. Defaults to 127.0.0.1. |
| port | `int | None` | `rw` | `1880` | Node-RED server IP port number. Defaults to 1880. |
:::
<!-- pyml enable line-length -->
@@ -134,21 +134,21 @@ There are two URLs that are used:
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| config_entity_ids | `Optional[dict[str, str]]` | `rw` | `None` | Mapping of EOS config keys to Home Assistant entity IDs.
| config_entity_ids | `dict[str, str] | None` | `rw` | `None` | Mapping of EOS config keys to Home Assistant entity IDs.
The config key has to be given by a /-separated path
e.g. devices/batteries/0/capacity_wh |
| device_instruction_entity_ids | `Optional[list[str]]` | `rw` | `None` | Entity IDs for device (resource) instructions to be updated by EOS.
| device_instruction_entity_ids | `list[str] | None` | `rw` | `None` | Entity IDs for device (resource) instructions to be updated by EOS.
The device ids (resource ids) have to be prepended by 'sensor.eos_' to build the entity_id.
E.g. The instruction for device id 'battery1' becomes the entity_id 'sensor.eos_battery1'. |
| device_measurement_entity_ids | `Optional[dict[str, str]]` | `rw` | `None` | Mapping of EOS measurement keys used by device (resource) simulations to Home Assistant entity IDs. |
| device_measurement_entity_ids | `dict[str, str] | None` | `rw` | `None` | Mapping of EOS measurement keys used by device (resource) simulations to Home Assistant entity IDs. |
| eos_device_instruction_entity_ids | `list[str]` | `ro` | `N/A` | Entity IDs for energy management instructions available at EOS. |
| eos_solution_entity_ids | `list[str]` | `ro` | `N/A` | Entity IDs for optimization solution available at EOS. |
| grid_export_emr_entity_ids | `Optional[list[str]]` | `rw` | `None` | Entity ID(s) of export to grid energy meter readings [kWh] |
| grid_import_emr_entity_ids | `Optional[list[str]]` | `rw` | `None` | Entity ID(s) of import from grid energy meter readings [kWh] |
| grid_export_emr_entity_ids | `list[str] | None` | `rw` | `None` | Entity ID(s) of export to grid energy meter readings [kWh] |
| grid_import_emr_entity_ids | `list[str] | None` | `rw` | `None` | Entity ID(s) of import from grid energy meter readings [kWh] |
| homeassistant_entity_ids | `list[str]` | `ro` | `N/A` | Entity IDs available at Home Assistant. |
| load_emr_entity_ids | `Optional[list[str]]` | `rw` | `None` | Entity ID(s) of load energy meter readings [kWh] |
| pv_production_emr_entity_ids | `Optional[list[str]]` | `rw` | `None` | Entity ID(s) of PV production energy meter readings [kWh] |
| solution_entity_ids | `Optional[list[str]]` | `rw` | `None` | Entity IDs for optimization solution keys to be updated by EOS.
| load_emr_entity_ids | `list[str] | None` | `rw` | `None` | Entity ID(s) of load energy meter readings [kWh] |
| pv_production_emr_entity_ids | `list[str] | None` | `rw` | `None` | Entity ID(s) of PV production energy meter readings [kWh] |
| solution_entity_ids | `list[str] | None` | `rw` | `None` | Entity IDs for optimization solution keys to be updated by EOS.
The solution keys have to be prepended by 'sensor.eos_' to build the entity_id.
E.g. solution key 'battery1_idle_op_mode' becomes the entity_id 'sensor.eos_battery1_idle_op_mode'. |
:::

View File

@@ -8,7 +8,7 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| cleanup_interval | `EOS_CACHE__CLEANUP_INTERVAL` | `float` | `rw` | `300.0` | Intervall in seconds for EOS file cache cleanup. |
| subpath | `EOS_CACHE__SUBPATH` | `Optional[pathlib.Path]` | `rw` | `cache` | Sub-path for the EOS cache data directory. |
| subpath | `EOS_CACHE__SUBPATH` | `pathlib.Path | None` | `rw` | `cache` | Sub-path for the EOS cache data directory. |
:::
<!-- pyml enable line-length -->

View File

@@ -13,16 +13,16 @@ Attributes:
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| autosave_interval_sec | `EOS_DATABASE__AUTOSAVE_INTERVAL_SEC` | `Optional[int]` | `rw` | `10` | Automatic saving interval [seconds].
| autosave_interval_sec | `EOS_DATABASE__AUTOSAVE_INTERVAL_SEC` | `int | None` | `rw` | `10` | Automatic saving interval [seconds].
Set to None to disable automatic saving. |
| batch_size | `EOS_DATABASE__BATCH_SIZE` | `int` | `rw` | `100` | Number of records to process in batch operations. |
| compaction_interval_sec | `EOS_DATABASE__COMPACTION_INTERVAL_SEC` | `Optional[int]` | `rw` | `604800` | Interval in between automatic tiered compaction runs [seconds].
| compaction_interval_sec | `EOS_DATABASE__COMPACTION_INTERVAL_SEC` | `int | None` | `rw` | `604800` | Interval in between automatic tiered compaction runs [seconds].
Compaction downsamples old records to reduce storage while retaining coverage. Set to None to disable automatic compaction. |
| compression_level | `EOS_DATABASE__COMPRESSION_LEVEL` | `int` | `rw` | `9` | Compression level for database record data. |
| initial_load_window_h | `EOS_DATABASE__INITIAL_LOAD_WINDOW_H` | `Optional[int]` | `rw` | `None` | Specifies the default duration of the initial load window when loading records from the database, in hours. If set to None, the full available range is loaded. The window is centered around the current time by default, unless a different center time is specified. Different database namespaces may define their own default windows. |
| keep_duration_h | `EOS_DATABASE__KEEP_DURATION_H` | `Optional[int]` | `rw` | `None` | Default maximum duration records shall be kept in database [hours, none].
| initial_load_window_h | `EOS_DATABASE__INITIAL_LOAD_WINDOW_H` | `int | None` | `rw` | `None` | Specifies the default duration of the initial load window when loading records from the database, in hours. If set to None, the full available range is loaded. The window is centered around the current time by default, unless a different center time is specified. Different database namespaces may define their own default windows. |
| keep_duration_h | `EOS_DATABASE__KEEP_DURATION_H` | `int | None` | `rw` | `None` | Default maximum duration records shall be kept in database [hours, none].
None indicates forever. Database namespaces may have diverging definitions. |
| provider | `EOS_DATABASE__PROVIDER` | `Optional[str]` | `rw` | `None` | Database provider id of provider to be used. |
| provider | `EOS_DATABASE__PROVIDER` | `str | None` | `rw` | `None` | Database provider id of provider to be used. |
| providers | | `List[str]` | `ro` | `N/A` | Return available database provider ids. |
:::
<!-- pyml enable line-length -->

View File

@@ -7,15 +7,15 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| batteries | `EOS_DEVICES__BATTERIES` | `Optional[list[akkudoktoreos.devices.devices.BatteriesCommonSettings]]` | `rw` | `None` | List of battery devices |
| electric_vehicles | `EOS_DEVICES__ELECTRIC_VEHICLES` | `Optional[list[akkudoktoreos.devices.devices.BatteriesCommonSettings]]` | `rw` | `None` | List of electric vehicle devices |
| home_appliances | `EOS_DEVICES__HOME_APPLIANCES` | `Optional[list[akkudoktoreos.devices.devices.HomeApplianceCommonSettings]]` | `rw` | `None` | List of home appliances |
| inverters | `EOS_DEVICES__INVERTERS` | `Optional[list[akkudoktoreos.devices.devices.InverterCommonSettings]]` | `rw` | `None` | List of inverters |
| max_batteries | `EOS_DEVICES__MAX_BATTERIES` | `Optional[int]` | `rw` | `None` | Maximum number of batteries that can be set |
| max_electric_vehicles | `EOS_DEVICES__MAX_ELECTRIC_VEHICLES` | `Optional[int]` | `rw` | `None` | Maximum number of electric vehicles that can be set |
| max_home_appliances | `EOS_DEVICES__MAX_HOME_APPLIANCES` | `Optional[int]` | `rw` | `None` | Maximum number of home_appliances that can be set |
| max_inverters | `EOS_DEVICES__MAX_INVERTERS` | `Optional[int]` | `rw` | `None` | Maximum number of inverters that can be set |
| measurement_keys | | `Optional[list[str]]` | `ro` | `N/A` | Return the measurement keys for the resource/ device stati that are measurements. |
| batteries | `EOS_DEVICES__BATTERIES` | `list[akkudoktoreos.devices.devices.BatteriesCommonSettings] | None` | `rw` | `None` | List of battery devices |
| electric_vehicles | `EOS_DEVICES__ELECTRIC_VEHICLES` | `list[akkudoktoreos.devices.devices.BatteriesCommonSettings] | None` | `rw` | `None` | List of electric vehicle devices |
| home_appliances | `EOS_DEVICES__HOME_APPLIANCES` | `list[akkudoktoreos.devices.devices.HomeApplianceCommonSettings] | None` | `rw` | `None` | List of home appliances |
| inverters | `EOS_DEVICES__INVERTERS` | `list[akkudoktoreos.devices.devices.InverterCommonSettings] | None` | `rw` | `None` | List of inverters |
| max_batteries | `EOS_DEVICES__MAX_BATTERIES` | `int | None` | `rw` | `None` | Maximum number of batteries that can be set |
| max_electric_vehicles | `EOS_DEVICES__MAX_ELECTRIC_VEHICLES` | `int | None` | `rw` | `None` | Maximum number of electric vehicles that can be set |
| max_home_appliances | `EOS_DEVICES__MAX_HOME_APPLIANCES` | `int | None` | `rw` | `None` | Maximum number of home_appliances that can be set |
| max_inverters | `EOS_DEVICES__MAX_INVERTERS` | `int | None` | `rw` | `None` | Maximum number of inverters that can be set |
| measurement_keys | | `list[str] | None` | `ro` | `N/A` | Return the measurement keys for the resource/ device stati that are measurements. |
:::
<!-- pyml enable line-length -->
@@ -206,10 +206,13 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| battery_id | `Optional[str]` | `rw` | `None` | ID of battery controlled by this inverter. |
| ac_to_dc_efficiency | `float` | `rw` | `1.0` | Efficiency of AC to DC conversion for grid-to-battery AC charging (0-1). Set to 0 to disable AC charging. Default 1.0 (no additional inverter loss). |
| battery_id | `str | None` | `rw` | `None` | ID of battery controlled by this inverter. |
| dc_to_ac_efficiency | `float` | `rw` | `1.0` | Efficiency of DC to AC conversion for battery discharging to AC load/grid (0-1). Default 1.0 (no additional inverter loss). |
| device_id | `str` | `rw` | `<unknown>` | ID of device |
| max_power_w | `Optional[float]` | `rw` | `None` | Maximum power [W]. |
| measurement_keys | `Optional[list[str]]` | `ro` | `N/A` | Measurement keys for the inverter stati that are measurements. |
| max_ac_charge_power_w | `float | None` | `rw` | `None` | Maximum AC charging power in watts. null means no additional limit. Set to 0 to disable AC charging. |
| max_power_w | `float | None` | `rw` | `None` | Maximum power [W]. |
| measurement_keys | `list[str] | None` | `ro` | `N/A` | Measurement keys for the inverter stati that are measurements. |
:::
<!-- pyml enable line-length -->
@@ -225,7 +228,10 @@
{
"device_id": "battery1",
"max_power_w": 10000.0,
"battery_id": null
"battery_id": null,
"ac_to_dc_efficiency": 0.95,
"dc_to_ac_efficiency": 0.95,
"max_ac_charge_power_w": null
}
]
}
@@ -246,6 +252,9 @@
"device_id": "battery1",
"max_power_w": 10000.0,
"battery_id": null,
"ac_to_dc_efficiency": 0.95,
"dc_to_ac_efficiency": 0.95,
"max_ac_charge_power_w": null,
"measurement_keys": []
}
]
@@ -266,8 +275,8 @@
| consumption_wh | `int` | `rw` | `required` | Energy consumption [Wh]. |
| device_id | `str` | `rw` | `<unknown>` | ID of device |
| duration_h | `int` | `rw` | `required` | Usage duration in hours [0 ... 24]. |
| measurement_keys | `Optional[list[str]]` | `ro` | `N/A` | Measurement keys for the home appliance stati that are measurements. |
| time_windows | `Optional[akkudoktoreos.utils.datetimeutil.TimeWindowSequence]` | `rw` | `None` | Sequence of allowed time windows. Defaults to optimization general time window. |
| measurement_keys | `list[str] | None` | `ro` | `N/A` | Measurement keys for the home appliance stati that are measurements. |
| time_windows | `akkudoktoreos.utils.datetimeutil.TimeWindowSequence | None` | `rw` | `None` | Sequence of allowed time windows. Defaults to optimization general time window. |
:::
<!-- pyml enable line-length -->
@@ -344,20 +353,20 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| capacity_wh | `int` | `rw` | `8000` | Capacity [Wh]. |
| charge_rates | `Optional[list[float]]` | `rw` | `[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]` | Charge rates as factor of maximum charging power [0.00 ... 1.00]. None triggers fallback to default charge-rates. |
| charge_rates | `list[float] | None` | `rw` | `[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]` | Charge rates as factor of maximum charging power [0.00 ... 1.00]. None triggers fallback to default charge-rates. |
| 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]. |
| max_charge_power_w | `Optional[float]` | `rw` | `5000` | Maximum charging power [W]. |
| max_charge_power_w | `float | None` | `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]. |
| measurement_key_power_l1_w | `str` | `ro` | `N/A` | Measurement key for the L1 power the battery is charged or discharged with [W]. |
| measurement_key_power_l2_w | `str` | `ro` | `N/A` | Measurement key for the L2 power the battery is charged or discharged with [W]. |
| measurement_key_power_l3_w | `str` | `ro` | `N/A` | Measurement key for the L3 power the battery is charged or discharged with [W]. |
| measurement_key_soc_factor | `str` | `ro` | `N/A` | Measurement key for the battery state of charge (SoC) as factor of total capacity [0.0 ... 1.0]. |
| measurement_keys | `Optional[list[str]]` | `ro` | `N/A` | Measurement keys for the battery stati that are measurements. |
| min_charge_power_w | `Optional[float]` | `rw` | `50` | Minimum charging power [W]. |
| measurement_keys | `list[str] | None` | `ro` | `N/A` | Measurement keys for the battery stati that are measurements. |
| min_charge_power_w | `float | None` | `rw` | `50` | Minimum charging power [W]. |
| min_soc_percentage | `int` | `rw` | `0` | Minimum state of charge (SOC) as percentage of capacity [%]. This is the target SoC for charging |
:::
<!-- pyml enable line-length -->

View File

@@ -7,12 +7,12 @@
| 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` | `float | None` | `rw` | `None` | Electricity price charges [€/kWh]. Will be added to variable market price. |
| elecpriceimport | `EOS_ELECPRICE__ELECPRICEIMPORT` | `ElecPriceImportCommonSettings` | `rw` | `required` | Import provider settings. |
| energycharts | `EOS_ELECPRICE__ENERGYCHARTS` | `ElecPriceEnergyChartsCommonSettings` | `rw` | `required` | Energy Charts provider settings. |
| provider | `EOS_ELECPRICE__PROVIDER` | `Optional[str]` | `rw` | `None` | Electricity price provider id of provider to be used. |
| provider | `EOS_ELECPRICE__PROVIDER` | `str | None` | `rw` | `None` | Electricity price provider id of provider to be used. |
| providers | | `list[str]` | `ro` | `N/A` | Available electricity price provider ids. |
| vat_rate | `EOS_ELECPRICE__VAT_RATE` | `Optional[float]` | `rw` | `1.19` | VAT rate factor applied to electricity price when charges are used. |
| vat_rate | `EOS_ELECPRICE__VAT_RATE` | `float | None` | `rw` | `1.19` | VAT rate factor applied to electricity price when charges are used. |
:::
<!-- pyml enable line-length -->
@@ -105,8 +105,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| import_file_path | `Union[str, pathlib.Path, NoneType]` | `rw` | `None` | Path to the file to import elecprice data from. |
| import_json | `Optional[str]` | `rw` | `None` | JSON string, dictionary of electricity price forecast value lists. |
| import_file_path | `str | pathlib.Path | None` | `rw` | `None` | Path to the file to import elecprice data from. |
| import_json | `str | None` | `rw` | `None` | JSON string, dictionary of electricity price forecast value lists. |
:::
<!-- pyml enable line-length -->

View File

@@ -8,7 +8,7 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| interval | `EOS_EMS__INTERVAL` | `float` | `rw` | `300.0` | Intervall between EOS energy management runs [seconds]. |
| mode | `EOS_EMS__MODE` | `Optional[akkudoktoreos.core.emsettings.EnergyManagementMode]` | `rw` | `None` | Energy management mode [OPTIMIZATION | PREDICTION]. |
| mode | `EOS_EMS__MODE` | `akkudoktoreos.core.emsettings.EnergyManagementMode | None` | `rw` | `None` | Energy management mode [OPTIMIZATION | PREDICTION]. |
| startup_delay | `EOS_EMS__STARTUP_DELAY` | `float` | `rw` | `5` | Startup delay in seconds for EOS energy management runs. |
:::
<!-- pyml enable line-length -->

View File

@@ -120,7 +120,7 @@
}
},
"general": {
"version": "0.2.0.dev2602250574650225",
"version": "0.2.0.dev2602272006923535",
"data_folder_path": "/home/user/.local/share/net.akkudoktoreos.net",
"data_output_subpath": "output",
"latitude": 52.52,

View File

@@ -7,7 +7,7 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| provider | `EOS_FEEDINTARIFF__PROVIDER` | `Optional[str]` | `rw` | `None` | Feed in tariff provider id of provider to be used. |
| provider | `EOS_FEEDINTARIFF__PROVIDER` | `str | None` | `rw` | `None` | Feed in tariff provider id of provider to be used. |
| provider_settings | `EOS_FEEDINTARIFF__PROVIDER_SETTINGS` | `FeedInTariffCommonProviderSettings` | `rw` | `required` | Provider settings |
| providers | | `list[str]` | `ro` | `N/A` | Available feed in tariff provider ids. |
:::
@@ -62,8 +62,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| import_file_path | `Union[str, pathlib.Path, NoneType]` | `rw` | `None` | Path to the file to import feed in tariff data from. |
| import_json | `Optional[str]` | `rw` | `None` | JSON string, dictionary of feed in tariff forecast value lists. |
| import_file_path | `str | pathlib.Path | None` | `rw` | `None` | Path to the file to import feed in tariff data from. |
| import_json | `str | None` | `rw` | `None` | JSON string, dictionary of feed in tariff forecast value lists. |
:::
<!-- pyml enable line-length -->
@@ -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 | `float | None` | `rw` | `None` | Electricity price feed in tariff [€/kWH]. |
:::
<!-- pyml enable line-length -->
@@ -126,8 +126,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| FeedInTariffFixed | `Optional[akkudoktoreos.prediction.feedintarifffixed.FeedInTariffFixedCommonSettings]` | `rw` | `None` | FeedInTariffFixed settings |
| FeedInTariffImport | `Optional[akkudoktoreos.prediction.feedintariffimport.FeedInTariffImportCommonSettings]` | `rw` | `None` | FeedInTariffImport settings |
| FeedInTariffFixed | `akkudoktoreos.prediction.feedintarifffixed.FeedInTariffFixedCommonSettings | None` | `rw` | `None` | FeedInTariffFixed settings |
| FeedInTariffImport | `akkudoktoreos.prediction.feedintariffimport.FeedInTariffImportCommonSettings | None` | `rw` | `None` | FeedInTariffImport settings |
:::
<!-- pyml enable line-length -->

View File

@@ -7,16 +7,16 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| config_file_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Path to EOS configuration file. |
| config_folder_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Path to EOS configuration directory. |
| config_file_path | | `pathlib.Path | None` | `ro` | `N/A` | Path to EOS configuration file. |
| config_folder_path | | `pathlib.Path | None` | `ro` | `N/A` | Path to EOS configuration directory. |
| data_folder_path | `EOS_GENERAL__DATA_FOLDER_PATH` | `Path` | `rw` | `required` | Path to EOS data folder. |
| data_output_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Computed data_output_path based on data_folder_path. |
| data_output_subpath | `EOS_GENERAL__DATA_OUTPUT_SUBPATH` | `Optional[pathlib.Path]` | `rw` | `output` | Sub-path for the EOS output data folder. |
| data_output_path | | `pathlib.Path | None` | `ro` | `N/A` | Computed data_output_path based on data_folder_path. |
| data_output_subpath | `EOS_GENERAL__DATA_OUTPUT_SUBPATH` | `pathlib.Path | None` | `rw` | `output` | Sub-path for the EOS output data folder. |
| home_assistant_addon | `EOS_GENERAL__HOME_ASSISTANT_ADDON` | `bool` | `rw` | `required` | EOS is running as home assistant add-on. |
| latitude | `EOS_GENERAL__LATITUDE` | `Optional[float]` | `rw` | `52.52` | Latitude in decimal degrees between -90 and 90. North is positive (ISO 19115) (°) |
| longitude | `EOS_GENERAL__LONGITUDE` | `Optional[float]` | `rw` | `13.405` | Longitude in decimal degrees within -180 to 180 (°) |
| timezone | | `Optional[str]` | `ro` | `N/A` | Computed timezone based on latitude and longitude. |
| version | `EOS_GENERAL__VERSION` | `str` | `rw` | `0.2.0.dev2602250574650225` | Configuration file version. Used to check compatibility. |
| latitude | `EOS_GENERAL__LATITUDE` | `float | None` | `rw` | `52.52` | Latitude in decimal degrees between -90 and 90. North is positive (ISO 19115) (°) |
| longitude | `EOS_GENERAL__LONGITUDE` | `float | None` | `rw` | `13.405` | Longitude in decimal degrees within -180 to 180 (°) |
| timezone | | `str | None` | `ro` | `N/A` | Computed timezone based on latitude and longitude. |
| version | `EOS_GENERAL__VERSION` | `str` | `rw` | `0.2.0.dev2602272006923535` | Configuration file version. Used to check compatibility. |
:::
<!-- pyml enable line-length -->
@@ -28,7 +28,7 @@
```json
{
"general": {
"version": "0.2.0.dev2602250574650225",
"version": "0.2.0.dev2602272006923535",
"data_folder_path": "/home/user/.local/share/net.akkudoktoreos.net",
"data_output_subpath": "output",
"latitude": 52.52,
@@ -46,7 +46,7 @@
```json
{
"general": {
"version": "0.2.0.dev2602250574650225",
"version": "0.2.0.dev2602272006923535",
"data_folder_path": "/home/user/.local/share/net.akkudoktoreos.net",
"data_output_subpath": "output",
"latitude": 52.52,

View File

@@ -10,7 +10,7 @@
| loadakkudoktor | `EOS_LOAD__LOADAKKUDOKTOR` | `LoadAkkudoktorCommonSettings` | `rw` | `required` | LoadAkkudoktor provider settings. |
| loadimport | `EOS_LOAD__LOADIMPORT` | `LoadImportCommonSettings` | `rw` | `required` | LoadImport provider settings. |
| loadvrm | `EOS_LOAD__LOADVRM` | `LoadVrmCommonSettings` | `rw` | `required` | LoadVrm provider settings. |
| provider | `EOS_LOAD__PROVIDER` | `Optional[str]` | `rw` | `None` | Load provider id of provider to be used. |
| provider | `EOS_LOAD__PROVIDER` | `str | None` | `rw` | `None` | Load provider id of provider to be used. |
| providers | | `list[str]` | `ro` | `N/A` | Available load provider ids. |
:::
<!-- pyml enable line-length -->
@@ -111,8 +111,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| import_file_path | `Union[str, pathlib.Path, NoneType]` | `rw` | `None` | Path to the file to import load data from. |
| import_json | `Optional[str]` | `rw` | `None` | JSON string, dictionary of load forecast value lists. |
| import_file_path | `str | pathlib.Path | None` | `rw` | `None` | Path to the file to import load data from. |
| import_json | `str | None` | `rw` | `None` | JSON string, dictionary of load forecast value lists. |
:::
<!-- pyml enable line-length -->
@@ -142,7 +142,7 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| loadakkudoktor_year_energy_kwh | `Optional[float]` | `rw` | `None` | Yearly energy consumption (kWh). |
| loadakkudoktor_year_energy_kwh | `float | None` | `rw` | `None` | Yearly energy consumption (kWh). |
:::
<!-- pyml enable line-length -->

View File

@@ -7,9 +7,9 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| console_level | `EOS_LOGGING__CONSOLE_LEVEL` | `Optional[str]` | `rw` | `None` | Logging level when logging to console. |
| file_level | `EOS_LOGGING__FILE_LEVEL` | `Optional[str]` | `rw` | `None` | Logging level when logging to file. |
| file_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Computed log file path based on data output path. |
| console_level | `EOS_LOGGING__CONSOLE_LEVEL` | `str | None` | `rw` | `None` | Logging level when logging to console. |
| file_level | `EOS_LOGGING__FILE_LEVEL` | `str | None` | `rw` | `None` | Logging level when logging to file. |
| file_path | | `pathlib.Path | None` | `ro` | `N/A` | Computed log file path based on data output path. |
:::
<!-- pyml enable line-length -->

View File

@@ -7,12 +7,12 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| grid_export_emr_keys | `EOS_MEASUREMENT__GRID_EXPORT_EMR_KEYS` | `Optional[list[str]]` | `rw` | `None` | The keys of the measurements that are energy meter readings of energy export to grid [kWh]. |
| grid_import_emr_keys | `EOS_MEASUREMENT__GRID_IMPORT_EMR_KEYS` | `Optional[list[str]]` | `rw` | `None` | The keys of the measurements that are energy meter readings of energy import from grid [kWh]. |
| historic_hours | `EOS_MEASUREMENT__HISTORIC_HOURS` | `Optional[int]` | `rw` | `17520` | Number of hours into the past for measurement data |
| grid_export_emr_keys | `EOS_MEASUREMENT__GRID_EXPORT_EMR_KEYS` | `list[str] | None` | `rw` | `None` | The keys of the measurements that are energy meter readings of energy export to grid [kWh]. |
| grid_import_emr_keys | `EOS_MEASUREMENT__GRID_IMPORT_EMR_KEYS` | `list[str] | None` | `rw` | `None` | The keys of the measurements that are energy meter readings of energy import from grid [kWh]. |
| historic_hours | `EOS_MEASUREMENT__HISTORIC_HOURS` | `int | None` | `rw` | `17520` | Number of hours into the past for measurement data |
| keys | | `list[str]` | `ro` | `N/A` | The keys of the measurements that can be stored. |
| load_emr_keys | `EOS_MEASUREMENT__LOAD_EMR_KEYS` | `Optional[list[str]]` | `rw` | `None` | The keys of the measurements that are energy meter readings of a load [kWh]. |
| pv_production_emr_keys | `EOS_MEASUREMENT__PV_PRODUCTION_EMR_KEYS` | `Optional[list[str]]` | `rw` | `None` | The keys of the measurements that are PV production energy meter readings [kWh]. |
| load_emr_keys | `EOS_MEASUREMENT__LOAD_EMR_KEYS` | `list[str] | None` | `rw` | `None` | The keys of the measurements that are energy meter readings of a load [kWh]. |
| pv_production_emr_keys | `EOS_MEASUREMENT__PV_PRODUCTION_EMR_KEYS` | `list[str] | None` | `rw` | `None` | The keys of the measurements that are PV production energy meter readings [kWh]. |
:::
<!-- pyml enable line-length -->

View File

@@ -7,10 +7,10 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| algorithm | `EOS_OPTIMIZATION__ALGORITHM` | `Optional[str]` | `rw` | `GENETIC` | The optimization algorithm. |
| genetic | `EOS_OPTIMIZATION__GENETIC` | `Optional[akkudoktoreos.optimization.optimization.GeneticCommonSettings]` | `rw` | `None` | Genetic optimization algorithm configuration. |
| horizon_hours | `EOS_OPTIMIZATION__HORIZON_HOURS` | `Optional[int]` | `rw` | `24` | The general time window within which the energy optimization goal shall be achieved [h]. Defaults to 24 hours. |
| interval | `EOS_OPTIMIZATION__INTERVAL` | `Optional[int]` | `rw` | `3600` | The optimization interval [sec]. |
| algorithm | `EOS_OPTIMIZATION__ALGORITHM` | `str | None` | `rw` | `GENETIC` | The optimization algorithm. |
| genetic | `EOS_OPTIMIZATION__GENETIC` | `akkudoktoreos.optimization.optimization.GeneticCommonSettings | None` | `rw` | `None` | Genetic optimization algorithm configuration. |
| horizon_hours | `EOS_OPTIMIZATION__HORIZON_HOURS` | `int | None` | `rw` | `24` | The general time window within which the energy optimization goal shall be achieved [h]. Defaults to 24 hours. |
| interval | `EOS_OPTIMIZATION__INTERVAL` | `int | None` | `rw` | `3600` | The optimization interval [sec]. |
| keys | | `list[str]` | `ro` | `N/A` | The keys of the solution. |
:::
<!-- pyml enable line-length -->
@@ -73,10 +73,10 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| generations | `Optional[int]` | `rw` | `400` | Number of generations to evaluate the optimal solution [>= 10]. Defaults to 400. |
| individuals | `Optional[int]` | `rw` | `300` | Number of individuals (solutions) to generate for the (initial) generation [>= 10]. Defaults to 300. |
| penalties | `Optional[dict[str, Union[float, int, str]]]` | `rw` | `None` | A dictionary of penalty function parameters consisting of a penalty function parameter name and the associated value. |
| seed | `Optional[int]` | `rw` | `None` | Fixed seed for genetic algorithm. Defaults to 'None' which means random seed. |
| generations | `int | None` | `rw` | `400` | Number of generations to evaluate the optimal solution [>= 10]. Defaults to 400. |
| individuals | `int | None` | `rw` | `300` | Number of individuals (solutions) to generate for the (initial) generation [>= 10]. Defaults to 300. |
| penalties | `dict[str, float | int | str] | None` | `rw` | `None` | A dictionary of penalty function parameters consisting of a penalty function parameter name and the associated value. |
| seed | `int | None` | `rw` | `None` | Fixed seed for genetic algorithm. Defaults to 'None' which means random seed. |
:::
<!-- pyml enable line-length -->

View File

@@ -7,8 +7,8 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| historic_hours | `EOS_PREDICTION__HISTORIC_HOURS` | `Optional[int]` | `rw` | `48` | Number of hours into the past for historical predictions data |
| hours | `EOS_PREDICTION__HOURS` | `Optional[int]` | `rw` | `48` | Number of hours into the future for predictions |
| historic_hours | `EOS_PREDICTION__HISTORIC_HOURS` | `int | None` | `rw` | `48` | Number of hours into the past for historical predictions data |
| hours | `EOS_PREDICTION__HOURS` | `int | None` | `rw` | `48` | Number of hours into the future for predictions |
:::
<!-- pyml enable line-length -->

View File

@@ -7,14 +7,14 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| max_planes | `EOS_PVFORECAST__MAX_PLANES` | `Optional[int]` | `rw` | `0` | Maximum number of planes that can be set |
| planes | `EOS_PVFORECAST__PLANES` | `Optional[list[akkudoktoreos.prediction.pvforecast.PVForecastPlaneSetting]]` | `rw` | `None` | Plane configuration. |
| max_planes | `EOS_PVFORECAST__MAX_PLANES` | `int | None` | `rw` | `0` | Maximum number of planes that can be set |
| planes | `EOS_PVFORECAST__PLANES` | `list[akkudoktoreos.prediction.pvforecast.PVForecastPlaneSetting] | None` | `rw` | `None` | Plane configuration. |
| planes_azimuth | | `List[float]` | `ro` | `N/A` | Compute a list of the azimuths per active planes. |
| planes_inverter_paco | | `Any` | `ro` | `N/A` | Compute a list of the maximum power rating of the inverter per active planes. |
| planes_peakpower | | `List[float]` | `ro` | `N/A` | Compute a list of the peak power per active planes. |
| planes_tilt | | `List[float]` | `ro` | `N/A` | Compute a list of the tilts per active planes. |
| planes_userhorizon | | `Any` | `ro` | `N/A` | Compute a list of the user horizon per active planes. |
| provider | `EOS_PVFORECAST__PROVIDER` | `Optional[str]` | `rw` | `None` | PVForecast provider id of provider to be used. |
| provider | `EOS_PVFORECAST__PROVIDER` | `str | None` | `rw` | `None` | PVForecast provider id of provider to be used. |
| provider_settings | `EOS_PVFORECAST__PROVIDER_SETTINGS` | `PVForecastCommonProviderSettings` | `rw` | `required` | Provider settings |
| providers | | `list[str]` | `ro` | `N/A` | Available PVForecast provider ids. |
:::
@@ -225,8 +225,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| import_file_path | `Union[str, pathlib.Path, NoneType]` | `rw` | `None` | Path to the file to import PV forecast data from. |
| import_json | `Optional[str]` | `rw` | `None` | JSON string, dictionary of PV forecast value lists. |
| import_file_path | `str | pathlib.Path | None` | `rw` | `None` | Path to the file to import PV forecast data from. |
| import_json | `str | None` | `rw` | `None` | JSON string, dictionary of PV forecast value lists. |
:::
<!-- pyml enable line-length -->
@@ -258,8 +258,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| PVForecastImport | `Optional[akkudoktoreos.prediction.pvforecastimport.PVForecastImportCommonSettings]` | `rw` | `None` | PVForecastImport settings |
| PVForecastVrm | `Optional[akkudoktoreos.prediction.pvforecastvrm.PVForecastVrmCommonSettings]` | `rw` | `None` | PVForecastVrm settings |
| PVForecastImport | `akkudoktoreos.prediction.pvforecastimport.PVForecastImportCommonSettings | None` | `rw` | `None` | PVForecastImport settings |
| PVForecastVrm | `akkudoktoreos.prediction.pvforecastvrm.PVForecastVrmCommonSettings | None` | `rw` | `None` | PVForecastVrm settings |
:::
<!-- pyml enable line-length -->
@@ -289,22 +289,22 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| albedo | `Optional[float]` | `rw` | `None` | Proportion of the light hitting the ground that it reflects back. |
| inverter_model | `Optional[str]` | `rw` | `None` | Model of the inverter of this plane. |
| inverter_paco | `Optional[int]` | `rw` | `None` | AC power rating of the inverter [W]. |
| loss | `Optional[float]` | `rw` | `14.0` | Sum of PV system losses in percent |
| module_model | `Optional[str]` | `rw` | `None` | Model of the PV modules of this plane. |
| modules_per_string | `Optional[int]` | `rw` | `None` | Number of the PV modules of the strings of this plane. |
| mountingplace | `Optional[str]` | `rw` | `free` | Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated. |
| optimal_surface_tilt | `Optional[bool]` | `rw` | `False` | Calculate the optimum tilt angle. Ignored for two-axis tracking. |
| optimalangles | `Optional[bool]` | `rw` | `False` | Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking. |
| peakpower | `Optional[float]` | `rw` | `None` | Nominal power of PV system in kW. |
| pvtechchoice | `Optional[str]` | `rw` | `crystSi` | PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'. |
| strings_per_inverter | `Optional[int]` | `rw` | `None` | Number of the strings of the inverter of this plane. |
| surface_azimuth | `Optional[float]` | `rw` | `180.0` | Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270). |
| surface_tilt | `Optional[float]` | `rw` | `30.0` | Tilt angle from horizontal plane. Ignored for two-axis tracking. |
| trackingtype | `Optional[int]` | `rw` | `None` | Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south. |
| userhorizon | `Optional[List[float]]` | `rw` | `None` | Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. |
| albedo | `float | None` | `rw` | `None` | Proportion of the light hitting the ground that it reflects back. |
| inverter_model | `str | None` | `rw` | `None` | Model of the inverter of this plane. |
| inverter_paco | `int | None` | `rw` | `None` | AC power rating of the inverter [W]. |
| loss | `float | None` | `rw` | `14.0` | Sum of PV system losses in percent |
| module_model | `str | None` | `rw` | `None` | Model of the PV modules of this plane. |
| modules_per_string | `int | None` | `rw` | `None` | Number of the PV modules of the strings of this plane. |
| mountingplace | `str | None` | `rw` | `free` | Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated. |
| optimal_surface_tilt | `bool | None` | `rw` | `False` | Calculate the optimum tilt angle. Ignored for two-axis tracking. |
| optimalangles | `bool | None` | `rw` | `False` | Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking. |
| peakpower | `float | None` | `rw` | `None` | Nominal power of PV system in kW. |
| pvtechchoice | `str | None` | `rw` | `crystSi` | PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'. |
| strings_per_inverter | `int | None` | `rw` | `None` | Number of the strings of the inverter of this plane. |
| surface_azimuth | `float | None` | `rw` | `180.0` | Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270). |
| surface_tilt | `float | None` | `rw` | `30.0` | Tilt angle from horizontal plane. Ignored for two-axis tracking. |
| trackingtype | `int | None` | `rw` | `None` | Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south. |
| userhorizon | `List[float] | None` | `rw` | `None` | Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. |
:::
<!-- pyml enable line-length -->

View File

@@ -7,12 +7,12 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| eosdash_host | `EOS_SERVER__EOSDASH_HOST` | `Optional[str]` | `rw` | `None` | EOSdash server IP address. Defaults to EOS server IP address. |
| eosdash_port | `EOS_SERVER__EOSDASH_PORT` | `Optional[int]` | `rw` | `None` | EOSdash server IP port number. Defaults to EOS server IP port number + 1. |
| host | `EOS_SERVER__HOST` | `Optional[str]` | `rw` | `127.0.0.1` | EOS server IP address. Defaults to 127.0.0.1. |
| port | `EOS_SERVER__PORT` | `Optional[int]` | `rw` | `8503` | EOS server IP port number. Defaults to 8503. |
| startup_eosdash | `EOS_SERVER__STARTUP_EOSDASH` | `Optional[bool]` | `rw` | `True` | EOS server to start EOSdash server. Defaults to True. |
| verbose | `EOS_SERVER__VERBOSE` | `Optional[bool]` | `rw` | `False` | Enable debug output |
| eosdash_host | `EOS_SERVER__EOSDASH_HOST` | `str | None` | `rw` | `None` | EOSdash server IP address. Defaults to EOS server IP address. |
| eosdash_port | `EOS_SERVER__EOSDASH_PORT` | `int | None` | `rw` | `None` | EOSdash server IP port number. Defaults to EOS server IP port number + 1. |
| host | `EOS_SERVER__HOST` | `str | None` | `rw` | `127.0.0.1` | EOS server IP address. Defaults to 127.0.0.1. |
| port | `EOS_SERVER__PORT` | `int | None` | `rw` | `8503` | EOS server IP port number. Defaults to 8503. |
| startup_eosdash | `EOS_SERVER__STARTUP_EOSDASH` | `bool | None` | `rw` | `True` | EOS server to start EOSdash server. Defaults to True. |
| verbose | `EOS_SERVER__VERBOSE` | `bool | None` | `rw` | `False` | Enable debug output |
:::
<!-- pyml enable line-length -->

View File

@@ -7,7 +7,7 @@
| Name | Environment Variable | Type | Read-Only | Default | Description |
| ---- | -------------------- | ---- | --------- | ------- | ----------- |
| provider | `EOS_WEATHER__PROVIDER` | `Optional[str]` | `rw` | `None` | Weather provider id of provider to be used. |
| provider | `EOS_WEATHER__PROVIDER` | `str | None` | `rw` | `None` | Weather provider id of provider to be used. |
| provider_settings | `EOS_WEATHER__PROVIDER_SETTINGS` | `WeatherCommonProviderSettings` | `rw` | `required` | Provider settings |
| providers | | `list[str]` | `ro` | `N/A` | Available weather provider ids. |
:::
@@ -61,8 +61,8 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| import_file_path | `Union[str, pathlib.Path, NoneType]` | `rw` | `None` | Path to the file to import weather data from. |
| import_json | `Optional[str]` | `rw` | `None` | JSON string, dictionary of weather forecast value lists. |
| import_file_path | `str | pathlib.Path | None` | `rw` | `None` | Path to the file to import weather data from. |
| import_json | `str | None` | `rw` | `None` | JSON string, dictionary of weather forecast value lists. |
:::
<!-- pyml enable line-length -->
@@ -94,7 +94,7 @@
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| WeatherImport | `Optional[akkudoktoreos.prediction.weatherimport.WeatherImportCommonSettings]` | `rw` | `None` | WeatherImport settings |
| WeatherImport | `akkudoktoreos.prediction.weatherimport.WeatherImportCommonSettings | None` | `rw` | `None` | WeatherImport settings |
:::
<!-- pyml enable line-length -->

View File

@@ -1,6 +1,6 @@
# Akkudoktor-EOS
**Version**: `v0.2.0.dev2602250574650225`
**Version**: `v0.2.0.dev2602272006923535`
<!-- pyml disable line-length -->
**Description**: This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period.

View File

@@ -182,11 +182,21 @@ The behavior of the genetic algorithm can be customized using the following conf
:::{note}
**Supported Penalty Functions**
Currently, the only supported penalty function parameter is:
- `ev_soc_miss`:
Applies a penalty when the **state of charge (SOC)** of the electric vehicle battery falls below
the required minimum. This encourages the optimizer to ensure sufficient EV charging.
Default: `10`.
- `ac_charge_break_even`:
Applies a penalty for each scheduled AC grid-charging hour where the round-trip losses
(AC→DC inverter, battery internal, DC→AC inverter) mean the stored energy can never be
discharged at a price that recovers the charging cost. Energy already stored in the battery
from PV generation is treated as free and covers the most expensive future hours first, so
the penalty only fires for the hours that remain genuinely uncovered.
A value of `1.0` (default) means the penalty equals the actual economic loss in €.
Use larger values (e.g. `3.0`) to make the optimizer more aggressively avoid unprofitable
AC charging, or `0.0` to disable this penalty entirely.
:::
#### Value Formats
@@ -214,7 +224,8 @@ Currently, the only supported penalty function parameter is:
"generations": 400,
"seed": null,
"penalties": {
"ev_soc_miss": 10
"ev_soc_miss": 10,
"ac_charge_break_even": 1.0
}
}
}
@@ -254,13 +265,25 @@ that is configured, even if more devices are configured.
{
"device_id": "inv1",
"max_power_w": 10000,
"battery_id": "bat1"
"battery_id": "bat1",
"ac_to_dc_efficiency": 0.95,
"dc_to_ac_efficiency": 0.95,
"max_ac_charge_power_w": 5000
}
]
}
}
```
The inverter supports separate AC↔DC conversion efficiencies:
- `ac_to_dc_efficiency`: Conversion loss when charging the battery from AC grid power (0-1).
Set to `0` to disable AC charging. Default `1.0`.
- `dc_to_ac_efficiency`: Conversion loss when discharging battery to AC load/grid (0-1).
Must be > 0. Default `1.0`.
- `max_ac_charge_power_w`: Maximum AC charging power in watts. `null` = no additional limit.
Set to `0` to disable AC charging. Default `null`.
#### Electric vehicle simulation configuration
**Example:**

View File

@@ -71,7 +71,10 @@ passed to the request. You have to set the parameters even if given in the confi
"inverter": {
"device_id": "inverter1",
"max_power_wh": 10000,
"battery_id": "battery1"
"battery_id": "battery1",
"ac_to_dc_efficiency": 0.95,
"dc_to_ac_efficiency": 0.95,
"max_ac_charge_power_w": 5000
},
"eauto": {
"device_id": "ev1",
@@ -160,6 +163,65 @@ Verify prices against your local tariffs.
- `device_id`: ID of inverter
- `max_power_wh`: Maximum inverter power in Wh
- `battery_id`: ID of battery
- `ac_to_dc_efficiency`: Efficiency of AC→DC conversion for grid-to-battery AC charging (0-1).
Set to `0` to disable AC charging via inverter. Default `1.0` (backward compatible, no additional
inverter loss — existing battery `charging_efficiency` applies).
- `dc_to_ac_efficiency`: Efficiency of DC→AC conversion for battery discharging to AC load/grid
(0-1). Must be > 0. Default `1.0` (backward compatible).
- `max_ac_charge_power_w`: Maximum AC charging power in watts. `null` means no additional limit
(battery's own `max_charge_power_w` applies). Set to `0` to disable AC charging. Default `null`.
#### Efficiency Model
The inverter efficiency parameters cleanly separate the **DC battery efficiency** from the
**AC↔DC inverter conversion efficiency**:
- **DC charging from PV surplus**: PV → Battery (direct DC, only `charging_efficiency` applies)
- **AC charging from grid**: Grid (AC) → Inverter (`ac_to_dc_efficiency`) → Battery
(`charging_efficiency`)
- **Discharging to AC load/grid**: Battery (`discharging_efficiency`) → Inverter
(`dc_to_ac_efficiency`) → Load/Grid (AC)
Round-trip efficiency for AC charging and discharging:
`η_round_trip = ac_to_dc_efficiency × charging_efficiency × discharging_efficiency × dc_to_ac_efficiency`
For profitability, the discharge electricity price must exceed:
`buy_price / η_round_trip`
**Backward compatibility**: With default values (`ac_to_dc_efficiency=1.0`,
`dc_to_ac_efficiency=1.0`, `max_ac_charge_power_w=null`), existing configurations work identically.
To model realistic inverter losses, set both efficiencies to a value like `0.95` and adjust
battery efficiencies to reflect pure DC losses only (typically `0.96``0.99` for Li-ion).
#### AC Charging Break-Even Penalty
The genetic optimizer includes an economic break-even check as a fitness penalty to guide
convergence away from unprofitable AC grid charging. For each scheduled AC charging hour the
optimizer checks whether the best future discharge price (after accounting for round-trip losses)
actually recovers the charging cost.
**Free PV energy handling**: Energy already stored in the battery from PV generation (zero
grid cost) is treated as a free resource that covers the most expensive future hours first.
AC grid charging is only evaluated against the *remaining* uncovered hours.
The penalty magnitude is:
```text
penalty = ac_wh_charged × (break_even_price best_uncovered_price) × factor
```
where:
- `break_even_price = charge_price / η_round_trip`
- `best_uncovered_price` = highest future price not already covered by free PV battery energy
- `factor` = `optimization.genetic.penalties.ac_charge_break_even` (default `1.0`)
The penalty does not replace the simulation cost — it amplifies the economic loss signal so the
algorithm converges faster away from unprofitable charging regions.
To tune the aggressiveness of this penalty, set `penalties.ac_charge_break_even` in the
optimization configuration. A value of `1.0` corresponds to the exact economic loss in €.
Larger values (e.g. `3.0`) make the algorithm more aggressively avoid unprofitable AC charging;
smaller values (e.g. `0.0`) disable the penalty entirely.
### Electric Vehicle (EV)

View File

@@ -5784,6 +5784,49 @@
null,
"battery1"
]
},
"ac_to_dc_efficiency": {
"type": "number",
"maximum": 1.0,
"minimum": 0.0,
"title": "Ac To Dc Efficiency",
"description": "Efficiency of AC to DC conversion for grid-to-battery AC charging (0-1). Set to 0 to disable AC charging. Default 1.0 (no additional inverter loss).",
"default": 1.0,
"examples": [
0.95,
1.0,
0.0
]
},
"dc_to_ac_efficiency": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Dc To Ac Efficiency",
"description": "Efficiency of DC to AC conversion for battery discharging to AC load/grid (0-1). Default 1.0 (no additional inverter loss).",
"default": 1.0,
"examples": [
0.95,
1.0
]
},
"max_ac_charge_power_w": {
"anyOf": [
{
"type": "number",
"minimum": 0.0
},
{
"type": "null"
}
],
"title": "Max Ac Charge Power W",
"description": "Maximum AC charging power in watts. null means no additional limit. Set to 0 to disable AC charging.",
"examples": [
null,
0,
5000
]
}
},
"type": "object",
@@ -5836,6 +5879,49 @@
"battery1"
]
},
"ac_to_dc_efficiency": {
"type": "number",
"maximum": 1.0,
"minimum": 0.0,
"title": "Ac To Dc Efficiency",
"description": "Efficiency of AC to DC conversion for grid-to-battery AC charging (0-1). Set to 0 to disable AC charging. Default 1.0 (no additional inverter loss).",
"default": 1.0,
"examples": [
0.95,
1.0,
0.0
]
},
"dc_to_ac_efficiency": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Dc To Ac Efficiency",
"description": "Efficiency of DC to AC conversion for battery discharging to AC load/grid (0-1). Default 1.0 (no additional inverter loss).",
"default": 1.0,
"examples": [
0.95,
1.0
]
},
"max_ac_charge_power_w": {
"anyOf": [
{
"type": "number",
"minimum": 0.0
},
{
"type": "null"
}
],
"title": "Max Ac Charge Power W",
"description": "Maximum AC charging power in watts. null means no additional limit. Set to 0 to disable AC charging.",
"examples": [
null,
0,
5000
]
},
"measurement_keys": {
"anyOf": [
{
@@ -5909,6 +5995,49 @@
null,
"battery1"
]
},
"ac_to_dc_efficiency": {
"type": "number",
"maximum": 1.0,
"minimum": 0.0,
"title": "Ac To Dc Efficiency",
"description": "Efficiency of AC to DC conversion (for AC/grid charging of battery). Set to 0 to disable AC charging via inverter. Default 1.0 for backward compatibility (no additional inverter loss).",
"default": 1.0,
"examples": [
0.95,
1.0,
0.0
]
},
"dc_to_ac_efficiency": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Dc To Ac Efficiency",
"description": "Efficiency of DC to AC conversion (for battery discharging to AC load/grid). Default 1.0 for backward compatibility (no additional inverter loss).",
"default": 1.0,
"examples": [
0.95,
1.0
]
},
"max_ac_charge_power_w": {
"anyOf": [
{
"type": "number",
"minimum": 0.0
},
{
"type": "null"
}
],
"title": "Max Ac Charge Power W",
"description": "Maximum AC charging power in watts. None means no additional limit (battery's own max_charge_power_w applies). Set to 0 to disable AC charging.",
"examples": [
null,
0,
5000
]
}
},
"additionalProperties": false,

View File

@@ -193,6 +193,44 @@ class InverterCommonSettings(DevicesBaseSettings):
},
)
ac_to_dc_efficiency: float = Field(
default=1.0,
ge=0,
le=1,
json_schema_extra={
"description": (
"Efficiency of AC to DC conversion for grid-to-battery AC charging (0-1). "
"Set to 0 to disable AC charging. Default 1.0 (no additional inverter loss)."
),
"examples": [0.95, 1.0, 0.0],
},
)
dc_to_ac_efficiency: float = Field(
default=1.0,
gt=0,
le=1,
json_schema_extra={
"description": (
"Efficiency of DC to AC conversion for battery discharging to AC load/grid (0-1). "
"Default 1.0 (no additional inverter loss)."
),
"examples": [0.95, 1.0],
},
)
max_ac_charge_power_w: Optional[float] = Field(
default=None,
ge=0,
json_schema_extra={
"description": (
"Maximum AC charging power in watts. "
"null means no additional limit. Set to 0 to disable AC charging."
),
"examples": [None, 0, 5000],
},
)
@computed_field # type: ignore[prop-decorator]
@property
def measurement_keys(self) -> Optional[list[str]]:

View File

@@ -26,6 +26,9 @@ class Inverter:
self.max_power_wh = (
self.parameters.max_power_wh
) # Maximum power that the inverter can handle
self.dc_to_ac_efficiency = self.parameters.dc_to_ac_efficiency
self.ac_to_dc_efficiency = self.parameters.ac_to_dc_efficiency
self.max_ac_charge_power_w = self.parameters.max_ac_charge_power_w
def process_energy(
self, generation: float, consumption: float, hour: int
@@ -35,6 +38,9 @@ class Inverter:
grid_import = 0.0
self_consumption = 0.0
# Cache inverter DC→AC efficiency for discharge path
dc_to_ac_eff = self.dc_to_ac_efficiency
if generation >= consumption:
if consumption > self.max_power_wh:
# If consumption exceeds maximum inverter power
@@ -56,21 +62,29 @@ class Inverter:
if remaining_load_evq > 0:
# Akku muss den Restverbrauch decken
if self.battery:
from_battery, discharge_losses = self.battery.discharge_energy(
remaining_load_evq, hour
# Request more DC from battery to account for DC→AC conversion loss
dc_request = remaining_load_evq / dc_to_ac_eff
from_battery_dc, discharge_losses = self.battery.discharge_energy(
dc_request, hour
)
remaining_load_evq -= from_battery # Restverbrauch nach Akkuentladung
losses += discharge_losses
# Convert DC output to AC
from_battery_ac = from_battery_dc * dc_to_ac_eff
inverter_discharge_losses = from_battery_dc - from_battery_ac
remaining_load_evq -= from_battery_ac
losses += discharge_losses + inverter_discharge_losses
else:
from_battery_ac = 0.0
# Wenn der Akku den Restverbrauch nicht vollständig decken kann, wird der Rest ins Netz gezogen
if remaining_load_evq > 0:
grid_import += remaining_load_evq
remaining_load_evq = 0
else:
from_battery = 0.0
from_battery_ac = 0.0
if remaining_power > 0:
# Load battery with excess energy
# Load battery with excess energy (DC path, no inverter conversion needed)
charge_losses = 0.0
if self.battery:
charged_energie, charge_losses = self.battery.charge_energy(
remaining_power, hour
@@ -88,7 +102,7 @@ class Inverter:
losses += charge_losses
self_consumption = (
consumption + from_battery
consumption + from_battery_ac
) # Self-consumption is equal to the load
else:
@@ -98,15 +112,21 @@ class Inverter:
# Discharge battery to cover shortfall, if possible
if self.battery:
battery_discharge, discharge_losses = self.battery.discharge_energy(
min(shortfall, available_ac_power), hour
# Need shortfall in AC, request more DC from battery for DC→AC conversion
ac_needed = min(shortfall, available_ac_power)
dc_request = ac_needed / dc_to_ac_eff
battery_discharge_dc, discharge_losses = self.battery.discharge_energy(
dc_request, hour
)
losses += discharge_losses
# Convert DC output to AC
battery_discharge_ac = battery_discharge_dc * dc_to_ac_eff
inverter_discharge_losses = battery_discharge_dc - battery_discharge_ac
losses += discharge_losses + inverter_discharge_losses
else:
battery_discharge = 0
battery_discharge_ac = 0
# Draw remaining required power from the grid (discharge_losses are already substraved in the battery)
grid_import = shortfall - battery_discharge
self_consumption = generation + battery_discharge
# Draw remaining required power from the grid (discharge_losses are already subtracted in the battery)
grid_import = shortfall - battery_discharge_ac
self_consumption = generation + battery_discharge_ac
return grid_export, grid_import, losses, self_consumption

View File

@@ -243,11 +243,30 @@ class GeneticSimulation(PydanticBaseModel):
soc_per_hour = np.full((total_hours), np.nan)
soc_per_hour[0] = battery_fast.current_soc_percentage()
# Determine AC charging availability from inverter parameters
if inverter_fast:
ac_to_dc_eff_fast = inverter_fast.ac_to_dc_efficiency
dc_to_ac_eff_fast = inverter_fast.dc_to_ac_efficiency
max_ac_charge_w_fast = inverter_fast.max_ac_charge_power_w
else:
ac_to_dc_eff_fast = 1.0
dc_to_ac_eff_fast = 1.0
max_ac_charge_w_fast = None
ac_charging_possible = ac_to_dc_eff_fast > 0 and (
max_ac_charge_w_fast is None or max_ac_charge_w_fast > 0
)
# If AC charging is disabled via inverter, zero out AC charge hours
if not ac_charging_possible:
ac_charge_hours_fast = np.zeros_like(ac_charge_hours_fast)
# Fill the charge array of the battery
dc_charge_hours_fast[0:start_hour] = 0
dc_charge_hours_fast[end_hour:] = 0
ac_charge_hours_fast[0:start_hour] = 0
dc_charge_hours_fast[end_hour:] = 0
ac_charge_hours_fast[end_hour:] = 0
battery_fast.charge_array = np.where(
ac_charge_hours_fast != 0, ac_charge_hours_fast, dc_charge_hours_fast
)
@@ -258,6 +277,10 @@ class GeneticSimulation(PydanticBaseModel):
else:
# Default return if no battery is available
soc_per_hour = np.full((total_hours), 0)
ac_to_dc_eff_fast = 1.0
dc_to_ac_eff_fast = 1.0
max_ac_charge_w_fast = None
ac_charging_possible = False
if ev_fast:
# Pre-allocate arrays for the results, optimized for speed
@@ -330,15 +353,37 @@ class GeneticSimulation(PydanticBaseModel):
if battery_fast:
soc_per_hour[hour_idx] = battery_fast.current_soc_percentage() # save begin state
hour_ac_charge = ac_charge_hours_fast[hour]
if hour_ac_charge > 0.0:
if hour_ac_charge > 0.0 and ac_charging_possible:
# Cap charge factor by max_ac_charge_power_w if set
effective_charge_factor = hour_ac_charge
if max_ac_charge_w_fast is not None and battery_fast.max_charge_power_w > 0:
# DC power = max_charge_power_w * factor
# AC power = DC power / ac_to_dc_eff
# AC power must be <= max_ac_charge_power_w
max_dc_factor = (
max_ac_charge_w_fast * ac_to_dc_eff_fast
) / battery_fast.max_charge_power_w
effective_charge_factor = min(effective_charge_factor, max_dc_factor)
if effective_charge_factor > 0:
battery_charged_energy_actual, battery_losses_actual = (
battery_fast.charge_energy(None, hour, charge_factor=hour_ac_charge)
battery_fast.charge_energy(
None, hour, charge_factor=effective_charge_factor
)
)
total_battery_energy = battery_charged_energy_actual + battery_losses_actual
consumption += total_battery_energy
energy_consumption_grid_actual += total_battery_energy
losses_wh_per_hour[hour_idx] += battery_losses_actual
# DC energy entering the battery (before battery internal efficiency)
dc_energy = battery_charged_energy_actual + battery_losses_actual
# AC energy consumed from grid (accounts for AC→DC conversion loss)
ac_energy = dc_energy / ac_to_dc_eff_fast
# Inverter AC→DC conversion losses
inverter_charge_losses = ac_energy - dc_energy
consumption += ac_energy
energy_consumption_grid_actual += ac_energy
losses_wh_per_hour[hour_idx] += (
battery_losses_actual + inverter_charge_losses
)
# Update hourly arrays
feedin_energy_per_hour[hour_idx] = energy_feedin_grid_actual
@@ -814,12 +859,110 @@ class GeneticOptimization(OptimizationBase):
# Adjust total balance with battery value and penalties for unmet SOC
if self.simulation.battery:
restwert_akku = (
self.simulation.battery.current_energy_content()
* parameters.ems.preis_euro_pro_wh_akku
)
battery_energy_content = self.simulation.battery.current_energy_content()
# Apply DC→AC inverter efficiency to residual battery value
# (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
# --- AC charging break-even penalty ---
# Penalise AC charging decisions that cannot be economically justified given the
# round-trip losses (AC→DC charge conversion, battery internal, DC→AC discharge
# conversion) and the best available future electricity prices.
#
# Key insight: energy already stored in the battery (from PV, zero grid cost) covers
# the most expensive future hours first. AC charging from the grid only makes sense
# for the hours that remain uncovered, and only when the discharge price exceeds
# P_charge / η_round_trip.
#
# This penalty does not double-count the simulation result it amplifies the "bad
# decision" signal so that the genetic algorithm converges faster away from
# unprofitable charging regions.
if (
self.simulation.battery
and self.simulation.inverter
and self.simulation.ac_charge_hours is not None
and self.simulation.elect_price_hourly is not None
and self.simulation.load_energy_array is not None
):
inv = self.simulation.inverter
bat = self.simulation.battery
# Full round-trip efficiency: 1 Wh drawn from grid → η Wh delivered to AC load
round_trip_eff = (
inv.ac_to_dc_efficiency
* bat.charging_efficiency
* bat.discharging_efficiency
* inv.dc_to_ac_efficiency
)
if round_trip_eff > 0:
ac_charge_arr = self.simulation.ac_charge_hours
prices_arr = self.simulation.elect_price_hourly
load_arr = self.simulation.load_energy_array
n = len(prices_arr)
# Usable AC energy already in battery from prior PV charging (zero grid cost).
# This covers the most expensive future hours first, pushing AC charging demand
# to cheaper hours where the break-even hurdle may not be met.
initial_soc_wh = (bat.initial_soc_percentage / 100.0) * bat.capacity_wh
free_ac_wh = (
max(0.0, initial_soc_wh - bat.min_soc_wh)
* bat.discharging_efficiency
* inv.dc_to_ac_efficiency
)
# Configurable penalty multiplier (default 1 = economic loss in €)
try:
ac_penalty_factor = float(
self.config.optimization.genetic.penalties["ac_charge_break_even"]
)
except Exception:
ac_penalty_factor = 1.0
for hour in range(start_hour, min(len(ac_charge_arr), n)):
ac_factor = ac_charge_arr[hour]
if ac_factor <= 0.0:
continue
charge_price = prices_arr[hour]
if charge_price <= 0:
continue
# Price that a future discharge hour must reach to break even
break_even_price = charge_price / round_trip_eff
# Build list of (price, load_wh) for all future hours in the horizon
future = [
(float(prices_arr[h]), float(load_arr[h])) for h in range(hour + 1, n)
]
# Sort descending by price so we "use" the most expensive hours first
future.sort(key=lambda x: -x[0])
# Consume free PV energy against the highest-price future hours.
# The first uncovered (partially or fully) hour defines the best
# price still available for the new AC charge.
remaining_free = free_ac_wh
best_uncovered_price = 0.0
for fp, fl in future:
if remaining_free >= fl:
# Entire expensive hour is already covered by free PV energy
remaining_free -= fl
else:
# First hour not (fully) covered: this is where new charge goes
best_uncovered_price = fp
break
if best_uncovered_price < break_even_price:
# AC charging at this hour is economically unjustified.
# Penalty = excess cost per Wh × DC energy requested this hour.
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
if self.optimize_ev and parameters.eauto and self.simulation.ev:
try:
penalty = self.config.optimization.genetic.penalties["ev_soc_miss"]

View File

@@ -157,3 +157,40 @@ class InverterParameters(DeviceParameters):
default=None,
json_schema_extra={"description": "ID of battery", "examples": [None, "battery1"]},
)
ac_to_dc_efficiency: float = Field(
default=1.0,
ge=0,
le=1,
json_schema_extra={
"description": (
"Efficiency of AC to DC conversion (for AC/grid charging of battery). "
"Set to 0 to disable AC charging via inverter. "
"Default 1.0 for backward compatibility (no additional inverter loss)."
),
"examples": [0.95, 1.0, 0.0],
},
)
dc_to_ac_efficiency: float = Field(
default=1.0,
gt=0,
le=1,
json_schema_extra={
"description": (
"Efficiency of DC to AC conversion (for battery discharging to AC load/grid). "
"Default 1.0 for backward compatibility (no additional inverter loss)."
),
"examples": [0.95, 1.0],
},
)
max_ac_charge_power_w: Optional[float] = Field(
default=None,
ge=0,
json_schema_extra={
"description": (
"Maximum AC charging power in watts. "
"None means no additional limit (battery's own max_charge_power_w applies). "
"Set to 0 to disable AC charging."
),
"examples": [None, 0, 5000],
},
)

View File

@@ -224,6 +224,11 @@ class GeneticOptimizationParameters(
if "ev_soc_miss" not in cls.config.optimization.genetic.penalties:
logger.info("ev_soc_miss penalty function parameter unknown - defaulting to 10.")
cls.config.optimization.genetic.penalties["ev_soc_miss"] = 10
if "ac_charge_break_even" not in cls.config.optimization.genetic.penalties:
# Default multiplier 1.0: penalty equals the exact economic loss in € from
# charging at a price that cannot be recovered given the round-trip efficiency
# and the best available future discharge price (after free PV energy is used).
cls.config.optimization.genetic.penalties["ac_charge_break_even"] = 1.0
# Get start solution from last run
start_solution = None
@@ -548,6 +553,9 @@ class GeneticOptimizationParameters(
device_id=inverter_config.device_id,
max_power_wh=inverter_config.max_power_w,
battery_id=inverter_config.battery_id,
ac_to_dc_efficiency=inverter_config.ac_to_dc_efficiency,
dc_to_ac_efficiency=inverter_config.dc_to_ac_efficiency,
max_ac_charge_power_w=inverter_config.max_ac_charge_power_w,
)
except:
logger.info(

View File

@@ -38,17 +38,20 @@ def compare_dict(actual: dict[str, Any], expected: dict[str, Any]):
@pytest.mark.parametrize(
"fn_in, fn_out, ngen",
"fn_in, fn_out, ngen, break_even",
[
("optimize_input_1.json", "optimize_result_1.json", 3),
("optimize_input_2.json", "optimize_result_2.json", 3),
("optimize_input_2.json", "optimize_result_2_full.json", 400),
("optimize_input_1.json", "optimize_result_1.json", 3, 0),
("optimize_input_2.json", "optimize_result_2.json", 3, 0),
("optimize_input_2.json", "optimize_result_2_full.json", 400, 0),
("optimize_input_1.json", "optimize_result_1_be.json", 3, 1),
("optimize_input_2.json", "optimize_result_2_be.json", 3, 1),
],
)
def test_optimize(
fn_in: str,
fn_out: str,
ngen: int,
break_even: int,
config_eos: ConfigEOS,
is_finalize: bool,
):
@@ -69,7 +72,8 @@ def test_optimize(
"individuals": 300,
"generations": 10,
"penalties": {
"ev_soc_miss": 10
"ev_soc_miss": 10,
"ac_charge_break_even": break_even,
}
}
},

View File

@@ -0,0 +1,880 @@
"""Tests for inverter AC/DC efficiency separation and AC charging break-even penalty.
Tests the new inverter parameters:
- dc_to_ac_efficiency: DC→AC conversion loss on battery discharge
- ac_to_dc_efficiency: AC→DC conversion loss on grid-to-battery charging
- max_ac_charge_power_w: Maximum AC charging power limit
And the economic break-even penalty in GeneticOptimization.evaluate():
- Penalises AC grid charging that cannot be recovered given round-trip losses and future prices
- Respects free PV-charged energy already in battery when ranking future discharge hours
"""
from types import SimpleNamespace
from typing import cast
from unittest.mock import Mock, patch
import numpy as np
import pytest
from akkudoktoreos.devices.genetic.battery import Battery
from akkudoktoreos.devices.genetic.inverter import Inverter
from akkudoktoreos.optimization.genetic.geneticdevices import (
InverterParameters,
SolarPanelBatteryParameters,
)
# ---------------------------------------------------------------------------
# Helpers / Fixtures
# ---------------------------------------------------------------------------
def _make_inverter(
dc_to_ac_efficiency: float = 1.0,
ac_to_dc_efficiency: float = 1.0,
max_ac_charge_power_w=None,
max_power_wh: float = 10000.0,
mock_battery=None,
) -> Inverter:
"""Create an Inverter with custom efficiency parameters and a mock battery."""
mock_self_consumption_predictor = Mock()
mock_self_consumption_predictor.calculate_self_consumption.return_value = 1.0
params = InverterParameters(
device_id="inv1",
max_power_wh=max_power_wh,
battery_id=mock_battery.parameters.device_id if mock_battery else None,
dc_to_ac_efficiency=dc_to_ac_efficiency,
ac_to_dc_efficiency=ac_to_dc_efficiency,
max_ac_charge_power_w=max_ac_charge_power_w,
)
with patch(
"akkudoktoreos.devices.genetic.inverter.get_eos_load_interpolator",
return_value=mock_self_consumption_predictor,
):
return Inverter(params, battery=mock_battery)
@pytest.fixture
def mock_battery() -> Mock:
mock_bat = Mock()
mock_bat.charge_energy = Mock(return_value=(0.0, 0.0))
mock_bat.discharge_energy = Mock(return_value=(0.0, 0.0))
mock_bat.parameters.device_id = "battery1"
return mock_bat
# ===================================================================
# 1. InverterParameters new fields and defaults
# ===================================================================
class TestInverterParametersDefaults:
"""Verify backward-compatible defaults for new parameters."""
def test_defaults(self):
params = InverterParameters(device_id="inv1", max_power_wh=5000)
assert params.dc_to_ac_efficiency == 1.0
assert params.ac_to_dc_efficiency == 1.0
assert params.max_ac_charge_power_w is None
def test_custom_values(self):
params = InverterParameters(
device_id="inv1",
max_power_wh=5000,
dc_to_ac_efficiency=0.95,
ac_to_dc_efficiency=0.93,
max_ac_charge_power_w=3000,
)
assert params.dc_to_ac_efficiency == 0.95
assert params.ac_to_dc_efficiency == 0.93
assert params.max_ac_charge_power_w == 3000
def test_ac_to_dc_zero_disables_ac_charging(self):
params = InverterParameters(
device_id="inv1", max_power_wh=5000, ac_to_dc_efficiency=0.0
)
assert params.ac_to_dc_efficiency == 0.0
def test_dc_to_ac_must_be_positive(self):
with pytest.raises(Exception):
InverterParameters(device_id="inv1", max_power_wh=5000, dc_to_ac_efficiency=0.0)
def test_max_ac_charge_power_zero(self):
params = InverterParameters(
device_id="inv1", max_power_wh=5000, max_ac_charge_power_w=0
)
assert params.max_ac_charge_power_w == 0
# ===================================================================
# 2. dc_to_ac_efficiency battery discharge through inverter
# ===================================================================
class TestDcToAcEfficiency:
"""Battery discharge energy is reduced by dc_to_ac_efficiency."""
def test_discharge_shortfall_with_95_percent_efficiency(self, mock_battery):
"""With 0.95 efficiency, 100 Wh DC from battery → 95 Wh AC delivered."""
mock_battery.discharge_energy.return_value = (100.0, 10.0)
inv = _make_inverter(dc_to_ac_efficiency=0.95, mock_battery=mock_battery)
generation = 0.0
consumption = 200.0
hour = 5
grid_export, grid_import, losses, self_consumption = inv.process_energy(
generation, consumption, hour
)
# Battery delivers 100 Wh DC → 95 Wh AC after inverter
# Inverter loss = 100 - 95 = 5 Wh
battery_discharge_ac = 100.0 * 0.95 # 95 Wh
assert self_consumption == pytest.approx(generation + battery_discharge_ac, rel=1e-5)
assert grid_import == pytest.approx(consumption - battery_discharge_ac, rel=1e-5)
# Total losses = battery internal (10) + inverter DC→AC (5)
expected_losses = 10.0 + (100.0 * 0.05)
assert losses == pytest.approx(expected_losses, rel=1e-5)
# Battery was asked for more DC to compensate for inverter loss
# ac_needed = min(200, max_power_wh - 0) = 200
# dc_request = 200 / 0.95 ≈ 210.526
expected_dc_request = 200.0 / 0.95
mock_battery.discharge_energy.assert_called_once_with(
pytest.approx(expected_dc_request, rel=1e-3), hour
)
def test_discharge_with_100_percent_efficiency_unchanged(self, mock_battery):
"""With 1.0 efficiency, behavior is identical to the legacy model."""
mock_battery.discharge_energy.return_value = (100.0, 10.0)
inv = _make_inverter(dc_to_ac_efficiency=1.0, mock_battery=mock_battery)
generation = 100.0
consumption = 300.0
hour = 5
grid_export, grid_import, losses, self_consumption = inv.process_energy(
generation, consumption, hour
)
# No inverter loss: battery_discharge_ac = 100 Wh
assert self_consumption == pytest.approx(200.0, rel=1e-5)
assert grid_import == pytest.approx(100.0, rel=1e-5)
assert losses == pytest.approx(10.0, rel=1e-5) # Only battery losses
def test_discharge_surplus_path_with_efficiency(self, mock_battery):
"""When generation > consumption but SCR < 1, discharge goes through inverter."""
mock_battery.discharge_energy.return_value = (50.0, 5.0)
mock_battery.charge_energy.return_value = (100.0, 10.0)
inv = _make_inverter(dc_to_ac_efficiency=0.90, mock_battery=mock_battery)
cast(Mock, inv.self_consumption_predictor).calculate_self_consumption.return_value = 0.90
generation = 500.0
consumption = 200.0
hour = 5
grid_export, grid_import, losses, self_consumption = inv.process_energy(
generation, consumption, hour
)
# surplus = 300, remaining_power = 300*0.9 = 270, remaining_load_evq = 300*0.1 = 30
# DC request for discharge = 30 / 0.90 = 33.333
expected_dc_request = 30.0 / 0.90
mock_battery.discharge_energy.assert_called_once_with(
pytest.approx(expected_dc_request, rel=1e-3), hour
)
# Battery delivers 50 Wh DC → 45 Wh AC
from_battery_ac = 50.0 * 0.90 # 45 Wh
inverter_discharge_loss = 50.0 - from_battery_ac # 5 Wh
assert self_consumption == pytest.approx(consumption + from_battery_ac, rel=1e-5)
# ===================================================================
# 3. ac_to_dc_efficiency + max_ac_charge_power_w in simulation
# ===================================================================
class TestAcChargingInSimulation:
"""Test AC charging logic with inverter efficiency in GeneticSimulation.
These tests use a real Battery object (not a mock) and directly exercise
the AC charging path in GeneticSimulation.simulate().
"""
@pytest.fixture
def simulation_setup(self, config_eos):
"""Set up a minimal GeneticSimulation with battery and inverter."""
from akkudoktoreos.optimization.genetic.genetic import GeneticSimulation
from akkudoktoreos.optimization.genetic.geneticparams import (
GeneticEnergyManagementParameters,
)
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 48}, "optimization": {"hours": 24}}
)
prediction_hours = config_eos.prediction.hours
def _build(
ac_to_dc_efficiency: float = 1.0,
dc_to_ac_efficiency: float = 1.0,
max_ac_charge_power_w=None,
battery_capacity_wh: int = 10000,
battery_charging_efficiency: float = 0.90,
battery_discharging_efficiency: float = 0.90,
battery_initial_soc_pct: int = 50,
battery_max_charge_power_w: int = 5000,
):
akku = Battery(
SolarPanelBatteryParameters(
device_id="battery1",
capacity_wh=battery_capacity_wh,
initial_soc_percentage=battery_initial_soc_pct,
charging_efficiency=battery_charging_efficiency,
discharging_efficiency=battery_discharging_efficiency,
min_soc_percentage=0,
max_soc_percentage=100,
max_charge_power_w=battery_max_charge_power_w,
),
prediction_hours=prediction_hours,
)
akku.reset()
inverter = Inverter(
InverterParameters(
device_id="inverter1",
max_power_wh=10000,
battery_id="battery1",
ac_to_dc_efficiency=ac_to_dc_efficiency,
dc_to_ac_efficiency=dc_to_ac_efficiency,
max_ac_charge_power_w=max_ac_charge_power_w,
),
battery=akku,
)
sim = GeneticSimulation()
sim.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[0.0] * prediction_hours, # No PV
strompreis_euro_pro_wh=[0.0003] * prediction_hours, # ~30ct/kWh
einspeiseverguetung_euro_pro_wh=0.00008,
preis_euro_pro_wh_akku=0.0001,
gesamtlast=[1000.0] * prediction_hours, # 1 kW constant load
),
optimization_hours=config_eos.optimization.horizon_hours,
prediction_hours=prediction_hours,
inverter=inverter,
ev=None,
home_appliance=None,
)
return sim, akku, inverter
return _build
def test_ac_charge_with_unity_efficiency_backward_compat(self, simulation_setup):
"""With ac_to_dc_efficiency=1.0, behavior matches legacy model."""
sim, akku, inverter = simulation_setup(ac_to_dc_efficiency=1.0)
# Enable AC charging for hour 1 at 50% power
sim.ac_charge_hours[1] = 0.5
sim.dc_charge_hours[:] = 0
sim.bat_discharge_hours[:] = 0
result = sim.simulate(start_hour=0)
# At hour 1: AC charge at 50% of 5000W = 2500W DC requested
# With efficiency 1.0, AC consumed from grid = DC = 2500W
# Battery stores: 2500 * 0.90 (battery eff) = 2250 Wh
# Battery loss: 2500 - 2250 = 250 Wh
# Total grid consumption for that hour = 1000 (load) + 2500 (AC charge)
hour_idx = 1
assert result["Netzbezug_Wh_pro_Stunde"][hour_idx] == pytest.approx(3500.0, rel=1e-3)
assert result["Verluste_Pro_Stunde"][hour_idx] == pytest.approx(250.0, rel=1e-3)
def test_ac_charge_with_95_percent_efficiency(self, simulation_setup):
"""With ac_to_dc_efficiency=0.95, more AC energy is consumed for same DC charge."""
sim, akku, inverter = simulation_setup(ac_to_dc_efficiency=0.95)
sim.ac_charge_hours[1] = 0.5
sim.dc_charge_hours[:] = 0
sim.bat_discharge_hours[:] = 0
result = sim.simulate(start_hour=0)
# At hour 1: AC charge at 50% of 5000W = 2500W DC requested
# With ac_to_dc_efficiency=0.95:
# AC consumed = 2500 / 0.95 ≈ 2631.58 Wh
# Inverter loss = 2631.58 - 2500 = 131.58 Wh
# Battery stores: 2500 * 0.90 = 2250 Wh
# Battery loss: 2500 - 2250 = 250 Wh
# Total losses: 250 + 131.58 = 381.58 Wh
hour_idx = 1
dc_energy = 2500.0
ac_energy = dc_energy / 0.95
inverter_loss = ac_energy - dc_energy
battery_loss = dc_energy * (1 - 0.90)
total_loss = battery_loss + inverter_loss
expected_grid = 1000.0 + ac_energy
assert result["Netzbezug_Wh_pro_Stunde"][hour_idx] == pytest.approx(
expected_grid, rel=1e-3
)
assert result["Verluste_Pro_Stunde"][hour_idx] == pytest.approx(total_loss, rel=1e-3)
def test_ac_charge_disabled_by_zero_efficiency(self, simulation_setup):
"""With ac_to_dc_efficiency=0.0, AC charging is completely disabled."""
sim, akku, inverter = simulation_setup(ac_to_dc_efficiency=0.0)
sim.ac_charge_hours[1] = 1.0 # Try to AC charge
sim.dc_charge_hours[:] = 0
sim.bat_discharge_hours[:] = 0
initial_soc = akku.soc_wh
result = sim.simulate(start_hour=0)
# Battery should not charge at all (AC charging disabled)
# Grid consumption = only load
hour_idx = 1
assert result["Netzbezug_Wh_pro_Stunde"][hour_idx] == pytest.approx(1000.0, rel=1e-3)
# Battery SoC should not change (no ac charge, no dc charge, no discharge)
assert result["akku_soc_pro_stunde"][hour_idx] == pytest.approx(50.0, rel=1e-3)
def test_ac_charge_disabled_by_zero_max_power(self, simulation_setup):
"""With max_ac_charge_power_w=0, AC charging is disabled."""
sim, akku, inverter = simulation_setup(
ac_to_dc_efficiency=0.95, max_ac_charge_power_w=0
)
sim.ac_charge_hours[1] = 1.0
sim.dc_charge_hours[:] = 0
sim.bat_discharge_hours[:] = 0
result = sim.simulate(start_hour=0)
hour_idx = 1
assert result["Netzbezug_Wh_pro_Stunde"][hour_idx] == pytest.approx(1000.0, rel=1e-3)
assert result["akku_soc_pro_stunde"][hour_idx] == pytest.approx(50.0, rel=1e-3)
def test_ac_charge_limited_by_max_ac_power(self, simulation_setup):
"""max_ac_charge_power_w limits the effective charge factor."""
# battery max_charge_power_w = 5000, ac_to_dc_efficiency = 0.95
# max_ac_charge_power_w = 2000
# max_dc_factor = (2000 * 0.95) / 5000 = 0.38
sim, akku, inverter = simulation_setup(
ac_to_dc_efficiency=0.95, max_ac_charge_power_w=2000
)
sim.ac_charge_hours[1] = 1.0 # Request full power
sim.dc_charge_hours[:] = 0
sim.bat_discharge_hours[:] = 0
result = sim.simulate(start_hour=0)
# Effective charge factor is capped at 0.38
# DC energy = 5000 * 0.38 = 1900 W
# AC energy = 1900 / 0.95 = 2000 W (respects limit)
hour_idx = 1
max_dc_factor = (2000 * 0.95) / 5000
dc_energy = 5000 * max_dc_factor
ac_energy = dc_energy / 0.95
expected_grid = 1000.0 + ac_energy # load + AC charge
assert result["Netzbezug_Wh_pro_Stunde"][hour_idx] == pytest.approx(
expected_grid, rel=1e-2
)
def test_discharge_with_dc_to_ac_efficiency(self, simulation_setup):
"""dc_to_ac_efficiency affects how much AC energy is delivered from battery."""
sim, akku, inverter = simulation_setup(
dc_to_ac_efficiency=0.90, battery_initial_soc_pct=80
)
# No PV, no AC charge, discharge only
sim.ac_charge_hours[:] = 0
sim.dc_charge_hours[:] = 1
sim.bat_discharge_hours[:] = 1
result = sim.simulate(start_hour=0)
# With dc_to_ac_efficiency=0.90, battery discharge delivers less AC
# This means more grid import compared to efficiency=1.0
# At hour 0: load=1000, PV=0
# Shortfall = 1000
# DC request = 1000 / 0.90 ≈ 1111 Wh
# Battery delivers (limited by capacity and efficiency):
# max raw = min(soc - min_soc, max_charge_power) = min(8000, 5000) = 5000
# max deliverable DC = 5000 * 0.90 (battery eff) = 4500
# delivered DC = min(1111, 4500) = 1111 Wh
# delivered AC = 1111 * 0.90 = 1000 Wh → covers full load
# So grid_import should be ≈ 0 for first hours while battery has charge
hour_idx = 0
assert result["Netzbezug_Wh_pro_Stunde"][hour_idx] == pytest.approx(0.0, abs=5.0)
# But losses should be higher due to inverter DC→AC conversion
# Inverter loss = 1111 * 0.10 = 111 Wh
# Battery loss = raw_used - delivered = delivered/bat_eff - delivered
# = 1111/0.90 - 1111 ≈ 123.5 Wh
assert result["Verluste_Pro_Stunde"][hour_idx] > 200.0 # Significant losses
def test_round_trip_efficiency_cost_impact(self, simulation_setup):
"""Verify AC charge + discharge round-trip losses increase total cost."""
# Reference run: no inverter losses
sim_ref, akku_ref, inv_ref = simulation_setup(
ac_to_dc_efficiency=1.0, dc_to_ac_efficiency=1.0
)
sim_ref.ac_charge_hours[1] = 0.5
sim_ref.dc_charge_hours[:] = 0
sim_ref.bat_discharge_hours[:] = 1
sim_ref.bat_discharge_hours[1] = 0 # Don't discharge while charging
result_ref = sim_ref.simulate(start_hour=0)
# Test run: with inverter losses
sim_test, akku_test, inv_test = simulation_setup(
ac_to_dc_efficiency=0.93, dc_to_ac_efficiency=0.93
)
sim_test.ac_charge_hours[1] = 0.5
sim_test.dc_charge_hours[:] = 0
sim_test.bat_discharge_hours[:] = 1
sim_test.bat_discharge_hours[1] = 0
result_test = sim_test.simulate(start_hour=0)
# With inverter losses, total cost should be HIGHER
assert result_test["Gesamtkosten_Euro"] > result_ref["Gesamtkosten_Euro"]
# And total losses should be HIGHER
assert result_test["Gesamt_Verluste"] > result_ref["Gesamt_Verluste"]
# ===================================================================
# 4. Integration with optimizer residual battery value
# ===================================================================
class TestResidualBatteryValue:
"""Verify that dc_to_ac_efficiency affects residual battery value calculation."""
def test_current_energy_content_unaffected(self):
"""Battery.current_energy_content() is DC-only; inverter eff applied in optimizer."""
akku = Battery(
SolarPanelBatteryParameters(
device_id="bat1",
capacity_wh=10000,
initial_soc_percentage=50,
discharging_efficiency=0.90,
min_soc_percentage=0,
),
prediction_hours=24,
)
akku.reset()
# DC energy content: (5000 - 0) * 0.90 = 4500
assert akku.current_energy_content() == pytest.approx(4500.0, rel=1e-5)
# ===================================================================
# 5. AC charging break-even penalty in GeneticOptimization.evaluate()
# ===================================================================
def _make_mock_simulation(
*,
# Inverter properties
ac_to_dc_efficiency: float = 0.93,
dc_to_ac_efficiency: float = 0.95,
# Battery properties
charging_efficiency: float = 0.95,
discharging_efficiency: float = 0.95,
capacity_wh: float = 10_000.0,
initial_soc_percentage: float = 0.0, # fraction of capacity already stored (0 = empty)
min_soc_wh: float = 0.0,
max_charge_power_w: float = 5_000.0,
# Arrays (must be same length)
ac_charge_hours: list | None = None,
elect_price_hourly: list | None = None,
load_energy_array: list | None = None,
):
"""Return a mock GeneticSimulation with configurable properties for penalty tests."""
n = 24
if ac_charge_hours is None:
ac_charge_hours = [0.0] * n
if elect_price_hourly is None:
elect_price_hourly = [0.0003] * n # 30 ct/kWh flat
if load_energy_array is None:
load_energy_array = [1000.0] * n # 1 kWh constant load
inv = SimpleNamespace(
ac_to_dc_efficiency=ac_to_dc_efficiency,
dc_to_ac_efficiency=dc_to_ac_efficiency,
)
bat = SimpleNamespace(
charging_efficiency=charging_efficiency,
discharging_efficiency=discharging_efficiency,
capacity_wh=capacity_wh,
initial_soc_percentage=initial_soc_percentage,
min_soc_wh=min_soc_wh,
max_charge_power_w=max_charge_power_w,
current_energy_content=Mock(return_value=0.0),
)
sim = Mock()
sim.battery = bat
sim.inverter = inv
sim.ev = None
sim.ac_charge_hours = np.array(ac_charge_hours, dtype=float)
sim.elect_price_hourly = np.array(elect_price_hourly, dtype=float)
sim.load_energy_array = np.array(load_energy_array, dtype=float)
return sim
def _run_evaluate_with_mocked_sim(
config_eos,
mock_sim,
*,
ac_charge_break_even: float = 1.0,
start_hour: int = 0,
base_gesamtbilanz: float = 0.0,
):
"""
Patch a GeneticOptimization so that:
- evaluate_inner() returns a controlled base Gesamtbilanz_Euro
- self.simulation is replaced by mock_sim
Then call evaluate() and return the fitness tuple.
"""
from akkudoktoreos.optimization.genetic.genetic import GeneticOptimization
config_eos.merge_settings_from_dict(
{
"prediction": {"hours": 48},
"optimization": {"hours": 24},
}
)
config_eos.optimization.genetic.penalties = {
"ev_soc_miss": 10,
"ac_charge_break_even": ac_charge_break_even,
}
optim = GeneticOptimization.__new__(GeneticOptimization)
# Minimal __init__ state expected by evaluate()
optim.config = config_eos
optim.optimize_ev = False
optim.verbose = False
optim.opti_param = {"home_appliance": 0}
optim.simulation = mock_sim
# evaluate_inner() just returns the base balance; we test the *additional* penalty
dummy_result = {
"Gesamtbilanz_Euro": base_gesamtbilanz,
"Gesamt_Verluste": 0.0,
"EAuto_SoC_pro_Stunde": np.zeros(48),
}
# DEAP individuals are lists that accept attribute assignment; use a trivial subclass
class _Ind(list): # noqa: N801
pass
fake_individual = _Ind([0] * 48)
with patch.object(optim, "evaluate_inner", return_value=dummy_result):
fitness = optim.evaluate(
fake_individual,
parameters=Mock(
ems=Mock(preis_euro_pro_wh_akku=0.0),
eauto=None,
),
start_hour=start_hour,
worst_case=False,
)
return fitness[0]
class TestAcChargeBreakEvenPenalty:
"""Break-even penalty in GeneticOptimization.evaluate().
The penalty adds a positive (bad) contribution to the fitness score whenever
AC grid charging is scheduled at an hour where the round-trip loss means the
stored energy can never be discharged at a price sufficient to recover costs,
taking into account that free PV-charged energy already in the battery covers
the most expensive future hours first.
"""
# -----------------------------------------------------------------
# 5a. No AC charging → no penalty
# -----------------------------------------------------------------
def test_no_ac_charging_no_penalty(self, config_eos):
"""When no AC charging is scheduled, fitness equals the base balance."""
n = 24
sim = _make_mock_simulation(
ac_charge_hours=[0.0] * n,
elect_price_hourly=[0.0003] * n,
load_energy_array=[1000.0] * n,
)
base = 1.5
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=base)
assert fitness == pytest.approx(base, rel=1e-9)
# -----------------------------------------------------------------
# 5b. AC charging profitable → no penalty
# -----------------------------------------------------------------
def test_profitable_ac_charging_no_penalty(self, config_eos):
"""When future discharge price > P_charge / η, charging is justified → no penalty."""
n = 24
# Charge at hour 0: 0.0001 €/Wh
# Round-trip: 0.93 * 0.95 * 0.95 * 0.95 ≈ 0.7975
# break-even price ≈ 0.0001 / 0.7975 ≈ 0.0001254 €/Wh
# Future hour 1 price: 0.0003 > break-even → profitable
prices = [0.0001] + [0.0003] * (n - 1)
ac_charge = [1.0] + [0.0] * (n - 1)
loads = [1000.0] * n
sim = _make_mock_simulation(
ac_to_dc_efficiency=0.93,
dc_to_ac_efficiency=0.95,
charging_efficiency=0.95,
discharging_efficiency=0.95,
ac_charge_hours=ac_charge,
elect_price_hourly=prices,
load_energy_array=loads,
initial_soc_percentage=0.0, # empty battery → no free PV energy
)
base = 0.0
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=base)
# penalty should be 0 (or very small due to floating-point rounding)
assert fitness == pytest.approx(base, abs=1e-9)
# -----------------------------------------------------------------
# 5c. AC charging unprofitable → penalty fires
# -----------------------------------------------------------------
def test_unprofitable_ac_charging_adds_penalty(self, config_eos):
"""When future discharge prices are too low to justify AC charging, penalty is added."""
n = 24
# Charge at hour 0: 0.0004 €/Wh
# Round-trip: 0.93 * 0.95 * 0.95 * 0.95 ≈ 0.7975
# break-even price ≈ 0.0004 / 0.7975 ≈ 0.000501 €/Wh
# All future prices: 0.0003 < break-even → unprofitable
prices = [0.0004] + [0.0003] * (n - 1)
ac_charge = [1.0] + [0.0] * (n - 1)
loads = [1000.0] * n
sim = _make_mock_simulation(
ac_to_dc_efficiency=0.93,
dc_to_ac_efficiency=0.95,
charging_efficiency=0.95,
discharging_efficiency=0.95,
ac_charge_hours=ac_charge,
elect_price_hourly=prices,
load_energy_array=loads,
initial_soc_percentage=0.0,
)
base = 0.0
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=base)
# Fitness must be worse (higher) than base
assert fitness > base + 1e-6
# -----------------------------------------------------------------
# 5d. Free PV energy covers expensive hours → penalty reduced/eliminated
# -----------------------------------------------------------------
def test_free_pv_energy_eliminates_penalty(self, config_eos):
"""PV energy covers the best future hour; penalty is larger than without PV energy."""
n = 24
# Charge at hour 0: 0.0005 €/Wh
# Round-trip: 0.93*0.95*0.95*0.95 ≈ 0.7974
# break-even ≈ 0.0005 / 0.7974 ≈ 0.000627 €/Wh
#
# Future: one expensive hour at 0.0006 (< break-even!) and rest at 0.0003
# → even the best future price 0.0006 < 0.000627 → AC charging is never profitable
#
# Empty battery: best_uncovered = 0.0006 < 0.000627 → penalty fires
# With PV (50% SoC → ~4512 Wh deliverable free AC): covers the 0.0006 hour (1000 Wh)
# → best_uncovered drops to 0.0003 → penalty fires with LARGER excess
prices = [0.0005] + [0.0003] * (n - 2) + [0.0006] # expensive hour at end
ac_charge = [1.0] + [0.0] * (n - 1)
loads = [1000.0] * n
sim_empty = _make_mock_simulation(
ac_to_dc_efficiency=0.93,
dc_to_ac_efficiency=0.95,
charging_efficiency=0.95,
discharging_efficiency=0.95,
ac_charge_hours=list(ac_charge),
elect_price_hourly=list(prices),
load_energy_array=list(loads),
initial_soc_percentage=0.0, # no free PV energy
)
sim_with_pv = _make_mock_simulation(
ac_to_dc_efficiency=0.93,
dc_to_ac_efficiency=0.95,
charging_efficiency=0.95,
discharging_efficiency=0.95,
ac_charge_hours=list(ac_charge),
elect_price_hourly=list(prices),
load_energy_array=list(loads),
capacity_wh=10_000.0,
initial_soc_percentage=50.0, # 5000 Wh free PV energy → ~4512 Wh deliverable AC
)
fitness_empty = _run_evaluate_with_mocked_sim(config_eos, sim_empty, base_gesamtbilanz=0.0)
fitness_pv = _run_evaluate_with_mocked_sim(config_eos, sim_with_pv, base_gesamtbilanz=0.0)
# Both are penalised (break-even > max future price)
assert fitness_empty > 1e-6, "Empty battery: best_uncovered=0.0006 < break_even→ penalty"
assert fitness_pv > 1e-6, "With PV: free energy covers 0.0006 hour, best drops to 0.0003"
# With PV the expensive hour is covered for free → uncovered best price is lower
# → excess_cost_per_wh = break_even - best_uncovered is larger → penalty is BIGGER
assert fitness_pv > fitness_empty, "PV covers expensive hour → uncovered best is cheaper"
def test_free_pv_energy_exposes_only_cheap_future_prices(self, config_eos):
"""When PV covers ALL expensive hours, best_uncovered_price = 0 → max penalty."""
n = 5
capacity_wh = 10_000.0
# Free PV: initial 80% → (8000 - 0) * 0.95 * 0.95 = 7220 Wh deliverable AC
# Future loads: 2 expensive hours × 1000 Wh = 2000 Wh → all covered by free PV
prices = [0.0010, 0.0008, 0.0008, 0.0002, 0.0002]
ac_charge = [1.0, 0.0, 0.0, 0.0, 0.0]
loads = [1000.0] * n
sim = _make_mock_simulation(
ac_to_dc_efficiency=0.93,
dc_to_ac_efficiency=0.95,
charging_efficiency=0.95,
discharging_efficiency=0.95,
capacity_wh=capacity_wh,
initial_soc_percentage=80.0,
ac_charge_hours=ac_charge,
elect_price_hourly=prices,
load_energy_array=loads,
)
# break_even = 0.001 / (0.93*0.95*0.95*0.95) ≈ 0.001254
# PV covers both 0.0008 hours → best_uncovered = 0.0002 → penalty fires
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=0.0)
assert fitness > 1e-6
# -----------------------------------------------------------------
# 5e. Penalty factor scales the penalty
# -----------------------------------------------------------------
def test_penalty_factor_scales_linearly(self, config_eos):
"""The ac_charge_break_even factor doubles the penalty when doubled."""
n = 24
prices = [0.0004] + [0.0003] * (n - 1)
ac_charge = [1.0] + [0.0] * (n - 1)
loads = [1000.0] * n
def _fitness(factor):
sim = _make_mock_simulation(
ac_to_dc_efficiency=0.93,
dc_to_ac_efficiency=0.95,
charging_efficiency=0.95,
discharging_efficiency=0.95,
ac_charge_hours=list(ac_charge),
elect_price_hourly=list(prices),
load_energy_array=list(loads),
initial_soc_percentage=0.0,
)
return _run_evaluate_with_mocked_sim(
config_eos, sim, ac_charge_break_even=factor, base_gesamtbilanz=0.0
)
f1 = _fitness(1.0)
f2 = _fitness(2.0)
# With factor=2 the penalty should be exactly double
assert f2 == pytest.approx(2.0 * f1, rel=1e-6)
# -----------------------------------------------------------------
# 5f. Zero or negative AC charge factor → no penalty contribution
# -----------------------------------------------------------------
def test_zero_ac_charge_factor_no_penalty(self, config_eos):
"""ac_charge_hours[h] = 0 means no charging, so no penalty."""
n = 24
prices = [0.0010] * n # Expensive, but no AC charging
ac_charge = [0.0] * n
loads = [1000.0] * n
sim = _make_mock_simulation(
ac_charge_hours=ac_charge,
elect_price_hourly=prices,
load_energy_array=loads,
)
base = 3.0
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=base)
assert fitness == pytest.approx(base, abs=1e-9)
# -----------------------------------------------------------------
# 5g. No battery / inverter → penalty skipped entirely
# -----------------------------------------------------------------
def test_no_battery_skips_penalty(self, config_eos):
"""When no battery is present, the penalty block is skipped."""
n = 24
sim = _make_mock_simulation(
ac_charge_hours=[1.0] * n,
elect_price_hourly=[0.001] * n,
load_energy_array=[1000.0] * n,
)
sim.battery = None # no battery → penalty skipped
base = 2.5
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=base)
assert fitness == pytest.approx(base, abs=1e-9)
def test_no_inverter_skips_penalty(self, config_eos):
"""When no inverter is present, the penalty block is skipped."""
n = 24
sim = _make_mock_simulation(
ac_charge_hours=[1.0] * n,
elect_price_hourly=[0.001] * n,
load_energy_array=[1000.0] * n,
)
sim.inverter = None # no inverter → penalty skipped
base = 2.5
fitness = _run_evaluate_with_mocked_sim(config_eos, sim, base_gesamtbilanz=base)
assert fitness == pytest.approx(base, abs=1e-9)
# -----------------------------------------------------------------
# 5h. Unit-level break-even maths (no optimizer setup needed)
# -----------------------------------------------------------------
def test_break_even_formula(self):
"""Verify the break-even formula: P_break_even = P_charge / η_round_trip."""
ac_to_dc = 0.93
bat_charge = 0.95
bat_discharge = 0.95
dc_to_ac = 0.95
eta_rt = ac_to_dc * bat_charge * bat_discharge * dc_to_ac
p_charge = 0.0004 # 40 ct/kWh
break_even = p_charge / eta_rt
# 1 Wh drawn from grid at p_charge → η_rt Wh delivered
# Need discharge price ≥ p_charge / η_rt to break even
assert break_even == pytest.approx(p_charge / eta_rt, rel=1e-9)
assert break_even > p_charge # Always worse due to losses
def test_free_pv_energy_formula(self):
"""Verify free pv energy: (initial_soc - min_soc) × η_bat_dis × η_inv_dis."""
capacity_wh = 10_000.0
initial_soc_pct = 60.0
min_soc_wh = 500.0
bat_dis = 0.95
inv_dis = 0.95
initial_soc_wh = (initial_soc_pct / 100.0) * capacity_wh # 6000
free_ac_wh = max(0.0, initial_soc_wh - min_soc_wh) * bat_dis * inv_dis
# = (6000 - 500) * 0.95 * 0.95 = 5500 * 0.9025 = 4963.75
expected = 5500.0 * 0.95 * 0.95
assert free_ac_wh == pytest.approx(expected, rel=1e-9)

720
tests/testdata/optimize_result_1_be.json vendored Normal file
View File

@@ -0,0 +1,720 @@
{
"ac_charge": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"dc_charge": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0
],
"discharge_allowed": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
1,
1,
1,
0,
0,
0,
1,
1,
1,
1,
1,
1,
0,
0,
0,
0,
0,
1,
1,
1,
0,
0,
1,
0,
1,
1,
1,
1,
1,
0,
1,
1,
1,
0
],
"eautocharge_hours_float": null,
"result": {
"Last_Wh_pro_Stunde": [
1053.07,
1063.91,
1320.56,
1132.03,
1163.67,
1176.82,
1216.22,
1103.78,
1129.12,
1178.71,
1050.98,
988.56,
912.38,
704.61,
516.37,
868.05,
694.34,
608.79,
556.31,
488.89,
506.91,
804.89,
1141.98,
1056.97,
992.46,
1155.99,
827.01,
1257.98,
1232.67,
871.26,
860.88,
1158.03,
1222.72,
1221.04,
949.99,
987.01,
733.99,
592.97
],
"EAuto_SoC_pro_Stunde": [
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0,
54.0
],
"Einnahmen_Euro_pro_Stunde": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.20401973777039792,
0.18681973047764083,
0.12880892587597292,
0.04260510586128589,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.07946910594375262,
0.14608958812480227,
0.07926913850394514,
0.010404817872487553,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Gesamt_Verluste": 3181.0071533862283,
"Gesamtbilanz_Euro": 0.5201969277249601,
"Gesamteinnahmen_Euro": 0.8774861504302851,
"Gesamtkosten_Euro": 1.3976830781552452,
"Home_appliance_wh_per_hour": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Kosten_Euro_pro_Stunde": [
0.027996119999999992,
0.0,
0.0,
0.0018232052307313393,
0.0,
0.0,
0.0,
0.016809914659344942,
0.061530097476751734,
0.05480703000000003,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.22802125600000003,
0.199865757,
0.182970359,
0.162995926,
0.16677339,
0.0,
0.0,
0.0,
0.007989913613567745,
0.028255713342252034,
0.0,
0.010682755832597498,
0.0,
0.0,
0.0,
0.0,
0.0,
0.08231598,
0.0,
0.0,
0.0,
0.16484566
],
"Netzbezug_Wh_pro_Stunde": [
122.78999999999996,
0.0,
0.0,
9.703061366318996,
0.0,
0.0,
0.0,
74.05248748610107,
205.30563055305882,
171.54000000000008,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
694.34,
608.79,
556.31,
488.89,
506.91,
0.0,
0.0,
0.0,
35.04348076126204,
127.73830624887898,
0.0,
56.853410498124,
0.0,
0.0,
0.0,
0.0,
0.0,
257.64,
0.0,
0.0,
0.0,
592.97
],
"Netzeinspeisung_Wh_pro_Stunde": [
0.0,
0.0,
0.0,
0.0,
0.0,
2914.5676824342563,
2668.8532925377262,
1840.127512513899,
608.6443694469413,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1135.2729420536089,
2086.994116068604,
1132.4162643420734,
148.64025532125078,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Verluste_Pro_Stunde": [
0.0,
2.817272727272737,
29.157272727272726,
2.3948326360417305,
582.6180000000041,
171.3218781078929,
10.782457846871594,
0.0,
0.0,
0.0,
99.72409090909093,
133.72909090909081,
124.41545454545451,
96.08318181818186,
70.41409090909087,
118.37045454545455,
0.0,
0.0,
0.0,
0.0,
0.0,
109.0704545454546,
109.96227272727276,
47.952272727272714,
11.233982308648535,
38.52740325013451,
161.62968357967037,
14.209990740225123,
538.2984000000038,
305.7193589211809,
10.13011689299087,
36.109951963721926,
44.377460775206174,
0.0,
77.27590909090907,
134.59227272727276,
100.08954545454549,
0.0
],
"akku_soc_pro_stunde": [
80.0,
79.91107093663912,
78.99070247933885,
79.05722560811779,
95.24105894144923,
100.0,
100.0,
100.0,
100.0,
100.0,
96.85214359504131,
92.63089703856748,
88.7036415289256,
85.67071280991735,
83.44804579889806,
79.71160468319557,
79.71160468319557,
79.71160468319557,
79.71160468319557,
79.71160468319557,
79.71160468319557,
76.26872417355371,
72.79769283746555,
71.28404786501376,
71.59610292914289,
72.66630857497995,
76.17424245972133,
76.56896442472758,
91.52169775805919,
100.0,
100.0,
100.0,
100.0,
100.0,
97.56073519283747,
93.3122417355372,
90.15284951790635,
90.15284951790635
],
"Electricity_price": [
0.000228,
0.0002212,
0.0002093,
0.0001879,
0.0001838,
0.0002004,
0.0002198,
0.000227,
0.0002997,
0.0003195,
0.0003081,
0.0002969,
0.0002921,
0.000278,
0.0003384,
0.0003318,
0.0003284,
0.0003283,
0.0003289,
0.0003334,
0.000329,
0.0003302,
0.0003042,
0.000243,
0.000228,
0.0002212,
0.0002093,
0.0001879,
0.0001838,
0.0002004,
0.0002198,
0.000227,
0.0002997,
0.0003195,
0.0003081,
0.0002969,
0.0002921,
0.000278
]
},
"eauto_obj": {
"device_id": "ev1",
"hours": 48,
"charge_array": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"discharge_array": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"discharging_efficiency": 1.0,
"capacity_wh": 60000,
"charging_efficiency": 0.95,
"max_charge_power_w": 11040,
"soc_wh": 32400.000000000004,
"initial_soc_percentage": 54
},
"start_solution": [
15.0,
5.0,
12.0,
19.0,
7.0,
17.0,
15.0,
20.0,
3.0,
7.0,
3.0,
9.0,
13.0,
14.0,
10.0,
7.0,
13.0,
3.0,
5.0,
4.0,
9.0,
11.0,
11.0,
12.0,
13.0,
7.0,
14.0,
4.0,
5.0,
5.0,
5.0,
13.0,
10.0,
8.0,
4.0,
6.0,
10.0,
3.0,
10.0,
8.0,
10.0,
13.0,
12.0,
14.0,
11.0,
12.0,
8.0,
1.0
],
"washingstart": null
}

818
tests/testdata/optimize_result_2_be.json vendored Normal file
View File

@@ -0,0 +1,818 @@
{
"ac_charge": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.375,
0.0,
0.0,
0.75,
0.0,
0.75,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.5,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"dc_charge": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0
],
"discharge_allowed": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
1,
0,
1,
1,
1,
1,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
1,
0,
1,
0,
1,
0,
0,
1,
0,
0,
0,
1,
1,
1,
0
],
"eautocharge_hours_float": [
0.375,
0.5,
0.625,
1.0,
0.0,
0.5,
0.5,
0.75,
0.625,
0.5,
1.0,
0.0,
0.0,
0.5,
1.0,
0.75,
0.625,
0.625,
0.0,
1.0,
0.625,
0.625,
0.625,
0.0,
0.375,
0.375,
0.625,
0.375,
0.875,
0.625,
0.75,
0.0,
0.875,
0.5,
0.0,
0.75,
1.0,
0.5,
0.625,
0.375,
0.625,
1.0,
0.5,
0.0,
1.0,
0.875,
0.625,
0.5
],
"result": {
"Last_Wh_pro_Stunde": [
13416.07,
1063.91,
1320.56,
10126.029999999999,
14151.67,
12042.82,
7771.22,
7658.78,
1129.12,
10617.91,
1050.98,
988.56,
912.38,
704.61,
516.37,
868.05,
694.34,
608.79,
556.31,
488.89,
506.91,
804.89,
1141.98,
1056.97,
992.46,
1155.99,
827.01,
1257.98,
3732.67,
871.26,
860.88,
1158.03,
1222.72,
1221.04,
949.99,
987.01,
733.99,
592.97
],
"EAuto_SoC_pro_Stunde": [
5.0,
22.48,
22.48,
22.48,
31.22,
48.699999999999996,
61.809999999999995,
72.735,
83.66,
83.66,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392
],
"Einnahmen_Euro_pro_Stunde": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Gesamt_Verluste": 8129.042662637932,
"Gesamtbilanz_Euro": 11.91553407959862,
"Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 11.91553407959862,
"Home_appliance_wh_per_hour": [
0.0,
0.0,
0.0,
0.0,
2500.0,
2500.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Kosten_Euro_pro_Stunde": [
2.84676012,
0.0,
0.0,
1.684399486,
1.4948178300000001,
1.3073595,
0.835534532,
0.05466613999999993,
0.061530097476751734,
1.66483143,
0.0,
0.0,
0.0,
0.0,
0.174739608,
0.0,
0.0,
0.199865757,
0.182970359,
0.162995926,
0.16677339,
0.26411047,
0.0,
0.0,
0.007989913613567745,
0.0,
0.025392879919306634,
0.0,
0.4595000000000417,
0.0003442778967139274,
0.0,
0.028137079449292023,
0.04565364324294593,
0.08231598,
0.0,
0.0,
0.0,
0.16484566
],
"Netzbezug_Wh_pro_Stunde": [
12485.789999999999,
0.0,
0.0,
8964.34,
8132.85,
6523.75,
3801.34,
240.8199999999997,
205.30563055305882,
5210.74,
0.0,
0.0,
0.0,
0.0,
516.37,
0.0,
0.0,
608.79,
556.31,
488.89,
506.91,
799.85,
0.0,
0.0,
35.04348076126204,
0.0,
121.32288542430308,
0.0,
2500.000000000227,
1.7179535764168035,
0.0,
123.95189184710142,
152.3311419517715,
257.64,
0.0,
0.0,
0.0,
592.97
],
"Netzeinspeisung_Wh_pro_Stunde": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Verluste_Pro_Stunde": [
777.0,
2.817272727272737,
29.157272727272726,
726.0,
552.0,
474.0,
345.0,
945.0,
73.03732433363291,
1096.800000000001,
99.72409090909093,
133.72909090909081,
124.41545454545451,
96.08318181818186,
0.0,
118.37045454545455,
94.68272727272722,
0.0,
0.0,
0.0,
0.0,
0.0,
109.96227272727276,
47.952272727272714,
11.233982308648535,
55.946263193163475,
145.08565374908358,
21.962728535423857,
838.2983999999728,
441.7178455708299,
260.56941082122324,
155.09737297834772,
41.441862965787436,
0.0,
77.27590909090907,
134.59227272727276,
100.08954545454549,
0.0
],
"akku_soc_pro_stunde": [
80.0,
86.16107093663912,
85.24070247933885,
85.24070247933885,
97.74070247933885,
97.74070247933885,
99.40736914600552,
80.46797520661157,
82.49678977143472,
63.557395832040775,
60.4095394270821,
56.18829287060828,
52.2610373609664,
49.22810864195814,
49.22810864195814,
45.49166752625566,
42.502945074465025,
42.502945074465025,
42.502945074465025,
42.502945074465025,
42.502945074465025,
42.502945074465025,
39.03191373837687,
37.518268765925086,
37.830323830054205,
38.35069172516435,
42.38084877375001,
42.53085048006944,
57.48358381340202,
78.08685730148063,
85.04350435504266,
89.35176471555232,
90.50292757571307,
90.50292757571307,
88.06366276855056,
83.81516931125029,
80.65577709361943,
80.65577709361943
],
"Electricity_price": [
0.000228,
0.0002212,
0.0002093,
0.0001879,
0.0001838,
0.0002004,
0.0002198,
0.000227,
0.0002997,
0.0003195,
0.0003081,
0.0002969,
0.0002921,
0.000278,
0.0003384,
0.0003318,
0.0003284,
0.0003283,
0.0003289,
0.0003334,
0.000329,
0.0003302,
0.0003042,
0.000243,
0.000228,
0.0002212,
0.0002093,
0.0001879,
0.0001838,
0.0002004,
0.0002198,
0.000227,
0.0002997,
0.0003195,
0.0003081,
0.0002969,
0.0002921,
0.000278
]
},
"eauto_obj": {
"device_id": "ev1",
"hours": 48,
"charge_array": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.5,
1.0,
0.75,
0.625,
0.625,
0.0,
0.9,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"discharge_array": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"discharging_efficiency": 1.0,
"capacity_wh": 60000,
"charging_efficiency": 0.95,
"max_charge_power_w": 11040,
"soc_wh": 59635.2,
"initial_soc_percentage": 5
},
"start_solution": [
0.0,
4.0,
13.0,
17.0,
14.0,
12.0,
9.0,
9.0,
17.0,
13.0,
15.0,
12.0,
9.0,
18.0,
14.0,
18.0,
14.0,
9.0,
2.0,
8.0,
10.0,
12.0,
10.0,
10.0,
14.0,
8.0,
7.0,
2.0,
6.0,
4.0,
4.0,
2.0,
8.0,
12.0,
2.0,
9.0,
14.0,
13.0,
16.0,
6.0,
8.0,
5.0,
1.0,
2.0,
13.0,
10.0,
9.0,
5.0,
1.0,
2.0,
3.0,
6.0,
0.0,
2.0,
2.0,
4.0,
3.0,
2.0,
6.0,
0.0,
0.0,
2.0,
6.0,
4.0,
3.0,
3.0,
0.0,
6.0,
3.0,
3.0,
3.0,
0.0,
1.0,
1.0,
3.0,
1.0,
5.0,
3.0,
4.0,
0.0,
5.0,
2.0,
0.0,
4.0,
6.0,
2.0,
3.0,
1.0,
3.0,
6.0,
2.0,
0.0,
6.0,
5.0,
3.0,
2.0,
14.0
],
"washingstart": 14
}