tuning prodeje
Some checks failed
CI and deploy / migration-check (push) Failing after 18s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-29 22:45:02 +02:00
parent 230351b38a
commit 877f5b6180
4 changed files with 150 additions and 60 deletions

View File

@@ -153,10 +153,9 @@ class EveningPushBudgetTests(unittest.TestCase):
per_slot = 17_000 * 0.95 * 0.25
bat = _battery(uc_wh=64_000.0, min_pct=10.0, max_pct=95.0)
soc_high = 0.92 * bat.soc_max_wh
profitable = set(range(len(slots)))
push_hi = _evening_battery_export_push_indices(
slots,
profitable_export_ts=profitable,
charge_acquisition_czk_kwh=0.5,
degrad_czk_kwh=0.15,
current_soc_wh=soc_high,
min_soc_wh=bat.min_soc_wh,
@@ -164,11 +163,11 @@ class EveningPushBudgetTests(unittest.TestCase):
per_slot_discharge_wh=per_slot,
discharge_slot_buffer=1.5,
)
self.assertGreaterEqual(len(push_hi), 3)
self.assertGreaterEqual(len(push_hi), 1)
soc_low = bat.min_soc_wh + 100.0
push_lo = _evening_battery_export_push_indices(
slots,
profitable_export_ts=profitable,
charge_acquisition_czk_kwh=0.5,
degrad_czk_kwh=0.15,
current_soc_wh=soc_low,
min_soc_wh=bat.min_soc_wh,
@@ -236,7 +235,7 @@ class EveningPushBudgetTests(unittest.TestCase):
per_slot = 18_000 * 0.95 * 0.25
push = _evening_battery_export_push_indices(
slots,
profitable_export_ts={0, 1, 2, 3},
charge_acquisition_czk_kwh=0.5,
degrad_czk_kwh=0.15,
current_soc_wh=0.9 * bat.soc_max_wh,
min_soc_wh=bat.min_soc_wh,
@@ -244,8 +243,7 @@ class EveningPushBudgetTests(unittest.TestCase):
per_slot_discharge_wh=per_slot,
discharge_slot_buffer=1.5,
)
self.assertIn(2, push, "nejvyšší sell 00:00 má být v push (top-3 v nočním úseku)")
self.assertEqual(max(float(slots[t].sell_price) for t in push), 3.586)
self.assertEqual(push, [2], "push jen slot(y) s max sell v nočním úseku")
def test_evening_push_budget_matches_r063_formula(self) -> None:
bat = _battery(uc_wh=64_000.0, min_pct=10.0, max_pct=95.0)
@@ -263,7 +261,7 @@ class EveningPushBudgetTests(unittest.TestCase):
def test_push_slot_count_follows_wh_budget_not_fixed_top_n(self) -> None:
"""v38: počet push slotů = floor(rozpočet Wh / per_slot), sell desc — ne pevné top-3."""
prague = ZoneInfo("Europe/Prague")
sells = [10.0, 9.92, 9.88, 5.0, 4.0, 3.0]
sells = [10.0, 10.0, 10.0, 5.0, 4.0, 3.0]
base = datetime(2026, 5, 25, 18, 0, tzinfo=prague)
slots = [
PlanningSlot(
@@ -282,8 +280,6 @@ class EveningPushBudgetTests(unittest.TestCase):
]
bat = _battery(uc_wh=64_000.0, min_pct=10.0, max_pct=95.0)
per_slot = 17_000 * 0.95 * 0.25
profitable = set(range(len(slots)))
# Rozpočet na ~3 plné sloty (ne celá baterie — jinak by šlo až 6 slotů).
soc_three_slots = bat.min_soc_wh + 3.2 * per_slot
budget = _evening_push_discharge_budget_wh(
current_soc_wh=soc_three_slots,
@@ -291,13 +287,10 @@ class EveningPushBudgetTests(unittest.TestCase):
soc_max_wh=bat.soc_max_wh,
discharge_slot_buffer=1.5,
)
expected_n = min(
len(slots),
max(0, int(budget // per_slot)),
)
expected_n = min(3, max(0, int(budget // per_slot)))
push = _evening_battery_export_push_indices(
slots,
profitable_export_ts=profitable,
charge_acquisition_czk_kwh=0.5,
degrad_czk_kwh=0.15,
current_soc_wh=soc_three_slots,
min_soc_wh=bat.min_soc_wh,
@@ -311,7 +304,7 @@ class EveningPushBudgetTests(unittest.TestCase):
# Více SoC → více push slotů (dynamicky, ne strop 3).
push_hi = _evening_battery_export_push_indices(
slots,
profitable_export_ts=profitable,
charge_acquisition_czk_kwh=0.5,
degrad_czk_kwh=0.15,
current_soc_wh=0.9 * bat.soc_max_wh,
min_soc_wh=bat.min_soc_wh,
@@ -2570,9 +2563,9 @@ class ChargeAcquisitionArbitrageTests(unittest.TestCase):
self.assertLess(evening.battery_setpoint_w, -500)
def test_evening_export_in_all_top_three_peak_slots_not_only_last(self) -> None:
"""MILP v38: export v každém z top-3 večerních sell slotů, ne až v posledním."""
"""MILP v41: plný export ve všech slotech se shodnou max sell v nočním úseku."""
prague = ZoneInfo("Europe/Prague")
sells = [10.0, 9.92, 9.88, 5.0, 4.0, 3.0]
sells = [10.0, 10.0, 10.0, 5.0, 4.0, 3.0]
base = datetime(2026, 5, 25, 18, 0, tzinfo=prague)
slots = [
PlanningSlot(
@@ -2627,6 +2620,57 @@ class ChargeAcquisitionArbitrageTests(unittest.TestCase):
)
self.assertEqual(r.export_mode, "BATTERY_SELL")
def test_evening_no_spread_export_below_segment_peak_home01(self) -> None:
"""home-01 večer: plný export jen v max-sell slotu, ne rozpliznutí do levnějších sousedů."""
prague = ZoneInfo("Europe/Prague")
sells = [3.834, 3.518, 3.204, 3.204, 3.136, 3.020]
base = datetime(2026, 5, 29, 20, 15, tzinfo=prague)
slots = [
PlanningSlot(
interval_start=base + timedelta(minutes=15 * i),
buy_price=5.5,
sell_price=sells[i],
pv_a_forecast_w=0,
pv_b_forecast_w=0,
load_baseline_w=2973,
ev1_connected=False,
ev2_connected=False,
allow_charge=False,
allow_discharge_export=True,
charge_acquisition_buy_czk_kwh=0.8,
)
for i in range(6)
]
battery = _battery(uc_wh=64_000.0, terminal_soc_value_factor=0.0)
battery.max_discharge_power_w = 18_000
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=13_500)
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),
]
results, _ms, snap = solve_dispatch(
slots,
battery,
hp,
grid,
[None, None],
vehicles,
0.81 * battery.soc_max_wh,
50.0,
operating_mode="AUTO",
)
self.assertEqual(snap["planner_build_tag"], PLANNER_BUILD_TAG)
push_iso = set(snap["inputs"].get("evening_push_ts") or [])
self.assertIn(slots[0].interval_start.isoformat(), push_iso)
self.assertGreaterEqual(abs(results[0].grid_setpoint_w), 12_500)
for i in range(1, 6):
self.assertGreaterEqual(
results[i].grid_setpoint_w,
-500,
msg=f"slot {i} sell={sells[i]} must not export below segment peak",
)
def test_no_pv_export_at_low_sell_when_evening_peak_much_higher(self) -> None:
"""Odpolední sell ~1,4 a večer ~5,5 — PV do baterie, ne FVE→síť za haléř."""
base = datetime(2026, 5, 21, 12, 0, tzinfo=timezone.utc)