fix(optimization): add battery self-consumption state

This commit is contained in:
Andreas
2026-07-16 12:59:02 +02:00
parent 5b8f7de113
commit 6465e22f07
9 changed files with 310 additions and 182 deletions
+8
View File
@@ -80,6 +80,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
are deprecated in favour of `appliance_starts` and `result.home_appliance_energy_wh`. are deprecated in favour of `appliance_starts` and `result.home_appliance_energy_wh`.
### Fixed ### Fixed
- Allow the direct-marketing optimizer to select a true battery self-consumption state with DC
charging and local-load discharge enabled in the same slot. Existing warm-start state numbers
remain compatible, and educated guesses now use the combined state for PV/load overlap instead
of unnecessarily bypassing PV while serving loads such as EV charging.
- Account for EV charging losses in the AC load seen by the inverter and grid, so fitness and
energy costs use the charger's raw input rather than only the energy stored in the EV battery.
- Treat fitness memoization as disabled for lightweight optimizer instances constructed without
the normal initializer, preserving isolated penalty evaluation and test callers.
- Re-simulate genetic candidates after removing EV charging genes from slots that begin at full - Re-simulate genetic candidates after removing EV charging genes from slots that begin at full
SoC, keeping the repaired genome and its assigned fitness consistent. SoC, keeping the repaired genome and its assigned fitness consistent.
- FeedInTariffEnergyCharts no longer aborts the whole prediction/optimization when the - FeedInTariffEnergyCharts no longer aborts the whole prediction/optimization when the
+5
View File
@@ -357,6 +357,11 @@ smaller values (e.g. `0.0`) disable the penalty entirely.
- `discharge_allowed`: Battery discharge permission for local self-consumption/load coverage (0 or 1) - `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) - `battery_grid_export_allowed`: Battery discharge permission for grid export/direct marketing (0 or 1)
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.
0 (no charge) 0 (no charge)
1 (charge with full load) 1 (charge with full load)
@@ -85,6 +85,17 @@ class FitnessCacheEntry:
extra_data: tuple[float, float, float] extra_data: tuple[float, float, float]
@dataclass(frozen=True)
class BatteryStateLayout:
"""Indices of optional battery states appended to the legacy state ranges."""
total_states: int
dc_not_allowed_state: Optional[int] = None
dc_allowed_state: Optional[int] = None
grid_export_state: Optional[int] = None
self_consumption_state: Optional[int] = None
class GeneticSimulation(PydanticBaseModel): class GeneticSimulation(PydanticBaseModel):
"""Device simulation for GENETIC optimization algorithm.""" """Device simulation for GENETIC optimization algorithm."""
@@ -411,10 +422,12 @@ class GeneticSimulation(PydanticBaseModel):
if ev_fast: if ev_fast:
soc_ev_per_hour[hour_idx] = ev_fast.current_soc_percentage() # save begin state soc_ev_per_hour[hour_idx] = ev_fast.current_soc_percentage() # save begin state
if ev_charge_hours_fast[hour] > 0: if ev_charge_hours_fast[hour] > 0:
loaded_energy_ev, verluste_eauto = ev_fast.charge_energy( stored_energy_ev, verluste_eauto = ev_fast.charge_energy(
wh=None, hour=hour, charge_factor=ev_charge_hours_fast[hour] wh=None, hour=hour, charge_factor=ev_charge_hours_fast[hour]
) )
consumption += loaded_energy_ev # The inverter/grid must supply the EV charger's raw input,
# not only the energy stored after charging losses.
consumption += stored_energy_ev + verluste_eauto
losses_wh_per_hour[hour_idx] += verluste_eauto losses_wh_per_hour[hour_idx] += verluste_eauto
# Save battery SOC before inverter processing = true begin-of-interval state. # Save battery SOC before inverter processing = true begin-of-interval state.
@@ -663,6 +676,40 @@ class GeneticOptimization(OptimizationBase):
except Exception: except Exception:
return False return False
def _battery_state_layout(self) -> BatteryStateLayout:
"""Build optional state indices without renumbering legacy warm starts.
The pre-existing order is retained exactly: base charge/discharge ranges,
two optional DC states, then optional grid export. SELF_CONSUMPTION is
appended last so an old export gene never changes its meaning.
"""
next_state = 3 * len(self.bat_possible_charge_values)
dc_not_allowed_state: Optional[int] = None
dc_allowed_state: Optional[int] = None
grid_export_state: Optional[int] = None
self_consumption_state: Optional[int] = None
if self.optimize_dc_charge:
dc_not_allowed_state = next_state
dc_allowed_state = next_state + 1
next_state += 2
if self.optimize_battery_grid_export:
grid_export_state = next_state
next_state += 1
if self.optimize_dc_charge:
self_consumption_state = next_state
next_state += 1
return BatteryStateLayout(
total_states=next_state,
dc_not_allowed_state=dc_not_allowed_state,
dc_allowed_state=dc_allowed_state,
grid_export_state=grid_export_state,
self_consumption_state=self_consumption_state,
)
def _appliance_horizon_end_slot(self) -> int: def _appliance_horizon_end_slot(self) -> int:
"""Exclusive upper slot bound for appliance runs (end of horizon). """Exclusive upper slot bound for appliance runs (end of horizon).
@@ -966,9 +1013,8 @@ class GeneticOptimization(OptimizationBase):
# AC Charge: 2*len_bat .. 3*len_bat - 1 (maps to bat_possible_charge_values) # AC Charge: 2*len_bat .. 3*len_bat - 1 (maps to bat_possible_charge_values)
# DC optional: 3*len_bat (not allowed), 3*len_bat + 1 (allowed) # DC optional: 3*len_bat (not allowed), 3*len_bat + 1 (allowed)
# Grid export: next state, if direct marketing/export optimization is enabled # Grid export: next state, if direct marketing/export optimization is enabled
# Self-consumption: final state, with DC charging and local discharge enabled
# Idle states state_layout = self._battery_state_layout()
idle_mask = (discharge_hours_bin_np >= 0) & (discharge_hours_bin_np < len_bat)
# Discharge states # Discharge states
discharge_mask = (discharge_hours_bin_np >= len_bat) & ( discharge_mask = (discharge_hours_bin_np >= len_bat) & (
@@ -980,24 +1026,28 @@ class GeneticOptimization(OptimizationBase):
ac_indices = (discharge_hours_bin_np[ac_mask] - 2 * len_bat).astype(int) ac_indices = (discharge_hours_bin_np[ac_mask] - 2 * len_bat).astype(int)
# DC states (if enabled) # DC states (if enabled)
if self.optimize_dc_charge: if state_layout.dc_allowed_state is not None:
dc_not_allowed_state = 3 * len_bat dc_mask = discharge_hours_bin_np == state_layout.dc_allowed_state
dc_allowed_state = 3 * len_bat + 1 if state_layout.self_consumption_state is not None:
dc_charge = np.where(discharge_hours_bin_np == dc_allowed_state, 1, 0) dc_mask |= discharge_hours_bin_np == state_layout.self_consumption_state
dc_charge = np.where(dc_mask, 1, 0)
else: else:
dc_charge = np.ones_like(discharge_hours_bin_np, dtype=float) dc_charge = np.ones_like(discharge_hours_bin_np, dtype=float)
# Generate the result arrays # Generate the result arrays
discharge = np.zeros_like(discharge_hours_bin_np, dtype=int) discharge = np.zeros_like(discharge_hours_bin_np, dtype=int)
discharge[discharge_mask] = 1 # Set Discharge states to 1 discharge[discharge_mask] = 1 # Set Discharge states to 1
if state_layout.self_consumption_state is not None:
discharge[discharge_hours_bin_np == state_layout.self_consumption_state] = 1
ac_charge = np.zeros_like(discharge_hours_bin_np, dtype=float) ac_charge = np.zeros_like(discharge_hours_bin_np, dtype=float)
ac_charge[ac_mask] = [self.bat_possible_charge_values[i] for i in ac_indices] ac_charge[ac_mask] = [self.bat_possible_charge_values[i] for i in ac_indices]
battery_grid_export = np.zeros_like(discharge_hours_bin_np, dtype=int) battery_grid_export = np.zeros_like(discharge_hours_bin_np, dtype=int)
if self.optimize_battery_grid_export: if state_layout.grid_export_state is not None:
grid_export_state = 3 * len_bat + (2 if self.optimize_dc_charge else 0) battery_grid_export = np.where(
battery_grid_export = np.where(discharge_hours_bin_np == grid_export_state, 1, 0) discharge_hours_bin_np == state_layout.grid_export_state, 1, 0
)
# Idle is just 0, already default. # Idle is just 0, already default.
@@ -1005,14 +1055,7 @@ class GeneticOptimization(OptimizationBase):
def mutate(self, individual: list[int]) -> tuple[list[int]]: def mutate(self, individual: list[int]) -> tuple[list[int]]:
"""Custom mutation function for the individual.""" """Custom mutation function for the individual."""
# Calculate the number of states using battery charge levels total_states = self._battery_state_layout().total_states
len_bat = len(self.bat_possible_charge_values)
if self.optimize_dc_charge:
total_states = 3 * len_bat + 2
else:
total_states = 3 * len_bat
if self.optimize_battery_grid_export:
total_states += 1
# 1. Mutating the charge_discharge part # 1. Mutating the charge_discharge part
charge_discharge_part = individual[: self.total_slots] charge_discharge_part = individual[: self.total_slots]
@@ -1146,14 +1189,15 @@ class GeneticOptimization(OptimizationBase):
must re-simulate after a change so the individual's genome, simulation must re-simulate after a change so the individual's genome, simulation
state and assigned fitness always describe the same schedule. state and assigned fitness always describe the same schedule.
""" """
if not self.optimize_ev or not self.ev_possible_charge_values: ev_possible_charge_values = getattr(self, "ev_possible_charge_values", None)
if not self.optimize_ev or not ev_possible_charge_values:
return False return False
zero_charge_index = min( zero_charge_index = min(
range(len(self.ev_possible_charge_values)), range(len(ev_possible_charge_values)),
key=lambda index: abs(self.ev_possible_charge_values[index]), key=lambda index: abs(ev_possible_charge_values[index]),
) )
if abs(self.ev_possible_charge_values[zero_charge_index]) > 1e-12: if abs(ev_possible_charge_values[zero_charge_index]) > 1e-12:
return False return False
_, ev_charge_indices, _ = self.split_individual(individual) _, ev_charge_indices, _ = self.split_individual(individual)
@@ -1172,7 +1216,7 @@ class GeneticOptimization(OptimizationBase):
charge_index = int(ev_charge_indices[slot]) charge_index = int(ev_charge_indices[slot])
if ( if (
ev_soc[offset] >= 100.0 - 1e-9 ev_soc[offset] >= 100.0 - 1e-9
and self.ev_possible_charge_values[charge_index] > 0.0 and ev_possible_charge_values[charge_index] > 0.0
): ):
ev_charge_indices[slot] = zero_charge_index ev_charge_indices[slot] = zero_charge_index
changed = True changed = True
@@ -1279,11 +1323,13 @@ class GeneticOptimization(OptimizationBase):
slots = self.total_slots slots = self.total_slots
start_slot = self._start_day_slot() start_slot = self._start_day_slot()
len_bat = len(self.bat_possible_charge_values) len_bat = len(self.bat_possible_charge_values)
state_layout = self._battery_state_layout()
idle_state = 0 idle_state = 0
discharge_state = len_bat discharge_state = len_bat
ac_charge_state = 3 * len_bat - 1 ac_charge_state = 3 * len_bat - 1
dc_allowed_state = 3 * len_bat + 1 dc_allowed_state = state_layout.dc_allowed_state
export_state = 3 * len_bat + (2 if self.optimize_dc_charge else 0) export_state = state_layout.grid_export_state
self_consumption_state = state_layout.self_consumption_state
prices = np.asarray(self.simulation.elect_price_hourly, dtype=float) prices = np.asarray(self.simulation.elect_price_hourly, dtype=float)
feed_in = np.asarray(self.simulation.elect_revenue_per_hour_arr, dtype=float) feed_in = np.asarray(self.simulation.elect_revenue_per_hour_arr, dtype=float)
@@ -1333,15 +1379,20 @@ class GeneticOptimization(OptimizationBase):
for slot in range(start_slot, slots): for slot in range(start_slot, slots):
high_feed_in = ( high_feed_in = (
export_quantile is not None export_quantile is not None
and self.optimize_battery_grid_export and export_state is not None
and feed_spread > 1e-12 and feed_spread > 1e-12
and feed_in[slot] > 0.0 and feed_in[slot] > 0.0
and feed_in[slot] >= export_threshold and feed_in[slot] >= export_threshold
) )
pv_surplus = pv[slot] > load[slot] * pv_surplus_ratio pv_surplus = pv[slot] > load[slot] * pv_surplus_ratio
if high_feed_in: if high_feed_in and export_state is not None:
battery_genes[slot] = export_state battery_genes[slot] = export_state
elif self.optimize_dc_charge and pv_surplus: elif self_consumption_state is not None and pv[slot] > 0.0 and load[slot] > 0.0:
# The probabilistic inverter model can see a residual load
# and a PV surplus within the same coarse slot. Normal
# self-consumption must therefore allow both directions.
battery_genes[slot] = self_consumption_state
elif dc_allowed_state is not None and pv_surplus:
battery_genes[slot] = dc_allowed_state battery_genes[slot] = dc_allowed_state
elif allow_ac_arbitrage and prices[slot] <= low_price_threshold: elif allow_ac_arbitrage and prices[slot] <= low_price_threshold:
battery_genes[slot] = ac_charge_state battery_genes[slot] = ac_charge_state
@@ -1415,7 +1466,9 @@ class GeneticOptimization(OptimizationBase):
for slot in random.sample(future_slots, min(perturbations, len(future_slots))): # noqa: S311 for slot in random.sample(future_slots, min(perturbations, len(future_slots))): # noqa: S311
if randomized[slot] != idle_state: if randomized[slot] != idle_state:
randomized[slot] = idle_state randomized[slot] = idle_state
elif self.optimize_dc_charge and pv[slot] > load[slot]: elif self_consumption_state is not None and pv[slot] > 0.0 and load[slot] > 0.0:
randomized[slot] = self_consumption_state
elif dc_allowed_state is not None and pv[slot] > load[slot]:
randomized[slot] = dc_allowed_state randomized[slot] = dc_allowed_state
elif load[slot] > pv[slot] and prices[slot] >= high_import_price: elif load[slot] > pv[slot] and prices[slot] >= high_import_price:
randomized[slot] = discharge_state randomized[slot] = discharge_state
@@ -1480,12 +1533,8 @@ class GeneticOptimization(OptimizationBase):
# AC-Charge: len_bat states (maps to bat_possible_charge_values) # AC-Charge: len_bat states (maps to bat_possible_charge_values)
# With DC: + 2 additional states # With DC: + 2 additional states
# With battery grid export: + 1 additional state # With battery grid export: + 1 additional state
if self.optimize_dc_charge: # With DC: + 1 final SELF_CONSUMPTION state
total_states = 3 * len_bat + 2 total_states = self._battery_state_layout().total_states
else:
total_states = 3 * len_bat
if self.optimize_battery_grid_export:
total_states += 1
# State space: 0 .. (total_states - 1) # State space: 0 .. (total_states - 1)
self.toolbox.register("attr_discharge_state", random.randint, 0, total_states - 1) self.toolbox.register("attr_discharge_state", random.randint, 0, total_states - 1)
@@ -1579,7 +1628,10 @@ class GeneticOptimization(OptimizationBase):
worst_case: bool, worst_case: bool,
) -> tuple[float]: ) -> tuple[float]:
"""Evaluate an individual, using run-local canonical memoization when active.""" """Evaluate an individual, using run-local canonical memoization when active."""
if not self._fitness_cache_enabled: # Some lightweight callers construct the optimizer without __init__
# (for example isolated penalty evaluations). Memoization is opt-in, so
# a missing flag must behave exactly like a disabled cache.
if not getattr(self, "_fitness_cache_enabled", False):
return self._evaluate_uncached(individual, parameters, start_hour, worst_case) return self._evaluate_uncached(individual, parameters, start_hour, worst_case)
original_key = tuple(int(value) for value in individual) original_key = tuple(int(value) for value in individual)
+3 -1
View File
@@ -187,9 +187,11 @@ def test_educated_guesses_encode_high_price_direct_marketing(config_eos: ConfigE
dc_allowed_state = 4 dc_allowed_state = 4
export_state = 5 export_state = 5
self_consumption_state = 6
assert len(guesses) == opt.EDUCATED_GUESS_TARGET assert len(guesses) == opt.EDUCATED_GUESS_TARGET
assert all(len(guess) == slots for guess in guesses) assert all(len(guess) == slots for guess in guesses)
assert any(guess[0] == dc_allowed_state for guess in guesses) assert any(dc_allowed_state in guess or self_consumption_state in guess for guess in guesses)
assert any(self_consumption_state in guess for guess in guesses)
assert any(guess[-1] == export_state for guess in guesses) assert any(guess[-1] == export_state for guess in guesses)
+43 -2
View File
@@ -338,7 +338,7 @@ def test_simulation(genetic_simulation):
# Verify the total balance # Verify the total balance
assert ( assert (
abs(result["Gesamtbilanz_Euro"] - 7.025236588371921) < 1e-5 abs(result["Gesamtbilanz_Euro"] - 7.224316588371922) < 1e-5
), "Total balance should reflect the shared per-slot battery power limit." ), "Total balance should reflect the shared per-slot battery power limit."
# Check total revenue and total costs # Check total revenue and total costs
@@ -346,7 +346,7 @@ def test_simulation(genetic_simulation):
abs(result["Gesamteinnahmen_Euro"] - 2.3247787887715) < 1e-5 abs(result["Gesamteinnahmen_Euro"] - 2.3247787887715) < 1e-5
), "Total revenue should respect the shared per-slot battery power limit." ), "Total revenue should respect the shared per-slot battery power limit."
assert ( assert (
abs(result["Gesamtkosten_Euro"] - 9.350015377143421) < 1e-5 abs(result["Gesamtkosten_Euro"] - 9.549095377143422) < 1e-5
), "Total costs should respect the shared per-slot battery power limit." ), "Total costs should respect the shared per-slot battery power limit."
# Check the losses # Check the losses
@@ -379,6 +379,47 @@ def test_simulation(genetic_simulation):
print("All tests passed successfully.") print("All tests passed successfully.")
def test_ev_charging_uses_raw_input_energy_for_load_and_grid(config_eos):
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 1}, "optimization": {"horizon_hours": 1}}
)
ev = Battery(
ElectricVehicleParameters(
device_id="ev1",
capacity_wh=1000,
charging_efficiency=0.8,
max_charge_power_w=100,
initial_soc_percentage=0,
min_soc_percentage=0,
),
prediction_hours=1,
)
inverter = Inverter(InverterParameters(device_id="inverter1", max_power_wh=1000.0))
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[0.0],
strompreis_euro_pro_wh=[0.001],
einspeiseverguetung_euro_pro_wh=[0.0],
preis_euro_pro_wh_akku=0.0,
gesamtlast=[0.0],
),
optimization_hours=1,
prediction_hours=1,
inverter=inverter,
ev=ev,
)
simulation.ev_charge_hours = np.array([1.0])
result = simulation.simulate(start_hour=0)
assert result["Last_Wh_pro_Stunde"][0] == pytest.approx(100.0)
assert result["Netzbezug_Wh_pro_Stunde"][0] == pytest.approx(100.0)
assert result["Kosten_Euro_pro_Stunde"][0] == pytest.approx(0.1)
assert result["Verluste_Pro_Stunde"][0] == pytest.approx(20.0)
assert ev.current_soc_percentage() == pytest.approx(8.0)
def test_direct_marketing_curtails_negative_feed_in(config_eos, monkeypatch): def test_direct_marketing_curtails_negative_feed_in(config_eos, monkeypatch):
config_eos.merge_settings_from_dict( config_eos.merge_settings_from_dict(
{"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}} {"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
+20
View File
@@ -54,3 +54,23 @@ def test_decode_charge_discharge_has_separate_battery_grid_export_state():
assert dc_charge.tolist() == [0] assert dc_charge.tolist() == [0]
assert discharge.tolist() == [0] assert discharge.tolist() == [0]
assert battery_grid_export.tolist() == [1] assert battery_grid_export.tolist() == [1]
def test_decode_charge_discharge_has_self_consumption_state_after_legacy_export():
optimization = GeneticOptimization()
optimization.bat_possible_charge_values = [1.0]
optimization.optimize_dc_charge = True
optimization.optimize_battery_grid_export = True
layout = optimization._battery_state_layout()
ac_charge, dc_charge, discharge, battery_grid_export = (
optimization.decode_charge_discharge(np.array([6]))
)
assert layout.total_states == 7
assert layout.grid_export_state == 5
assert layout.self_consumption_state == 6
assert ac_charge.tolist() == [0.0]
assert dc_charge.tolist() == [1]
assert discharge.tolist() == [1]
assert battery_grid_export.tolist() == [0]
Binary file not shown.
+71 -71
View File
@@ -202,15 +202,15 @@
], ],
"result": { "result": {
"Last_Wh_pro_Stunde": [ "Last_Wh_pro_Stunde": [
10230.07, 10713.07,
7618.91, 7963.91,
7875.5599999999995, 8220.56,
7565.03, 7772.03,
12840.67, 13323.67,
9042.82, 9456.82,
9082.22, 9496.22,
5036.78, 5243.78,
2177.92, 2233.12,
1178.71, 1178.71,
1050.98, 1050.98,
988.56, 988.56,
@@ -313,7 +313,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.05936927575872234, 0.053661230306193436,
0.05435777788576338, 0.05435777788576338,
0.0, 0.0,
0.0, 0.0,
@@ -322,9 +322,9 @@
0.0 0.0
], ],
"Gesamt_Verluste": 6227.580163897914, "Gesamt_Verluste": 6227.580163897914,
"Gesamtbilanz_Euro": 11.420868526053463, "Gesamtbilanz_Euro": 12.033027623527284,
"Gesamteinnahmen_Euro": 0.11372705364448572, "Gesamteinnahmen_Euro": 0.10801900819195681,
"Gesamtkosten_Euro": 11.534595579697948, "Gesamtkosten_Euro": 12.14104663171924,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -408,15 +408,15 @@
] ]
}, },
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
2.1203521199999997, 2.23047612,
1.4545721927072854, 1.5308798733801507,
1.4167558060312964, 1.4889564723943407,
1.2032659414129376, 1.242149738556099,
1.2892826874611185, 1.3742255912165289,
0.770122611209644, 0.8479283355019763,
1.1459236583154115, 1.2336553365360492,
0.4965507655670841, 0.5407163922683618,
0.1912938683161112, 0.20558284318867343,
0.1614775630079859, 0.1614775630079859,
0.0, 0.0,
0.0, 0.0,
@@ -448,15 +448,15 @@
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
9299.789999999999, 9782.789999999999,
6575.823656000386, 6920.795087613701,
6769.01961792306, 7113.9821901306295,
6403.757005923032, 6610.695787951565,
7014.595688036554, 7476.74423948057,
3842.9272016449304, 4231.179318872138,
5213.483431826258, 5612.626644840988,
2187.4483064629258, 2382.0105386271443,
638.2845122326032, 685.962106068313,
505.40708296709204, 505.40708296709204,
0.0, 0.0,
0.0, 0.0,
@@ -519,7 +519,7 @@
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
848.1325108388907, 766.589004374192,
776.5396840823341, 776.5396840823341,
0.0, 0.0,
0.0, 0.0,
@@ -529,14 +529,14 @@
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
483.0, 483.0,
345.0196387200463, 345.0162105136441,
345.0239541507672, 345.0194628156755,
207.05004071076388, 207.04269455418785,
506.1294825643864, 503.6273087376684,
452.3012641973916, 449.2115182646565,
426.13721181915116, 424.3543973809186,
227.23539677555112, 225.74286463525735,
103.61214146791241, 102.70945272819762,
40.06404995605101, 40.06404995605101,
106.80230977350088, 106.80230977350088,
133.7321802766326, 133.7321802766326,
@@ -559,7 +559,7 @@
538.2984000000001, 538.2984000000001,
441.9379674303641, 441.9379674303641,
261.1952696860876, 261.1952696860876,
75.0748095419566, 84.86003031772043,
0.0, 0.0,
111.78035844493081, 111.78035844493081,
90.18403329253945, 90.18403329253945,
@@ -570,36 +570,36 @@
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
80.0, 80.0,
80.00054552000128, 80.00045029204567,
80.00121091307815, 80.00099092581443,
80.0026009328216, 80.00217688565297,
80.6450865596101, 80.57515768392155,
81.70901056509321, 81.55325541349534,
82.04615533784741, 81.8408775629653,
82.60824969272383, 82.36151269172245,
83.95303140016584, 83.68121971195016,
85.06592167672281, 84.79410998850713,
81.89125383667168, 81.619442148456,
77.66999557804807, 77.39818388983241,
77.66999557804807, 77.39818388983241,
77.66999557804807, 77.39818388983241,
75.44732856702878, 75.17551687881311,
71.71088745132629, 71.43907576311062,
68.72216499953565, 68.45035331131999,
66.10168462763482, 65.82987293941916,
63.7070995725384, 63.43528788432273,
61.60271768548606, 61.33090599727039,
59.42077037143647, 59.14895868322081,
55.97788013048819, 55.706068442272525,
52.48021001194556, 52.208398323729895,
53.09999027665313, 52.82817858843747,
54.60520137546928, 54.33338968725362,
57.01140766586834, 56.73959597765268,
61.59918181733974, 61.327370129124084,
63.43037648171088, 63.158564793495216,
78.38310981504422, 78.11129812682856,
90.65916446588767, 90.387352777672,
97.91458862383455, 97.64277693561888,
100.0, 100.0,
100.0, 100.0,
98.60068046043587, 98.60068046043587,
+70 -70
View File
@@ -202,14 +202,14 @@
], ],
"result": { "result": {
"Last_Wh_pro_Stunde": [ "Last_Wh_pro_Stunde": [
11541.07, 12093.07,
6307.91, 6583.91,
9186.56, 9600.56,
10309.03, 10792.03,
6407.67, 6683.67,
5109.82, 5316.82,
11704.22, 12256.22,
5036.78, 5243.78,
1129.12, 1129.12,
1178.71, 1178.71,
1050.98, 1050.98,
@@ -321,10 +321,10 @@
0.0, 0.0,
0.0 0.0
], ],
"Gesamt_Verluste": 7430.87215820259, "Gesamt_Verluste": 7415.050669156861,
"Gesamtbilanz_Euro": 9.4881560601163, "Gesamtbilanz_Euro": 10.086958235191952,
"Gesamteinnahmen_Euro": 0.0, "Gesamteinnahmen_Euro": 0.0,
"Gesamtkosten_Euro": 9.4881560601163, "Gesamtkosten_Euro": 10.086958235191952,
"Home_appliance_wh_per_hour": [ "Home_appliance_wh_per_hour": [
0.0, 0.0,
0.0, 0.0,
@@ -408,14 +408,14 @@
] ]
}, },
"Kosten_Euro_pro_Stunde": [ "Kosten_Euro_pro_Stunde": [
1.4160601199999998, 1.5419161199999998,
0.19137741085396395, 0.2524105556335792,
1.691123530177008, 1.7777665549410084,
1.7187910298948639, 1.809540886,
0.20791005863613377, 0.24971825220475874,
0.09234842570092357, 0.12061259291950001,
1.709869129414328, 1.83015129135275,
0.4965507655670841, 0.5407163922683618,
0.05258762370598476, 0.05258762370598476,
0.1614775630079859, 0.1614775630079859,
0.0, 0.0,
@@ -448,14 +448,14 @@
0.16484566 0.16484566
], ],
"Netzbezug_Wh_pro_Stunde": [ "Netzbezug_Wh_pro_Stunde": [
6210.789999999999, 6762.789999999999,
865.1781684175585, 1141.0965444556023,
8079.902198647912, 8493.867916583891,
9147.371101090283, 9630.34,
1131.1755094457767, 1358.64119806724,
460.8204875295587, 601.8592461052895,
7779.204410438253, 8326.43899614536,
2187.4483064629258, 2382.0105386271443,
175.4675465665157, 175.4675465665157,
505.40708296709204, 505.40708296709204,
0.0, 0.0,
@@ -529,13 +529,13 @@
], ],
"Verluste_Pro_Stunde": [ "Verluste_Pro_Stunde": [
1152.0, 1152.0,
876.0621802101069, 876.0523853346722,
414.00986383774955, 414.00574999006693,
483.00373213083384, 483.0,
365.07906113349316, 359.25494376806876,
311.4084585035471, 303.4931095326348,
557.3837292525905, 556.8118795374434,
227.23539677555112, 225.74286463525735,
118.73010558798194, 118.73010558798194,
40.06404995605101, 40.06404995605101,
106.80230977350088, 106.80230977350088,
@@ -570,42 +570,42 @@
"akku_soc_pro_stunde": [ "akku_soc_pro_stunde": [
80.0, 80.0,
61.06060606060606, 61.06060606060606,
42.12293934927065, 42.12266726939745,
42.12321334476369, 42.122826991343764,
42.123317015064636, 42.122826991343764,
44.59773537988389, 44.435464318234565,
47.49797033831576, 47.11582847191886,
47.64751837310994, 47.249491792403404,
48.209612727986354, 47.770126921160546,
51.507671216541404, 51.0681854097156,
52.620561493098386, 52.181075686272585,
49.44589365304724, 49.006407846221435,
45.224635394423636, 44.78514958759783,
45.224635394423636, 44.78514958759783,
45.224635394423636, 44.78514958759783,
43.00196838340435, 42.562482576578546,
39.26552726770188, 38.82604146087607,
36.27680481591124, 35.837319009085434,
33.656324444010416, 33.2168386371846,
31.261739388913995, 30.822253582088187,
29.157357501861657, 28.717871695035846,
26.97541018781207, 26.53592438098626,
23.53251994686378, 23.09303414003797,
20.034849828321157, 19.595364021495346,
20.654630093028718, 20.215144286202914,
22.159841191844876, 21.72035538501907,
24.566047482243945, 24.126561675418138,
29.15382163371534, 28.71433582688953,
29.229581775454538, 28.79009596862873,
35.84898177545453, 35.40949596862873,
48.12503642629798, 47.68555061947217,
55.38046058424485, 54.94097477741905,
60.29298032987328, 59.85349452304748,
61.68112016833327, 61.241634361507465,
62.6777205736456, 62.2382347668198,
60.18956135662841, 59.7500755498026,
55.94106789932813, 55.50158209250233,
55.94106789932813 55.50158209250233
], ],
"Electricity_price": [ "Electricity_price": [
0.000228, 0.000228,