2025-01-09 16:52:57 +01:00
|
|
|
|
% SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
2025-10-28 02:50:31 +01:00
|
|
|
|
# `POST /optimize` Optimization
|
2025-01-09 16:52:57 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
## Introduction
|
|
|
|
|
|
|
2025-02-12 14:24:17 +01:00
|
|
|
|
The `POST /optimize` API endpoint optimizes your energy management system based on various inputs
|
|
|
|
|
|
including electricity prices, battery storage capacity, PV forecast, and temperature data.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
2025-10-28 02:50:31 +01:00
|
|
|
|
The `POST /optimize` optimization interface is the "classical" interface developed by Andreas at the
|
|
|
|
|
|
start of the projects and used and described in his videos. It allows and requires to define all the
|
|
|
|
|
|
optimization paramters on the endpoint request.
|
|
|
|
|
|
|
|
|
|
|
|
:::{admonition} Warning
|
|
|
|
|
|
:class: warning
|
|
|
|
|
|
The `POST /optimize` endpoint interface does not regard configurations set for the parameters
|
|
|
|
|
|
passed to the request. You have to set the parameters even if given in the configuration.
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
2026-03-13 15:48:43 +01:00
|
|
|
|
:::{admonition} Warning
|
|
|
|
|
|
:class: warning
|
|
|
|
|
|
To prevent automatic optimization from interfering with `POST /optimize` requests, set `ems.mode`
|
|
|
|
|
|
to `DISABLED` in the configuration.
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
## Input Payload
|
|
|
|
|
|
|
|
|
|
|
|
### Sample Request
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"ems": {
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"preis_euro_pro_wh_akku": 0.0001,
|
|
|
|
|
|
"einspeiseverguetung_euro_pro_wh": [
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
|
|
|
|
|
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007
|
|
|
|
|
|
],
|
|
|
|
|
|
"gesamtlast": [
|
|
|
|
|
|
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28,
|
|
|
|
|
|
1011.68, 995.00, 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
|
|
|
|
|
|
],
|
|
|
|
|
|
"pv_prognose_wh": [
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74,
|
|
|
|
|
|
1161.69, 6018.82, 5519.07, 3969.88, 3017.96, 1943.07, 1007.17,
|
|
|
|
|
|
319.67, 7.88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5.04, 335.59, 705.32,
|
|
|
|
|
|
1121.12, 1604.79, 2157.38, 1433.25, 5718.49, 4553.96, 3027.55,
|
|
|
|
|
|
2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
|
|
|
|
|
|
],
|
|
|
|
|
|
"strompreis_euro_pro_wh": [
|
|
|
|
|
|
0.0003384, 0.0003318, 0.0003284, 0.0003283, 0.0003289, 0.0003334,
|
|
|
|
|
|
0.0003290, 0.0003302, 0.0003042, 0.0002430, 0.0002280, 0.0002212,
|
|
|
|
|
|
0.0002093, 0.0001879, 0.0001838, 0.0002004, 0.0002198, 0.0002270,
|
|
|
|
|
|
0.0002997, 0.0003195, 0.0003081, 0.0002969, 0.0002921, 0.0002780,
|
|
|
|
|
|
0.0003384, 0.0003318, 0.0003284, 0.0003283, 0.0003289, 0.0003334,
|
|
|
|
|
|
0.0003290, 0.0003302, 0.0003042, 0.0002430, 0.0002280, 0.0002212,
|
|
|
|
|
|
0.0002093, 0.0001879, 0.0001838, 0.0002004, 0.0002198, 0.0002270,
|
|
|
|
|
|
0.0002997, 0.0003195, 0.0003081, 0.0002969, 0.0002921, 0.0002780
|
|
|
|
|
|
]
|
2025-02-11 22:47:23 +01:00
|
|
|
|
},
|
|
|
|
|
|
"pv_akku": {
|
2025-06-02 10:47:02 +02:00
|
|
|
|
"device_id": "battery1",
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"capacity_wh": 26400,
|
2026-07-15 08:52:16 +02:00
|
|
|
|
"levelized_cost_of_storage_kwh": 0.12,
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"max_charge_power_w": 5000,
|
|
|
|
|
|
"initial_soc_percentage": 80,
|
2026-09-03 17:53:33 +02:00
|
|
|
|
"min_soc_percentage": 15,
|
|
|
|
|
|
"grid_export_rates": [0.25, 0.5, 0.75, 1.0]
|
2025-02-11 22:47:23 +01:00
|
|
|
|
},
|
|
|
|
|
|
"inverter": {
|
2025-06-02 10:47:02 +02:00
|
|
|
|
"device_id": "inverter1",
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"max_power_wh": 10000,
|
2026-02-27 23:12:08 +01:00
|
|
|
|
"battery_id": "battery1",
|
|
|
|
|
|
"ac_to_dc_efficiency": 0.95,
|
|
|
|
|
|
"dc_to_ac_efficiency": 0.95,
|
|
|
|
|
|
"max_ac_charge_power_w": 5000
|
2025-02-11 22:47:23 +01:00
|
|
|
|
},
|
|
|
|
|
|
"eauto": {
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"device_id": "ev1",
|
|
|
|
|
|
"capacity_wh": 60000,
|
|
|
|
|
|
"charging_efficiency": 0.95,
|
2025-10-30 17:11:30 +01:00
|
|
|
|
"charge_rates": [0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"discharging_efficiency": 1.0,
|
2025-02-11 22:47:23 +01:00
|
|
|
|
"max_charge_power_w": 11040,
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"initial_soc_percentage": 54,
|
2026-09-03 17:53:33 +02:00
|
|
|
|
"min_soc_percentage": 0,
|
|
|
|
|
|
"min_soc_deadline_datetime": null,
|
|
|
|
|
|
"min_soc_max_duration_h": null
|
2025-02-11 22:47:23 +01:00
|
|
|
|
},
|
2026-07-15 14:19:46 +02:00
|
|
|
|
"home_appliances": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"device_id": "dishwasher1",
|
|
|
|
|
|
"consumption_wh": 2000,
|
|
|
|
|
|
"duration_h": 3,
|
|
|
|
|
|
"schedule_mode": "ONCE",
|
2026-09-03 17:53:33 +02:00
|
|
|
|
"time_windows": null,
|
|
|
|
|
|
"earliest_start_datetime": null,
|
|
|
|
|
|
"deadline_datetime": "2026-07-16T03:00:00+02:00",
|
|
|
|
|
|
"deadline_policy": "BEST_EFFORT"
|
2026-07-15 14:19:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-10-28 02:50:31 +01:00
|
|
|
|
"temperature_forecast": [
|
|
|
|
|
|
18.3, 17.8, 16.9, 16.2, 15.6, 15.1, 14.6, 14.2, 14.3, 14.8, 15.7, 16.7, 17.4,
|
|
|
|
|
|
18.0, 18.6, 19.2, 19.1, 18.7, 18.5, 17.7, 16.2, 14.6, 13.6, 13.0, 12.6, 12.2,
|
|
|
|
|
|
11.7, 11.6, 11.3, 11.0, 10.7, 10.2, 11.4, 14.4, 16.4, 18.3, 19.5, 20.7, 21.9,
|
|
|
|
|
|
22.7, 23.1, 23.1, 22.8, 21.8, 20.2, 19.1, 18.0, 17.4
|
|
|
|
|
|
],
|
2025-02-11 22:47:23 +01:00
|
|
|
|
"start_solution": null
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Input Parameters
|
|
|
|
|
|
|
|
|
|
|
|
### Energy Management System (EMS)
|
|
|
|
|
|
|
2026-07-15 08:52:16 +02:00
|
|
|
|
#### Battery Terminal Value (`preis_euro_pro_wh_akku`)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- Unit: €/Wh
|
|
|
|
|
|
- Purpose: Represents the residual value of energy stored in the battery
|
2025-10-28 02:50:31 +01:00
|
|
|
|
- Impact: Lower values encourage battery depletion, higher values preserve charge at the end of the
|
|
|
|
|
|
simulation.
|
2026-07-15 08:52:16 +02:00
|
|
|
|
- Separation from LCOS: This value is only applied to usable battery energy remaining at the end of
|
|
|
|
|
|
the optimization horizon. Battery discharge throughput is priced separately with
|
|
|
|
|
|
`pv_akku.levelized_cost_of_storage_kwh`.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
|
|
|
|
|
#### Feed-in Tariff (`einspeiseverguetung_euro_pro_wh`)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- Unit: €/Wh
|
|
|
|
|
|
- Purpose: Compensation received for feeding excess energy back to the grid
|
|
|
|
|
|
|
|
|
|
|
|
#### Total Load Forecast (`gesamtlast`)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- Unit: W
|
|
|
|
|
|
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
|
|
|
|
|
|
- Format: Array of hourly values
|
|
|
|
|
|
- Note: Exclude optimizable loads (EV charging, battery charging, etc.)
|
|
|
|
|
|
|
2025-02-12 14:24:17 +01:00
|
|
|
|
##### Data Sources
|
|
|
|
|
|
|
|
|
|
|
|
1. Standard Load Profile: `GET /v1/prediction/list?key=load_mean` for a standard load profile based
|
|
|
|
|
|
on your yearly consumption.
|
|
|
|
|
|
2. Adjusted Load Profile: `GET /v1/prediction/list?key=load_mean_adjusted` for a combination of a
|
|
|
|
|
|
standard load profile based on your yearly consumption incl. data from last 48h.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
|
|
|
|
|
#### PV Generation Forecast (`pv_prognose_wh`)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- Unit: W
|
|
|
|
|
|
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
|
|
|
|
|
|
- Format: Array of hourly values
|
|
|
|
|
|
- Data Source: `GET /v1/prediction/series?key=pvforecast_ac_power`
|
|
|
|
|
|
|
2026-07-15 08:52:16 +02:00
|
|
|
|
#### Probabilistic Direct PV Consumption and Bypass
|
|
|
|
|
|
|
|
|
|
|
|
Hourly or 15-minute mean values alone would optimistically assume that the smaller of mean PV
|
|
|
|
|
|
generation and mean load is consumed directly. Real household load varies within the interval. EOS
|
|
|
|
|
|
therefore uses a conditional probability table derived from one-minute load samples. For a forecast
|
|
|
|
|
|
mean load \(\mu_L\), the table contains load-bin powers \(L_i\) and their conditional probabilities
|
|
|
|
|
|
\(p_i = P(L=L_i\mid\mu_L)\), with \(\sum_i p_i=1\).
|
|
|
|
|
|
|
|
|
|
|
|
Because the finite 50 W table grid can deviate slightly from the requested forecast mean, the load
|
|
|
|
|
|
bins are first normalized without changing the shape of the distribution:
|
|
|
|
|
|
|
|
|
|
|
|
```{math}
|
|
|
|
|
|
\widetilde{L}_i = L_i \frac{\mu_L}{\sum_j p_j L_j}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
For mean PV power \(P_{PV}\), the expected power flowing directly from PV to the load is:
|
|
|
|
|
|
|
|
|
|
|
|
```{math}
|
|
|
|
|
|
P_{direct} = \sum_i p_i \min\left(\widetilde{L}_i, P_{PV}\right)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
For a slot of duration \(\Delta t\), EOS converts this power into energy and derives both residual
|
|
|
|
|
|
flows from the same direct-consumption value:
|
|
|
|
|
|
|
|
|
|
|
|
```{math}
|
|
|
|
|
|
\begin{aligned}
|
|
|
|
|
|
E_{direct} &= \Delta t\,P_{direct} \\
|
|
|
|
|
|
E_{load,residual} &= E_{load}-E_{direct} \\
|
|
|
|
|
|
E_{PV,surplus} &= E_{PV}-E_{direct}
|
|
|
|
|
|
\end{aligned}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The residual load is supplied by the battery and then the grid. The PV surplus charges the battery;
|
|
|
|
|
|
any remainder bypasses the battery and is exported. Both residual load and PV surplus may be
|
|
|
|
|
|
positive in the same coarse slot because they occur during different sub-intervals. This is expected
|
|
|
|
|
|
and preserves the energy balances
|
|
|
|
|
|
\(E_{direct}+E_{load,residual}=E_{load}\) and
|
|
|
|
|
|
\(E_{direct}+E_{PV,surplus}=E_{PV}\).
|
|
|
|
|
|
|
|
|
|
|
|
The bundled table is conditioned on a one-hour mean load and models load variation only; mean PV is
|
|
|
|
|
|
treated as constant inside the slot. For a 15-minute grid produced by splitting hourly energy, the
|
|
|
|
|
|
power lookup retains the original hourly mean. A native 15-minute load forecast uses the same table
|
|
|
|
|
|
as an approximation until a separately calibrated 15-minute distribution is available. Fast PV
|
|
|
|
|
|
variability, for example from clouds, is not represented by this table.
|
|
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
#### Electricity Price Forecast (`strompreis_euro_pro_wh`)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- Unit: €/Wh
|
|
|
|
|
|
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
|
|
|
|
|
|
- Format: Array of hourly values
|
|
|
|
|
|
- Data Source: `GET /v1/prediction/list?key=elecprice_marketprice_wh`
|
|
|
|
|
|
|
|
|
|
|
|
Verify prices against your local tariffs.
|
|
|
|
|
|
|
|
|
|
|
|
### Battery Storage System
|
|
|
|
|
|
|
|
|
|
|
|
#### Configuration
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-06-02 10:47:02 +02:00
|
|
|
|
- `device_id`: ID of battery
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `capacity_wh`: Total battery capacity in Wh
|
|
|
|
|
|
- `charging_efficiency`: Charging efficiency (0-1)
|
|
|
|
|
|
- `discharging_efficiency`: Discharging efficiency (0-1)
|
2026-07-15 08:52:16 +02:00
|
|
|
|
- `levelized_cost_of_storage_kwh`: LCOS in EUR/kWh, charged once for every kWh of DC energy
|
|
|
|
|
|
delivered by the battery. Default: `0.0`.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `max_charge_power_w`: Maximum charging power in W
|
2026-09-03 17:53:33 +02:00
|
|
|
|
- `charge_rates`: Selectable AC charge levels as factor of `max_charge_power_w`.
|
|
|
|
|
|
Defaults to the configured `devices.batteries[0].charge_rates`.
|
|
|
|
|
|
- `grid_export_rates`: Selectable battery-to-grid export levels, see below.
|
|
|
|
|
|
Defaults to the configured `devices.batteries[0].grid_export_rates`.
|
|
|
|
|
|
|
|
|
|
|
|
#### Battery Grid Export Levels (`grid_export_rates`)
|
|
|
|
|
|
|
|
|
|
|
|
With direct marketing enabled (`feedintariff.direct_marketing_enabled`) the battery may discharge
|
|
|
|
|
|
into the grid. The export is not all-or-nothing: `grid_export_rates` lists the selectable export
|
|
|
|
|
|
levels as a factor of the battery's rated discharge power, for example
|
|
|
|
|
|
`[0.25, 0.5, 0.75, 1.0]` (the default). The optimizer picks one level per slot, so it can spread a
|
|
|
|
|
|
limited amount of stored energy over several expensive slots instead of emptying the battery into
|
|
|
|
|
|
the first one.
|
|
|
|
|
|
|
|
|
|
|
|
Each rate is one more state in the genetic state space, which is why the default is deliberately
|
|
|
|
|
|
coarse. `[1.0]` restores the previous all-or-nothing behaviour.
|
|
|
|
|
|
|
2026-09-03 18:36:48 +02:00
|
|
|
|
When EOS writes its configuration file it omits every value that equals the field default, so a
|
|
|
|
|
|
`grid_export_rates` of exactly `[0.25, 0.5, 0.75, 1.0]` disappears from `EOS.config.json` on the
|
|
|
|
|
|
next save. The rates are still active - `GET /v1/config` shows the effective configuration, the
|
|
|
|
|
|
saved file only shows the deviations from it.
|
|
|
|
|
|
|
|
|
|
|
|
Whether a partial level is ever selected depends on the scenario. Exporting at full power in the
|
|
|
|
|
|
best-priced slots is optimal whenever the stored energy has no more valuable use; a partial level
|
|
|
|
|
|
pays when the export competes with a later, more expensive self-consumption and the right amount
|
|
|
|
|
|
of energy falls between two whole slots.
|
|
|
|
|
|
|
2026-09-03 17:53:33 +02:00
|
|
|
|
The exported energy of one slot is bounded by
|
|
|
|
|
|
|
|
|
|
|
|
```{math}
|
|
|
|
|
|
E_{export} \le \min\bigl(P_{inv,free}\,\Delta t,\; E_{bat,remaining},\; r\,P_{bat,rated}\,\Delta t\bigr)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
where `r` is the selected rate. The rate applies to the *rated* discharge power, so it stays a plain
|
|
|
|
|
|
power setpoint: local self-consumption earlier in the same slot lowers `E_bat,remaining`, but it does
|
|
|
|
|
|
not silently raise the export level.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
2026-07-15 08:52:16 +02:00
|
|
|
|
#### Battery LCOS (`levelized_cost_of_storage_kwh`)
|
|
|
|
|
|
|
|
|
|
|
|
LCOS and terminal value have different purposes. LCOS is a variable battery-use cost and is added
|
|
|
|
|
|
once when the battery delivers energy, both for local load coverage and battery-to-grid export. It
|
|
|
|
|
|
is not charged when the battery is charged and is not charged again on battery-internal or
|
|
|
|
|
|
DC-to-AC inverter losses.
|
|
|
|
|
|
|
|
|
|
|
|
For battery-delivered DC energy `E_bat,out` in one slot:
|
|
|
|
|
|
|
|
|
|
|
|
```{math}
|
|
|
|
|
|
C_{LCOS} = \frac{E_{bat,out}}{1000}\,c_{LCOS}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
where `E_bat,out` is in Wh and `c_LCOS` is in EUR/kWh. This cost is included in
|
2026-09-04 10:37:48 +02:00
|
|
|
|
`Kosten_Euro_pro_Stunde`, `Gesamtkosten_Euro`, and therefore `Gesamtbilanz_Euro`. The terminal value,
|
|
|
|
|
|
by contrast, applies only to usable energy remaining after the last slot.
|
|
|
|
|
|
|
|
|
|
|
|
#### Terminal Value of Stored Energy
|
|
|
|
|
|
|
|
|
|
|
|
The optimization stops at the horizon, but the energy still in the battery keeps its worth: it
|
|
|
|
|
|
replaces grid imports that would otherwise be paid for afterwards. How that worth is credited is
|
|
|
|
|
|
set by `optimization.terminal_value_mode`.
|
|
|
|
|
|
|
|
|
|
|
|
`AUTO` (the default) derives a **concave value curve** instead of using a single price. The value of
|
|
|
|
|
|
stored energy is not linear in the amount stored:
|
|
|
|
|
|
|
|
|
|
|
|
- The first kWh replaces the most expensive hour that PV cannot cover.
|
|
|
|
|
|
- The next one replaces the second most expensive hour, and so on.
|
|
|
|
|
|
- Once every such hour is served, further energy replaces nothing - it is worth an export at best,
|
|
|
|
|
|
and nothing at worst.
|
|
|
|
|
|
|
|
|
|
|
|
A single price has to pick one slope for all of it: high enough for the first kWh means hoarding a
|
|
|
|
|
|
full battery, low enough for the last kWh means running the battery empty by the end of the horizon.
|
|
|
|
|
|
The latter is what `terminal_value_euro_per_kwh = 0` does, and it is why `AUTO` is the default.
|
|
|
|
|
|
|
|
|
|
|
|
There is no forecast beyond the horizon, so the trailing window of the horizon itself
|
|
|
|
|
|
(`optimization.terminal_value_window_hours`, 24 h by default) stands in for the day that follows:
|
|
|
|
|
|
same season, same household rhythm, same tariff structure. Within that window the residual load
|
|
|
|
|
|
`max(load - PV, 0)` of every slot is priced at its import price, sorted by price and accumulated -
|
|
|
|
|
|
that is the curve. The battery LCOS is subtracted from every marginal value so stored energy is not
|
|
|
|
|
|
credited twice, and energy beyond the residual load is only credited when direct marketing allows
|
|
|
|
|
|
the battery to export.
|
|
|
|
|
|
|
|
|
|
|
|
`FIXED` restores the previous behaviour: every stored kWh is credited with
|
|
|
|
|
|
`optimization.terminal_value_euro_per_kwh`, or with `preis_euro_pro_wh_akku` of the request. In
|
|
|
|
|
|
`AUTO` mode that request field is ignored.
|
|
|
|
|
|
|
|
|
|
|
|
The curve is built once per optimization run and only interpolated during the search, so it costs
|
|
|
|
|
|
nothing per candidate solution. It is a planning aid derived from a proxy day, not a forecast - see
|
|
|
|
|
|
`terminal_value` in the response to check what a run actually used.
|
2026-07-15 08:52:16 +02:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
#### State of Charge (SoC)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `initial_soc_percentage`: Current battery level (%)
|
|
|
|
|
|
- `min_soc_percentage`: Minimum allowed SoC (%)
|
|
|
|
|
|
- `max_soc_percentage`: Maximum allowed SoC (%)
|
|
|
|
|
|
|
|
|
|
|
|
### Inverter
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-06-02 10:47:02 +02:00
|
|
|
|
- `device_id`: ID of inverter
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `max_power_wh`: Maximum inverter power in Wh
|
2025-06-02 10:47:02 +02:00
|
|
|
|
- `battery_id`: ID of battery
|
2026-02-27 23:12:08 +01:00
|
|
|
|
- `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:
|
2026-07-15 08:52:16 +02:00
|
|
|
|
`buy_price / η_round_trip + LCOS / dc_to_ac_efficiency`
|
2026-02-27 23:12:08 +01:00
|
|
|
|
|
|
|
|
|
|
**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:
|
2026-07-15 08:52:16 +02:00
|
|
|
|
- `break_even_price = charge_price / η_round_trip + LCOS / dc_to_ac_efficiency`
|
2026-02-27 23:12:08 +01:00
|
|
|
|
- `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.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
|
|
|
|
|
### Electric Vehicle (EV)
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-06-02 10:47:02 +02:00
|
|
|
|
- `device_id`: ID of electric vehicle
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `capacity_wh`: Battery capacity in Wh
|
|
|
|
|
|
- `charging_efficiency`: Charging efficiency (0-1)
|
|
|
|
|
|
- `discharging_efficiency`: Discharging efficiency (0-1)
|
|
|
|
|
|
- `max_charge_power_w`: Maximum charging power in W
|
|
|
|
|
|
- `initial_soc_percentage`: Current charge level (%)
|
2026-09-03 17:53:33 +02:00
|
|
|
|
- `min_soc_percentage`: Charging target; minimum allowed SoC (%)
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `max_soc_percentage`: Maximum allowed SoC (%)
|
2026-09-03 17:53:33 +02:00
|
|
|
|
- `min_soc_deadline_datetime`: Absolute moment by which `min_soc_percentage` has to be reached
|
|
|
|
|
|
- `min_soc_max_duration_h`: Maximum time from the start of the optimization until
|
|
|
|
|
|
`min_soc_percentage` has to be reached (h)
|
|
|
|
|
|
|
|
|
|
|
|
#### Charging Deadline
|
|
|
|
|
|
|
|
|
|
|
|
By default `min_soc_percentage` only has to be reached by the end of the optimization horizon, so
|
|
|
|
|
|
the optimizer is free to charge in the cheapest slots anywhere in the horizon. A deadline moves
|
|
|
|
|
|
that requirement forward - typically to the next departure:
|
|
|
|
|
|
|
|
|
|
|
|
- `min_soc_deadline_datetime`: an absolute instant (`2026-07-16T07:00:00+02:00`). A value without
|
|
|
|
|
|
timezone is read as local time.
|
|
|
|
|
|
- `min_soc_max_duration_h`: the same thing relative to the start of the optimization
|
|
|
|
|
|
("full in 6 hours"), which avoids timestamp arithmetic in the calling automation.
|
|
|
|
|
|
|
|
|
|
|
|
Both may be given; the earlier one applies. A deadline beyond the horizon is ignored, a deadline in
|
|
|
|
|
|
the past means the target is due immediately. The SoC-miss penalty
|
|
|
|
|
|
(`optimization.genetic.penalties.ev_soc_miss`) is then evaluated at the deadline instead of at the
|
|
|
|
|
|
end of the horizon, and the seeding heuristics only propose charge slots before it. Charging after
|
|
|
|
|
|
the deadline is not forbidden - it simply no longer helps to avoid the penalty.
|
|
|
|
|
|
|
|
|
|
|
|
The deadline is a target, not a hard constraint: if the remaining time is too short to reach
|
|
|
|
|
|
`min_soc_percentage`, the optimizer charges as much as it can and accepts the penalty. Check
|
|
|
|
|
|
`result.EAuto_SoC_pro_Stunde` at the deadline slot to see what was actually achieved.
|
|
|
|
|
|
|
2026-09-03 18:36:48 +02:00
|
|
|
|
In practice the target behaves as if it were binding. The default penalty of `10` per missing
|
|
|
|
|
|
percentage point is roughly forty times the cost of the energy itself (one point of a 60 kWh
|
|
|
|
|
|
battery is 600 Wh, some 0.25 EUR at 0.40 EUR/kWh), so the optimizer keeps the deadline whenever
|
|
|
|
|
|
charging power and remaining time allow it. The soft formulation only exists so that an
|
|
|
|
|
|
unreachable target degrades gracefully instead of failing the whole optimization.
|
|
|
|
|
|
|
|
|
|
|
|
Note that the target is met *tightly*: the optimizer stops at the first SoC that satisfies
|
|
|
|
|
|
`min_soc_percentage`, because every further kWh only adds cost.
|
|
|
|
|
|
|
2026-09-03 17:53:33 +02:00
|
|
|
|
### Flexible Consumers (Home Appliances)
|
|
|
|
|
|
|
|
|
|
|
|
Each entry of `home_appliances` describes one consumer whose run the optimizer may place in time.
|
|
|
|
|
|
The load of a single complete run is defined **either** by an explicit profile
|
|
|
|
|
|
(`load_profile_power_w` with `load_profile_interval_seconds`) **or** by the flat fallback
|
|
|
|
|
|
`consumption_wh` + `duration_h`.
|
|
|
|
|
|
|
|
|
|
|
|
- `device_id`: Unique ID of the consumer, used in all result columns
|
|
|
|
|
|
- `schedule_mode`: `ONCE` (a single run within the horizon) or `DAILY` (one run per local calendar
|
|
|
|
|
|
day that still has a feasible full run)
|
|
|
|
|
|
|
|
|
|
|
|
Three independent constraints decide *when* a run may happen; all of them have to hold at once:
|
|
|
|
|
|
|
|
|
|
|
|
- `time_windows`: recurring wall-clock windows, e.g. "only between 10:00 and 13:00", optionally
|
|
|
|
|
|
restricted to a weekday or a date. See {doc}`configtimewindow`.
|
|
|
|
|
|
- `earliest_start_datetime`: absolute lower bound. The run may not start before this moment.
|
|
|
|
|
|
- `deadline_datetime`: absolute upper bound. The complete run must have **finished** at or before
|
|
|
|
|
|
this moment - with a 3 h program and a deadline of 03:00, the last allowed start is 00:00.
|
|
|
|
|
|
|
|
|
|
|
|
Both datetimes are absolute instants and never roll over into the next day. A value without
|
|
|
|
|
|
timezone is read as local time; sending an ISO-8601 timestamp with offset
|
|
|
|
|
|
(`2026-07-16T03:00:00+02:00`) is unambiguous.
|
|
|
|
|
|
|
|
|
|
|
|
#### Missed Deadlines (`deadline_policy`)
|
|
|
|
|
|
|
|
|
|
|
|
Depending on the current time, the run duration, the horizon and the time windows, a deadline can
|
|
|
|
|
|
be unreachable. `deadline_policy` decides what happens then:
|
|
|
|
|
|
|
|
|
|
|
|
- `BEST_EFFORT` (default): the run is scheduled as early as the remaining constraints allow -
|
|
|
|
|
|
minimize the delay instead of the cost ("it should have been done by 03:00, so start now").
|
|
|
|
|
|
A warning is logged and `appliance_deadline_missed` reports the miss.
|
|
|
|
|
|
- `STRICT`: the deadline is kept. A `ONCE` consumer without a feasible start makes the optimization
|
|
|
|
|
|
fail; a `DAILY` consumer is simply not scheduled on days without one.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
|
|
|
|
|
### Temperature Forecast
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- Unit: °C
|
|
|
|
|
|
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
|
|
|
|
|
|
- Format: Array of hourly values
|
|
|
|
|
|
- Data Source: `GET /v1/prediction/list?key=weather_temp_air`
|
|
|
|
|
|
|
|
|
|
|
|
## Output Format
|
|
|
|
|
|
|
|
|
|
|
|
### Sample Response
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"ac_charge": [0.625, 0, ..., 0.75, 0],
|
|
|
|
|
|
"dc_charge": [1, 1, ..., 1, 1],
|
|
|
|
|
|
"discharge_allowed": [0, 0, 1, ..., 0, 0],
|
2026-07-12 09:01:11 +02:00
|
|
|
|
"battery_grid_export_allowed": [0, 0, 0, ..., 1, 0],
|
2026-09-03 17:53:33 +02:00
|
|
|
|
"battery_grid_export_factor": [0.0, 0.0, 0.0, ..., 0.5, 0.0],
|
2025-02-11 22:47:23 +01:00
|
|
|
|
"eautocharge_hours_float": [0.625, 0, ..., 0.75, 0],
|
|
|
|
|
|
"result": {
|
|
|
|
|
|
"Last_Wh_pro_Stunde": [...],
|
|
|
|
|
|
"EAuto_SoC_pro_Stunde": [...],
|
|
|
|
|
|
"Einnahmen_Euro_pro_Stunde": [...],
|
|
|
|
|
|
"Gesamt_Verluste": 1514.96,
|
|
|
|
|
|
"Gesamtbilanz_Euro": 2.51,
|
|
|
|
|
|
"Gesamteinnahmen_Euro": 2.88,
|
|
|
|
|
|
"Gesamtkosten_Euro": 5.39,
|
|
|
|
|
|
"akku_soc_pro_stunde": [...]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Output Parameters
|
|
|
|
|
|
|
|
|
|
|
|
#### Battery Control
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-10-28 02:50:31 +01:00
|
|
|
|
- `ac_charge`: Grid charging schedule (0.0-1.0)
|
2025-02-11 22:47:23 +01:00
|
|
|
|
- `dc_charge`: DC charging schedule (0-1)
|
2026-07-12 09:01:11 +02:00
|
|
|
|
- `discharge_allowed`: Battery discharge permission for local self-consumption/load coverage (0 or 1)
|
|
|
|
|
|
- `battery_grid_export_allowed`: Battery discharge permission for grid export/direct marketing (0 or 1)
|
2026-09-03 17:53:33 +02:00
|
|
|
|
- `battery_grid_export_factor`: Export level per slot as factor of the rated discharge power
|
|
|
|
|
|
(`0.0` where no export is planned). Empty when direct marketing is disabled. A solution without
|
|
|
|
|
|
this array exports at full power wherever `battery_grid_export_allowed` is 1.
|
2026-09-04 10:37:48 +02:00
|
|
|
|
- `terminal_value`: What the run credited for the energy left in the battery, and the curve it was
|
|
|
|
|
|
read from:
|
|
|
|
|
|
- `mode`: `AUTO` or `FIXED`
|
|
|
|
|
|
- `battery_energy_wh`: usable AC energy left at the end of the horizon
|
|
|
|
|
|
- `credited_euro`: the credit applied to the total balance
|
|
|
|
|
|
- `curve.energy_wh` / `curve.value_euro`: breakpoints of the value curve
|
|
|
|
|
|
- `curve.marginal_euro_per_kwh`: slope of each segment, monotonically decreasing
|
2026-09-04 11:41:23 +02:00
|
|
|
|
- `curve.residual_energy_wh`: the knee - up to here the value is backed by residual load,
|
|
|
|
|
|
beyond it only by an export
|
2026-09-04 10:37:48 +02:00
|
|
|
|
- `curve.window_slots`: how many trailing horizon slots the curve was derived from
|
2026-09-04 11:23:35 +02:00
|
|
|
|
- `reason`: why that mode applied. Empty in `AUTO` mode. In `FIXED` mode it distinguishes a
|
|
|
|
|
|
configured `FIXED` from an `AUTO` run that found no priced residual load in its window - the
|
|
|
|
|
|
latter is nearly always an all-zero price forecast in the request.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
2026-07-16 12:59:02 +02:00
|
|
|
|
With direct marketing enabled, `dc_charge = 1` and `discharge_allowed = 1` may occur together. This
|
|
|
|
|
|
is the normal self-consumption mode: within a coarse optimization slot, the battery may cover
|
|
|
|
|
|
probabilistic load gaps and store PV surplus from different sub-intervals. A discharge-only state
|
|
|
|
|
|
remains available when deliberately bypassing PV charging is economically preferable.
|
|
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
0 (no charge)
|
|
|
|
|
|
1 (charge with full load)
|
|
|
|
|
|
|
2025-10-28 02:50:31 +01:00
|
|
|
|
`ac_charge` multiplied by the maximum charge power of the battery results in the planned charging
|
|
|
|
|
|
power.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
|
|
|
|
|
#### EV Charging
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-10-28 02:50:31 +01:00
|
|
|
|
- `eautocharge_hours_float`: EV charging schedule (0.0-1.0)
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
2026-09-03 17:53:33 +02:00
|
|
|
|
#### Flexible Consumers
|
|
|
|
|
|
|
|
|
|
|
|
- `appliance_starts`: Scheduled run start times per `device_id` as absolute local datetimes
|
|
|
|
|
|
- `appliance_deadline_missed`: Per `device_id` with a `deadline_datetime`, whether the scheduled run
|
|
|
|
|
|
misses that deadline (or was not scheduled at all). Consumers without a deadline are not listed.
|
|
|
|
|
|
- `result.home_appliance_energy_wh`: Per-device load curve of the scheduled runs in Wh
|
|
|
|
|
|
|
2025-02-11 22:47:23 +01:00
|
|
|
|
#### Results
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
2025-10-28 02:50:31 +01:00
|
|
|
|
The `result` object contains detailed information about the optimization outcome. The length of the
|
|
|
|
|
|
array is between 25 and 48 and starts at the current hour and ends at 23:00 tomorrow.
|
2025-02-11 22:47:23 +01:00
|
|
|
|
|
|
|
|
|
|
- `Last_Wh_pro_Stunde`: Array of hourly load values in Wh
|
|
|
|
|
|
- Shows the total energy consumption per hour
|
|
|
|
|
|
- Includes household load, battery charging/discharging, and EV charging
|
|
|
|
|
|
|
|
|
|
|
|
- `EAuto_SoC_pro_Stunde`: Array of hourly EV state of charge values (%)
|
|
|
|
|
|
- Shows the projected EV battery level throughout the optimization period
|
|
|
|
|
|
|
|
|
|
|
|
- `Einnahmen_Euro_pro_Stunde`: Array of hourly revenue values in Euro
|
|
|
|
|
|
|
|
|
|
|
|
- `Gesamt_Verluste`: Total energy losses in Wh
|
|
|
|
|
|
|
|
|
|
|
|
- `Gesamtbilanz_Euro`: Overall financial balance in Euro
|
|
|
|
|
|
|
|
|
|
|
|
- `Gesamteinnahmen_Euro`: Total revenue in Euro
|
|
|
|
|
|
|
|
|
|
|
|
- `Gesamtkosten_Euro`: Total costs in Euro
|
|
|
|
|
|
|
|
|
|
|
|
- `akku_soc_pro_stunde`: Array of hourly battery state of charge values (%)
|
|
|
|
|
|
|
|
|
|
|
|
## Timeframe overview
|
2025-02-12 14:24:17 +01:00
|
|
|
|
|
|
|
|
|
|
```{figure} ../_static/optimization_timeframes.png
|
|
|
|
|
|
:alt: Timeframe Overview
|
|
|
|
|
|
|
|
|
|
|
|
Timeframe Overview
|
|
|
|
|
|
```
|