tuning prodeje
This commit is contained in:
@@ -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-neg-prep-infeasible-relax-v40b"
|
||||
PLANNER_BUILD_TAG = "2026-05-29-evening-peak-only-export-v41"
|
||||
# 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).
|
||||
@@ -1632,10 +1632,38 @@ def _evening_push_discharge_budget_wh(
|
||||
return min(available_wh, exportable_full_wh * buf)
|
||||
|
||||
|
||||
def _slot_evening_push_profitable(
|
||||
slot: PlanningSlot,
|
||||
*,
|
||||
charge_acquisition_czk_kwh: float,
|
||||
min_spread: float,
|
||||
) -> bool:
|
||||
"""Push večerní špičky: spot marže (acq+spread), ne fixní buy z konstantního horizontu."""
|
||||
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 peak−degrad — ten rozplizňoval export do levnějších slotů).
|
||||
"""
|
||||
candidates: list[int] = []
|
||||
for seg in _night_export_window_segments(slots):
|
||||
if not seg:
|
||||
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
|
||||
|
||||
|
||||
def _evening_battery_export_push_indices(
|
||||
slots: list[PlanningSlot],
|
||||
*,
|
||||
profitable_export_ts: set[int],
|
||||
charge_acquisition_czk_kwh: float,
|
||||
degrad_czk_kwh: float,
|
||||
current_soc_wh: float,
|
||||
min_soc_wh: float,
|
||||
@@ -1645,27 +1673,21 @@ def _evening_battery_export_push_indices(
|
||||
evening_start_hour: int = 17,
|
||||
) -> list[int]:
|
||||
"""
|
||||
Noční push: plný ge_bat v tolika nejdražších peak-band slotech, kolik unese Wh rozpočet.
|
||||
|
||||
Kandidáti = profitable ∩ noční okno ∩ večerní peak pásmo (max sell v úseku − degrad, R__063).
|
||||
Řazení sell desc; přidávat sloty dokud kumulované Wh ≤ push_budget. Žádné pevné top-N.
|
||||
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.
|
||||
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 []
|
||||
peak_ts = set(
|
||||
_evening_peak_export_indices(
|
||||
slots,
|
||||
degrad_czk_kwh=degrad_czk_kwh,
|
||||
evening_start_hour=evening_start_hour,
|
||||
)
|
||||
)
|
||||
candidates = [
|
||||
t
|
||||
for t, s in enumerate(slots)
|
||||
if t in peak_ts
|
||||
and t in profitable_export_ts
|
||||
and float(s.sell_price) >= 0.0
|
||||
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 []
|
||||
@@ -1740,22 +1762,23 @@ def _evening_push_hysteresis_active(
|
||||
def _evening_early_export_penalty_indices(
|
||||
slots: list[PlanningSlot],
|
||||
*,
|
||||
profitable_export_ts: set[int],
|
||||
discharge_export_slots: set[int],
|
||||
evening_push_ts: set[int],
|
||||
exempt_ts: set[int] | None = None,
|
||||
) -> set[int]:
|
||||
"""ge_bat=0 pro profitable noční sloty pod peak−eps mimo evening_push (v38: i po prvním push)."""
|
||||
"""
|
||||
ge_bat=0 v nočním okně mimo tvrdý evening_push (a mimo pre-neg / neg-evening větve).
|
||||
"""
|
||||
exempt = exempt_ts or set()
|
||||
out: set[int] = set()
|
||||
for t_ev, s_ev in enumerate(slots):
|
||||
if not _in_night_battery_export_window(s_ev):
|
||||
continue
|
||||
if t_ev not in profitable_export_ts or t_ev not in discharge_export_slots:
|
||||
if t_ev not in discharge_export_slots:
|
||||
continue
|
||||
if t_ev in evening_push_ts:
|
||||
if t_ev in evening_push_ts or t_ev in exempt:
|
||||
continue
|
||||
peak_sell = _night_peak_sell_czk_kwh(slots, t_ev)
|
||||
if float(s_ev.sell_price) < peak_sell - EVENING_PEAK_SELL_EPS_CZK_KWH:
|
||||
out.add(t_ev)
|
||||
out.add(t_ev)
|
||||
return out
|
||||
|
||||
|
||||
@@ -2392,7 +2415,7 @@ def solve_dispatch(
|
||||
computed_evening_push_ts = set(
|
||||
_evening_battery_export_push_indices(
|
||||
slots,
|
||||
profitable_export_ts=profitable_export_ts_pre,
|
||||
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
|
||||
degrad_czk_kwh=float(degradation_cost_effective),
|
||||
current_soc_wh=float(current_soc_wh),
|
||||
min_soc_wh=float(min_soc_wh),
|
||||
@@ -2406,12 +2429,6 @@ def solve_dispatch(
|
||||
evening_push_hysteresis_retained = True
|
||||
else:
|
||||
evening_push_ts = computed_evening_push_ts
|
||||
evening_early_export_penalty_ts = _evening_early_export_penalty_indices(
|
||||
slots,
|
||||
profitable_export_ts=profitable_export_ts_pre,
|
||||
discharge_export_slots=discharge_export_slots,
|
||||
evening_push_ts=evening_push_ts,
|
||||
)
|
||||
last_pos_sell_pre_neg_buy = _last_non_negative_sell_before_neg_buy(
|
||||
slots, first_neg_buy_idx
|
||||
)
|
||||
@@ -2421,6 +2438,19 @@ def solve_dispatch(
|
||||
pre_neg_buy_empty_ts = _pre_neg_buy_empty_discharge_indices(
|
||||
slots, first_neg_buy_idx, last_pos_sell_pre_neg_buy
|
||||
)
|
||||
if om == "AUTO":
|
||||
evening_export_exempt_ts = (
|
||||
set(morning_pre_neg_export_ts)
|
||||
| set(pre_neg_buy_discharge_ts)
|
||||
| set(pre_neg_buy_empty_ts)
|
||||
| set(neg_evening_push_ts)
|
||||
)
|
||||
evening_early_export_penalty_ts = _evening_early_export_penalty_indices(
|
||||
slots,
|
||||
discharge_export_slots=discharge_export_slots,
|
||||
evening_push_ts=evening_push_ts,
|
||||
exempt_ts=evening_export_exempt_ts,
|
||||
)
|
||||
pre_neg_buy_soc_ceiling_wh = _pre_neg_buy_soc_ceiling_wh(
|
||||
slots,
|
||||
first_neg_buy_idx=first_neg_buy_idx,
|
||||
@@ -2530,6 +2560,9 @@ def solve_dispatch(
|
||||
continue
|
||||
if t in evening_push_ts:
|
||||
continue
|
||||
if _in_night_battery_export_window(slots[t]):
|
||||
# Večerní export jen v tvrdém push; jinak by shortfall rozplizňoval ge_bat.
|
||||
continue
|
||||
if _battery_export_push_defer_to_pv(slots[t]):
|
||||
continue
|
||||
if not _slot_profitable_battery_export(
|
||||
|
||||
Reference in New Issue
Block a user