dalsi oprava
Some checks failed
CI and deploy / migration-check (push) Failing after 17s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-27 07:45:50 +02:00
parent 8c7072da07
commit 4e5de5df90
3 changed files with 144 additions and 17 deletions

View File

@@ -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 D1 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"]