refactor export limit semantics
Some checks failed
CI and deploy / migration-check (pull_request) Failing after 15s
CI and deploy / deploy (pull_request) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-03 22:24:35 +02:00
parent 349a15e96a
commit e8eb867a2a
16 changed files with 134 additions and 17 deletions

View File

@@ -15,6 +15,8 @@ from services.control.exporter_monolith import (
deye_reg_triggers_self_sustain_after_verify_exhaust,
get_deye_mode,
)
from services.control.models import OperatingModeInfo
from services.control.setpoints import _build_setpoints
def _inv(*, min_soc: int | None = 12, reserve_soc: int | None = 20) -> InverterConfig:
@@ -110,6 +112,30 @@ class DeyeTouParamsTests(unittest.TestCase):
)
self.assertEqual(get_deye_mode(sp), "PASSIVE")
def test_build_setpoints_uses_explicit_export_limit(self) -> None:
mode = OperatingModeInfo(
mode_code="AUTO",
battery_mode="AUTO",
grid_mode="AUTO",
ev_enabled=False,
heat_pump_enabled_def=False,
loxone_mode_value=1,
)
pi = {
"battery_setpoint_w": 0,
"grid_setpoint_w": -3000,
"export_limit_w": 13_500,
"export_mode": "PV_SURPLUS",
"ev1_setpoint_w": 0,
"ev2_setpoint_w": 0,
"heat_pump_enabled": False,
"battery_soc_target_pct": 50,
"effective_sell_price": 1.0,
}
sp = _build_setpoints(mode, pi)
self.assertIsNotNone(sp)
self.assertEqual(sp.grid_export_limit, 13_500)
def test_pv_led_export_with_small_battery_is_sell(self) -> None:
"""Obě záporné → SELL (bez porovnání |bat| vs |grid|)."""
sp = ControlSetpoints(

View File

@@ -254,6 +254,47 @@ class PlanningDispatchMilpTests(unittest.TestCase):
self.assertEqual(int(results[0].pv_a_curtailed_w), 5000)
self.assertGreaterEqual(int(results[0].grid_setpoint_w), 0)
def test_pv_surplus_export_uses_hard_export_cap(self) -> None:
slots = [
_slot(load=0, buy=3.0, sell=2.5, pv_a=20_000, pv_b=0),
]
battery = _battery()
hp = SimpleNamespace(
rated_heating_power_w=0,
tuv_min_temp_c=45.0,
tuv_target_temp_c=55.0,
)
grid = SimpleNamespace(max_import_power_w=20_000, max_export_power_w=13_500)
vehicles = [
SimpleNamespace(
max_charge_power_w=0,
battery_capacity_kwh=1.0,
default_target_soc_pct=80.0,
),
SimpleNamespace(
max_charge_power_w=0,
battery_capacity_kwh=1.0,
default_target_soc_pct=80.0,
),
]
soc0 = battery.soc_max_wh
results, _ms = solve_dispatch(
slots,
battery,
hp,
grid,
[None, None],
vehicles,
soc0,
50.0,
tuv_delta_stats=None,
operating_mode="AUTO",
)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].export_mode, "PV_SURPLUS")
self.assertEqual(results[0].export_limit_w, 13_500)
self.assertGreater(results[0].pv_a_curtailed_w, 0)
def test_two_tier_soc_solves_optimal(self) -> None:
slots = [_slot()]
battery = _battery()