feat(optimization): schedule any number of flexible consumers

Replace the single hourly "dishwasher" home appliance with a list of
flexible consumers (home_appliances). Each consumer defines its load
either as an explicit power profile (energy-preservingly resampled onto
the optimization slot grid, incl. 15-min and non-integer interval ratios)
or the flat consumption_wh/duration_h fallback, and runs ONCE or DAILY
within its time windows and the optimization horizon.

- ConsumerScheduleMode + shared load-definition validation (XOR of
  profile/fallback, reject negative/NaN/inf, unique device_id)
- ApplianceGeneLayout: variable appliance gene block (index into
  allowed_start_slots), ONCE/DAILY calendar-day based, no snapping
- per-device output: result.home_appliance_energy_wh, appliance_starts
  (absolute local times), per-device solution columns and DDBC RUN/OFF
  instructions on state transitions only
- deprecate dishwasher/washingstart/Home_appliance_wh_per_hour with
  backward-compatible mapping and explicit conflict rejection
- max_home_appliances is now an upper bound only; no demo appliance and
  no on/off behaviour
- docs, openapi.json, CHANGELOG and optimize_result_2* fixtures updated;
  new tests/test_homeappliance.py covers the mandatory test matrix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andreas
2026-07-15 14:19:46 +02:00
co-authored by Claude Opus 4.8
parent c59bf1b486
commit 67cf6f7d8a
21 changed files with 2505 additions and 1094 deletions
+17 -3
View File
@@ -362,7 +362,12 @@ as a cohesive unit for scheduling and availability checking.
```
<!-- pyml enable line-length -->
### Home Appliance devices base settings
### Flexible consumer (home appliance) devices base settings
A consumer's load is defined **either** by an explicit power profile
(``load_profile_power_w`` with an optional ``load_profile_interval_seconds``)
**or** by the flat fallback ``consumption_wh`` + ``duration_h``. Exactly one
of the two must be provided.
<!-- pyml disable line-length -->
:::{table} devices::home_appliances::list
@@ -371,10 +376,13 @@ as a cohesive unit for scheduling and availability checking.
| Name | Type | Read-Only | Default | Description |
| ---- | ---- | --------- | ------- | ----------- |
| consumption_wh | `int` | `rw` | `required` | Energy consumption [Wh]. |
| consumption_wh | `Optional[int]` | `rw` | `None` | Flat fallback: total energy consumption of one run [Wh]. Used only when no load_profile_power_w is given. |
| device_id | `str` | `rw` | `<unknown>` | ID of device |
| duration_h | `int` | `rw` | `required` | Usage duration in hours [0 ... 24]. |
| duration_h | `Optional[int]` | `rw` | `None` | Flat fallback: run duration in hours [0 ... 24]. Used only when no load_profile_power_w is given. |
| load_profile_interval_seconds | `Optional[int]` | `rw` | `None` | Duration of one 'load_profile_power_w' step in seconds. Defaults to the configured optimization interval when a profile is given. |
| load_profile_power_w | `Optional[list[float]]` | `rw` | `None` | Explicit load profile describing a single complete run as a sequence of non-negative power values in watts (e.g. [200.0, 2000.0, 1800.0, 100.0]). Each value covers 'load_profile_interval_seconds'. Mutually exclusive with consumption_wh/duration_h. |
| measurement_keys | `Optional[list[str]]` | `ro` | `N/A` | Measurement keys for the home appliance stati that are measurements. |
| schedule_mode | `<enum 'ConsumerScheduleMode'>` | `rw` | `ONCE` | Scheduling mode: ONCE (a single run within the horizon) or DAILY (one run per local calendar day with a feasible full run). |
| time_windows | `Optional[akkudoktoreos.config.configabc.TimeWindowSequence]` | `rw` | `None` | Sequence of allowed time windows. Defaults to optimization general time window. |
:::
<!-- pyml enable line-length -->
@@ -390,6 +398,9 @@ as a cohesive unit for scheduling and availability checking.
"home_appliances": [
{
"device_id": "battery1",
"load_profile_power_w": null,
"load_profile_interval_seconds": null,
"schedule_mode": "ONCE",
"consumption_wh": 2000,
"duration_h": 1,
"time_windows": {
@@ -421,6 +432,9 @@ as a cohesive unit for scheduling and availability checking.
"home_appliances": [
{
"device_id": "battery1",
"load_profile_power_w": null,
"load_profile_interval_seconds": null,
"schedule_mode": "ONCE",
"consumption_wh": 2000,
"duration_h": 1,
"time_windows": {
+5 -4
View File
@@ -257,13 +257,14 @@ The GENETIC algorithm supports 4 devices:
- **electric_vehicle**: An electric vehicle, basically the battery of an electric vehicle. The
The electrical vehicle is optional.
- **battery**: A battery that can be charged by the inverter. The battery is mandatory.
- **home_appliance**: A home appliance, like a washing machine or a dish washer. The home
appliance is optional.
- **home_appliance**: A flexible consumer, like a washing machine or a dish washer. Any number of
home appliances can be configured; each is scheduled independently. Home appliances are optional.
:::{admonition} Warning
:class: warning
The GENETIC algorithm can only use the first inverter, electrical vehicle, battery, home appliance
that is configured, even if more devices are configured.
The GENETIC algorithm can only use the first inverter, electrical vehicle and battery that is
configured, even if more devices are configured. Home appliances are the exception: all configured
home appliances are scheduled.
:::
#### Inverter simulation configuration
+9 -6
View File
@@ -93,12 +93,15 @@ to `DISABLED` in the configuration.
"initial_soc_percentage": 54,
"min_soc_percentage": 0
},
"dishwasher": {
"device_id": "dishwasher1",
"consumption_wh": 2000,
"duration_h": 3,
"time_windows": null
},
"home_appliances": [
{
"device_id": "dishwasher1",
"consumption_wh": 2000,
"duration_h": 3,
"schedule_mode": "ONCE",
"time_windows": null
}
],
"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,
+33 -9
View File
@@ -193,28 +193,52 @@ indicates no power transfer. Intermediate values scale the power proportionally.
## Home Appliance
The optimization algorithm supports one start of the home appliance within the optimization
horizon.
The optimization algorithm schedules any number of flexible consumers (home appliances). Each
consumer has a unique `device_id` and is scheduled independently. The `schedule_mode` selects how
often a consumer runs:
- `ONCE` — a single run somewhere within the optimization horizon ("fire and forget").
- `DAILY` — one run per local calendar day that still has a feasible complete run.
### Home Appliance Simulation
Each consumer's load is described by the energy of a single complete run, resampled onto the
optimization slot grid (hourly or 15-minute). Multiple consumers and multiple daily runs may
overlap; their energy adds up.
### Home Appliance Configuration
Home appliance to run within the optimization horizon.
A consumer's load is defined **either** by an explicit power profile or by the flat
`consumption_wh` + `duration_h` fallback (exactly one of the two).
Two consumers, one defined by the flat fallback (runs once), one by an explicit 15-minute power
profile that runs once per day:
```json
[
{
"device_id": "dishwasher1",
"consumption_wh": 2000,
"duration_h": 3
"duration_h": 3,
"schedule_mode": "ONCE"
},
{
"device_id": "washingmachine1",
"load_profile_power_w": [200, 2000, 1800, 100],
"load_profile_interval_seconds": 900,
"schedule_mode": "DAILY"
}
]
```
Home appliance to run within a time window of 5 hours starting at 8:00 every day and another time
window of 3 hours starting at 15:00 every day. See
[Time Window Sequence Configuration](configtimewindow-page) for more information.
- `load_profile_power_w`: non-negative power values in watts describing one complete run. Each value
covers `load_profile_interval_seconds` (default: the configured optimization interval). The profile
is resampled energy-preservingly onto the optimization slot grid.
- `consumption_wh` / `duration_h`: flat fallback used when no `load_profile_power_w` is given.
A consumer may be restricted to run within a time window of 5 hours starting at 8:00 every day and
another time window of 3 hours starting at 15:00 every day. The complete run must fit inside a
single window. See [Time Window Sequence Configuration](configtimewindow-page) for more information.
```json
[
@@ -240,8 +264,8 @@ window of 3 hours starting at 15:00 every day. See
:::{admonition} Note
:class: note
The optimization algorithm always restricts to one start within the optimization horizon per
energy management run.
A `ONCE` consumer without any valid start (given its time windows and the horizon) is rejected. For
`DAILY`, a calendar day without a feasible run simply gets no run for that day.
:::
### Home Appliance Instructions