fix(optimization): clear disabled AC charge in the reported plan

With grid charging switched off (inverter.max_ac_charge_power_w = 0) the
simulation zeroes the AC charge array before using it, but it did so by
rebinding a local name. The solution is read back from the original array,
so it still carried the optimizer's AC charge genes - genes the fitness
never evaluated, because the simulation ignored them, and which are
therefore arbitrary.

The effect was visible as a slot with ac_charge = 0.8 where the battery SoC
does not move. Harmless inside EOS, but a controller that follows the plan
would grid-charge the battery at a time nobody planned or paid for.

Zero the array in place so the reported plan matches what was simulated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Andreas
2026-09-03 18:56:42 +02:00
co-authored by Claude Opus 5
parent a6b20e6de2
commit 2a9543e710
3 changed files with 69 additions and 2 deletions
+59
View File
@@ -560,3 +560,62 @@ def test_battery_lcos_is_charged_once_on_delivered_energy(config_eos):
assert result["Gesamtkosten_Euro"] == pytest.approx(0.06)
assert result["Einnahmen_Euro_pro_Stunde"][0] == pytest.approx(0.08)
assert result["Gesamtbilanz_Euro"] == pytest.approx(-0.02)
def test_disabled_ac_charging_clears_the_reported_plan(config_eos):
"""With AC charging off the reported plan must not keep charge commands.
The simulation ignores the AC charge genes when the inverter forbids grid
charging. The solution is read back from the same array, so a controller
acting on it would grid-charge the battery although no such charge was ever
simulated or paid for.
"""
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
)
battery = Battery(
SolarPanelBatteryParameters(
device_id="battery1",
capacity_wh=10000,
initial_soc_percentage=50,
min_soc_percentage=0,
charging_efficiency=1.0,
discharging_efficiency=1.0,
max_charge_power_w=5000,
),
prediction_hours=config_eos.prediction.hours,
)
inverter = Inverter(
InverterParameters(
device_id="inverter1",
max_power_wh=5000.0,
battery_id=battery.parameters.device_id,
max_ac_charge_power_w=0, # Netzladen deaktiviert
),
battery=battery,
)
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[0.0, 0.0],
strompreis_euro_pro_wh=[0.0003, 0.0003],
einspeiseverguetung_euro_pro_wh=[0.0001, 0.0001],
preis_euro_pro_wh_akku=0.0,
gesamtlast=[0.0, 0.0],
),
optimization_hours=config_eos.optimization.horizon_hours,
prediction_hours=config_eos.prediction.hours,
inverter=inverter,
)
simulation.ac_charge_hours = np.array([0.8, 0.0])
soc_before = battery.current_soc_percentage()
simulation.simulate(start_hour=0)
# Nothing was charged ...
assert battery.current_soc_percentage() == pytest.approx(soc_before)
# ... and the plan says so.
assert simulation.ac_charge_hours is not None
assert list(simulation.ac_charge_hours) == [0.0, 0.0]