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

This commit is contained in:
Dusan Vojacek
2026-05-23 22:30:46 +02:00
parent dbc004a949
commit 0f922c91f5
4 changed files with 192 additions and 2 deletions

View File

@@ -215,6 +215,63 @@ def _select_charge_slots(
if float(s.buy_price) < 0:
selected.add(t)
elif purchase_pricing_mode == "fixed" and any(
float(s.sell_price) > float(s.buy_price) + degrad for s in slots
):
am_candidates = [
(t, getattr(slots[t], "is_predicted_price", False))
for t in range(len(slots))
if _prague_hour(slots[t]) < 12
]
am_candidates.sort(
key=lambda x: (
_grid_sort_key(x[0], x[1], 0.0)[0],
_grid_sort_key(x[0], x[1], 0.0)[1],
_grid_sort_key(x[0], x[1], 0.0)[2],
x[0],
)
)
cum = 0.0
grid_am = 0
for t, _pred in am_candidates:
if cum >= chg_am or per_slot_full_wh <= 0 or grid_am >= cap_am:
break
selected.add(t)
cum += per_slot_full_wh
grid_am += 1
grid_filled_wh += cum
chg_pm = max(chg_pm, charge_target_wh - grid_filled_wh)
if per_slot_full_wh > 0:
cap_pm = max(
cap_pm,
min(
_MAX_GRID_CHARGE_CAP,
int(chg_pm / per_slot_full_wh * buf_mult) + 1,
),
)
pm_candidates = [
(t, getattr(slots[t], "is_predicted_price", False))
for t in range(len(slots))
if _prague_hour(slots[t]) >= 12
]
pm_candidates.sort(
key=lambda x: (
_grid_sort_key(x[0], x[1], 0.0)[0],
_grid_sort_key(x[0], x[1], 0.0)[1],
_grid_sort_key(x[0], x[1], 0.0)[2],
x[0],
)
)
cum = 0.0
grid_pm = 0
for t, _pred in pm_candidates:
if cum >= chg_pm or per_slot_full_wh <= 0 or grid_pm >= cap_pm:
break
selected.add(t)
cum += per_slot_full_wh
grid_pm += 1
grid_filled_wh += cum
pv_layer_cap = max(charge_target_wh - grid_filled_wh, 0.0)
pv_candidates: list[tuple[int, float, float]] = []
for t, s in enumerate(slots):
@@ -636,7 +693,8 @@ class SelectDischargeExportSlotsTests(unittest.TestCase):
class FixedPurchasePricingTests(unittest.TestCase):
def test_fixed_skips_non_pv_grid_charge_slots(self) -> None:
def test_fixed_skips_grid_charge_when_no_sell_arbitrage(self) -> None:
"""Fixní buy bez výkupu nad buy+degrad → žádné grid nabíjení."""
slots = [
_slot(buy=6.35, sell=2.0, hour_utc=14, load=500),
_slot(buy=6.35, sell=3.5, hour_utc=18, load=500),
@@ -650,6 +708,31 @@ class FixedPurchasePricingTests(unittest.TestCase):
)
self.assertEqual(out, set())
def test_fixed_grid_charge_before_evening_export(self) -> None:
"""BA81: konstantní buy, večerní sell > buy+degrad → NT/AM grid sloty."""
base = datetime(2026, 5, 24, 0, 0, tzinfo=_PRAGUE)
slots: list[PlanningSlot] = []
for i in range(96):
t = base + timedelta(minutes=15 * i)
sell = 3.75 if t.hour >= 18 else 2.8
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",
)
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_allows_discharge_on_high_sell(self) -> None:
slots = [
_slot(buy=3.09, sell=1.0, hour_utc=10),