"""PASSIVE + PV_SURPLUS: reg 108 sleduje charge intent (fix 2026-06-16). bat_w>0 (plán chce nabíjet z přebytku) → 108=max (baterka nabere co zvládne, zbytek ven); SoC u maxima + přebytek → 108=max (BMS kalibrace na 100 %); jen "prodej PV a drž baterku" daleko od maxima (bat_w<=0) → 108=0. 109=max, 142 zůstává zero-export (1/2). """ from __future__ import annotations import unittest from services.control.setpoints import deye_battery_charge_discharge_amps class PassivePvSurplusChargeAmpsTests(unittest.TestCase): def test_pv_surplus_export_zeros_charge_amps(self) -> None: ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="PASSIVE", self_sustain_local_use=False, bat_w=-177, grid_w=-2851, max_charge_a=100, max_discharge_a=90, export_mode="PV_SURPLUS", export_ban=False, ) self.assertEqual(ch, 0) self.assertEqual(dis, 90) def test_pv_surplus_with_positive_battery_w_charges_at_max(self) -> None: """Fix 2026-06-16: plán chce nabíjet z přebytku (bat_w>0) → 108=max (ne 0). Baterka nabere kolik zvládne, přebytek nad nabíjecí rychlost jde do sítě (BA81). """ ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="PASSIVE", self_sustain_local_use=False, bat_w=5000, grid_w=-2000, max_charge_a=100, max_discharge_a=100, export_mode="PV_SURPLUS", export_ban=False, ) self.assertEqual(ch, 100) self.assertEqual(dis, 100) def test_pv_surplus_near_full_tops_off_for_calibration(self) -> None: """SoC u maxima (97 >= 100-3) + přebytek → 108=max i při bat_w<=0 (BMS kalibrace).""" ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="PASSIVE", self_sustain_local_use=False, bat_w=0, grid_w=-2000, max_charge_a=100, max_discharge_a=100, export_mode="PV_SURPLUS", export_ban=False, current_soc_pct=97.0, max_soc_pct=100, ) self.assertEqual(ch, 100) def test_pv_surplus_sell_hold_far_from_full_zeros_charge(self) -> None: """Prodej PV a drž baterku daleko od maxima (bat_w<=0, SoC nízko) → 108=0.""" ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="PASSIVE", self_sustain_local_use=False, bat_w=0, grid_w=-2000, max_charge_a=100, max_discharge_a=100, export_mode="PV_SURPLUS", export_ban=False, current_soc_pct=60.0, max_soc_pct=100, ) self.assertEqual(ch, 0) self.assertEqual(dis, 100) def test_passive_charge_without_export_mode_uses_max_108(self) -> None: ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="PASSIVE", self_sustain_local_use=False, bat_w=5000, grid_w=0, max_charge_a=100, max_discharge_a=100, export_mode="NONE", export_ban=False, ) self.assertEqual(ch, 100) self.assertEqual(dis, 100) def test_legacy_negative_grid_infers_pv_surplus(self) -> None: ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="PASSIVE", self_sustain_local_use=False, bat_w=0, grid_w=-2000, max_charge_a=100, max_discharge_a=100, export_mode=None, export_ban=False, ) self.assertEqual(ch, 0) self.assertEqual(dis, 100) def test_charge_mode_still_scales_108_from_battery_w(self) -> None: ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="CHARGE", self_sustain_local_use=False, bat_w=2000, grid_w=3000, max_charge_a=100, max_discharge_a=100, ) self.assertLess(ch, 100) self.assertGreater(ch, 0) self.assertEqual(dis, 0) def test_sell_skips_charge_amp_write(self) -> None: ch, dis = deye_battery_charge_discharge_amps( lock_battery=False, deye_mode="SELL", self_sustain_local_use=False, bat_w=-3000, grid_w=-2000, max_charge_a=100, max_discharge_a=80, ) self.assertIsNone(ch) self.assertEqual(dis, 80) if __name__ == "__main__": unittest.main()