dalsi oprava
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user