revert a nove upravy
Some checks failed
CI and deploy / migration-check (push) Failing after 29s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-25 01:05:23 +02:00
parent 67d34aba41
commit 095676e3b1
3 changed files with 68 additions and 5 deletions

View File

@@ -64,7 +64,7 @@ NEG_SELL_PV_B_VENT_PENALTY_CZK_KWH = 4.0
# Výboj baterie při sell<0 jen těsně před extrémně záporným buy (round-trip arbitráž).
EXTREME_BUY_DUMP_PREWINDOW_SLOTS = 12
NEG_SELL_BAT_DUMP_SHORTFALL_PENALTY_CZK_KWH = 80.0
PLANNER_BUILD_TAG = "2026-05-27-neg-sell-soc-reservation-v13"
PLANNER_BUILD_TAG = "2026-05-27-simple-buy-neg-window-v16"
CORRECTION_WINDOW_H = 1 # hodina zpět pro výpočet korekčního faktoru
CORRECTION_MIN_CLAMP = 0.5 # spodní limit korekčního faktoru
CORRECTION_MAX_CLAMP = 1.5 # horní limit korekčního faktoru
@@ -1200,6 +1200,28 @@ def solve_dispatch(
discharge_export_slots = {
t for t, s in enumerate(slots) if s.allow_discharge_export
}
# Vybití baterie před `buy<0` oknem: pokud je v horizontu buy<0, můžeme baterii
# vybít teď za `sell` a v buy<0 okně ji nabít za záporný buy (= příjem).
# Ekonomicky výhodné dokud: sell_t > avg_buy_neg + degradation
# (vybíjet ztratíme ~discharge_eff loss, nabíjení v buy<0 nás platí; marže ~ sell buy_neg degrad).
# Cílem je obejít to, že R__063 v noci dává allow_discharge_export=false a LP
# by jinak nemohl baterku vyklidit přes ge_bat.
_neg_buy_for_disch = next(
(t for t, s in enumerate(slots) if float(s.buy_price) < 0), None
)
if _neg_buy_for_disch is not None and _neg_buy_for_disch > 0:
_neg_buy_prices = [
float(slots[t].buy_price)
for t in range(_neg_buy_for_disch, T)
if float(slots[t].buy_price) < 0
]
_avg_neg_buy = sum(_neg_buy_prices) / len(_neg_buy_prices) if _neg_buy_prices else 0.0
# Práh = avg buy<0 + degradation cycle overhead; default fallback 0.1 Kč/kWh
# když z nějakého důvodu neumíme spočítat (ochrana proti vybití do mínusu).
_disch_sell_thr = max(_avg_neg_buy + float(degradation_cost_effective), 0.1)
for t in range(_neg_buy_for_disch):
if float(slots[t].sell_price) >= _disch_sell_thr:
discharge_export_slots.add(t)
# SELF_SUSTAIN dřív vynucoval ge[t] == 0, což umí udělat MILP infeasible v okamžiku, kdy:
# - baterie je na max SoC (nelze nabíjet),
# - PV pole B není curtailable,
@@ -1913,6 +1935,16 @@ def solve_dispatch(
prob += bd[t] == 0
# Slot pre-selection (z DB fn_load_planning_slots_full → allow_*)
# PŘED prvním buy<0 slotem v horizontu (= rezervační okno):
# - sell ≥ 0 → bc_pv = 0 (PV poteče do gridu / curtail, baterka se nenabíjí z PV
# protože v buy<0 okně bude akvizice levnější — záporná).
# - sell < 0 → slot je v charge_slots (R__063), bc_pv ≤ pv_surplus (= nemůžeme
# pole A vyhodit do mínusu, raději nabít baterii).
# JINDY (po buy<0 okně, nebo žádné buy<0 v horizontu): původní permissive
# bc_pv ≤ pv_surplus aby nedošlo k regresi normálních dnů.
_neg_buy_idx_main = next(
(t for t, s in enumerate(slots) if float(s.buy_price) < 0), None
)
if om == "AUTO":
for t in range(T):
if t not in charge_slots:
@@ -1923,11 +1955,17 @@ def solve_dispatch(
+ int(s.pv_b_forecast_w)
- int(s.load_baseline_w),
)
# Mimo grid-charge masku: jen PV přebytek; výjimka záporný buy (spot arbitráž).
if float(s.buy_price) >= 0.0:
prob += bc_gi[t] == 0
in_pre_neg_buy_window = (
_neg_buy_idx_main is not None and t < _neg_buy_idx_main
)
if pv_surplus_w <= 0:
prob += bc_pv[t] == 0
elif in_pre_neg_buy_window:
# Strukturální preference: PV jde do gridu (sell≥0) nebo curtail,
# ne do baterie — kapacitu si šetříme na buy<0 okno.
prob += bc_pv[t] == 0
else:
prob += bc_pv[t] <= float(pv_surplus_w)
if t not in discharge_export_slots and t not in neg_sell_bat_dump_slots: