dalsi ladenik
This commit is contained in:
@@ -32,10 +32,17 @@ def _future_sell(slots: list[PlanningSlot], t: int) -> float:
|
||||
return max(tail) if tail else float(slots[t].sell_price)
|
||||
|
||||
|
||||
def _buy_min_next_n(slots: list[PlanningSlot], t: int, n: int = _LOOKAHEAD_SLOTS) -> float | None:
|
||||
def _buy_min_next_n(
|
||||
slots: list[PlanningSlot],
|
||||
t: int,
|
||||
n: int = _LOOKAHEAD_SLOTS,
|
||||
*,
|
||||
export_window_start: datetime | None = None,
|
||||
) -> float | None:
|
||||
tail = [
|
||||
float(slots[i].buy_price)
|
||||
for i in range(t + 1, min(t + 1 + n, len(slots)))
|
||||
if export_window_start is None or slots[i].interval_start < export_window_start
|
||||
]
|
||||
return min(tail) if tail else None
|
||||
|
||||
@@ -74,6 +81,12 @@ def _select_charge_slots(
|
||||
(float(s.buy_price) for s in slots if _prague_hour(s) >= 12),
|
||||
default=min(float(s.buy_price) for s in slots),
|
||||
)
|
||||
ref_buy_global = min(float(s.buy_price) for s in slots)
|
||||
export_window_start: datetime | None = None
|
||||
for s in slots:
|
||||
if float(s.sell_price) > ref_buy_global + degrad:
|
||||
if export_window_start is None or s.interval_start < export_window_start:
|
||||
export_window_start = s.interval_start
|
||||
|
||||
eta = float(getattr(battery, "charge_efficiency", 1.0) or 1.0)
|
||||
max_p_w = float(getattr(battery, "max_charge_power_w", 0.0) or 0.0)
|
||||
@@ -117,18 +130,26 @@ def _select_charge_slots(
|
||||
buy = float(s.buy_price)
|
||||
if buy > ref_buy_seg + _BUY_CHARGE_BAND:
|
||||
return False
|
||||
nxt = _buy_min_next_n(slots, t)
|
||||
nxt = _buy_min_next_n(slots, t, export_window_start=export_window_start)
|
||||
if nxt is not None and buy > nxt + _BUY_LOOKAHEAD_EPS:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _grid_sort_key(t: int, pred: bool, price: float) -> tuple[int, int, float, int]:
|
||||
before_export = 0
|
||||
if export_window_start is not None and slots[t].interval_start < export_window_start:
|
||||
before_export = 0
|
||||
else:
|
||||
before_export = 1
|
||||
return (before_export, int(pred), price, t)
|
||||
|
||||
if purchase_pricing_mode != "fixed":
|
||||
am_candidates = [
|
||||
(t, getattr(slots[t], "is_predicted_price", False), float(slots[t].buy_price))
|
||||
for t in range(len(slots))
|
||||
if _grid_b_ok(t, ref_buy_am) and _prague_hour(slots[t]) < 12
|
||||
]
|
||||
am_candidates.sort(key=lambda x: (int(x[1]), x[2], x[0]))
|
||||
am_candidates.sort(key=lambda x: _grid_sort_key(x[0], x[1], x[2]))
|
||||
cum = 0.0
|
||||
grid_am = 0
|
||||
for t, _pred, _price in am_candidates:
|
||||
@@ -144,7 +165,7 @@ def _select_charge_slots(
|
||||
for t in range(len(slots))
|
||||
if _grid_b_ok(t, ref_buy_pm) and _prague_hour(slots[t]) >= 12
|
||||
]
|
||||
pm_candidates.sort(key=lambda x: (int(x[1]), x[2], x[0]))
|
||||
pm_candidates.sort(key=lambda x: _grid_sort_key(x[0], x[1], x[2]))
|
||||
cum = 0.0
|
||||
grid_pm = 0
|
||||
for t, _pred, _price in pm_candidates:
|
||||
@@ -326,6 +347,37 @@ class SelectChargeSlotsTests(unittest.TestCase):
|
||||
out = _select_charge_slots(slots, battery, current_soc_wh=0.0)
|
||||
self.assertIn(2, out, "Nejlevnější buy v horizontu (PM) musí být vybrán")
|
||||
|
||||
def test_pm_grid_prefers_today_before_export_over_tomorrow_cheaper(self) -> None:
|
||||
"""Dnes PM levné před večerním exportem má prioritu před zítřejším min(buy)."""
|
||||
base = datetime(2026, 5, 21, 10, 0, tzinfo=timezone.utc)
|
||||
slots = [
|
||||
_slot(buy=0.72, sell=-0.1, pv=500, load=3000, interval_start=base),
|
||||
_slot(buy=0.68, sell=-0.15, pv=500, load=3000, interval_start=base + timedelta(minutes=15)),
|
||||
_slot(
|
||||
buy=5.5,
|
||||
sell=3.8,
|
||||
pv=0,
|
||||
load=2500,
|
||||
interval_start=base + timedelta(hours=7, minutes=30),
|
||||
),
|
||||
_slot(
|
||||
buy=0.50,
|
||||
sell=-0.3,
|
||||
pv=2000,
|
||||
load=5000,
|
||||
interval_start=base + timedelta(hours=26),
|
||||
),
|
||||
]
|
||||
battery = _battery(uc_wh=64_000.0)
|
||||
out = _select_charge_slots(slots, battery, current_soc_wh=0.46 * battery.usable_capacity_wh)
|
||||
self.assertIn(0, out)
|
||||
self.assertIn(1, out)
|
||||
self.assertEqual(
|
||||
min(out),
|
||||
0,
|
||||
"Před exportním oknem musí být vybrány dnešní levné PM sloty dřív než zítřejší min(buy)",
|
||||
)
|
||||
|
||||
def test_cheap_pm_with_pv_surplus_gets_grid_charge(self) -> None:
|
||||
"""Regrese home-01: levné PM VT (~0,8) i s FVE musí projít grid maskou B."""
|
||||
base = datetime(2026, 5, 21, 10, 0, tzinfo=timezone.utc)
|
||||
|
||||
Reference in New Issue
Block a user