"""Deye TOU SOC % podle fyzického režimu (SELL vs PASSIVE).""" from __future__ import annotations import unittest from services.control_exporter import ( ControlSetpoints, InverterConfig, _deye_tou_params, get_deye_mode, ) def _inv(*, min_soc: int | None = 12, reserve_soc: int | None = 20) -> InverterConfig: return InverterConfig( id=1, code="deye-main", host="127.0.0.1", port=502, unit_id=1, max_export_power_w=13_500, max_import_power_w=13_500, no_export=False, max_battery_charge_w=10_000, max_battery_discharge_w=10_000, min_soc_percent=min_soc, reserve_soc_percent=reserve_soc, max_soc_percent=95, usable_capacity_wh=64_000, max_charge_a=100, max_discharge_a=100, ) class DeyeTouParamsTests(unittest.TestCase): def test_sell_uses_reserve_soc(self) -> None: sp = ControlSetpoints( battery_w=-600, grid_export_limit=5000, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=-500, ev1_power_w=0, ev2_power_w=0, target_soc_pct=50, ) self.assertEqual(get_deye_mode(sp), "SELL") p, s, g = _deye_tou_params(sp, _inv()) self.assertFalse(g) self.assertEqual(s, 20) def test_passive_uses_min_soc(self) -> None: sp = ControlSetpoints( battery_w=0, grid_export_limit=0, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=0, ev1_power_w=0, ev2_power_w=0, target_soc_pct=None, effective_sell_price_czk_kwh=None, ) self.assertEqual(get_deye_mode(sp), "PASSIVE") p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20)) self.assertFalse(g) self.assertEqual(s, 12) def test_passive_negative_sell_steers_tou_above_current_soc(self) -> None: """Záporná vykupní → TOU SOC = 100 % (priorita akumulace vs. přetok).""" sp = ControlSetpoints( battery_w=-400, grid_export_limit=0, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=0, ev1_power_w=0, ev2_power_w=0, target_soc_pct=14, effective_sell_price_czk_kwh=-0.25, ) self.assertEqual(get_deye_mode(sp), "PASSIVE") _p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20)) self.assertFalse(g) self.assertEqual(s, 100) def test_passive_planned_charge_steers_tou(self) -> None: sp = ControlSetpoints( battery_w=800, grid_export_limit=0, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=0, ev1_power_w=0, ev2_power_w=0, target_soc_pct=60, effective_sell_price_czk_kwh=1.0, ) self.assertEqual(get_deye_mode(sp), "PASSIVE") _p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20)) self.assertFalse(g) self.assertEqual(s, 100) def test_charge_unchanged_grid_charge(self) -> None: sp = ControlSetpoints( battery_w=5000, grid_export_limit=0, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=5000, ev1_power_w=0, ev2_power_w=0, target_soc_pct=80, ) self.assertEqual(get_deye_mode(sp), "CHARGE") _p, s, g = _deye_tou_params(sp, _inv()) self.assertTrue(g) self.assertEqual(s, 95) def test_self_sustain_tou_stays_min_soc_even_if_sell_negative(self) -> None: """SELF_SUSTAIN: nízké TOU (min_soc), ne 100 % z negativní vykupní — LP se nepoužívá.""" sp = ControlSetpoints( battery_w=None, grid_export_limit=0, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=0, ev1_power_w=0, ev2_power_w=0, target_soc_pct=None, effective_sell_price_czk_kwh=-0.48, self_sustain_local_use=True, ) self.assertEqual(get_deye_mode(sp), "PASSIVE") _p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20)) self.assertFalse(g) self.assertEqual(s, 12) def test_lock_battery_uses_min_soc(self) -> None: sp = ControlSetpoints( battery_w=0, grid_export_limit=0, ev1_current_a=0, ev2_current_a=0, heat_pump_enable=False, grid_setpoint_w=-500, ev1_power_w=0, ev2_power_w=0, target_soc_pct=None, lock_battery=True, ) p, s, g = _deye_tou_params(sp, _inv(min_soc=12)) self.assertEqual(p, 0) self.assertFalse(g) self.assertEqual(s, 12) if __name__ == "__main__": unittest.main()