cileni k vybiti pred ranem kdy nabiju z fve
This commit is contained in:
@@ -71,11 +71,13 @@ 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-neg-prep-window-v36"
|
||||
PLANNER_BUILD_TAG = "2026-05-28-neg-prep-window-v36b"
|
||||
# Po t_detach v prep: necpát PV do bat (měkké; tvrdý hold přes soc_target z rampy).
|
||||
NEG_SELL_POST_DETACH_BCPV_DISCOURAGE_CZK_KWH = 250.0
|
||||
# Večer před neg dnem: výboj směrem k soc_need na začátku zítřejšího sell<0 okna.
|
||||
NEG_EVENING_PREP_DISCHARGE_SHORTFALL_PENALTY_CZK_KWH = 70.0
|
||||
# Večer před neg dnem: výboj do sítě (měkký shortfall na ge_bat).
|
||||
NEG_EVENING_PREP_DISCHARGE_SHORTFALL_PENALTY_CZK_KWH = 120.0
|
||||
# Kotva: SoC na konci večera D−1 ≤ reserve_soc (+ slack) před ranním sell<0 dnem D.
|
||||
NEG_EVENING_RESERVE_SOC_SLACK_PENALTY_CZK_PER_WH = 4.0
|
||||
# Před prvním sell<0: export FVE jen pokud predikce v sell<0 okně pokryje dobítí na prep cíl.
|
||||
PRE_NEG_PV_EXPORT_FORECAST_MARGIN = 1.15
|
||||
PRE_NEG_PV_EXPORT_MIN_NEEDED_WH = 2500.0
|
||||
@@ -1248,6 +1250,42 @@ def _evening_discharge_before_neg_day_ts(
|
||||
return out
|
||||
|
||||
|
||||
def _neg_evening_reserve_soc_anchors(
|
||||
slots: list[PlanningSlot],
|
||||
neg_sell_day_meta: dict[str, Any],
|
||||
battery: Any,
|
||||
) -> list[tuple[int, float]]:
|
||||
"""
|
||||
Poslední večerní slot kalendářního dne D−1 před dnem D s sell<0: cíl SoC ≤ reserve_soc.
|
||||
Headroom pro ranní nabíjení z FVE / levného spotu v neg okně (ne držet 60 %+ přes noc).
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
reserve_wh = float(
|
||||
getattr(battery, "reserve_soc_wh", getattr(battery, "min_soc_wh", 0.0))
|
||||
)
|
||||
out: list[tuple[int, float]] = []
|
||||
for day_info in neg_sell_day_meta.get("days") or []:
|
||||
first_neg = int(day_info.get("first_neg_idx", -1))
|
||||
if first_neg < 0 or first_neg >= len(slots):
|
||||
continue
|
||||
neg_date = _prague_calendar_date(slots[first_neg])
|
||||
prev_date = neg_date - timedelta(days=1)
|
||||
anchors = [
|
||||
t
|
||||
for t, st in enumerate(slots)
|
||||
if _prague_calendar_date(st) == prev_date
|
||||
and (
|
||||
17 <= _prague_hour(st) <= 23
|
||||
or _in_night_battery_export_window(st)
|
||||
)
|
||||
]
|
||||
if not anchors:
|
||||
continue
|
||||
out.append((max(anchors), reserve_wh))
|
||||
return out
|
||||
|
||||
|
||||
MORNING_PRENEG_START_HOUR = 5
|
||||
MORNING_PRENEG_END_HOUR = 11
|
||||
|
||||
@@ -2043,6 +2081,7 @@ def solve_dispatch(
|
||||
pre_neg_cushion_by_day: dict[str, bool] = {}
|
||||
pre_neg_pv_export_ts: set[int] = set()
|
||||
neg_evening_before_neg_ts: set[int] = set()
|
||||
neg_evening_reserve_anchors: list[tuple[int, float]] = []
|
||||
if om == "AUTO" and not purchase_fixed_pre and neg_sell_phases_en:
|
||||
pre_neg_pv_export_ts, pre_neg_cushion_by_day = _pre_neg_pv_export_bundle(
|
||||
slots,
|
||||
@@ -2056,6 +2095,11 @@ def solve_dispatch(
|
||||
slots,
|
||||
neg_sell_day_meta,
|
||||
)
|
||||
neg_evening_reserve_anchors = _neg_evening_reserve_soc_anchors(
|
||||
slots,
|
||||
neg_sell_day_meta,
|
||||
battery,
|
||||
)
|
||||
elif om == "AUTO" and not purchase_fixed_pre:
|
||||
legacy_ok = bool(
|
||||
first_neg_sell_idx is not None
|
||||
@@ -2247,6 +2291,7 @@ def solve_dispatch(
|
||||
pre_neg_pv_charge_shortfall: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
pre_neg_pv_export_shortfall: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
neg_evening_before_neg_shortfall: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
neg_evening_reserve_soc_slack: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
neg_sell_bat_dump_shortfall: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
neg_sell_soc_underfill: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
neg_buy_charge_shortfall: list[tuple[int, pulp.LpVariable, float]] = []
|
||||
@@ -2371,6 +2416,17 @@ def solve_dispatch(
|
||||
export_cap_evening,
|
||||
)
|
||||
neg_evening_before_neg_shortfall.append((t_ev, sf_ev, export_cap_evening))
|
||||
for t_anchor, reserve_tgt in neg_evening_reserve_anchors:
|
||||
slack_cap = max(
|
||||
0.0,
|
||||
float(battery.soc_max_wh) - float(reserve_tgt),
|
||||
)
|
||||
sl = pulp.LpVariable(
|
||||
f"neg_eve_reserve_soc_slack_{t_anchor}",
|
||||
0,
|
||||
slack_cap,
|
||||
)
|
||||
neg_evening_reserve_soc_slack.append((t_anchor, sl, float(reserve_tgt)))
|
||||
for t in range(T):
|
||||
if not post_neg_pv_topup[t]:
|
||||
continue
|
||||
@@ -2599,6 +2655,10 @@ def solve_dispatch(
|
||||
/ 1000.0
|
||||
for _t, sf, _cap in neg_evening_before_neg_shortfall
|
||||
)
|
||||
+ pulp.lpSum(
|
||||
sl * NEG_EVENING_RESERVE_SOC_SLACK_PENALTY_CZK_PER_WH
|
||||
for _t, sl, _tgt in neg_evening_reserve_soc_slack
|
||||
)
|
||||
+ pulp.lpSum(
|
||||
bc_pv[t]
|
||||
* PRE_NEG_PV_BCPV_DISCOURAGE_CZK_KWH
|
||||
@@ -2701,6 +2761,8 @@ def solve_dispatch(
|
||||
prob += sf >= cap_w - ge_pv[t_sf]
|
||||
for t_sf, sf, cap_w in neg_evening_before_neg_shortfall:
|
||||
prob += sf >= cap_w - ge_bat[t_sf]
|
||||
for t_sl, sl, reserve_tgt in neg_evening_reserve_soc_slack:
|
||||
prob += soc[t_sl] <= float(reserve_tgt) + sl
|
||||
preneg_export_min_soc_wh = float(min_soc_wh) + max(
|
||||
float(battery.max_discharge_power_w)
|
||||
* float(battery.discharge_efficiency)
|
||||
@@ -3543,6 +3605,11 @@ def solve_dispatch(
|
||||
"neg_evening_before_neg": (
|
||||
t in neg_evening_before_neg_ts if neg_sell_phases_en else None
|
||||
),
|
||||
"neg_evening_reserve_anchor": (
|
||||
any(t == ta for ta, _ in neg_evening_reserve_anchors)
|
||||
if neg_sell_phases_en
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
tgt_s = st.safety_soc_target_wh if daytime_en else None
|
||||
@@ -3663,6 +3730,13 @@ def solve_dispatch(
|
||||
slots[i].interval_start.isoformat()
|
||||
for i in sorted(neg_evening_before_neg_ts)
|
||||
],
|
||||
"neg_evening_reserve_soc_anchors": [
|
||||
{
|
||||
"slot": slots[t_a].interval_start.isoformat(),
|
||||
"target_reserve_soc_wh": float(tgt_wh),
|
||||
}
|
||||
for t_a, tgt_wh in neg_evening_reserve_anchors
|
||||
],
|
||||
"neg_sell_prep_window_v36": bool(neg_sell_phases_en),
|
||||
"neg_sell_day_pv_usable_wh": (
|
||||
_neg_sell_day_pv_usable_wh(
|
||||
|
||||
Reference in New Issue
Block a user