dalsi a dalsi oprava
This commit is contained in:
@@ -31,18 +31,26 @@ def _prague_date(s: PlanningSlot) -> date:
|
||||
return s.interval_start.astimezone(_PRAGUE).date()
|
||||
|
||||
|
||||
def _export_window_start(slots: list[PlanningSlot], degrad: float) -> datetime | None:
|
||||
"""Kopie R__063: sell > min(buy) téhož kalendářního dne (Prague) + degrad."""
|
||||
result: datetime | None = None
|
||||
def _export_window_start_by_day(
|
||||
slots: list[PlanningSlot], degrad: float
|
||||
) -> dict[date, datetime]:
|
||||
"""Kopie R__063: první sell > min(buy) téhož kalendářního dne (Prague) + degrad."""
|
||||
out: dict[date, datetime] = {}
|
||||
for s in slots:
|
||||
day = _prague_date(s)
|
||||
day_min = min(
|
||||
float(x.buy_price) for x in slots if _prague_date(x) == day
|
||||
)
|
||||
day_min = min(float(x.buy_price) for x in slots if _prague_date(x) == day)
|
||||
if float(s.sell_price) > day_min + degrad:
|
||||
if result is None or s.interval_start < result:
|
||||
result = s.interval_start
|
||||
return result
|
||||
prev = out.get(day)
|
||||
if prev is None or s.interval_start < prev:
|
||||
out[day] = s.interval_start
|
||||
return out
|
||||
|
||||
|
||||
def _before_day_export(
|
||||
slots: list[PlanningSlot], t: int, export_by_day: dict[date, datetime]
|
||||
) -> bool:
|
||||
start = export_by_day.get(_prague_date(slots[t]))
|
||||
return start is not None and slots[t].interval_start < start
|
||||
|
||||
|
||||
def _future_sell(slots: list[PlanningSlot], t: int) -> float:
|
||||
@@ -55,13 +63,13 @@ def _buy_min_next_n(
|
||||
t: int,
|
||||
n: int = _LOOKAHEAD_SLOTS,
|
||||
*,
|
||||
export_window_start: datetime | None = None,
|
||||
export_by_day: dict[date, 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
|
||||
]
|
||||
tail: list[float] = []
|
||||
for i in range(t + 1, min(t + 1 + n, len(slots))):
|
||||
day_start = export_by_day.get(_prague_date(slots[i])) if export_by_day else None
|
||||
if day_start is None or slots[i].interval_start < day_start:
|
||||
tail.append(float(slots[i].buy_price))
|
||||
return min(tail) if tail else None
|
||||
|
||||
|
||||
@@ -100,7 +108,7 @@ def _select_charge_slots(
|
||||
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 = _export_window_start(slots, degrad)
|
||||
export_by_day = _export_window_start_by_day(slots, degrad)
|
||||
plan_day = _prague_date(slots[0])
|
||||
|
||||
eta = float(getattr(battery, "charge_efficiency", 1.0) or 1.0)
|
||||
@@ -161,12 +169,7 @@ def _select_charge_slots(
|
||||
|
||||
def _grid_sort_key(t: int, pred: bool, price: float) -> tuple[int, int, int, float, int]:
|
||||
today_first = 0 if _prague_date(slots[t]) == plan_day else 1
|
||||
before_export = (
|
||||
0
|
||||
if export_window_start is not None
|
||||
and slots[t].interval_start < export_window_start
|
||||
else 1
|
||||
)
|
||||
before_export = 0 if _before_day_export(slots, t, export_by_day) else 1
|
||||
return (today_first, before_export, int(pred), price, t)
|
||||
|
||||
if purchase_pricing_mode != "fixed":
|
||||
@@ -733,6 +736,38 @@ class FixedPurchasePricingTests(unittest.TestCase):
|
||||
night = {t for t in out if _prague_hour(slots[t]) < 8}
|
||||
self.assertGreater(len(night), 0, "očekáváno grid nabíjení v noci před večerním výkupem")
|
||||
|
||||
def test_fixed_nt_charge_after_yesterday_evening_peak(self) -> None:
|
||||
"""Včerejší večerní peak nesmí zablokovat dnešní 00–06 grid (per-day export okno)."""
|
||||
base = datetime(2026, 5, 23, 20, 0, tzinfo=_PRAGUE)
|
||||
slots: list[PlanningSlot] = []
|
||||
for i in range(64):
|
||||
t = base + timedelta(minutes=15 * i)
|
||||
sell = 3.8 if t.hour >= 20 and t.date() == date(2026, 5, 23) else 2.9
|
||||
if t.date() == date(2026, 5, 24) and t.hour >= 19:
|
||||
sell = 3.7
|
||||
slots.append(
|
||||
_slot(
|
||||
buy=3.088,
|
||||
sell=sell,
|
||||
load=200,
|
||||
interval_start=t.astimezone(timezone.utc),
|
||||
)
|
||||
)
|
||||
battery = _battery(charge_buf=1.3, uc_wh=12_500.0)
|
||||
out = _select_charge_slots(
|
||||
slots,
|
||||
battery,
|
||||
current_soc_wh=0.33 * battery.usable_capacity_wh,
|
||||
purchase_pricing_mode="fixed",
|
||||
)
|
||||
may24_night = {
|
||||
t
|
||||
for t in out
|
||||
if _prague_date(slots[t]) == date(2026, 5, 24)
|
||||
and _prague_hour(slots[t]) < 6
|
||||
}
|
||||
self.assertGreater(len(may24_night), 0)
|
||||
|
||||
def test_fixed_allows_discharge_on_high_sell(self) -> None:
|
||||
slots = [
|
||||
_slot(buy=3.09, sell=1.0, hour_utc=10),
|
||||
|
||||
Reference in New Issue
Block a user