nerezta PV A pri prodeji z baterie
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-26 07:34:52 +02:00
parent 25c864db61
commit 8494ea26de
6 changed files with 166 additions and 11 deletions

View File

@@ -68,7 +68,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-28-night-export-window-midnight-v30"
PLANNER_BUILD_TAG = "2026-05-28-morning-pv-export-priority-v31"
POS_SELL_PRE_NEG_SOC_SHORTFALL_PENALTY_CZK_PER_WH = 0.30
PRE_NEG_BUY_SOC_CEILING_SLACK_PENALTY_CZK_PER_WH = 0.25
PRE_NEG_BUY_EMPTY_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
@@ -971,6 +971,16 @@ def _slot_pv_surplus_w(slot: PlanningSlot) -> float:
return max(0.0, pv_w - load_w)
def _battery_export_push_defer_to_pv(slot: PlanningSlot) -> bool:
"""
Při kladném sell a PV přebytku nevnucovat plný ge_bat push (pre-neg / ranní větve).
Exportní cap má pokrýt ge_pv; baterii řeší večerní push a sell<0 okna.
"""
if float(slot.sell_price) < 0.0:
return False
return _slot_pv_surplus_w(slot) > NIGHT_EXPORT_PV_SUNRISE_SURPLUS_W
def _in_night_battery_export_window(slot: PlanningSlot) -> bool:
"""
Noční okno pro večerní push / peak sell: >=17h Prague, nebo 05h (přes půlnoc).
@@ -1746,6 +1756,8 @@ def solve_dispatch(
continue
if t in evening_push_ts:
continue
if _battery_export_push_defer_to_pv(slots[t]):
continue
if not _slot_profitable_battery_export(
slots[t],
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
@@ -1761,9 +1773,13 @@ def solve_dispatch(
peak_export_shortfall.append((t, sf, cap_w))
export_cap_w = _battery_export_cap_w(battery, grid)
for t_pnd in sorted(pre_neg_buy_discharge_ts):
if _battery_export_push_defer_to_pv(slots[t_pnd]):
continue
sf_pnd = pulp.LpVariable(f"pre_neg_bat_export_sf_{t_pnd}", 0, export_cap_w)
pre_neg_batt_export_shortfall.append((t_pnd, sf_pnd, export_cap_w))
for t_empty in pre_neg_buy_empty_ts:
if _battery_export_push_defer_to_pv(slots[t_empty]):
continue
sf_e = pulp.LpVariable(f"pre_neg_buy_empty_sf_{t_empty}", 0, export_cap_w)
pre_neg_buy_empty_shortfall.append((t_empty, sf_e, export_cap_w))
if not relaxed_neg_buy_charge:
@@ -2032,11 +2048,17 @@ def solve_dispatch(
export_push_w = _battery_export_cap_w(battery, grid)
for t_peak in morning_pre_neg_export_ts:
if t_peak in profitable_export_ts:
if _battery_export_push_defer_to_pv(slots[t_peak]):
continue
prob += ge_bat[t_peak] >= export_push_w * z_export[t_peak]
for t_pnd in pre_neg_buy_discharge_ts:
if _battery_export_push_defer_to_pv(slots[t_pnd]):
continue
prob += ge_bat[t_pnd] >= export_push_w * z_export[t_pnd]
for t_empty in pre_neg_buy_empty_ts:
if t_empty in discharge_export_slots:
if _battery_export_push_defer_to_pv(slots[t_empty]):
continue
prob += ge_bat[t_empty] >= export_push_w * z_export[t_empty]
for t_early in sorted(evening_early_export_penalty_ts):
prob += ge_bat[t_early] == 0