Revert "dalsi a dalsi fix"

This reverts commit b03855b3d1.
This commit is contained in:
Dusan Vojacek
2026-05-25 01:00:00 +02:00
parent b46da6b2dc
commit 67d34aba41
3 changed files with 4 additions and 85 deletions

View File

@@ -50,10 +50,6 @@ DEFAULT_PLANNER_DISCHARGE_RELAX_PREWINDOW_SLOTS = 8
# Penalizace je v Kč/Wh (např. 0.20 = 200 Kč/kWh). Musí být dost velká, aby přebila
# bezpečnostní SoC buffer + terminal shadow cenu a solver skutečně „dovylil“ před sell<0.
PRENEG_SELL_SOC_ANCHOR_SLACK_PENALTY_CZK_PER_WH = 0.20
# Penalita za překročení SoC capu před prvním buy<0 slotem. Měla by být VYŠŠÍ než
# alternativní marginal arbitrage (acquisition - avg_neg_buy ~ 1 Kč/kWh) aby LP
# preferoval buy<0 nabíjení před ranním PV.
PRE_NEG_BUY_SOC_SLACK_PENALTY_CZK_PER_WH = 0.005
PEAK_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
# Měkký tlak: v okně sell<0 + block_export využít PV přebytek do baterie (ne curtail).
PV_CHARGE_SHORTFALL_PENALTY_CZK_KWH = 120.0
@@ -68,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-pre-neg-buy-soc-cap-v14"
PLANNER_BUILD_TAG = "2026-05-27-neg-sell-soc-reservation-v13"
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
@@ -1260,33 +1256,6 @@ def solve_dispatch(
# Kotva: poslední slot před prvním sell<0 by měl končit u planner floor (pokud relaxace existuje).
# Slack penalizujeme v objective; samotné omezení přidáme až po definici soc.
first_neg_sell_idx, pre_neg_export_last_t = _pre_negative_sell_export_window(slots)
# buy<0 okno: pokud je v horizontu, baterie musí dorazit DO něj s dostatečnou volnou
# kapacitou, aby tam mohla maximálně nasát z OTE záporné cenny. LP samo to nevidí
# (acquisition je vstupní konstanta, ne endogenní funkce slotů) — přidáme tvrdý
# constraint na SoC v posledním slotu před prvním buy<0.
neg_buy_indices = [t for t, s in enumerate(slots) if float(s.buy_price) < 0]
first_neg_buy_idx = neg_buy_indices[0] if neg_buy_indices else None
pre_neg_buy_soc_cap_wh: float | None = None
if first_neg_buy_idx is not None and first_neg_buy_idx > 0:
# Spočítat max nabíjecí kapacitu v buy<0 oknu (per slot ≤ max_charge_power_w,
# PV přebytek + grid import). Konzervativně: jen sloty se buy<0, nikoli okolní.
neg_window_capacity_wh = 0.0
for t in neg_buy_indices:
s_neg = slots[t]
pv_sur = max(
0.0,
float(s_neg.pv_a_forecast_w) + float(s_neg.pv_b_forecast_w) - float(s_neg.load_baseline_w),
)
slot_charge_pot_w = min(
float(battery.max_charge_power_w),
pv_sur + float(grid.max_import_power_w),
)
neg_window_capacity_wh += slot_charge_pot_w * INTERVAL_H * float(battery.charge_efficiency)
# Konzervativní headroom 10 % kapacity (PV forecast error, load fluctuation)
usable_capacity = float(battery.soc_max_wh) - float(min_soc_wh)
reserve_target_wh = min(neg_window_capacity_wh, usable_capacity * 0.9)
pre_neg_buy_soc_cap_wh = max(float(min_soc_wh), float(battery.soc_max_wh) - reserve_target_wh)
last_neg_sell_by_prague_date: dict[object, int] = {}
for t_ln, st_ln in enumerate(slots):
if float(st_ln.sell_price) < 0:
@@ -1343,23 +1312,6 @@ def solve_dispatch(
t_anchor = first_neg_sell_idx - 1
soc_anchor_slack = pulp.LpVariable("soc_anchor_slack_wh", 0, float(battery.usable_capacity_wh))
# Tvrdý cap SoC před prvním buy<0 slotem: rezervovat volnou kapacitu, aby LP
# nasál maximum levné OTE záporné ceny + PV (acquisition v LP je konstanta,
# bez tohoto omezení LP nepreferuje buy<0 sloty před PV nabíjením).
pre_neg_buy_anchor_slack: pulp.LpVariable | None = None
if (
om == "AUTO"
and first_neg_buy_idx is not None
and first_neg_buy_idx > 0
and pre_neg_buy_soc_cap_wh is not None
):
# Měkký constraint přes slack — nesmí dělat LP infeasible v patologických případech
# (např. startovní SoC = 100 % a krátký horizont k buy<0).
pre_neg_buy_anchor_slack = pulp.LpVariable(
"pre_neg_buy_soc_slack_wh", 0, float(battery.usable_capacity_wh)
)
prob += soc[first_neg_buy_idx - 1] <= float(pre_neg_buy_soc_cap_wh) + pre_neg_buy_anchor_slack
daytime_en = bool(getattr(battery, "planner_daytime_charge_target_enabled", True))
safety_pen_czk_per_wh: list[float] = []
safety_vars: list[Optional[pulp.LpVariable]] = []
@@ -1590,11 +1542,6 @@ def solve_dispatch(
if soc_anchor_slack is not None
else 0
)
+ (
pre_neg_buy_anchor_slack * PRE_NEG_BUY_SOC_SLACK_PENALTY_CZK_PER_WH
if pre_neg_buy_anchor_slack is not None
else 0
)
+ pulp.lpSum(
safety_vars[t] * safety_pen_czk_per_wh[t]
for t in range(T)
@@ -2391,15 +2338,6 @@ def solve_dispatch(
if slots[0].charge_acquisition_cutoff_at is not None
else None
),
"pre_neg_buy_soc_cap_wh": (
float(pre_neg_buy_soc_cap_wh)
if pre_neg_buy_soc_cap_wh is not None else None
),
"pre_neg_buy_soc_slack_wh": (
float(pulp.value(pre_neg_buy_anchor_slack) or 0)
if pre_neg_buy_anchor_slack is not None else None
),
"first_neg_buy_idx": first_neg_buy_idx,
},
"masks": masks_snap,
"soc_bounds": soc_bounds_snap,