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

This commit is contained in:
Dusan Vojacek
2026-05-30 23:23:17 +02:00
parent 830aa7a4cc
commit a03b45d4a9
7 changed files with 177 additions and 23 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-31-evening-push-budget-primary-night-v49"
PLANNER_BUILD_TAG = "2026-05-31-ba81-dawn-no-micro-curtail-v51"
# Ranní slabá FVE: neaplikovat pv_store ge_pv=0 (jinak curtail při sell < večerní peak).
DAWN_LOW_PV_NO_CURTAIL_W = 1500
# Mimo evening_push: preferovat bd pro dům místo gi, když buy >> acq (účinná cena importu).
@@ -1564,8 +1564,9 @@ def _slot_pv_surplus_w(slot: PlanningSlot) -> float:
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.
Při kladném sell a PV přebytku nevnucovat vývoz z baterie (ge_bat / z_export).
Platí pro ranní pre-neg, večerní push i KV1 odpoledne (block_export + fixní tarif):
přetok řeší ge_pv / Deye PASSIVE, ne BATTERY_SELL.
"""
if float(slot.sell_price) < 0.0:
return False
@@ -2629,6 +2630,11 @@ def solve_dispatch(
slots, evening_push_ts
)
night_self_consume_discourage_ts |= post_evening_push_night_ts
battery_export_defer_pv_ts = {
t for t in range(T) if _battery_export_push_defer_to_pv(slots[t])
}
else:
battery_export_defer_pv_ts = set()
pre_neg_buy_soc_ceiling_wh = _pre_neg_buy_soc_ceiling_wh(
slots,
first_neg_buy_idx=first_neg_buy_idx,
@@ -3249,6 +3255,8 @@ def solve_dispatch(
for t_peak in sorted(evening_push_ts):
if t_peak not in discharge_export_slots:
continue
if t_peak in battery_export_defer_pv_ts:
continue
push_floor_w = _evening_push_battery_export_w(
slots[t_peak], battery, grid
)
@@ -3256,6 +3264,17 @@ def solve_dispatch(
prob += z_export[t_peak] == 1
prob += ge_bat[t_peak] >= push_floor_w
prob += soc[t_peak] >= float(discharge_floor_wh)
for t_pv in sorted(battery_export_defer_pv_ts):
if t_pv in evening_push_ts:
continue
if t_pv in morning_pre_neg_export_ts:
continue
if t_pv in pre_neg_buy_discharge_ts:
continue
if t_pv in pre_neg_buy_empty_ts:
continue
prob += ge_bat[t_pv] == 0
prob += z_export[t_pv] == 0
# Ostatní profitable sloty: měkká shortfall penalizace (ne večerní push).
if (
last_pos_sell_pre_neg_buy is not None
@@ -3738,35 +3757,56 @@ def solve_dispatch(
fixed_pre_neg_pv_export = (
purchase_fixed_pre
and sell_t >= 0.0
and pv_surplus_w > 500.0
and pv_surplus_w > NIGHT_EXPORT_PV_SUNRISE_SURPLUS_W
and (
first_neg_sell_idx is None
or t < first_neg_sell_idx
)
)
skip_pv_store_block = (
float(s.pv_b_forecast_w) > 0
fixed_block_pv_surplus_export = (
purchase_fixed_pre
and bool(getattr(grid, "block_export_on_negative_sell", False))
and sell_t >= 0.0
and pv_surplus_w > NIGHT_EXPORT_PV_SUNRISE_SURPLUS_W
)
# BA81: ge_pv≤pv_b jen při významném poli A — při úsvitu nechat Deye bez plného curtail A.
fixed_mi_low_pv_surplus_export = (
purchase_fixed_pre
and float(s.pv_b_forecast_w) > 0
and not getattr(grid, "block_export_on_negative_sell", False)
and sell_t < 0
and buy_t >= 0.0
and not purchase_fixed_pre
and (
first_neg_buy_idx is None
or t < first_neg_buy_idx
and sell_t >= 0.0
and int(s.pv_a_forecast_w) < DAWN_LOW_PV_NO_CURTAIL_W
and pv_surplus_w > 0.0
)
skip_pv_store_block = (
(
float(s.pv_b_forecast_w) > 0
and not getattr(grid, "block_export_on_negative_sell", False)
and sell_t < 0
and buy_t >= 0.0
and not purchase_fixed_pre
and (
first_neg_buy_idx is None
or t < first_neg_buy_idx
)
)
) or (
# Spot: při sell>=0 neblokovat ge_pv (export vs bc_pv; večerní peak = ge_bat).
not purchase_fixed_pre
and sell_t >= 0
and pv_surplus_w > 500
) or fixed_pre_neg_pv_export
# BA81: export pole B jen při kladném sell mimo pre-neg okno (jinak jen ge_pv≤pv_b → curtail A).
or (
# Spot: při sell>=0 neblokovat ge_pv (export vs bc_pv; večerní peak = ge_bat).
not purchase_fixed_pre
and sell_t >= 0
and pv_surplus_w > NIGHT_EXPORT_PV_SUNRISE_SURPLUS_W
)
or fixed_pre_neg_pv_export
or fixed_block_pv_surplus_export
or fixed_mi_low_pv_surplus_export
)
fixed_pv_b_export_cap = (
purchase_fixed_pre
and float(s.pv_b_forecast_w) > 0
and not getattr(grid, "block_export_on_negative_sell", False)
and sell_t >= 0
and not fixed_pre_neg_pv_export
and int(s.pv_a_forecast_w) >= DAWN_LOW_PV_NO_CURTAIL_W
)
if fixed_pre_neg_pv_export:
prob += ge_pv[t] <= max(0.0, pv_surplus_w)