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

@@ -30,8 +30,23 @@ class Inverter:
self.ac_to_dc_efficiency = self.parameters.ac_to_dc_efficiency
self.max_ac_charge_power_w = self.parameters.max_ac_charge_power_w
def _discharge_battery_to_ac(self, requested_ac_wh: float, hour: int) -> tuple[float, float]:
"""Discharge battery energy and convert it to AC energy."""
if not self.battery or requested_ac_wh <= 0.0:
return 0.0, 0.0
dc_request = requested_ac_wh / self.dc_to_ac_efficiency
battery_discharge_dc, discharge_losses = self.battery.discharge_energy(dc_request, hour)
battery_discharge_ac = battery_discharge_dc * self.dc_to_ac_efficiency
inverter_discharge_losses = battery_discharge_dc - battery_discharge_ac
return battery_discharge_ac, discharge_losses + inverter_discharge_losses
def process_energy(
self, generation: float, consumption: float, hour: int
self,
generation: float,
consumption: float,
hour: int,
allow_battery_grid_export: bool = False,
) -> tuple[float, float, float, float]:
losses = 0.0
grid_export = 0.0
@@ -59,6 +74,7 @@ class Inverter:
# Remaining load Self Consumption not perfect
remaining_load_evq = (generation - consumption) * (1.0 - scr)
from_battery_dc = 0.0
if remaining_load_evq > 0:
# Akku muss den Restverbrauch decken
if self.battery:
@@ -105,6 +121,20 @@ class Inverter:
consumption + from_battery_ac
) # Self-consumption is equal to the load
if allow_battery_grid_export and self.battery:
export_capacity = max(self.max_power_wh - consumption - grid_export, 0.0)
max_discharge_dc = getattr(self.battery, "max_charge_power_w", None)
if max_discharge_dc is not None:
remaining_battery_ac = max(
(max_discharge_dc - from_battery_dc) * dc_to_ac_eff, 0.0
)
export_capacity = min(export_capacity, remaining_battery_ac)
battery_export_ac, battery_export_losses = self._discharge_battery_to_ac(
export_capacity, hour
)
grid_export += battery_export_ac
losses += battery_export_losses
else:
# Case 2: Insufficient generation, cover shortfall
shortfall = consumption - generation
@@ -129,4 +159,18 @@ class Inverter:
grid_import = shortfall - battery_discharge_ac
self_consumption = generation + battery_discharge_ac
if allow_battery_grid_export and self.battery and grid_import <= 0.0:
export_capacity = max(self.max_power_wh - consumption, 0.0)
max_discharge_dc = getattr(self.battery, "max_charge_power_w", None)
if max_discharge_dc is not None:
remaining_battery_ac = max(
(max_discharge_dc - battery_discharge_dc) * dc_to_ac_eff, 0.0
)
export_capacity = min(export_capacity, remaining_battery_ac)
battery_export_ac, battery_export_losses = self._discharge_battery_to_ac(
export_capacity, hour
)
grid_export += battery_export_ac
losses += battery_export_losses
return grid_export, grid_import, losses, self_consumption