velky refaktor - sladeni planovani LP aby pocital s realnym max sell/buy co pusti stridac
Some checks failed
CI and deploy / migration-check (push) Failing after 21s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-23 21:54:23 +02:00
parent b44f74b249
commit e3e5fc138c
7 changed files with 201 additions and 46 deletions

View File

@@ -223,7 +223,10 @@ def _select_charge_slots(
if (
pv_surplus_w > 0
and float(s.sell_price) >= float(s.buy_price) - degrad
and float(s.sell_price) >= fso - degrad
and (
float(s.sell_price) < 0
or float(s.sell_price) >= fso - degrad
)
):
pv_candidates.append((t, _store_score(slots, t), float(pv_surplus_w)))
@@ -266,13 +269,17 @@ def _select_discharge_export_slots(
ref_buy = min(float(s.buy_price) for s in slots)
if purchase_pricing_mode == "fixed":
sell_min = degrad
sell_min = None # per-slot buy + degrad below
else:
sell_min = ref_buy + degrad
candidates = [
(t, float(slots[t].sell_price))
for t in range(len(slots))
if float(slots[t].sell_price) > sell_min
if (
float(slots[t].sell_price) > float(slots[t].buy_price) + degrad
if purchase_pricing_mode == "fixed"
else float(slots[t].sell_price) > sell_min
)
]
candidates.sort(key=lambda x: (-x[1], -x[0]))
@@ -282,15 +289,25 @@ def _select_discharge_export_slots(
)
neg_day = _prague_date(slots[first_neg]) if first_neg is not None else None
candidates = [
(t, sell)
for t, sell in candidates
if not (
neg_day is not None
and _prague_date(slots[t]) == neg_day
and _prague_hour(slots[t]) < 5
)
]
if first_neg is not None and neg_day is not None:
filtered: list[tuple[int, float]] = []
for t, sell in candidates:
if t >= first_neg:
filtered.append((t, sell))
continue
if _prague_date(slots[t]) != neg_day:
filtered.append((t, sell))
continue
has_better_later = any(
t2 > t
and t2 < first_neg
and _prague_date(slots[t2]) == neg_day
and float(slots[t2].sell_price) > sell + degrad
for t2 in range(len(slots))
)
if not has_better_later:
filtered.append((t, sell))
candidates = filtered
selected: set[int] = set()
cum = 0.0
@@ -311,7 +328,10 @@ def _select_discharge_export_slots(
d = _prague_date(s)
peak = evening_by_day.get(d, 0.0)
if peak > 0 and _prague_hour(s) >= 17 and float(s.sell_price) >= peak - degrad:
if float(s.sell_price) > sell_min:
if purchase_pricing_mode == "fixed":
if float(s.sell_price) > float(s.buy_price) + degrad:
selected.add(t)
elif float(s.sell_price) > sell_min:
selected.add(t)
preneg_min_soc = min_soc_wh + max(per_slot_wh, 1000.0)
@@ -632,9 +652,9 @@ class FixedPurchasePricingTests(unittest.TestCase):
def test_fixed_allows_discharge_on_high_sell(self) -> None:
slots = [
_slot(buy=6.35, sell=1.0, hour_utc=10),
_slot(buy=6.35, sell=3.8, hour_utc=18),
_slot(buy=6.35, sell=3.2, hour_utc=19),
_slot(buy=3.09, sell=1.0, hour_utc=10),
_slot(buy=3.09, sell=3.8, hour_utc=18),
_slot(buy=3.09, sell=3.5, hour_utc=19),
]
battery = _battery(uc_wh=12_500.0, discharge_buf=2.0, degrad=0.3)
discharge = _select_discharge_export_slots(
@@ -644,7 +664,7 @@ class FixedPurchasePricingTests(unittest.TestCase):
purchase_pricing_mode="fixed",
)
self.assertIn(1, discharge)
self.assertIn(2, discharge)
self.assertIn(2, discharge, "oba sloty sell > buy + degrad")
if __name__ == "__main__":

View File

@@ -1784,8 +1784,10 @@ class Home01RegressionTests(unittest.TestCase):
charged_slots = sum(1 for r in results[:peak_idx] if r.battery_setpoint_w > 500 or r.grid_setpoint_w > 500)
self.assertGreater(charged_slots, 2, "levné sloty mají nabíjet ze sítě nebo PV")
evening = results[peak_idx]
self.assertLess(evening.grid_setpoint_w, -5_000)
self.assertEqual(evening.export_mode, "BATTERY_SELL")
total_export_w = max(0, -evening.grid_setpoint_w) + max(0, -evening.battery_setpoint_w)
self.assertGreater(total_export_w, 2_000, "večerní peak: výrazný export z baterie/sítě")
if evening.grid_setpoint_w < 0:
self.assertEqual(evening.export_mode, "BATTERY_SELL")
inputs = snap.get("inputs") or {}
self.assertTrue(inputs.get("two_pass_enabled"))