feat(optimization): concave terminal value for the energy left in the battery

The energy still stored when the horizon ends keeps its worth: it replaces
grid imports that are paid for afterwards. Crediting that with a single
price per kWh cannot describe it, because the value is not linear in the
amount stored. The first kWh replaces the most expensive hour that PV
cannot cover, the next one the second most expensive, and once every such
hour is served, further energy replaces nothing.

A scalar 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 it
empty by the end of the horizon - which is exactly what the previous default
of 0 EUR/kWh did.

terminal_value_mode = AUTO (the new default) builds the curve instead. There
is no forecast beyond the horizon, so its trailing window stands in for the
day that follows: residual load max(load - PV, 0) per slot, priced at its
import price, sorted and accumulated. LCOS is subtracted from every marginal
value so stored energy is not credited twice, and the tail beyond the
residual load is only credited when direct marketing allows an export. The
curve is built once per run; the search only interpolates on it.

The solution reports what a run used as terminal_value, curve included, so
the shape can be inspected instead of guessed. FIXED restores the previous
scalar behaviour.

In a 48 h scenario with two cheap slots at the end, AUTO keeps the battery
at 50 % and credits 3.85 EUR where FIXED with 0 EUR/kWh drains it to empty.
The stored optimization results move accordingly - the objective changed.
This commit is contained in:
Andreas
2026-09-04 10:37:48 +02:00
parent 2a9543e710
commit 1c10ab83ad
16 changed files with 1834 additions and 766 deletions
+8
View File
@@ -53,6 +53,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
`ac_charge` array kept the optimizer's unused gene values. The simulation ignored them, so
they were never costed - but a controller acting on the plan would grid-charge the battery
anyway. The disabled AC charge is now cleared in the reported plan as well.
- The energy left in the battery at the end of the horizon is now valued with a concave curve
derived from the trailing horizon window (`optimization.terminal_value_mode = AUTO`, the new
default): the first stored kWh replaces the most expensive hour that PV cannot cover, the next
one the second most expensive, and energy beyond the residual load is credited only when it can
be exported. A single price per kWh could not express this - with the previous default of 0 the
optimizer emptied the battery towards the end of the horizon, with a high value it hoarded it.
The curve is built once per run and reported as `terminal_value` in the solution.
`terminal_value_mode = FIXED` restores the old scalar behaviour.
- EV Bug (wrong output in genetic.py / no senseful results)
- Direktvermarktung active / Battery discharge into grid (new state / action battery_grid_export_allowed) + (new simulation output Feed_in_tariff)
- New PV forecast providers giving operators more cloud forecast sources to choose from in
+2
View File
@@ -240,7 +240,9 @@
"interval": 3600,
"algorithm": "GENETIC",
"visualize_pdf": true,
"terminal_value_mode": "AUTO",
"terminal_value_euro_per_kwh": 0.0,
"terminal_value_window_hours": 24,
"genetic": {
"individuals": 400,
"generations": 400,
+7 -1
View File
@@ -13,7 +13,9 @@
| horizon_hours | `EOS_OPTIMIZATION__HORIZON_HOURS` | `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` | `int` | `rw` | `3600` | The optimization interval (slot length) [sec]. The genetic optimizer supports 3600 (1 hour) and 900 (15 min); other values fall back to 3600. Defaults to 3600 seconds (1 hour). |
| keys | | `list[str]` | `ro` | `N/A` | The keys of the solution. |
| terminal_value_euro_per_kwh | `EOS_OPTIMIZATION__TERMINAL_VALUE_EURO_PER_KWH` | `float` | `rw` | `0.0` | Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Defaults to 0 EUR/kWh. |
| terminal_value_euro_per_kwh | `EOS_OPTIMIZATION__TERMINAL_VALUE_EURO_PER_KWH` | `float` | `rw` | `0.0` | Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Only used with terminal_value_mode = FIXED. Defaults to 0 EUR/kWh. |
| terminal_value_mode | `EOS_OPTIMIZATION__TERMINAL_VALUE_MODE` | `<enum 'TerminalValueMode'>` | `rw` | `AUTO` | How to value the energy left in the battery at the end of the optimization horizon. AUTO derives a concave value curve from the trailing horizon window and needs no configuration; FIXED uses 'terminal_value_euro_per_kwh'. Defaults to AUTO. |
| terminal_value_window_hours | `EOS_OPTIMIZATION__TERMINAL_VALUE_WINDOW_HOURS` | `int` | `rw` | `24` | Length of the trailing horizon window the AUTO terminal value curve is derived from [h]. One day covers a full load and PV cycle. Defaults to 24 hours. |
| visualize_pdf | `EOS_OPTIMIZATION__VISUALIZE_PDF` | `bool` | `rw` | `True` | Generate the PDF visualization after each optimization run. Disable for headless setups (e.g. Node-RED integration) to save several seconds per run. Defaults to True. |
:::
<!-- pyml enable line-length -->
@@ -30,7 +32,9 @@
"interval": 3600,
"algorithm": "GENETIC",
"visualize_pdf": true,
"terminal_value_mode": "AUTO",
"terminal_value_euro_per_kwh": 0.0,
"terminal_value_window_hours": 24,
"genetic": {
"individuals": 400,
"generations": 400,
@@ -56,7 +60,9 @@
"interval": 3600,
"algorithm": "GENETIC",
"visualize_pdf": true,
"terminal_value_mode": "AUTO",
"terminal_value_euro_per_kwh": 0.0,
"terminal_value_window_hours": 24,
"genetic": {
"individuals": 400,
"generations": 400,
+1 -1
View File
@@ -1,6 +1,6 @@
# Akkudoktor-EOS
**Version**: `v0.3.0.dev2609031505836006`
**Version**: `v0.3.0.dev2609040861878062`
<!-- 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.
+44 -2
View File
@@ -274,8 +274,42 @@ 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
`Kosten_Euro_pro_Stunde`, `Gesamtkosten_Euro`, and therefore `Gesamtbilanz_Euro`. The terminal value
`preis_euro_pro_wh_akku`, by contrast, applies only to usable energy remaining after the last slot.
`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.
#### State of Charge (SoC)
@@ -469,6 +503,14 @@ be unreachable. `deadline_policy` decides what happens then:
- `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.
- `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
- `curve.window_slots`: how many trailing horizon slots the curve was derived from
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
+140 -3
View File
@@ -8,7 +8,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "v0.3.0.dev2609031505836006"
"version": "v0.3.0.dev2609040861878062"
},
"paths": {
"/v1/admin/cache/clear": {
@@ -5570,6 +5570,17 @@
"title": "Battery Grid Export Allowed",
"description": "Array with battery-to-grid export values (1 for export discharge, 0 otherwise)."
},
"terminal_value": {
"anyOf": [
{
"$ref": "#/components/schemas/TerminalValueResult"
},
{
"type": "null"
}
],
"description": "The terminal value applied to the energy left in the battery at the end of the horizon, including the curve it was read from. None when no battery is part of the optimization."
},
"battery_grid_export_factor": {
"items": {
"type": "number"
@@ -7533,16 +7544,35 @@
false
]
},
"terminal_value_mode": {
"$ref": "#/components/schemas/TerminalValueMode",
"description": "How to value the energy left in the battery at the end of the optimization horizon. AUTO derives a concave value curve from the trailing horizon window and needs no configuration; FIXED uses 'terminal_value_euro_per_kwh'. Defaults to AUTO.",
"default": "AUTO",
"examples": [
"AUTO",
"FIXED"
]
},
"terminal_value_euro_per_kwh": {
"type": "number",
"title": "Terminal Value Euro Per Kwh",
"description": "Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Defaults to 0 EUR/kWh.",
"description": "Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Only used with terminal_value_mode = FIXED. Defaults to 0 EUR/kWh.",
"default": 0.0,
"examples": [
0.0,
0.2
]
},
"terminal_value_window_hours": {
"type": "integer",
"minimum": 1.0,
"title": "Terminal Value Window Hours",
"description": "Length of the trailing horizon window the AUTO terminal value curve is derived from [h]. One day covers a full load and PV cycle. Defaults to 24 hours.",
"default": 24,
"examples": [
24
]
},
"genetic": {
"$ref": "#/components/schemas/GeneticCommonSettings",
"description": "Genetic optimization algorithm configuration.",
@@ -7603,16 +7633,35 @@
false
]
},
"terminal_value_mode": {
"$ref": "#/components/schemas/TerminalValueMode",
"description": "How to value the energy left in the battery at the end of the optimization horizon. AUTO derives a concave value curve from the trailing horizon window and needs no configuration; FIXED uses 'terminal_value_euro_per_kwh'. Defaults to AUTO.",
"default": "AUTO",
"examples": [
"AUTO",
"FIXED"
]
},
"terminal_value_euro_per_kwh": {
"type": "number",
"title": "Terminal Value Euro Per Kwh",
"description": "Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Defaults to 0 EUR/kWh.",
"description": "Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Only used with terminal_value_mode = FIXED. Defaults to 0 EUR/kWh.",
"default": 0.0,
"examples": [
0.0,
0.2
]
},
"terminal_value_window_hours": {
"type": "integer",
"minimum": 1.0,
"title": "Terminal Value Window Hours",
"description": "Length of the trailing horizon window the AUTO terminal value curve is derived from [h]. One day covers a full load and PV cycle. Defaults to 24 hours.",
"default": 24,
"examples": [
24
]
},
"genetic": {
"$ref": "#/components/schemas/GeneticCommonSettings",
"description": "Genetic optimization algorithm configuration.",
@@ -9612,6 +9661,94 @@
"title": "SolarPanelBatteryParameters",
"description": "PV battery device simulation configuration."
},
"TerminalValueCurve": {
"properties": {
"energy_wh": {
"items": {
"type": "number"
},
"type": "array",
"title": "Energy Wh",
"description": "Breakpoints of usable AC energy left in the battery [Wh]."
},
"value_euro": {
"items": {
"type": "number"
},
"type": "array",
"title": "Value Euro",
"description": "Cumulative credit at each breakpoint [EUR]."
},
"marginal_euro_per_kwh": {
"items": {
"type": "number"
},
"type": "array",
"title": "Marginal Euro Per Kwh",
"description": "Marginal value of the segment that starts at each breakpoint [EUR/kWh]. Monotonically decreasing."
},
"window_slots": {
"type": "integer",
"title": "Window Slots",
"description": "Number of trailing horizon slots the curve was derived from. Fewer slots than a full day mean a shorter proxy period.",
"default": 0
}
},
"type": "object",
"title": "TerminalValueCurve",
"description": "Piecewise linear, concave value of battery energy left at the horizon.\n\n``energy_wh`` and ``value_euro`` are the breakpoints of the cumulative\nvalue, ``marginal_euro_per_kwh`` the slope of each segment. Both arrays\nstart at the origin; the curve is flat beyond its last breakpoint."
},
"TerminalValueMode": {
"type": "string",
"enum": [
"AUTO",
"FIXED"
],
"title": "TerminalValueMode",
"description": "How the energy left in the battery at the end of the horizon is valued.\n\nModes\n-----\n- AUTO:\n Derive a concave value curve from the trailing horizon window: the\n first stored kWh replaces the most expensive hour that PV cannot\n cover, the next one the second most expensive, and so on. Needs no\n configuration and adapts to prices, load and PV of the day.\n\n- FIXED:\n Credit every stored kWh with the configured\n ``terminal_value_euro_per_kwh`` (or ``preis_euro_pro_wh_akku`` of the\n request). The historical behaviour; a value of 0 makes the optimizer\n empty the battery towards the end of the horizon."
},
"TerminalValueResult": {
"properties": {
"mode": {
"type": "string",
"title": "Mode",
"description": "Terminal value mode the run used: AUTO or FIXED.",
"examples": [
"AUTO",
"FIXED"
]
},
"battery_energy_wh": {
"type": "number",
"title": "Battery Energy Wh",
"description": "Usable AC energy left in the battery at the end of the horizon [Wh].",
"default": 0.0
},
"credited_euro": {
"type": "number",
"title": "Credited Euro",
"description": "Credit applied to the total balance [EUR].",
"default": 0.0
},
"curve": {
"anyOf": [
{
"$ref": "#/components/schemas/TerminalValueCurve"
},
{
"type": "null"
}
],
"description": "The value curve the credit was read from; None in FIXED mode."
}
},
"type": "object",
"required": [
"mode"
],
"title": "TerminalValueResult",
"description": "What the optimizer credited for the energy left in the battery."
},
"TimeWindow-Input": {
"properties": {
"start_time": {
@@ -22,6 +22,12 @@ from akkudoktoreos.optimization.genetic.geneticparams import (
GeneticEnergyManagementParameters,
GeneticOptimizationParameters,
)
from akkudoktoreos.optimization.genetic.terminalvalue import (
TerminalValueCurve,
TerminalValueResult,
build_terminal_value_curve,
trailing_window,
)
from akkudoktoreos.optimization.genetic.geneticsolution import (
GeneticSimulationResult,
GeneticSolution,
@@ -673,6 +679,9 @@ class GeneticOptimization(OptimizationBase):
# Slot by which the EV has to reach its target SoC. None means the SoC is
# only required at the end of the horizon (the behaviour without a deadline).
self._ev_soc_deadline_slot: Optional[int] = None
# Concave value of the energy left in the battery at the end of the
# horizon. None means the fixed scalar terminal value is used instead.
self._terminal_value_curve: Optional[TerminalValueCurve] = None
self.verbose = verbose
self.fix_seed = fixed_seed
self.optimize_ev = True
@@ -811,6 +820,109 @@ class GeneticOptimization(OptimizationBase):
# A deadline in the past means the target is due right now.
return max(deadline_slot, start_slot)
def _build_terminal_value_curve(
self,
battery: Optional[Battery],
inverter: Optional[Inverter],
) -> Optional[TerminalValueCurve]:
"""Derive the terminal value curve from the trailing horizon window.
Only built in AUTO mode and only with a battery: the curve describes
what the energy left in that battery is worth once the horizon ends.
Args:
battery: The house battery of this run, if any.
inverter: The inverter, needed for the DC/AC conversion.
Returns:
The curve, or None when the fixed scalar terminal value applies.
"""
if battery is None:
return None
try:
mode = self.config.optimization.terminal_value_mode
window_hours = self.config.optimization.terminal_value_window_hours
except Exception:
return None
if str(mode) != "AUTO":
return None
dc_to_ac = inverter.dc_to_ac_efficiency if inverter else 1.0
# A full battery, expressed in the same unit as the curve: AC energy
# that can actually leave the house.
max_energy_wh = (
max(battery.max_soc_wh - battery.min_soc_wh, 0.0)
* battery.discharging_efficiency
* dc_to_ac
)
window_slots = max(int(window_hours) * self.slots_per_hour, 1)
end_slot = min(
self.total_slots,
self._start_day_slot() + self.config.optimization.horizon_hours * self.slots_per_hour,
)
curve = build_terminal_value_curve(
prices_euro_per_wh=trailing_window(
self.simulation.elect_price_hourly, end_slot, window_slots
),
load_wh=trailing_window(self.simulation.load_energy_array, end_slot, window_slots),
pv_wh=trailing_window(self.simulation.pv_prediction_wh, end_slot, window_slots),
feed_in_euro_per_wh=trailing_window(
self.simulation.elect_revenue_per_hour_arr, end_slot, window_slots
),
max_energy_wh=max_energy_wh,
lcos_euro_per_kwh=getattr(battery, "levelized_cost_of_storage_kwh", 0.0),
dc_to_ac_efficiency=dc_to_ac,
grid_export_allowed=self.optimize_battery_grid_export,
)
if curve.energy_wh:
logger.debug(
"Terminal value curve: {} segments, first {:.3f} EUR/kWh, last {:.3f} EUR/kWh, "
"knee at {:.0f} Wh.",
len(curve.marginal_euro_per_kwh),
curve.marginal_euro_per_kwh[0],
curve.marginal_euro_per_kwh[-1],
curve.energy_wh[-1],
)
return curve
def _terminal_value(
self, parameters: GeneticOptimizationParameters
) -> tuple[float, TerminalValueResult]:
"""Credit for the energy left in the battery, plus its report.
Args:
parameters: Optimization parameters, holding the fixed scalar value.
Returns:
The credit in EUR and the result object for the solution.
"""
battery = self.simulation.battery
if battery is None:
return 0.0, TerminalValueResult(mode="FIXED")
# Usable DC energy, converted to the AC energy that can serve a load.
energy_wh = battery.current_energy_content()
if self.simulation.inverter:
energy_wh *= self.simulation.inverter.dc_to_ac_efficiency
curve = getattr(self, "_terminal_value_curve", None)
if curve is not None and curve.energy_wh:
credit = curve.value(energy_wh)
return credit, TerminalValueResult(
mode="AUTO",
battery_energy_wh=energy_wh,
credited_euro=credit,
curve=curve,
)
credit = energy_wh * parameters.ems.preis_euro_pro_wh_akku
return credit, TerminalValueResult(
mode="FIXED",
battery_energy_wh=energy_wh,
credited_euro=credit,
)
def _build_appliance_layout(
self, appliances: list[HomeAppliance], slot0_datetime: Any
) -> ApplianceGeneLayout:
@@ -2433,14 +2545,12 @@ class GeneticOptimization(OptimizationBase):
else 0,
)
# Adjust total balance with battery value and penalties for unmet SOC
# Adjust total balance with battery value and penalties for unmet SOC.
# The terminal value is concave in AUTO mode: the first stored kWh
# replaces the most expensive hour after the horizon, the last one
# replaces nothing. A scalar cannot express that (see terminalvalue.py).
if self.simulation.battery:
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
restwert_akku, _ = self._terminal_value(parameters)
gesamtbilanz += -restwert_akku
# --- AC charging break-even penalty ---
@@ -2958,6 +3068,11 @@ class GeneticOptimization(OptimizationBase):
direct_marketing_enabled=direct_marketing_enabled,
)
# Terminal value of the energy left in the battery. Built once per run -
# it needs the prepared price/load/PV series - so every fitness
# evaluation only interpolates on it.
self._terminal_value_curve = self._build_terminal_value_curve(akku, inverter)
# Setup the DEAP environment and optimization process. The appliance
# genome layout (built above) drives the appliance gene block; evaluate
# gets the slot index (its break-even loop walks the slot arrays from "now").
@@ -2978,6 +3093,8 @@ class GeneticOptimization(OptimizationBase):
# Perform final evaluation on the best solution
simulation_result = self.evaluate_inner(start_solution)
# Read the terminal value off the final battery state, for the solution.
_, terminal_value_result = self._terminal_value(parameters)
# Prepare results
discharge_hours_bin, eautocharge_hours_index, appliance_gene_values = self.split_individual(
@@ -3086,6 +3203,7 @@ class GeneticOptimization(OptimizationBase):
"discharge_allowed": discharge,
"battery_grid_export_allowed": battery_grid_export,
"battery_grid_export_factor": battery_grid_export_factor,
"terminal_value": terminal_value_result,
"eautocharge_hours_float": eautocharge_hours_float,
"result": GeneticSimulationResult(**simulation_result),
"eauto_obj": self.simulation.ev,
@@ -24,6 +24,7 @@ from akkudoktoreos.devices.devicesabc import (
)
from akkudoktoreos.devices.genetic.battery import Battery
from akkudoktoreos.optimization.genetic.geneticdevices import GeneticParametersBaseModel
from akkudoktoreos.optimization.genetic.terminalvalue import TerminalValueResult
from akkudoktoreos.optimization.optimization import OptimizationSolution
from akkudoktoreos.utils.datetimeutil import DateTime, to_datetime, to_duration
from akkudoktoreos.utils.utils import NumpyEncoder
@@ -201,6 +202,16 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
"description": "Array with battery-to-grid export values (1 for export discharge, 0 otherwise)."
},
)
terminal_value: Optional[TerminalValueResult] = Field(
default=None,
json_schema_extra={
"description": (
"The terminal value applied to the energy left in the battery at "
"the end of the horizon, including the curve it was read from. "
"None when no battery is part of the optimization."
)
},
)
battery_grid_export_factor: list[float] = Field(
default_factory=list,
json_schema_extra={
@@ -236,8 +247,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
default_factory=dict,
json_schema_extra={
"description": (
"Scheduled run start times per appliance device_id as absolute "
"local datetimes."
"Scheduled run start times per appliance device_id as absolute " "local datetimes."
)
},
)
@@ -0,0 +1,239 @@
"""Terminal value of the energy left in the battery at the end of the horizon.
The optimizer stops at the horizon, but the energy still stored in the battery
keeps its worth: it replaces grid imports that would otherwise be paid for
afterwards. Crediting that worth with a single price per kWh - the historical
``preis_euro_pro_wh_akku`` - cannot describe it, because the value of stored
energy is **not linear in the amount stored**:
- The first kWh replaces the most expensive hour after the horizon.
- The next one replaces the second most expensive hour, and so on.
- Once every hour that PV cannot cover is served, further energy replaces
nothing; it is worth an export at best, and nothing at worst.
The resulting value function is monotone and concave. A scalar has to pick one
slope: high enough for the first kWh means hoarding a full battery, low enough
for the last kWh means running it empty by midnight. This module builds the
curve instead.
There is no forecast beyond the horizon, so the trailing window of the horizon
itself stands in for the day that follows: same season, same household rhythm,
same tariff structure. That approximation is the reason the curve is a planning
aid, not a prediction - which is also why the marginal values are deliberately
conservative wherever a choice exists.
"""
from typing import Optional
import numpy as np
from loguru import logger
from akkudoktoreos.core.pydantic import PydanticBaseModel
from pydantic import Field
class TerminalValueCurve(PydanticBaseModel):
"""Piecewise linear, concave value of battery energy left at the horizon.
``energy_wh`` and ``value_euro`` are the breakpoints of the cumulative
value, ``marginal_euro_per_kwh`` the slope of each segment. Both arrays
start at the origin; the curve is flat beyond its last breakpoint.
"""
energy_wh: list[float] = Field(
default_factory=list,
json_schema_extra={
"description": "Breakpoints of usable AC energy left in the battery [Wh]."
},
)
value_euro: list[float] = Field(
default_factory=list,
json_schema_extra={"description": "Cumulative credit at each breakpoint [EUR]."},
)
marginal_euro_per_kwh: list[float] = Field(
default_factory=list,
json_schema_extra={
"description": (
"Marginal value of the segment that starts at each breakpoint "
"[EUR/kWh]. Monotonically decreasing."
)
},
)
window_slots: int = Field(
default=0,
json_schema_extra={
"description": (
"Number of trailing horizon slots the curve was derived from. "
"Fewer slots than a full day mean a shorter proxy period."
)
},
)
def value(self, energy_wh: float) -> float:
"""Return the credit for ``energy_wh`` of usable AC energy [EUR].
Args:
energy_wh: Usable AC energy left in the battery.
Returns:
Interpolated value of the curve; 0.0 for an empty curve.
"""
if not self.energy_wh or energy_wh <= 0.0:
return 0.0
return float(np.interp(energy_wh, self.energy_wh, self.value_euro))
class TerminalValueResult(PydanticBaseModel):
"""What the optimizer credited for the energy left in the battery."""
mode: str = Field(
json_schema_extra={
"description": "Terminal value mode the run used: AUTO or FIXED.",
"examples": ["AUTO", "FIXED"],
}
)
battery_energy_wh: float = Field(
default=0.0,
json_schema_extra={
"description": "Usable AC energy left in the battery at the end of the horizon [Wh]."
},
)
credited_euro: float = Field(
default=0.0,
json_schema_extra={"description": "Credit applied to the total balance [EUR]."},
)
curve: Optional[TerminalValueCurve] = Field(
default=None,
json_schema_extra={
"description": "The value curve the credit was read from; None in FIXED mode."
},
)
def build_terminal_value_curve(
*,
prices_euro_per_wh: np.ndarray,
load_wh: np.ndarray,
pv_wh: np.ndarray,
feed_in_euro_per_wh: np.ndarray,
max_energy_wh: float,
lcos_euro_per_kwh: float = 0.0,
dc_to_ac_efficiency: float = 1.0,
grid_export_allowed: bool = False,
) -> TerminalValueCurve:
"""Build the terminal value curve from the trailing horizon window.
Every slot of the window contributes its residual load - the part of the
load that PV does not cover - at its import price. Sorting those slots by
price and accumulating them yields the marginal value of the first, second,
... kWh in the battery. Energy beyond the residual load can only be
exported, and only when direct marketing allows it.
Args:
prices_euro_per_wh: Import prices of the window [EUR/Wh].
load_wh: Load per slot of the window [Wh].
pv_wh: PV generation per slot of the window [Wh].
feed_in_euro_per_wh: Feed-in tariff of the window [EUR/Wh].
max_energy_wh: Usable AC energy of a full battery [Wh]; the curve ends here.
lcos_euro_per_kwh: Levelized cost of storage, already charged per
delivered DC energy in the simulation and therefore subtracted here
so stored energy is not credited twice.
dc_to_ac_efficiency: Inverter efficiency, used to convert the LCOS from
delivered DC energy to the AC energy of the curve.
grid_export_allowed: Whether the battery may feed the grid (direct
marketing). Without it, energy beyond the residual load gets no
credit: it can neither be exported nor is its use covered by the
proxy window.
Returns:
The curve; empty when the window carries no usable information.
"""
window = min(len(prices_euro_per_wh), len(load_wh), len(pv_wh))
if window <= 0 or max_energy_wh <= 0.0:
return TerminalValueCurve()
residual = np.maximum(load_wh[:window] - pv_wh[:window], 0.0)
prices = np.asarray(prices_euro_per_wh[:window], dtype=float)
# LCOS is charged on delivered DC energy; the curve is in AC energy.
lcos_per_wh_ac = (lcos_euro_per_kwh / 1000.0) / max(dc_to_ac_efficiency, 1e-9)
order = np.argsort(-prices)
energy_points: list[float] = [0.0]
value_points: list[float] = [0.0]
marginals: list[float] = []
cumulative_energy = 0.0
cumulative_value = 0.0
for index in order:
slot_energy = float(residual[index])
if slot_energy <= 0.0:
continue
# Negative or very cheap hours are not worth storing energy for.
marginal = max(float(prices[index]) - lcos_per_wh_ac, 0.0)
if marginal <= 0.0:
continue
slot_energy = min(slot_energy, max_energy_wh - cumulative_energy)
if slot_energy <= 0.0:
break
cumulative_energy += slot_energy
cumulative_value += slot_energy * marginal
energy_points.append(cumulative_energy)
value_points.append(cumulative_value)
marginals.append(marginal * 1000.0)
# Everything beyond the residual load can only be sold. A median feed-in
# tariff rather than the best one: exporting all of it in the single best
# slot is not something the horizon can promise.
if grid_export_allowed and cumulative_energy < max_energy_wh:
positive_feed_in = [
float(value) for value in feed_in_euro_per_wh[:window] if float(value) > 0.0
]
export_marginal = max(
(float(np.median(positive_feed_in)) if positive_feed_in else 0.0) - lcos_per_wh_ac,
0.0,
)
if export_marginal > 0.0:
remaining = max_energy_wh - cumulative_energy
cumulative_energy += remaining
cumulative_value += remaining * export_marginal
energy_points.append(cumulative_energy)
value_points.append(cumulative_value)
marginals.append(export_marginal * 1000.0)
if len(energy_points) <= 1:
logger.debug("Terminal value curve is empty - no priced residual load in the window.")
return TerminalValueCurve(window_slots=window)
# The segment slopes are decreasing by construction (prices were sorted),
# so the curve is concave; the export tail is the flattest segment.
return TerminalValueCurve(
energy_wh=energy_points,
value_euro=value_points,
marginal_euro_per_kwh=marginals,
window_slots=window,
)
def trailing_window(
values: Optional[np.ndarray],
end_slot: int,
window_slots: int,
) -> np.ndarray:
"""Return the ``window_slots`` values in front of ``end_slot``.
Args:
values: Full slot array, or None.
end_slot: Exclusive end of the window (end of the optimization horizon).
window_slots: Desired window length; a shorter horizon yields less.
Returns:
The window as a float array, empty when no data is available.
"""
if values is None:
return np.zeros(0, dtype=float)
end = min(int(end_slot), len(values))
start = max(end - int(window_slots), 0)
if end <= start:
return np.zeros(0, dtype=float)
return np.asarray(values[start:end], dtype=float)
+51 -1
View File
@@ -1,3 +1,4 @@
from enum import StrEnum
from typing import Optional, Union
from pydantic import Field, computed_field
@@ -11,6 +12,28 @@ from akkudoktoreos.core.pydantic import (
from akkudoktoreos.utils.datetimeutil import DateTime
class TerminalValueMode(StrEnum):
"""How the energy left in the battery at the end of the horizon is valued.
Modes
-----
- AUTO:
Derive a concave value curve from the trailing horizon window: the
first stored kWh replaces the most expensive hour that PV cannot
cover, the next one the second most expensive, and so on. Needs no
configuration and adapts to prices, load and PV of the day.
- FIXED:
Credit every stored kWh with the configured
``terminal_value_euro_per_kwh`` (or ``preis_euro_pro_wh_akku`` of the
request). The historical behaviour; a value of 0 makes the optimizer
empty the battery towards the end of the horizon.
"""
AUTO = "AUTO"
FIXED = "FIXED"
class GeneticCommonSettings(SettingsBaseModel):
"""General Genetic Optimization Algorithm Configuration."""
@@ -101,18 +124,45 @@ class OptimizationCommonSettings(SettingsBaseModel):
},
)
terminal_value_mode: TerminalValueMode = Field(
default=TerminalValueMode.AUTO,
json_schema_extra={
"description": (
"How to value the energy left in the battery at the end of the "
"optimization horizon. AUTO derives a concave value curve from "
"the trailing horizon window and needs no configuration; FIXED "
"uses 'terminal_value_euro_per_kwh'. Defaults to AUTO."
),
"examples": ["AUTO", "FIXED"],
},
)
terminal_value_euro_per_kwh: float = Field(
default=0.0,
json_schema_extra={
"description": (
"Value assigned to usable battery energy remaining at the end of the "
"optimization horizon [EUR/kWh]. This terminal value is independent "
"of the battery LCOS. Defaults to 0 EUR/kWh."
"of the battery LCOS. Only used with terminal_value_mode = FIXED. "
"Defaults to 0 EUR/kWh."
),
"examples": [0.0, 0.20],
},
)
terminal_value_window_hours: int = Field(
default=24,
ge=1,
json_schema_extra={
"description": (
"Length of the trailing horizon window the AUTO terminal value "
"curve is derived from [h]. One day covers a full load and PV "
"cycle. Defaults to 24 hours."
),
"examples": [24],
},
)
genetic: GeneticCommonSettings = Field(
default_factory=GeneticCommonSettings,
json_schema_extra={
+90
View File
@@ -379,3 +379,93 @@ def test_ev_deadline_charges_before_departure(config_eos: ConfigEOS):
# Slot 6 is the first slot at or after the deadline, so its start-of-slot SoC
# is what the target is checked against.
assert soc_per_hour[6] >= 60.0
def _terminal_value_run(config_eos: ConfigEOS, mode: str) -> GeneticSolution:
"""48 h with expensive energy and two dirt-cheap slots at the very end.
Charging in those last slots only pays off when the stored energy keeps a
value beyond the horizon.
"""
hours = 48
config_eos.merge_settings_from_dict(
{
"prediction": {"hours": hours},
"optimization": {
"horizon_hours": hours,
"interval": 3600,
"terminal_value_mode": mode,
"terminal_value_euro_per_kwh": 0.0,
"genetic": {"individuals": 80, "generations": 20},
},
}
)
ems_eos.set_start_datetime(to_datetime().set(hour=0, minute=0))
CacheEnergyManagementStore().clear()
prices = [0.0004] * (hours - 2) + [0.00002] * 2
parameters = GeneticOptimizationParameters(
ems={
"pv_prognose_wh": [0.0] * hours,
"strompreis_euro_pro_wh": prices,
"einspeiseverguetung_euro_pro_wh": [0.00007] * hours,
"preis_euro_pro_wh_akku": 0.0,
"gesamtlast": [200.0] * hours,
},
pv_akku={
"device_id": "battery1",
"capacity_wh": 10000,
"initial_soc_percentage": 20,
"min_soc_percentage": 0,
"max_soc_percentage": 100,
"charging_efficiency": 1.0,
"discharging_efficiency": 1.0,
"max_charge_power_w": 5000,
},
inverter={
"device_id": "inverter1",
"max_power_wh": 10000,
"battery_id": "battery1",
"ac_to_dc_efficiency": 1.0,
"dc_to_ac_efficiency": 1.0,
"max_ac_charge_power_w": 5000,
},
eauto=None,
)
return GeneticOptimization(fixed_seed=7).optimierung_ems(
parameters=parameters, start_hour=0, ngen=20
)
def test_terminal_value_auto_keeps_energy_that_fixed_zero_throws_away(config_eos: ConfigEOS):
"""AUTO values the energy left in the battery, a fixed zero does not."""
auto = _terminal_value_run(config_eos, "AUTO")
fixed = _terminal_value_run(config_eos, "FIXED")
assert auto.terminal_value is not None
assert auto.terminal_value.mode == "AUTO"
assert auto.terminal_value.curve is not None
assert auto.terminal_value.credited_euro > 0.0
assert fixed.terminal_value is not None
assert fixed.terminal_value.mode == "FIXED"
assert fixed.terminal_value.credited_euro == 0.0
# The cheap slots at the end are only worth using with a terminal value.
assert auto.result.akku_soc_pro_stunde[-1] > fixed.result.akku_soc_pro_stunde[-1]
def test_terminal_value_curve_is_concave_and_reported(config_eos: ConfigEOS):
"""The reported curve is what the credit was read from."""
solution = _terminal_value_run(config_eos, "AUTO")
curve = solution.terminal_value.curve
assert curve.window_slots == 24
assert len(curve.energy_wh) == len(curve.value_euro)
assert len(curve.marginal_euro_per_kwh) == len(curve.energy_wh) - 1
marginals = curve.marginal_euro_per_kwh
assert all(a >= b for a, b in zip(marginals, marginals[1:]))
# The credit is the curve evaluated at the energy left in the battery.
expected = curve.value(solution.terminal_value.battery_energy_wh)
assert solution.terminal_value.credited_euro == pytest.approx(expected)
+114
View File
@@ -0,0 +1,114 @@
"""Tests for the concave terminal value of the energy left in the battery."""
import numpy as np
import pytest
from akkudoktoreos.optimization.genetic.terminalvalue import (
build_terminal_value_curve,
trailing_window,
)
def _curve(**overrides):
"""Two expensive slots, one cheap one, no PV, 10 kWh of usable battery."""
params = dict(
prices_euro_per_wh=np.array([0.0004, 0.0003, 0.0001]),
load_wh=np.array([1000.0, 1000.0, 1000.0]),
pv_wh=np.array([0.0, 0.0, 0.0]),
feed_in_euro_per_wh=np.array([0.00008, 0.00008, 0.00008]),
max_energy_wh=10000.0,
lcos_euro_per_kwh=0.0,
dc_to_ac_efficiency=1.0,
grid_export_allowed=False,
)
params.update(overrides)
return build_terminal_value_curve(**params)
def test_marginal_value_follows_the_most_expensive_hours_first():
"""The first stored kWh replaces the most expensive slot, then the next."""
curve = _curve()
# 0.40, 0.30 and 0.10 EUR/kWh, in that order.
assert curve.marginal_euro_per_kwh == pytest.approx([0.4, 0.3, 0.1])
assert curve.energy_wh == pytest.approx([0.0, 1000.0, 2000.0, 3000.0])
assert curve.value_euro == pytest.approx([0.0, 0.4, 0.7, 0.8])
def test_curve_is_concave_and_saturates():
"""Marginal values only decrease, and beyond the last breakpoint nothing is added."""
curve = _curve()
marginals = curve.marginal_euro_per_kwh
assert all(a >= b for a, b in zip(marginals, marginals[1:]))
# The residual load of the window is 3 kWh - more energy replaces nothing.
assert curve.value(3000.0) == pytest.approx(0.8)
assert curve.value(9000.0) == pytest.approx(0.8)
def test_value_interpolates_within_a_segment():
"""Half of the first slot is worth half of the first segment."""
curve = _curve()
assert curve.value(500.0) == pytest.approx(0.2)
def test_pv_reduces_the_residual_load():
"""Only load that PV cannot cover can be replaced by stored energy."""
curve = _curve(pv_wh=np.array([600.0, 1000.0, 0.0]))
# Slot 0 keeps 400 Wh, slot 1 is fully covered by PV, slot 2 keeps 1000 Wh.
assert curve.energy_wh == pytest.approx([0.0, 400.0, 1400.0])
assert curve.marginal_euro_per_kwh == pytest.approx([0.4, 0.1])
def test_lcos_is_subtracted_from_the_marginal_value():
"""Storage cost is already charged on discharge and must not be credited twice."""
curve = _curve(lcos_euro_per_kwh=0.05, dc_to_ac_efficiency=1.0)
assert curve.marginal_euro_per_kwh == pytest.approx([0.35, 0.25, 0.05])
def test_negative_prices_do_not_create_value():
"""Storing energy for an hour that pays nothing is not worth anything."""
curve = _curve(prices_euro_per_wh=np.array([0.0004, -0.0001, 0.0]))
assert curve.marginal_euro_per_kwh == pytest.approx([0.4])
assert curve.value(5000.0) == pytest.approx(0.4)
def test_export_tail_only_with_direct_marketing():
"""Surplus beyond the residual load is worth an export - if export is allowed."""
without = _curve(grid_export_allowed=False)
with_export = _curve(grid_export_allowed=True)
assert without.value(10000.0) == pytest.approx(0.8)
# 7 kWh beyond the residual load at the median feed-in tariff of 0.08 EUR/kWh.
assert with_export.value(10000.0) == pytest.approx(0.8 + 7.0 * 0.08)
assert with_export.marginal_euro_per_kwh[-1] == pytest.approx(0.08)
def test_curve_is_capped_by_the_usable_battery_energy():
"""A battery smaller than the residual load ends the curve early."""
curve = _curve(max_energy_wh=1500.0)
assert curve.energy_wh[-1] == pytest.approx(1500.0)
assert curve.value(5000.0) == pytest.approx(0.4 + 0.5 * 0.3)
def test_empty_window_yields_an_empty_curve():
"""Without data there is no curve, and no credit."""
curve = build_terminal_value_curve(
prices_euro_per_wh=np.zeros(0),
load_wh=np.zeros(0),
pv_wh=np.zeros(0),
feed_in_euro_per_wh=np.zeros(0),
max_energy_wh=10000.0,
)
assert curve.energy_wh == []
assert curve.value(5000.0) == 0.0
def test_trailing_window_takes_the_end_of_the_horizon():
values = np.arange(10, dtype=float)
assert list(trailing_window(values, end_slot=8, window_slots=3)) == [5.0, 6.0, 7.0]
# A window longer than the horizon yields what there is.
assert list(trailing_window(values, end_slot=2, window_slots=5)) == [0.0, 1.0]
assert list(trailing_window(None, end_slot=8, window_slots=3)) == []
+161 -98
View File
@@ -116,13 +116,14 @@
0,
0,
0,
0,
0,
0,
1,
1,
1,
1,
1,
1,
1,
0,
0,
1,
1,
1,
@@ -133,9 +134,7 @@
1,
1,
0,
0,
0,
0,
1,
0,
0,
0,
@@ -144,12 +143,75 @@
0,
0,
1,
1,
1,
1,
0,
1,
1,
1
0
],
"battery_grid_export_allowed": [],
"terminal_value": {
"mode": "AUTO",
"battery_energy_wh": 17857.05093527103,
"credited_euro": 2.9274511210000003,
"curve": {
"energy_wh": [
0.0,
516.37,
1005.26,
1873.31,
2673.16,
3180.0699999999997,
3736.3799999999997,
4430.719999999999,
5039.509999999999,
5297.15,
5863.84,
6670.2300000000005,
7657.240000000001,
8391.230000000001,
8984.2,
9335.85
],
"value_euro": [
0.0,
0.174739608,
0.337735534,
0.625754524,
0.889864994,
1.056638384,
1.239608743,
1.4676299990000001,
1.667495756,
1.749811736,
1.924408925,
2.169712763,
2.462756032,
2.6771545110000003,
2.8420001710000005,
2.9274511210000003
],
"marginal_euro_per_kwh": [
0.3384,
0.33340000000000003,
0.3318,
0.3302,
0.32899999999999996,
0.3289,
0.3284,
0.32830000000000004,
0.3195,
0.3081,
0.3042,
0.2969,
0.2921,
0.27799999999999997,
0.243
],
"window_slots": 24
}
},
"battery_grid_export_factor": [],
"eautocharge_hours_float": null,
"result": {
"Last_Wh_pro_Stunde": [
@@ -239,9 +301,9 @@
0.0,
0.022582049506752234,
0.3039575,
0.1926250109597104,
0.13346423958241627,
0.053398267180554584,
0.19320652266312205,
0.1358062627100041,
0.0692592282596561,
0.0,
0.0,
0.0,
@@ -262,20 +324,20 @@
0.0,
0.0,
0.0,
0.024351216461056053,
0.065955694066579,
0.15236390731688437,
0.10316291465819699,
0.05435777788576338,
0.029150936607659956,
0.0,
0.0,
0.0,
0.0,
0.0
],
"Gesamt_Verluste": 3807.1176630027076,
"Gesamtbilanz_Euro": 0.22702041221600888,
"Gesamteinnahmen_Euro": 1.0402628835513343,
"Gesamtkosten_Euro": 1.2672832957673432,
"Gesamt_Verluste": 3596.923518513394,
"Gesamtbilanz_Euro": 0.4923980569215065,
"Gesamteinnahmen_Euro": 1.0754450157888547,
"Gesamtkosten_Euro": 1.5678430727103612,
"Home_appliance_wh_per_hour": [
0.0,
0.0,
@@ -324,6 +386,14 @@
0.07557452231671152,
0.0,
4.55656845588237e-17,
0.001414013162203277,
0.005881449073870462,
0.05258762370598476,
0.0,
0.0,
0.0,
0.26650619799999997,
0.19588158,
0.0,
0.0,
0.0,
@@ -333,16 +403,8 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.25364873699864443,
0.1306329312971816,
0.07362195915902499,
0.0,
0.060401289430882174,
0.009619897970888898,
0.07029121023060134,
@@ -350,12 +412,12 @@
2.3325608707865465e-05,
0.0021886029750169123,
0.013012984677295973,
0.08357424731947552,
0.0,
0.19011028252189552,
0.0,
0.0,
0.0
0.0,
0.0,
0.214398479,
0.16484566
],
"Netzbezug_Wh_pro_Stunde": [
439.1848126015972,
@@ -364,6 +426,14 @@
402.20607938643707,
0.0,
2.2737367544323206e-13,
6.433180901743754,
25.909467285772962,
175.4675465665157,
0.0,
0.0,
0.0,
912.38,
704.61,
0.0,
0.0,
0.0,
@@ -373,16 +443,8 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
833.8222781020527,
537.58407941227,
322.9033296448464,
0.0,
273.0618871197205,
45.96224544141853,
374.088399311343,
@@ -390,12 +452,12 @@
0.11639525303326081,
9.957247384062384,
57.32592368852852,
278.85968408233407,
0.0,
617.0408390843736,
0.0,
0.0,
0.0
0.0,
0.0,
733.99,
592.97
],
"Netzeinspeisung_Wh_pro_Stunde": [
0.0,
@@ -404,9 +466,9 @@
0.0,
322.60070723931767,
4342.25,
2751.785870853006,
1906.6319940345184,
762.832388293637,
2760.093180901744,
1940.089467285773,
989.4175465665157,
0.0,
0.0,
0.0,
@@ -427,10 +489,10 @@
0.0,
0.0,
0.0,
347.87452087222937,
942.2242009511288,
2176.6272473840627,
1473.7559236885286,
776.5396840823341,
416.4419515379994,
0.0,
0.0,
0.0,
@@ -444,14 +506,14 @@
51.82392952637247,
543.9059151312817,
0.0,
1.8741291469953731,
7.548005965483245,
51.11761170636123,
0.0,
0.0,
0.0,
108.98319763338179,
106.80230977350088,
133.7321802766326,
124.41545454545451,
96.08318181818186,
0.0,
0.0,
70.41409090909087,
118.37045454545455,
94.68272727272722,
@@ -460,22 +522,22 @@
66.66681818181814,
69.12409090909085,
109.07302361034766,
3.2918733722463145,
116.99491129525349,
22.312089529472388,
54.18759955738153,
98.21987178167876,
86.6234264543665,
165.15986945297027,
65.92300791736113,
538.2984000000001,
400.1930249256966,
0.0,
328.87106331622863,
0.0,
0.0,
81.2380484620021,
111.78035844493081,
6.04210069012484,
90.18403329253945,
134.59227272727276,
100.08954545454549,
80.85954545454547
0.0,
0.0
],
"akku_soc_pro_stunde": [
80.0,
@@ -491,31 +553,31 @@
98.93741213017658,
95.76274429012544,
91.54148603150185,
87.61423052185997,
84.5813018028517,
82.35863479183242,
78.62219367612994,
75.6334712243393,
73.01299085243846,
70.61840579734205,
68.5140239102897,
66.33207659624011,
62.88918635529183,
62.98062728229866,
63.600407547006235,
65.10561864582239,
67.51182493622146,
72.09959908769285,
73.93079375206398,
88.88352708539732,
91.54148603150185,
91.54148603150185,
89.31881902048255,
85.58237790478007,
82.59365545298944,
79.97317508108861,
77.57859002599218,
75.47420813893983,
73.29226082489025,
69.84937058394196,
66.35170046539932,
66.97148073010689,
67.08678424608539,
69.49299053648446,
74.08076468795585,
75.91195935232699,
90.86469268566032,
100.0,
100.0,
100.0,
100.0,
98.60068046043587,
98.76851659071711,
94.52002313341684,
91.360630915786
96.11252124341868,
91.86402778611841,
91.86402778611841
],
"Electricity_price": [
0.000228,
@@ -709,12 +771,15 @@
"initial_soc_percentage": 54
},
"start_solution": [
2.0,
2.0,
1.0,
1.0,
0.0,
2.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -728,10 +793,8 @@
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
0.0,
0.0,
1.0,
1.0,
1.0,
@@ -742,9 +805,7 @@
1.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
@@ -753,11 +814,13 @@
0.0,
0.0,
1.0,
1.0,
1.0,
1.0,
0.0,
1.0,
1.0,
1.0
0.0
],
"washingstart": null,
"appliance_starts": {}
"appliance_starts": {},
"appliance_deadline_missed": {}
}
+64 -1
View File
@@ -150,6 +150,68 @@
0
],
"battery_grid_export_allowed": [],
"terminal_value": {
"mode": "AUTO",
"battery_energy_wh": 17857.05093527103,
"credited_euro": 2.9274511210000003,
"curve": {
"energy_wh": [
0.0,
516.37,
1005.26,
1873.31,
2673.16,
3180.0699999999997,
3736.3799999999997,
4430.719999999999,
5039.509999999999,
5297.15,
5863.84,
6670.2300000000005,
7657.240000000001,
8391.230000000001,
8984.2,
9335.85
],
"value_euro": [
0.0,
0.174739608,
0.337735534,
0.625754524,
0.889864994,
1.056638384,
1.239608743,
1.4676299990000001,
1.667495756,
1.749811736,
1.924408925,
2.169712763,
2.462756032,
2.6771545110000003,
2.8420001710000005,
2.9274511210000003
],
"marginal_euro_per_kwh": [
0.3384,
0.33340000000000003,
0.3318,
0.3302,
0.32899999999999996,
0.3289,
0.3284,
0.32830000000000004,
0.3195,
0.3081,
0.3042,
0.2969,
0.2921,
0.27799999999999997,
0.243
],
"window_slots": 24
}
},
"battery_grid_export_factor": [],
"eautocharge_hours_float": null,
"result": {
"Last_Wh_pro_Stunde": [
@@ -759,5 +821,6 @@
0.0
],
"washingstart": null,
"appliance_starts": {}
"appliance_starts": {},
"appliance_deadline_missed": {}
}
+392 -329
View File
@@ -11,8 +11,6 @@
0.0,
0.0,
0.0,
1.0,
1.0,
0.0,
0.0,
0.0,
@@ -23,7 +21,9 @@
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -113,15 +113,14 @@
0,
0,
0,
1,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
1,
1,
0,
0,
@@ -133,16 +132,17 @@
1,
1,
1,
0,
0,
1,
1,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
1,
@@ -150,6 +150,68 @@
0
],
"battery_grid_export_allowed": [],
"terminal_value": {
"mode": "AUTO",
"battery_energy_wh": 10043.348787942483,
"credited_euro": 2.9274511210000003,
"curve": {
"energy_wh": [
0.0,
516.37,
1005.26,
1873.31,
2673.16,
3180.0699999999997,
3736.3799999999997,
4430.719999999999,
5039.509999999999,
5297.15,
5863.84,
6670.2300000000005,
7657.240000000001,
8391.230000000001,
8984.2,
9335.85
],
"value_euro": [
0.0,
0.174739608,
0.337735534,
0.625754524,
0.889864994,
1.056638384,
1.239608743,
1.4676299990000001,
1.667495756,
1.749811736,
1.924408925,
2.169712763,
2.462756032,
2.6771545110000003,
2.8420001710000005,
2.9274511210000003
],
"marginal_euro_per_kwh": [
0.3384,
0.33340000000000003,
0.3318,
0.3302,
0.32899999999999996,
0.3289,
0.3284,
0.32830000000000004,
0.3195,
0.3081,
0.3042,
0.2969,
0.2921,
0.27799999999999997,
0.243
],
"window_slots": 24
}
},
"battery_grid_export_factor": [],
"eautocharge_hours_float": [
0.0,
0.0,
@@ -162,123 +224,123 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.625,
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.75,
0.0,
0.0,
0.625,
0.0,
0.0,
0.0,
0.5,
0.875,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.75,
0.0,
0.0,
0.375,
0.875,
0.625,
0.5,
0.375,
0.875,
0.1,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"result": {
"Last_Wh_pro_Stunde": [
1053.07,
5677.336751616955,
10723.91,
1320.56,
8032.03,
1132.03,
1163.67,
9456.82,
1176.82,
1216.22,
1103.78,
1129.12,
1178.71,
3550.98,
3488.56,
1050.98,
988.56,
912.38,
2704.61,
704.61,
516.37,
868.05,
694.34,
608.79,
556.31,
8768.89,
488.89,
506.91,
804.89,
8041.98,
1141.98,
1056.97,
992.46,
1155.99,
6347.01,
10917.98,
9512.67,
11607.01,
3757.98,
1232.67,
5011.26,
1964.88,
1158.03,
1222.72,
1221.04,
949.99,
987.01,
10520.88,
8058.03,
6742.72,
5361.04,
10609.99,
2091.01,
733.99,
592.97
],
"EAuto_SoC_pro_Stunde": [
5.0,
5.0,
5.0,
5.0,
15.925,
15.925,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
29.035,
42.144999999999996,
42.144999999999996,
42.144999999999996,
53.06999999999999,
53.06999999999999,
53.06999999999999,
53.06999999999999,
61.809999999999995,
77.105,
90.215,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
20.294999999999998,
33.405,
33.405,
33.405,
39.96,
55.254999999999995,
66.18,
74.92,
81.475,
96.77,
98.518,
98.518,
98.518,
98.518,
98.518,
98.518,
98.518
],
"Einnahmen_Euro_pro_Stunde": [
@@ -287,10 +349,10 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.29947942211993916,
0.19320652266312205,
0.1358062627100041,
0.0692592282596561,
0.0,
0.0,
0.0,
@@ -321,11 +383,27 @@
0.0,
0.0
],
"Gesamt_Verluste": 10997.311193085054,
"Gesamtbilanz_Euro": 9.400002543796743,
"Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 9.400002543796743,
"Gesamt_Verluste": 7016.419901387913,
"Gesamtbilanz_Euro": 11.897504510077836,
"Gesamteinnahmen_Euro": 0.6977514357527215,
"Gesamtkosten_Euro": 12.595255945830557,
"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,
@@ -347,26 +425,26 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"home_appliance_energy_wh": {
"dishwasher1": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -388,101 +466,85 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
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.10013413727316416,
1.110569992,
2.141361992,
0.11436917344552285,
0.4642292905233547,
0.0,
0.07557452231671152,
0.0,
4.55656845588237e-17,
0.001414013162203277,
0.005881449073870462,
0.05258762370598476,
0.0,
0.995566611,
1.033413892,
0.0,
0.7518815799999999,
0.0,
0.26650619799999997,
0.19588158,
0.174739608,
0.28801899,
0.0,
0.0,
0.0,
1.4565879259999999,
0.0,
0.0,
1.0058038379999998,
0.0,
0.0,
0.0,
0.1306329312971816,
0.07362195915902499,
0.060401289430882174,
1.9781208038728355,
0.4410873661898387,
4.179128154646605e-17,
0.19837609024735353,
1.6519184206056259,
1.2504841688205104,
1.5077152912298486,
0.0,
0.0,
0.9554559982283223,
0.1443477211581475,
0.14848868609267263,
0.0,
0.013012984677295973,
0.0,
0.0,
0.0,
1.7952031890000002,
0.0,
0.214398479,
0.16484566
],
"Netzbezug_Wh_pro_Stunde": [
439.1848126015972,
5020.66,
9680.66,
546.436566868241,
2470.61889581349,
0.0,
402.20607938643707,
0.0,
2.2737367544323206e-13,
6.433180901743754,
25.909467285772962,
175.4675465665157,
0.0,
3231.31,
3480.68,
0.0,
2704.61,
0.0,
912.38,
704.61,
516.37,
868.05,
0.0,
0.0,
0.0,
4368.889999999999,
0.0,
0.0,
3306.3899999999994,
0.0,
0.0,
0.0,
537.58407941227,
322.9033296448464,
273.0618871197205,
9451.12663102167,
2347.45804252176,
2.2737367544323206e-13,
989.9006499368938,
7515.552414038334,
5508.740831808416,
5030.748385818647,
0.0,
0.0,
5084.917499884632,
785.3521281727285,
740.9615074484663,
0.0,
57.32592368852852,
0.0,
0.0,
0.0,
5826.6900000000005,
0.0,
733.99,
592.97
@@ -493,10 +555,10 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
4278.277458856274,
2760.093180901744,
1940.089467285773,
989.4175465665157,
0.0,
0.0,
0.0,
@@ -529,83 +591,83 @@
],
"Verluste_Pro_Stunde": [
37.96737751219166,
599.9999999999998,
483.0,
39.91398802418894,
945.0334674976189,
51.82392952637247,
582.6179999999995,
1026.1905162926748,
331.2111817082091,
232.81073607429266,
118.73010558798194,
7.676704937247038,
0.0,
0.0,
0.0,
108.98319763338179,
106.80230977350088,
133.7321802766326,
0.0,
0.0,
124.41545454545451,
240.0,
0.0,
0.0,
118.37045454545455,
94.68272727272722,
83.01681818181817,
75.86045454545456,
1014.0,
66.66681818181814,
69.12409090909085,
109.07302361034766,
945.0,
116.99491129525349,
22.312089529472388,
54.18759955738153,
123.8591383343284,
853.5941151729826,
1083.022499986156,
906.7055359846831,
304.8583246953946,
225.52229247529465,
176.85071084262336,
131.21108264656203,
111.78035844493081,
90.18403329253945,
134.59227272727276,
86.6234264543665,
414.1795957226005,
2.727365102611202,
538.2984000000001,
270.9120779924272,
485.6666896846002,
348.0204998170099,
277.01140629823766,
806.8360377309995,
1083.0,
340.3377272727273,
0.0,
0.0
],
"akku_soc_pro_stunde": [
80.0,
81.05464937533866,
97.72131604200533,
98.83003793156611,
79.89157364488383,
96.07540697821715,
78.84078381044188,
88.04109441344768,
94.50805930440028,
97.80611779295532,
96.7435299231319,
96.7435299231319,
96.7435299231319,
92.81627441349004,
99.48294108015669,
99.48294108015669,
99.48294108015669,
96.49421862836606,
93.87373825646522,
91.4791532013688,
72.53975926197485,
70.35781194792527,
66.91492170697698,
47.975527767583046,
48.59530803229061,
50.10051913110676,
51.331355728325214,
33.27368862539725,
14.334919685618749,
1.0715355651189202,
1.7753354997896456,
5.155440452534742,
10.06796019816317,
11.456100036623162,
10.05678049705903,
7.568621280041836,
3.32012782274156,
3.32012782274156
81.05464937533866,
82.16337126489945,
83.60292486285425,
99.78675819618759,
100.0,
100.0,
100.0,
100.0,
98.93741213017658,
95.76274429012544,
91.54148603150185,
91.54148603150185,
91.54148603150185,
91.54148603150185,
87.80504491579936,
84.81632246400872,
82.1958420921079,
79.80125703701147,
77.69687514995913,
75.51492783590953,
72.07203759496124,
68.5743674764186,
69.19414774112617,
70.69935883994233,
73.1055651303414,
73.11055390041365,
73.18631404215284,
88.13904737548619,
89.91438287527582,
89.98845758873692,
90.07236036143165,
90.10045498082714,
71.17062150457336,
52.23122756517942,
43.23066799217666,
43.23066799217666
],
"Electricity_price": [
0.000228,
@@ -703,42 +765,42 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.625,
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.75,
0.0,
0.0,
0.625,
0.0,
0.0,
0.0,
0.5,
0.875,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.75,
0.0,
0.0,
0.375,
0.875,
0.625,
0.5,
0.375,
0.875,
0.1,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"discharge_array": [
@@ -799,51 +861,6 @@
"initial_soc_percentage": 5
},
"start_solution": [
2.0,
2.0,
1.0,
1.0,
1.0,
0.0,
1.0,
2.0,
2.0,
0.0,
0.0,
2.0,
2.0,
1.0,
0.0,
1.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.0,
2.0,
0.0,
0.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
0.0,
0.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
0.0,
1.0,
1.0,
1.0,
1.0,
0.0,
0.0,
@@ -860,9 +877,36 @@
0.0,
0.0,
0.0,
3.0,
0.0,
4.0,
0.0,
0.0,
1.0,
1.0,
1.0,
0.0,
0.0,
0.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
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,
0.0,
0.0,
0.0,
@@ -876,31 +920,50 @@
0.0,
0.0,
0.0,
4.0,
0.0,
0.0,
3.0,
0.0,
0.0,
0.0,
2.0,
5.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
4.0,
0.0,
0.0,
1.0,
2.0,
6.0,
5.0,
6.0,
4.0,
5.0,
3.0,
2.0,
1.0,
10.0
5.0,
2.0,
3.0,
4.0,
26.0
],
"washingstart": 20,
"washingstart": 36,
"appliance_starts": {
"dishwasher1": [
"2025-01-15 20:00:00+01:00"
"2025-01-16 12:00:00+01:00"
]
}
},
"appliance_deadline_missed": {}
}
+384 -321
View File
@@ -113,17 +113,13 @@
0,
0,
0,
0,
0,
0,
0,
1,
0,
1,
1,
0,
0,
0,
1,
1,
1,
1,
1,
1,
1,
1,
@@ -134,7 +130,8 @@
1,
1,
0,
0,
1,
1,
0,
0,
0,
@@ -147,29 +144,75 @@
0,
0,
1,
0
0,
0,
0,
1
],
"battery_grid_export_allowed": [],
"terminal_value": {
"mode": "AUTO",
"battery_energy_wh": 11418.875532127204,
"credited_euro": 2.9274511210000003,
"curve": {
"energy_wh": [
0.0,
516.37,
1005.26,
1873.31,
2673.16,
3180.0699999999997,
3736.3799999999997,
4430.719999999999,
5039.509999999999,
5297.15,
5863.84,
6670.2300000000005,
7657.240000000001,
8391.230000000001,
8984.2,
9335.85
],
"value_euro": [
0.0,
0.174739608,
0.337735534,
0.625754524,
0.889864994,
1.056638384,
1.239608743,
1.4676299990000001,
1.667495756,
1.749811736,
1.924408925,
2.169712763,
2.462756032,
2.6771545110000003,
2.8420001710000005,
2.9274511210000003
],
"marginal_euro_per_kwh": [
0.3384,
0.33340000000000003,
0.3318,
0.3302,
0.32899999999999996,
0.3289,
0.3284,
0.32830000000000004,
0.3195,
0.3081,
0.3042,
0.2969,
0.2921,
0.27799999999999997,
0.243
],
"window_slots": 24
}
},
"battery_grid_export_factor": [],
"eautocharge_hours_float": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -181,17 +224,36 @@
0.0,
0.0,
0.625,
0.5,
0.75,
0.375,
1.0,
0.75,
1.0,
0.875,
0.0,
0.75,
0.5,
0.5,
0.0,
0.5,
0.5,
0.4,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -202,14 +264,14 @@
],
"result": {
"Last_Wh_pro_Stunde": [
1053.07,
1063.91,
1320.56,
1132.03,
1163.67,
1176.82,
7953.07,
5203.91,
12360.56,
9412.03,
12203.67,
10836.82,
1216.22,
1103.78,
9383.78,
1129.12,
1178.71,
1050.98,
@@ -221,19 +283,19 @@
694.34,
608.79,
556.31,
7388.89,
6026.91,
9084.89,
5281.98,
488.89,
506.91,
3304.89,
3641.98,
1056.97,
9272.46,
6675.99,
6347.01,
992.46,
1155.99,
827.01,
1257.98,
6752.67,
6391.26,
7776.88,
3658.0299999999997,
1232.67,
871.26,
860.88,
1158.03,
1222.72,
1221.04,
949.99,
@@ -242,55 +304,55 @@
592.97
],
"EAuto_SoC_pro_Stunde": [
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
5.0,
15.925,
24.665,
37.775,
44.330000000000005,
44.330000000000005,
57.440000000000005,
66.18,
74.92,
74.92,
83.66,
92.4,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392,
99.392
22.48,
39.96,
53.06999999999999,
70.55,
85.845,
85.845,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955,
98.955
],
"Einnahmen_Euro_pro_Stunde": [
0.0,
0.0,
0.0,
0.0,
0.022582049506752234,
0.3039575,
0.19320652266312205,
0.13346423958241627,
0.0692592282596561,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -321,10 +383,10 @@
0.0,
0.0
],
"Gesamt_Verluste": 7236.761593008068,
"Gesamtbilanz_Euro": 10.765839318494601,
"Gesamteinnahmen_Euro": 0.7224695400119466,
"Gesamtkosten_Euro": 11.488308858506548,
"Gesamt_Verluste": 8640.018140681446,
"Gesamtbilanz_Euro": 11.094627860497381,
"Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 11.094627860497381,
"Home_appliance_wh_per_hour": [
0.0,
0.0,
@@ -347,15 +409,6 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
2500.0,
2500.0,
0.0,
@@ -363,6 +416,15 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"home_appliance_energy_wh": {
@@ -388,15 +450,6 @@
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
2500.0,
2500.0,
0.0,
@@ -404,99 +457,108 @@
0.0,
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.10013413727316416,
0.09007999454232955,
0.11436917344552285,
0.07557452231671152,
1.6012075076051746,
0.9205989357975131,
2.3554245259999997,
0.7234986866944951,
0.405034546,
0.22805572907568722,
0.0,
4.55656845588237e-17,
0.001414013162203277,
0.0,
0.05258762370598476,
0.0,
0.0,
0.29116746986009023,
0.26650619799999997,
0.19588158,
0.453046634437127,
0.0,
0.0,
0.0,
0.0,
0.0,
0.9964959260000001,
0.5352533899999999,
1.5452864699999995,
0.16621183799999983,
0.0,
0.0,
0.23080181969641217,
0.22802125600000003,
0.199865757,
0.182970359,
0.162995926,
0.16677339,
1.08961047,
1.005803838,
0.1306329312971816,
1.8585251365618425,
1.1230913228365589,
0.8820174288094884,
0.07362195915902499,
0.060401289430882174,
0.009619897970888898,
0.07029121023060134,
0.3006368911258292,
0.437965956045862,
1.0551552313933648,
0.2896168262092554,
4.179128154646605e-17,
2.3325608707865465e-05,
0.0021886029750169123,
0.013012984677295973,
0.08357424731947552,
0.17784012884918773,
0.0,
0.19011028252189552,
0.293043269,
0.0,
0.16484566
0.214398479,
0.0
],
"Netzbezug_Wh_pro_Stunde": [
439.1848126015972,
407.23324838304495,
546.436566868241,
402.20607938643707,
7022.839945636731,
4161.83967358731,
11253.82,
3850.4453788956635,
2203.67,
1138.0026400982397,
0.0,
2.2737367544323206e-13,
6.433180901743754,
0.0,
175.4675465665157,
0.0,
0.0,
980.6920507244535,
912.38,
704.61,
1995.8001517054054,
0.0,
0.0,
0.0,
0.0,
0.0,
2988.8900000000003,
1626.9099999999999,
4679.8499999999985,
546.3899999999994,
0.0,
0.0,
695.6052432079933,
694.34,
608.79,
556.31,
488.89,
506.91,
3299.85,
3306.3900000000003,
537.58407941227,
8151.426037551941,
5077.266378103792,
4214.130094646385,
322.9033296448464,
273.0618871197205,
45.96224544141853,
374.088399311343,
1635.6740540034234,
2185.4588625043016,
4800.5242556568,
1275.8450493799796,
2.2737367544323206e-13,
0.11639525303326081,
9.957247384062384,
57.32592368852852,
278.85968408233407,
556.6201215937018,
0.0,
617.0408390843736,
987.01,
0.0,
592.97
733.99,
0.0
],
"Netzeinspeisung_Wh_pro_Stunde": [
0.0,
0.0,
0.0,
0.0,
322.60070723931767,
4342.25,
2760.093180901744,
1906.6319940345184,
989.4175465665157,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -528,84 +590,84 @@
0.0
],
"Verluste_Pro_Stunde": [
37.96737751219166,
46.38878980596536,
39.91398802418894,
51.82392952637247,
543.9059151312817,
0.0,
0.0,
7.548005965483245,
0.0,
345.00599347640775,
207.1415608304772,
552.0,
1014.0126454674794,
1151.7833573253401,
1109.4303168117888,
332.088433649356,
1017.5976182046486,
142.6574983015977,
108.98319763338179,
106.80230977350088,
0.0014460869344160802,
0.0,
0.0,
133.7321802766326,
124.41545454545451,
96.08318181818186,
70.41409090909087,
118.37045454545455,
94.68272727272722,
83.01681818181817,
75.86045454545456,
945.0,
876.0,
1014.0,
807.0,
23.515194108000884,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0658141036401502e-16,
6.8212102632969615e-15,
22.312089529472388,
414.01032450623296,
276.72796537245506,
278.9400113575662,
54.18759955738153,
86.6234264543665,
165.15986945297027,
65.92300791736113,
348.1792864804107,
317.77906350051614,
226.9433106788162,
23.073005925597585,
538.2984000000001,
441.9379674303641,
261.1952696860876,
176.85071084262336,
93.18476208988011,
35.877614591244196,
111.78035844493081,
6.04210069012484,
0.0,
100.08954545454549,
0.0
0.0,
80.85954545454547
],
"akku_soc_pro_stunde": [
80.0,
81.05464937533866,
82.3432268699488,
83.45194875950959,
84.8915023574644,
100.0,
100.0,
100.0,
100.0,
100.0,
98.93741213017658,
95.76274429012544,
95.76278445920696,
95.76278445920696,
95.76278445920696,
93.54011744818767,
89.80367633248518,
86.81495388069455,
84.19447350879372,
81.79988845369729,
62.860494514303355,
43.92110057490942,
24.981706635515476,
6.042312696121536,
6.662092960829104,
6.662379752668908,
6.682601013014882,
6.764267995169498,
8.595462659540642,
10.600442839552054,
11.760972381233058,
11.931619900089059,
12.57253673135566,
15.161002344963439,
16.157602750275778,
16.325438880557027,
16.325438880557027,
13.16604666292617
80.00016648545576,
80.0040987307468,
80.0040987307468,
61.0650560543384,
43.71292212507574,
25.507703652675932,
34.68032322218595,
15.840863121810026,
18.38363774945549,
17.321049879632074,
14.146382039580937,
9.925123780957328,
5.997868271315456,
2.964939552307191,
0.7422725412879071,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
2.960594732333751e-18,
1.9243865760169378e-16,
0.6197802647075668,
2.124991363523721,
4.5311976539227885,
9.118971805394184,
10.950166469765326,
25.902899803098663,
38.1789544539421,
45.434378611888974,
50.34689835751741,
52.93536397112519,
51.53604443156106,
51.7038805618423,
51.7038805618423,
51.7038805618423
],
"Electricity_price": [
0.000228,
@@ -692,25 +754,6 @@
"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,
@@ -722,17 +765,36 @@
0.0,
0.0,
0.625,
0.5,
0.75,
0.375,
1.0,
0.75,
1.0,
0.875,
0.0,
0.75,
0.5,
0.5,
0.0,
0.5,
0.5,
0.4,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
@@ -795,7 +857,7 @@
"capacity_wh": 60000,
"charging_efficiency": 0.95,
"max_charge_power_w": 11040,
"soc_wh": 59635.2,
"soc_wh": 59373.0,
"initial_soc_percentage": 5
},
"start_solution": [
@@ -803,11 +865,6 @@
0.0,
0.0,
0.0,
2.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
@@ -816,12 +873,6 @@
0.0,
0.0,
0.0,
1.0,
0.0,
1.0,
1.0,
0.0,
0.0,
0.0,
1.0,
1.0,
@@ -832,8 +883,16 @@
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
0.0,
0.0,
1.0,
1.0,
0.0,
0.0,
0.0,
@@ -849,58 +908,62 @@
0.0,
0.0,
0.0,
1.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
3.0,
0.0,
0.0,
3.0,
2.0,
3.0,
3.0,
6.0,
4.0,
3.0,
1.0,
6.0,
4.0,
6.0,
5.0,
0.0,
4.0,
2.0,
2.0,
0.0,
2.0,
2.0,
4.0,
0.0,
3.0,
3.0,
2.0,
0.0,
5.0,
1.0,
2.0,
5.0,
30.0
4.0,
2.0,
4.0,
5.0,
5.0,
0.0,
3.0,
3.0,
5.0,
6.0,
6.0,
4.0,
1.0,
1.0,
1.0,
4.0,
1.0,
6.0,
1.0,
2.0,
5.0,
0.0,
0.0,
0.0,
21.0
],
"washingstart": 40,
"washingstart": 31,
"appliance_starts": {
"dishwasher1": [
"2025-01-16 16:00:00+01:00"
"2025-01-16 07:00:00+01:00"
]
}
},
"appliance_deadline_missed": {}
}