dalsi pokus o fix nevyliti baterky pred zapornou cenou
This commit is contained in:
@@ -670,8 +670,8 @@ def _prague_calendar_date(slot: PlanningSlot):
|
||||
|
||||
MORNING_PRENEG_START_HOUR = 5
|
||||
MORNING_PRENEG_END_HOUR = 11
|
||||
NEGATIVE_BUY_GRID_CHARGE_MIN_W = 8_000.0
|
||||
PRENEG_MORNING_EXPORT_MIN_W = 5_000.0
|
||||
PRENEG_MORNING_EXPORT_MIN_W = 8_000.0
|
||||
EVENING_BATTERY_EXPORT_MIN_W = 8_000.0
|
||||
|
||||
|
||||
def _prague_hour(slot: PlanningSlot) -> int:
|
||||
@@ -747,6 +747,30 @@ def _morning_pre_neg_export_indices(
|
||||
return out
|
||||
|
||||
|
||||
def _evening_peak_export_indices(
|
||||
slots: list[PlanningSlot],
|
||||
*,
|
||||
degrad_czk_kwh: float,
|
||||
evening_start_hour: int = 17,
|
||||
) -> list[int]:
|
||||
"""Večerní špičky per den (shodně s R__063, hour >= 17 Prague)."""
|
||||
peak_by_day: dict = {}
|
||||
for s in slots:
|
||||
if _prague_hour(s) < evening_start_hour:
|
||||
continue
|
||||
d = _prague_calendar_date(s)
|
||||
peak_by_day[d] = max(peak_by_day.get(d, 0.0), float(s.sell_price))
|
||||
out: list[int] = []
|
||||
for t, s in enumerate(slots):
|
||||
if _prague_hour(s) < evening_start_hour:
|
||||
continue
|
||||
d = _prague_calendar_date(s)
|
||||
peak = peak_by_day.get(d, 0.0)
|
||||
if peak > 0 and float(s.sell_price) >= peak - degrad_czk_kwh:
|
||||
out.append(t)
|
||||
return out
|
||||
|
||||
|
||||
def _pv_forced_vent_export_allowed(
|
||||
t: int,
|
||||
*,
|
||||
@@ -974,6 +998,9 @@ def solve_dispatch(
|
||||
discharge_export_slots: set[int] = set()
|
||||
if om == "AUTO":
|
||||
charge_slots = {t for t, s in enumerate(slots) if s.allow_charge}
|
||||
charge_slots |= {
|
||||
t for t, s in enumerate(slots) if float(s.buy_price) < 0.0
|
||||
}
|
||||
discharge_export_slots = {
|
||||
t for t, s in enumerate(slots) if s.allow_discharge_export
|
||||
}
|
||||
@@ -1037,6 +1064,19 @@ def solve_dispatch(
|
||||
first_neg_sell_idx,
|
||||
degrad_czk_kwh=float(degradation_cost_effective),
|
||||
)
|
||||
evening_peak_export_ts = _evening_peak_export_indices(
|
||||
slots,
|
||||
degrad_czk_kwh=float(degradation_cost_effective),
|
||||
)
|
||||
non_negative_buys_pre = [
|
||||
float(s.buy_price) for s in slots if float(s.buy_price) >= 0.0
|
||||
]
|
||||
ref_buy_horizon_pre = (
|
||||
min(non_negative_buys_pre)
|
||||
if non_negative_buys_pre
|
||||
else min(float(s.buy_price) for s in slots)
|
||||
)
|
||||
min_spread_pre = float(degradation_cost_effective)
|
||||
if first_neg_sell_idx is not None and first_neg_sell_idx > 0 and floor_pct is not None:
|
||||
# Kotva na ranním peaku (ne na posledním slotu před sell<0) — jinak dump až v 07:30.
|
||||
if (
|
||||
@@ -1180,8 +1220,19 @@ def solve_dispatch(
|
||||
)
|
||||
if om == "AUTO":
|
||||
for t_peak in morning_pre_neg_export_ts:
|
||||
if t_peak in discharge_export_slots:
|
||||
if (
|
||||
t_peak in discharge_export_slots
|
||||
and float(slots[t_peak].sell_price)
|
||||
> ref_buy_horizon_pre + min_spread_pre
|
||||
):
|
||||
prob += ge_bat[t_peak] >= PRENEG_MORNING_EXPORT_MIN_W * z_export[t_peak]
|
||||
for t_peak in evening_peak_export_ts:
|
||||
if (
|
||||
t_peak in discharge_export_slots
|
||||
and float(slots[t_peak].sell_price)
|
||||
> ref_buy_horizon_pre + min_spread_pre
|
||||
):
|
||||
prob += ge_bat[t_peak] >= EVENING_BATTERY_EXPORT_MIN_W * z_export[t_peak]
|
||||
if t_anchor is not None and soc_anchor_slack is not None:
|
||||
target_floor_wh = float(planner_floor_effective_wh)
|
||||
prob += soc[t_anchor] <= target_floor_wh + soc_anchor_slack
|
||||
@@ -1464,29 +1515,6 @@ def solve_dispatch(
|
||||
prob += ge_bat[t] == 0
|
||||
prob += z_export[t] == 0
|
||||
|
||||
# Záporný buy: minimální grid import (spot arbitráž), jen pokud není baterie prakticky plná.
|
||||
for t in range(T):
|
||||
if float(slots[t].buy_price) >= 0.0:
|
||||
continue
|
||||
load_t = float(slots[t].load_baseline_w)
|
||||
min_gi = min(
|
||||
gi_upper,
|
||||
load_t + NEGATIVE_BUY_GRID_CHARGE_MIN_W,
|
||||
load_t + float(battery.max_charge_power_w) * 0.9,
|
||||
)
|
||||
if min_gi <= load_t + 500.0:
|
||||
continue
|
||||
if t == 0:
|
||||
if current_soc_wh >= float(battery.soc_max_wh) - soc_headroom_wh - 500.0:
|
||||
continue
|
||||
prob += gi[t] >= min_gi
|
||||
else:
|
||||
z_neg_chg = pulp.LpVariable(f"z_neg_chg_{t}", cat="Binary")
|
||||
prob += soc[t - 1] <= float(battery.soc_max_wh) - soc_headroom_wh - 500.0 + float(
|
||||
battery.usable_capacity_wh
|
||||
) * (1 - z_neg_chg)
|
||||
prob += gi[t] >= min_gi * z_neg_chg
|
||||
|
||||
# Ekonomické guardy: ceny v objective nestačí proti maskám / terminal SoC.
|
||||
# Referenční buy jen z ne-záporných slotů: jinak jeden buy<0 v horizontu označí
|
||||
# téměř všechny sloty jako „drahé“ (gi=0 pro dům) → Infeasible (home-01).
|
||||
@@ -1548,7 +1576,7 @@ def solve_dispatch(
|
||||
expensive_import_slot = expensive_import_slot or (
|
||||
buy_t > charge_acquisition_czk_kwh + min_spread
|
||||
)
|
||||
if expensive_import_slot and t not in charge_slots:
|
||||
if expensive_import_slot and t not in charge_slots and buy_t >= 0.0:
|
||||
# Strict: síť jen EV+TČ; baseload z baterie/FVE. Relaxed: síť smí krmit baseload (nouzový režim).
|
||||
prob += gi[t] <= ev_cap_t + hp[t] + (
|
||||
float(s.load_baseline_w) if relaxed_expensive_import else 0.0
|
||||
|
||||
Reference in New Issue
Block a user