zkraceni intervalu planneru na max 35h
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
from services.planning_engine import (
|
||||
@@ -128,7 +128,6 @@ class PlanningDispatchMilpTests(unittest.TestCase):
|
||||
50.0,
|
||||
tuv_delta_stats=None,
|
||||
operating_mode="AUTO",
|
||||
price_failsafe_active=False,
|
||||
)
|
||||
self.assertGreaterEqual(ms, 0)
|
||||
self.assertEqual(len(results), 1)
|
||||
@@ -169,7 +168,6 @@ class PlanningDispatchMilpTests(unittest.TestCase):
|
||||
50.0,
|
||||
tuv_delta_stats=None,
|
||||
operating_mode="AUTO",
|
||||
price_failsafe_active=False,
|
||||
)
|
||||
self.assertEqual(len(results), 2)
|
||||
|
||||
@@ -206,7 +204,6 @@ class PlanningDispatchMilpTests(unittest.TestCase):
|
||||
50.0,
|
||||
tuv_delta_stats=None,
|
||||
operating_mode="AUTO",
|
||||
price_failsafe_active=False,
|
||||
)
|
||||
self.assertGreaterEqual(results[0].grid_setpoint_w, 0)
|
||||
|
||||
@@ -247,7 +244,6 @@ class PlanningDispatchMilpTests(unittest.TestCase):
|
||||
50.0,
|
||||
tuv_delta_stats=None,
|
||||
operating_mode="AUTO",
|
||||
price_failsafe_active=False,
|
||||
)
|
||||
reserve_pct = 20.0
|
||||
for r in results:
|
||||
@@ -302,7 +298,6 @@ class PlanningDispatchMilpTests(unittest.TestCase):
|
||||
50.0,
|
||||
tuv_delta_stats=None,
|
||||
operating_mode="AUTO",
|
||||
price_failsafe_active=False,
|
||||
)
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertGreaterEqual(
|
||||
@@ -312,5 +307,79 @@ class PlanningDispatchMilpTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TerminalSocShadowTests(unittest.TestCase):
|
||||
"""Terminal SoC shadow price v objective drží konec horizontu nad holým minimem."""
|
||||
|
||||
def test_terminal_soc_shadow_price_prevents_drain(self) -> None:
|
||||
base = datetime(2026, 4, 3, 12, 0, tzinfo=timezone.utc)
|
||||
slots = []
|
||||
for i in range(3):
|
||||
slots.append(
|
||||
PlanningSlot(
|
||||
interval_start=base + timedelta(minutes=15 * i),
|
||||
buy_price=2.0,
|
||||
sell_price=0.6,
|
||||
pv_a_forecast_w=0,
|
||||
pv_b_forecast_w=0,
|
||||
load_baseline_w=600,
|
||||
ev1_connected=False,
|
||||
ev2_connected=False,
|
||||
is_predicted_price=False,
|
||||
)
|
||||
)
|
||||
slots.append(
|
||||
PlanningSlot(
|
||||
interval_start=base + timedelta(minutes=45),
|
||||
buy_price=2.0,
|
||||
sell_price=14.0,
|
||||
pv_a_forecast_w=0,
|
||||
pv_b_forecast_w=0,
|
||||
load_baseline_w=600,
|
||||
ev1_connected=False,
|
||||
ev2_connected=False,
|
||||
is_predicted_price=False,
|
||||
)
|
||||
)
|
||||
battery = _battery(uc_wh=12_000.0, min_pct=12.0, arb_pct=20.0)
|
||||
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=20_000)
|
||||
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 = 0.5 * battery.usable_capacity_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), 4)
|
||||
# Bez shadow price by solver mohl končit u min SoC; kotva drží znatelnou rezervu.
|
||||
self.assertGreaterEqual(
|
||||
results[-1].battery_soc_target,
|
||||
15.0,
|
||||
msg="terminal SoC shadow price should keep end-of-horizon SoC above bare minimum",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user