feat: Direktvermarktung mit Batterie-Netzeinspeisung

Fügt einen Direktvermarktungs-Modus (feedintariff.direct_marketing_enabled)
hinzu, der den Börsenpreis als Einspeisevergütung nutzt und aktive
Batterie-Entladung ins Netz (battery_grid_export_allowed) sowie
DC-Charge-Bypass optimiert.

- FeedInTariffEnergyCharts-Provider (Börsen-Einspeisetarif inkl. Prognose)
- Inverter: DC/AC-Wirkungsgrade und Batterie-Grid-Export in process_energy
- Genetik: Export-/DC-Charge-Zustände, Restwert-Bewertung des Akkus
- Solution-Result: neues Feld Feed_in_tariff (verwendeter Tarif je Stunde)
- Tests für neue Provider, Solution und Simulation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Andreas
2026-07-12 09:01:11 +02:00
parent cc583600d8
commit 7f2ac9098c
19 changed files with 960 additions and 72 deletions

View File

@@ -1,3 +1,5 @@
from unittest.mock import Mock
import numpy as np
import pytest
@@ -249,6 +251,7 @@ def genetic_simulation(config_eos) -> GeneticSimulation:
assert simulation.ac_charge_hours is not None
assert simulation.dc_charge_hours is not None
assert simulation.bat_discharge_hours is not None
assert simulation.bat_grid_export_hours is not None
assert simulation.ev_charge_hours is not None
simulation.ac_charge_hours[start_hour] = 1.0
simulation.dc_charge_hours[start_hour] = 1.0
@@ -374,3 +377,101 @@ def test_simulation(genetic_simulation):
), "The sum of 'Home_appliance_wh_per_hour' should be 2000."
print("All tests passed successfully.")
def test_direct_marketing_curtails_negative_feed_in(config_eos):
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
)
inverter = Inverter(InverterParameters(device_id="inverter1", max_power_wh=1000.0))
inverter.self_consumption_predictor.calculate_self_consumption = Mock(return_value=1.0)
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[500.0, 500.0],
strompreis_euro_pro_wh=[-0.0001, -0.0001],
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,
direct_marketing_enabled=True,
)
result = simulation.simulate(start_hour=0)
assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == 0.0
assert result["Einnahmen_Euro_pro_Stunde"][0] == 0.0
assert result["Verluste_Pro_Stunde"][0] == pytest.approx(500.0)
def _direct_marketing_battery_export_simulation(config_eos) -> GeneticSimulation:
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
)
battery = Battery(
SolarPanelBatteryParameters(
device_id="battery1",
capacity_wh=1000,
initial_soc_percentage=100,
min_soc_percentage=0,
charging_efficiency=1.0,
discharging_efficiency=1.0,
max_charge_power_w=500,
),
prediction_hours=config_eos.prediction.hours,
)
inverter = Inverter(
InverterParameters(
device_id="inverter1",
max_power_wh=500.0,
battery_id=battery.parameters.device_id,
),
battery=battery,
)
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[0.0, 0.0],
strompreis_euro_pro_wh=[0.0, 0.0],
einspeiseverguetung_euro_pro_wh=[0.0002, 0.0002],
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,
direct_marketing_enabled=True,
)
return simulation
def test_direct_marketing_discharge_allowed_does_not_export_battery(config_eos):
simulation = _direct_marketing_battery_export_simulation(config_eos)
assert simulation.bat_discharge_hours is not None
simulation.bat_discharge_hours[0] = 1
result = simulation.simulate(start_hour=0)
assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == 0.0
assert simulation.battery is not None
assert simulation.battery.current_soc_percentage() == 100.0
def test_direct_marketing_battery_grid_export_uses_separate_signal(config_eos):
simulation = _direct_marketing_battery_export_simulation(config_eos)
assert simulation.bat_grid_export_hours is not None
simulation.bat_grid_export_hours[0] = 1
result = simulation.simulate(start_hour=0)
assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == pytest.approx(500.0)
assert result["Einnahmen_Euro_pro_Stunde"][0] == pytest.approx(0.1)
assert simulation.battery is not None
assert simulation.battery.current_soc_percentage() == 50.0