dalsi oprava
This commit is contained in:
@@ -3206,6 +3206,96 @@ class PreNegativeSellExportTests(unittest.TestCase):
|
||||
self.assertGreater(neg.battery_setpoint_w, 500, "záporný sell: PV do baterie")
|
||||
self.assertEqual(neg.export_mode, "NONE")
|
||||
|
||||
def test_ba81_fixed_morning_exports_pv_a_not_curtail(self) -> None:
|
||||
"""BA81: před sell<0 export celého přebytku FVE, ne jen MI (pv_b)."""
|
||||
prague = ZoneInfo("Europe/Prague")
|
||||
base = datetime(2026, 5, 27, 7, 30, tzinfo=prague)
|
||||
slots: list[PlanningSlot] = []
|
||||
for i in range(12):
|
||||
sell = 3.2 if i < 8 else -0.2
|
||||
slots.append(
|
||||
PlanningSlot(
|
||||
interval_start=(base + timedelta(minutes=15 * i)).astimezone(timezone.utc),
|
||||
buy_price=3.088,
|
||||
sell_price=sell,
|
||||
pv_a_forecast_w=5000,
|
||||
pv_b_forecast_w=700,
|
||||
load_baseline_w=200,
|
||||
ev1_connected=False,
|
||||
ev2_connected=False,
|
||||
allow_charge=True,
|
||||
allow_discharge_export=False,
|
||||
charge_acquisition_buy_czk_kwh=3.088,
|
||||
future_sell_opportunity_czk_kwh=6.5,
|
||||
)
|
||||
)
|
||||
battery = _battery(uc_wh=12_500.0, min_pct=10.0, arb_pct=30.0)
|
||||
battery.max_charge_power_w = 6250
|
||||
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=17_000,
|
||||
max_export_power_w=8000,
|
||||
block_export_on_negative_sell=False,
|
||||
purchase_pricing_mode="fixed",
|
||||
)
|
||||
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),
|
||||
]
|
||||
res, _, _ = solve_dispatch(
|
||||
slots, battery, hp, grid, [None, None], vehicles,
|
||||
0.95 * battery.soc_max_wh, 50.0, operating_mode="AUTO",
|
||||
)
|
||||
r0 = res[0]
|
||||
self.assertLess(r0.pv_a_curtailed_w, 500, "pole A nesmí jít do curtail při sell>0 před neg")
|
||||
self.assertLess(r0.grid_setpoint_w, -4000, "export přebytku A+B do site")
|
||||
|
||||
def test_rolling_horizon_drains_to_reserve_before_first_neg(self) -> None:
|
||||
"""Rolling bez D−1 večera: výboj před 1. sell<0 na reserve (+ slack)."""
|
||||
prague = ZoneInfo("Europe/Prague")
|
||||
base = datetime(2026, 5, 27, 7, 0, tzinfo=prague)
|
||||
slots: list[PlanningSlot] = []
|
||||
for i in range(16):
|
||||
local = base + timedelta(minutes=15 * i)
|
||||
sell = 3.0 if i < 10 else -0.2
|
||||
slots.append(
|
||||
PlanningSlot(
|
||||
interval_start=local.astimezone(timezone.utc),
|
||||
buy_price=5.0,
|
||||
sell_price=sell,
|
||||
pv_a_forecast_w=3000,
|
||||
pv_b_forecast_w=1500,
|
||||
load_baseline_w=500,
|
||||
ev1_connected=False,
|
||||
ev2_connected=False,
|
||||
allow_charge=True,
|
||||
allow_discharge_export=True,
|
||||
)
|
||||
)
|
||||
bat = NegSellSocPhaseTests._phase_battery()
|
||||
bat.reserve_soc_wh = 0.20 * bat.usable_capacity_wh
|
||||
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,
|
||||
block_export_on_negative_sell=False,
|
||||
)
|
||||
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),
|
||||
]
|
||||
res, _, snap = solve_dispatch(
|
||||
slots, bat, hp, grid, [None, None], vehicles,
|
||||
0.55 * bat.soc_max_wh, 50.0, operating_mode="AUTO",
|
||||
)
|
||||
anchors = snap["inputs"].get("neg_evening_reserve_soc_anchors") or []
|
||||
self.assertGreaterEqual(len(anchors), 1)
|
||||
anchor_iso = anchors[-1]["slot"]
|
||||
idx = next(i for i, s in enumerate(slots) if s.interval_start.isoformat() == anchor_iso)
|
||||
cap_wh = float(bat.reserve_soc_wh) + 400.0
|
||||
soc_wh = res[idx].battery_soc_target / 100.0 * bat.soc_max_wh
|
||||
self.assertLessEqual(soc_wh, cap_wh + 800.0)
|
||||
|
||||
def test_kv1_evening_battery_push_when_sell_below_fixed_buy(self) -> None:
|
||||
"""KV1: večerní sell < fixní buy — přesto vývoz bat (ne jen jeden peak slot)."""
|
||||
prague = ZoneInfo("Europe/Prague")
|
||||
@@ -4248,7 +4338,7 @@ class NegSellPrepWindowV36Tests(unittest.TestCase):
|
||||
50.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-28-neg-prep-window-v36e")
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-28-neg-prep-window-v36g")
|
||||
anchors = snap["inputs"].get("neg_evening_reserve_soc_anchors") or []
|
||||
self.assertGreaterEqual(len(anchors), 1)
|
||||
anchor_iso = anchors[-1]["slot"]
|
||||
|
||||
Reference in New Issue
Block a user