fix KV1/BA81 cyklovani
This commit is contained in:
@@ -56,6 +56,7 @@ def _slot(
|
||||
safety: float | None = None,
|
||||
fut_buy: float | None = None,
|
||||
fut_sell: float | None = None,
|
||||
daytime_pv_surplus: bool = False,
|
||||
) -> PlanningSlot:
|
||||
return PlanningSlot(
|
||||
interval_start=t0 + timedelta(minutes=15 * idx),
|
||||
@@ -71,6 +72,7 @@ def _slot(
|
||||
safety_soc_target_wh=safety,
|
||||
future_avoided_buy_czk_kwh=fut_buy,
|
||||
future_sell_opportunity_czk_kwh=fut_sell,
|
||||
is_daytime_pv_surplus_slot=daytime_pv_surplus,
|
||||
)
|
||||
|
||||
|
||||
@@ -135,6 +137,150 @@ class PlanningSafetyCommitmentTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(snap2["chosen_slots"]["charge_commitment"], [])
|
||||
|
||||
def test_export_floor_uses_safety_target_in_non_high_sell_slot(self) -> None:
|
||||
"""Regrese: safety target nemá tlačit jen přes objective, ale chránit export floor."""
|
||||
t0 = datetime(2026, 5, 4, 8, 0, tzinfo=timezone.utc)
|
||||
# Slot 0 není high-sell (future max sell je vyšší), ale safety target je nad arb_base.
|
||||
slots = [
|
||||
_slot(
|
||||
t0,
|
||||
0,
|
||||
buy=3.0,
|
||||
sell=2.0,
|
||||
pv_a=8000,
|
||||
load=500,
|
||||
safety=12_000.0,
|
||||
fut_sell=6.0, # high-sell somewhere later, not this slot
|
||||
daytime_pv_surplus=True,
|
||||
),
|
||||
_slot(
|
||||
t0,
|
||||
1,
|
||||
buy=3.0,
|
||||
sell=6.0,
|
||||
pv_a=0,
|
||||
load=500,
|
||||
safety=12_000.0,
|
||||
fut_sell=6.0,
|
||||
daytime_pv_surplus=False,
|
||||
),
|
||||
]
|
||||
hp, grid = _hp(), _grid()
|
||||
vehicles = [
|
||||
SimpleNamespace(max_charge_power_w=0, battery_capacity_kwh=1.0, default_target_soc_pct=80.0)
|
||||
] * 2
|
||||
_res, _ms, snap = solve_dispatch(
|
||||
slots,
|
||||
_bat(arb_floor_wh=4000.0, reserve_soc_wh=4000.0, min_soc_wh=2000.0, soc_max_wh=19_000.0),
|
||||
hp,
|
||||
grid,
|
||||
[None, None],
|
||||
vehicles,
|
||||
current_soc_wh=8000.0,
|
||||
current_tuv_temp_c=50.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
b0 = snap["soc_bounds"][0]
|
||||
self.assertEqual(b0["export_floor_reason"], "safety_export_floor")
|
||||
self.assertEqual(float(b0["export_soc_floor_wh"]), 12_000.0)
|
||||
self.assertFalse(bool(b0["high_sell_slot"]))
|
||||
|
||||
def test_export_floor_keeps_arb_base_in_high_sell_slot(self) -> None:
|
||||
"""High-sell výjimka: v peak slotu nesmí safety floor blokovat arbitráž."""
|
||||
t0 = datetime(2026, 5, 4, 8, 0, tzinfo=timezone.utc)
|
||||
# Slot 0 je high-sell (sell == future max), safety target je nad arb_base, ale nemá se aplikovat.
|
||||
slots = [
|
||||
_slot(
|
||||
t0,
|
||||
0,
|
||||
buy=3.0,
|
||||
sell=6.0,
|
||||
pv_a=0,
|
||||
load=500,
|
||||
safety=12_000.0,
|
||||
fut_sell=6.0,
|
||||
daytime_pv_surplus=False,
|
||||
),
|
||||
_slot(
|
||||
t0,
|
||||
1,
|
||||
buy=3.0,
|
||||
sell=2.0,
|
||||
pv_a=0,
|
||||
load=500,
|
||||
safety=12_000.0,
|
||||
fut_sell=6.0,
|
||||
daytime_pv_surplus=False,
|
||||
),
|
||||
]
|
||||
hp, grid = _hp(), _grid()
|
||||
vehicles = [
|
||||
SimpleNamespace(max_charge_power_w=0, battery_capacity_kwh=1.0, default_target_soc_pct=80.0)
|
||||
] * 2
|
||||
_res, _ms, snap = solve_dispatch(
|
||||
slots,
|
||||
_bat(arb_floor_wh=4000.0, reserve_soc_wh=4000.0, min_soc_wh=2000.0, soc_max_wh=19_000.0),
|
||||
hp,
|
||||
grid,
|
||||
[None, None],
|
||||
vehicles,
|
||||
current_soc_wh=8000.0,
|
||||
current_tuv_temp_c=50.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
b0 = snap["soc_bounds"][0]
|
||||
self.assertTrue(bool(b0["high_sell_slot"]))
|
||||
self.assertEqual(b0["export_floor_reason"], "arb_base")
|
||||
self.assertEqual(float(b0["export_soc_floor_wh"]), 4000.0)
|
||||
|
||||
def test_safety_penalty_only_active_in_daytime_pv_surplus_slots(self) -> None:
|
||||
t0 = datetime(2026, 5, 4, 8, 0, tzinfo=timezone.utc)
|
||||
slots = [
|
||||
_slot(
|
||||
t0,
|
||||
0,
|
||||
buy=3.0,
|
||||
sell=2.0,
|
||||
pv_a=8000,
|
||||
load=500,
|
||||
safety=12_000.0,
|
||||
fut_sell=6.0,
|
||||
daytime_pv_surplus=True,
|
||||
),
|
||||
_slot(
|
||||
t0,
|
||||
1,
|
||||
buy=3.0,
|
||||
sell=2.0,
|
||||
pv_a=0,
|
||||
load=500,
|
||||
safety=12_000.0,
|
||||
fut_sell=6.0,
|
||||
daytime_pv_surplus=False,
|
||||
),
|
||||
]
|
||||
hp, grid = _hp(), _grid()
|
||||
vehicles = [
|
||||
SimpleNamespace(max_charge_power_w=0, battery_capacity_kwh=1.0, default_target_soc_pct=80.0)
|
||||
] * 2
|
||||
_res, _ms, snap = solve_dispatch(
|
||||
slots,
|
||||
_bat(),
|
||||
hp,
|
||||
grid,
|
||||
[None, None],
|
||||
vehicles,
|
||||
current_soc_wh=8000.0,
|
||||
current_tuv_temp_c=50.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
t0o = snap["objective_terms"][0]
|
||||
t1o = snap["objective_terms"][1]
|
||||
self.assertTrue(bool(t0o["safety_penalty_active"]))
|
||||
self.assertGreater(float(t0o["safety_deficit_penalty_czk_per_wh"]), 0.0)
|
||||
self.assertFalse(bool(t1o["safety_penalty_active"]))
|
||||
self.assertEqual(float(t1o["safety_deficit_penalty_czk_per_wh"]), 0.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user