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

This commit is contained in:
Dusan Vojacek
2026-05-29 23:04:27 +02:00
parent 877f5b6180
commit b73c3323e1
4 changed files with 115 additions and 53 deletions

View File

@@ -71,7 +71,7 @@ NEG_BUY_CHARGE_SHORTFALL_PENALTY_CZK_KWH = 100.0
PRE_NEG_CHARGE_PENALTY_CZK_KWH = 400.0
PRE_NEG_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
PRE_NEG_BATT_EXPORT_MIN_SELL_CZK_KWH = 1.0
PLANNER_BUILD_TAG = "2026-05-29-evening-peak-only-export-v41"
PLANNER_BUILD_TAG = "2026-05-29-evening-push-budget-rank-v42"
# Po t_detach v prep: necpát PV do bat (měkké; tvrdý hold přes soc_target z rampy).
NEG_SELL_POST_DETACH_BCPV_DISCOURAGE_CZK_KWH = 250.0
# Večer před neg dnem: výboj do sítě (měkký shortfall na ge_bat).
@@ -1642,22 +1642,26 @@ def _slot_evening_push_profitable(
return float(slot.sell_price) > float(charge_acquisition_czk_kwh) + float(min_spread)
def _evening_push_peak_candidates(slots: list[PlanningSlot]) -> list[int]:
"""
Kandidáti tvrdého večerního push: sloty na **max sell** v nočním úseku
(ne široké pásmo peakdegrad — ten rozplizňoval export do levnějších slotů).
"""
candidates: list[int] = []
for seg in _night_export_window_segments(slots):
if not seg:
def _evening_push_segment_candidates(
slots: list[PlanningSlot],
seg: list[int],
*,
charge_acquisition_czk_kwh: float,
min_spread: float,
) -> list[int]:
"""Profitable sloty v nočním úseku — výběr pořadí a strop dělá rozpočet Wh (sell desc)."""
if not seg:
return []
out: list[int] = []
for t in seg:
if not _slot_evening_push_profitable(
slots[t],
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
min_spread=min_spread,
):
continue
seg_peak = max(float(slots[t].sell_price) for t in seg)
if seg_peak <= 0.0:
continue
for t in seg:
if float(slots[t].sell_price) >= seg_peak - 1e-6:
candidates.append(t)
return candidates
out.append(t)
return out
def _evening_battery_export_push_indices(
@@ -1673,24 +1677,13 @@ def _evening_battery_export_push_indices(
evening_start_hour: int = 17,
) -> list[int]:
"""
Noční push: plný ge_bat v tolika nejdražších peak slotech (shodná max sell v úseku),
kolik unese Wh rozpočet. Řazení sell desc; přidávat sloty dokud kumulované Wh ≤ push_budget.
Noční push: plný ge_bat v tolika nejdražších slotách (sell desc v rámci úseku),
kolik unese Wh rozpočet — ne jen jeden slot s exact max sell (v41).
per_slot_discharge_wh: volající předá min(BMS, export cap) × účinnost × 0,25 h.
"""
_ = evening_start_hour # kompatibilita volání
if per_slot_discharge_wh <= 0.0:
return []
candidates = [
t
for t in _evening_push_peak_candidates(slots)
if _slot_evening_push_profitable(
slots[t],
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
min_spread=degrad_czk_kwh,
)
]
if not candidates:
return []
push_budget_wh = _evening_push_discharge_budget_wh(
current_soc_wh=current_soc_wh,
min_soc_wh=min_soc_wh,
@@ -1699,18 +1692,27 @@ def _evening_battery_export_push_indices(
)
if push_budget_wh < per_slot_discharge_wh * 0.5:
return []
ranked = sorted(
candidates,
key=lambda i: (float(slots[i].sell_price), -i),
reverse=True,
)
out: list[int] = []
cum_wh = 0.0
for t in ranked:
if cum_wh + per_slot_discharge_wh > push_budget_wh + 1e-6:
break
out.append(t)
cum_wh += per_slot_discharge_wh
remaining_wh = float(push_budget_wh)
for seg in _night_export_window_segments(slots):
candidates = _evening_push_segment_candidates(
slots,
seg,
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
min_spread=degrad_czk_kwh,
)
if not candidates:
continue
ranked = sorted(
candidates,
key=lambda i: (float(slots[i].sell_price), -i),
reverse=True,
)
for t in ranked:
if remaining_wh + 1e-6 < per_slot_discharge_wh:
break
out.append(t)
remaining_wh -= per_slot_discharge_wh
return sorted(out)