dalsi
This commit is contained in:
@@ -71,7 +71,9 @@ 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-29-evening-push-budget-rank-v42"
|
||||
PLANNER_BUILD_TAG = "2026-05-29-night-selfconsume-evening-arb-v43"
|
||||
# Mimo evening_push: preferovat bd pro dům místo gi, když buy >> acq (účinná cena importu).
|
||||
NIGHT_SELF_CONSUME_IMPORT_SURCHARGE_CZK_KWH = 4.0
|
||||
# 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 do sítě (měkký shortfall na ge_bat).
|
||||
@@ -1565,6 +1567,11 @@ def _in_night_battery_export_window(slot: PlanningSlot) -> bool:
|
||||
return h <= NIGHT_EXPORT_MORNING_END_HOUR
|
||||
|
||||
|
||||
def _in_evening_push_hour_window(slot: PlanningSlot) -> bool:
|
||||
"""Tvrdý večerní push jen ≥17h Prague — ne noční vývoz ve 02–06h (sell < buy)."""
|
||||
return _prague_hour(slot) >= NIGHT_EXPORT_EVENING_START_HOUR
|
||||
|
||||
|
||||
def _night_export_window_segments(slots: list[PlanningSlot]) -> list[list[int]]:
|
||||
"""Souvislé úseky nočního okna v horizontu (oddělené denní pauzou / východem FVE)."""
|
||||
segments: list[list[int]] = []
|
||||
@@ -1648,12 +1655,17 @@ def _evening_push_segment_candidates(
|
||||
*,
|
||||
charge_acquisition_czk_kwh: float,
|
||||
min_spread: float,
|
||||
discharge_export_ok: set[int] | None = None,
|
||||
) -> list[int]:
|
||||
"""Profitable sloty v nočním úseku — výběr pořadí a strop dělá rozpočet Wh (sell desc)."""
|
||||
if not seg:
|
||||
return []
|
||||
out: list[int] = []
|
||||
for t in seg:
|
||||
if discharge_export_ok is not None and t not in discharge_export_ok:
|
||||
continue
|
||||
if not _in_evening_push_hour_window(slots[t]):
|
||||
continue
|
||||
if not _slot_evening_push_profitable(
|
||||
slots[t],
|
||||
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
|
||||
@@ -1664,6 +1676,44 @@ def _evening_push_segment_candidates(
|
||||
return out
|
||||
|
||||
|
||||
def _evening_push_calendar_segments(
|
||||
slots: list[PlanningSlot],
|
||||
discharge_export_ok: set[int] | None = None,
|
||||
) -> list[list[int]]:
|
||||
"""Kalendářní večery (≥17h) v nočním okně — každý den vlastní push rozpočet."""
|
||||
by_date: dict[object, list[int]] = {}
|
||||
for t, s in enumerate(slots):
|
||||
if not _in_evening_push_hour_window(s):
|
||||
continue
|
||||
if not _in_night_battery_export_window(s):
|
||||
continue
|
||||
if discharge_export_ok is not None and t not in discharge_export_ok:
|
||||
continue
|
||||
by_date.setdefault(_prague_calendar_date(s), []).append(t)
|
||||
return [sorted(v) for v in by_date.values() if v]
|
||||
|
||||
|
||||
def _night_self_consume_discourage_import_indices(
|
||||
slots: list[PlanningSlot],
|
||||
evening_early_export_penalty_ts: set[int],
|
||||
*,
|
||||
charge_acquisition_czk_kwh: float,
|
||||
min_spread: float,
|
||||
) -> set[int]:
|
||||
"""
|
||||
Sloty mimo evening_push, kde import pro dům nahrazuje levnou zásobu z baterie.
|
||||
"""
|
||||
out: set[int] = set()
|
||||
for t in evening_early_export_penalty_ts:
|
||||
buy_t = float(slots[t].buy_price)
|
||||
if buy_t <= float(charge_acquisition_czk_kwh) + float(min_spread):
|
||||
continue
|
||||
if float(slots[t].load_baseline_w) <= 0:
|
||||
continue
|
||||
out.add(t)
|
||||
return out
|
||||
|
||||
|
||||
def _evening_battery_export_push_indices(
|
||||
slots: list[PlanningSlot],
|
||||
*,
|
||||
@@ -1674,12 +1724,13 @@ def _evening_battery_export_push_indices(
|
||||
soc_max_wh: float,
|
||||
per_slot_discharge_wh: float,
|
||||
discharge_slot_buffer: float,
|
||||
discharge_export_ok: set[int] | None = None,
|
||||
evening_start_hour: int = 17,
|
||||
) -> list[int]:
|
||||
"""
|
||||
Noční push: plný ge_bat v tolika nejdražších slotách (sell desc v rámci úseku),
|
||||
kolik unese Wh rozpočet — ne jen jeden slot s exact max sell (v41).
|
||||
per_slot_discharge_wh: volající předá min(BMS, export cap) × účinnost × 0,25 h.
|
||||
Večerní push (≥17h): plný ge_bat v nejdražších slotách (sell desc), rozpočet Wh
|
||||
**per kalendářní večer** — druhý den v horizontu nedostane nulový push (v42 bug).
|
||||
per_slot_discharge_wh: min(BMS, export cap) × účinnost × 0,25 h.
|
||||
"""
|
||||
_ = evening_start_hour # kompatibilita volání
|
||||
if per_slot_discharge_wh <= 0.0:
|
||||
@@ -1692,14 +1743,21 @@ def _evening_battery_export_push_indices(
|
||||
)
|
||||
if push_budget_wh < per_slot_discharge_wh * 0.5:
|
||||
return []
|
||||
evening_segments = _evening_push_calendar_segments(
|
||||
slots,
|
||||
discharge_export_ok=discharge_export_ok,
|
||||
)
|
||||
if not evening_segments:
|
||||
return []
|
||||
seg_budget_wh = push_budget_wh / float(len(evening_segments))
|
||||
out: list[int] = []
|
||||
remaining_wh = float(push_budget_wh)
|
||||
for seg in _night_export_window_segments(slots):
|
||||
for seg in evening_segments:
|
||||
candidates = _evening_push_segment_candidates(
|
||||
slots,
|
||||
seg,
|
||||
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
|
||||
min_spread=degrad_czk_kwh,
|
||||
discharge_export_ok=discharge_export_ok,
|
||||
)
|
||||
if not candidates:
|
||||
continue
|
||||
@@ -1708,6 +1766,7 @@ def _evening_battery_export_push_indices(
|
||||
key=lambda i: (float(slots[i].sell_price), -i),
|
||||
reverse=True,
|
||||
)
|
||||
remaining_wh = float(seg_budget_wh)
|
||||
for t in ranked:
|
||||
if remaining_wh + 1e-6 < per_slot_discharge_wh:
|
||||
break
|
||||
@@ -2400,6 +2459,7 @@ def solve_dispatch(
|
||||
profitable_export_ts_pre.add(_t)
|
||||
evening_push_ts: set[int] = set()
|
||||
evening_early_export_penalty_ts: set[int] = set()
|
||||
night_self_consume_discourage_ts: set[int] = set()
|
||||
evening_push_hysteresis_retained = False
|
||||
if om == "AUTO":
|
||||
per_slot_discharge_wh_pre = max(
|
||||
@@ -2424,6 +2484,7 @@ def solve_dispatch(
|
||||
soc_max_wh=float(battery.soc_max_wh),
|
||||
per_slot_discharge_wh=per_slot_push_wh_pre,
|
||||
discharge_slot_buffer=discharge_buf_pre,
|
||||
discharge_export_ok=discharge_export_slots,
|
||||
)
|
||||
)
|
||||
if evening_push_ts_override is not None:
|
||||
@@ -2453,6 +2514,12 @@ def solve_dispatch(
|
||||
evening_push_ts=evening_push_ts,
|
||||
exempt_ts=evening_export_exempt_ts,
|
||||
)
|
||||
night_self_consume_discourage_ts = _night_self_consume_discourage_import_indices(
|
||||
slots,
|
||||
evening_early_export_penalty_ts,
|
||||
charge_acquisition_czk_kwh=charge_acquisition_czk_kwh,
|
||||
min_spread=float(degradation_cost_effective),
|
||||
)
|
||||
pre_neg_buy_soc_ceiling_wh = _pre_neg_buy_soc_ceiling_wh(
|
||||
slots,
|
||||
first_neg_buy_idx=first_neg_buy_idx,
|
||||
@@ -2842,6 +2909,20 @@ def solve_dispatch(
|
||||
)
|
||||
else 0
|
||||
)
|
||||
+ (
|
||||
gi[t]
|
||||
* max(
|
||||
NIGHT_SELF_CONSUME_IMPORT_SURCHARGE_CZK_KWH,
|
||||
max(
|
||||
0.0,
|
||||
float(slots[t].buy_price) - charge_acquisition_czk_kwh,
|
||||
),
|
||||
)
|
||||
* INTERVAL_H
|
||||
/ 1000
|
||||
if om == "AUTO" and t in night_self_consume_discourage_ts
|
||||
else 0
|
||||
)
|
||||
+ pulp.lpSum(
|
||||
ev_direct[e][t] * slots[t].buy_price * INTERVAL_H / 1000
|
||||
+ ev_via_bat[e][t] * slots[t].buy_price * EV_ROUNDTRIP_FACTOR * INTERVAL_H / 1000
|
||||
@@ -3946,6 +4027,9 @@ def solve_dispatch(
|
||||
"evening_early_export_ban": (
|
||||
t in evening_early_export_penalty_ts if om == "AUTO" else None
|
||||
),
|
||||
"night_self_consume_discourage_import": (
|
||||
t in night_self_consume_discourage_ts if om == "AUTO" else None
|
||||
),
|
||||
}
|
||||
)
|
||||
tgt_s = st.safety_soc_target_wh if daytime_en else None
|
||||
@@ -4114,6 +4198,10 @@ def solve_dispatch(
|
||||
else _evening_night_peak_sell_czk(slots)
|
||||
),
|
||||
"evening_push_hysteresis_retained": bool(evening_push_hysteresis_retained),
|
||||
"night_self_consume_discourage_ts": [
|
||||
slots[i].interval_start.isoformat()
|
||||
for i in sorted(night_self_consume_discourage_ts)
|
||||
],
|
||||
},
|
||||
"masks": masks_snap,
|
||||
"soc_bounds": soc_bounds_snap,
|
||||
|
||||
Reference in New Issue
Block a user