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

This commit is contained in:
Dusan Vojacek
2026-05-23 22:41:00 +02:00
parent 0f922c91f5
commit 645f48036d
3 changed files with 101 additions and 45 deletions

View File

@@ -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í 0006 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),