prej final final v2 verze
This commit is contained in:
@@ -71,7 +71,7 @@ NEG_BUY_CHARGE_SHORTFALL_PENALTY_CZK_KWH = 100.0
|
|||||||
PRE_NEG_CHARGE_PENALTY_CZK_KWH = 400.0
|
PRE_NEG_CHARGE_PENALTY_CZK_KWH = 400.0
|
||||||
PRE_NEG_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
PRE_NEG_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||||||
PRE_NEG_BATT_EXPORT_MIN_SELL_CZK_KWH = 1.0
|
PRE_NEG_BATT_EXPORT_MIN_SELL_CZK_KWH = 1.0
|
||||||
PLANNER_BUILD_TAG = "2026-06-06-home01-degraded-night-guard-v4"
|
PLANNER_BUILD_TAG = "2026-06-06-home01-strict-late-replan-v5"
|
||||||
SOLVER_RELAX_STEPS: tuple[str, ...] = (
|
SOLVER_RELAX_STEPS: tuple[str, ...] = (
|
||||||
"strict",
|
"strict",
|
||||||
"relaxed_expensive_import",
|
"relaxed_expensive_import",
|
||||||
@@ -1940,6 +1940,56 @@ def _evening_push_segment_candidates(
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _strict_late_replan_evening_slot_indices(
|
||||||
|
slots: list[PlanningSlot],
|
||||||
|
*,
|
||||||
|
first_neg_buy_idx: int | None,
|
||||||
|
observed_soc_wh: float,
|
||||||
|
reserve_soc_wh: float,
|
||||||
|
) -> set[int]:
|
||||||
|
"""
|
||||||
|
Strict solve: večer D0 (17–22h) před dnem s buy<0 — vývoz k reserve, výjimka z pos_sell ge=0.
|
||||||
|
"""
|
||||||
|
if first_neg_buy_idx is None or first_neg_buy_idx <= 0:
|
||||||
|
return set()
|
||||||
|
if not any(float(s.buy_price) < 0.0 for s in slots):
|
||||||
|
return set()
|
||||||
|
if observed_soc_wh <= float(reserve_soc_wh) + 500.0:
|
||||||
|
return set()
|
||||||
|
replan_day = _prague_calendar_date(slots[0])
|
||||||
|
out: set[int] = set()
|
||||||
|
for t, s in enumerate(slots):
|
||||||
|
if _prague_calendar_date(s) != replan_day:
|
||||||
|
continue
|
||||||
|
h = _prague_hour(s)
|
||||||
|
if h < NIGHT_EXPORT_EVENING_START_HOUR or h > 22:
|
||||||
|
continue
|
||||||
|
if float(s.sell_price) < 0.0:
|
||||||
|
continue
|
||||||
|
out.add(t)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _strict_late_replan_night_self_consume_indices(
|
||||||
|
slots: list[PlanningSlot],
|
||||||
|
*,
|
||||||
|
evening_export_ts: set[int],
|
||||||
|
) -> set[int]:
|
||||||
|
"""Strict: noc po 22h — dům z baterie, ne drahý import (mimo večerní export sloty)."""
|
||||||
|
out: set[int] = set()
|
||||||
|
for t, s in enumerate(slots):
|
||||||
|
if t in evening_export_ts:
|
||||||
|
continue
|
||||||
|
if not _in_night_battery_export_window(s):
|
||||||
|
continue
|
||||||
|
if float(s.load_baseline_w) <= 0:
|
||||||
|
continue
|
||||||
|
if float(s.buy_price) < 0.0:
|
||||||
|
continue
|
||||||
|
out.add(t)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _degraded_relaxed_night_self_consume_indices(
|
def _degraded_relaxed_night_self_consume_indices(
|
||||||
slots: list[PlanningSlot],
|
slots: list[PlanningSlot],
|
||||||
) -> set[int]:
|
) -> set[int]:
|
||||||
@@ -2658,6 +2708,9 @@ def solve_dispatch(
|
|||||||
or relaxed_solver_masks
|
or relaxed_solver_masks
|
||||||
)
|
)
|
||||||
prep_hold_relaxed = relaxed_neg_prep_hold_only or relaxed_neg_prep_window
|
prep_hold_relaxed = relaxed_neg_prep_hold_only or relaxed_neg_prep_window
|
||||||
|
late_replan_strict_active = False
|
||||||
|
strict_late_replan_evening_ts: set[int] = set()
|
||||||
|
strict_late_replan_night_ts: set[int] = set()
|
||||||
EV = len(vehicles) # počet EV (typicky 2)
|
EV = len(vehicles) # počet EV (typicky 2)
|
||||||
planner_version_resolved = _planner_engine_version(planner_version)
|
planner_version_resolved = _planner_engine_version(planner_version)
|
||||||
planner_v2 = planner_version_resolved == "v2"
|
planner_v2 = planner_version_resolved == "v2"
|
||||||
@@ -2902,10 +2955,60 @@ def solve_dispatch(
|
|||||||
)
|
)
|
||||||
min_spread_pre = float(degradation_cost_effective)
|
min_spread_pre = float(degradation_cost_effective)
|
||||||
fixed_tariff_like_pre = purchase_fixed_pre or _horizon_fixed_tariff_like(slots)
|
fixed_tariff_like_pre = purchase_fixed_pre or _horizon_fixed_tariff_like(slots)
|
||||||
|
reserve_soc_wh_solver = float(
|
||||||
|
getattr(battery, "reserve_soc_wh", getattr(battery, "min_soc_wh", 0.0))
|
||||||
|
)
|
||||||
|
if om == "AUTO" and not purchase_fixed_pre and not relaxed_solver_masks:
|
||||||
|
strict_late_replan_evening_ts = _strict_late_replan_evening_slot_indices(
|
||||||
|
slots,
|
||||||
|
first_neg_buy_idx=first_neg_buy_idx,
|
||||||
|
observed_soc_wh=observed_soc_wh,
|
||||||
|
reserve_soc_wh=reserve_soc_wh_solver,
|
||||||
|
)
|
||||||
|
late_replan_strict_active = bool(strict_late_replan_evening_ts)
|
||||||
|
if late_replan_strict_active:
|
||||||
|
slots = [
|
||||||
|
replace(
|
||||||
|
s,
|
||||||
|
allow_charge=True,
|
||||||
|
allow_discharge_export=True,
|
||||||
|
)
|
||||||
|
if i in strict_late_replan_evening_ts
|
||||||
|
else s
|
||||||
|
for i, s in enumerate(slots)
|
||||||
|
]
|
||||||
|
charge_slots |= strict_late_replan_evening_ts
|
||||||
|
discharge_export_slots |= strict_late_replan_evening_ts
|
||||||
|
if not relaxed_pos_sell_ge_block and not relaxed_solver_masks:
|
||||||
|
slots = _relax_solver_slot_masks(slots)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
charge_slots |= {
|
||||||
|
t
|
||||||
|
for t, s in enumerate(slots)
|
||||||
|
if float(s.sell_price) < 0.0
|
||||||
|
and max(
|
||||||
|
0,
|
||||||
|
int(s.pv_a_forecast_w)
|
||||||
|
+ int(s.pv_b_forecast_w)
|
||||||
|
- int(s.load_baseline_w),
|
||||||
|
)
|
||||||
|
> 500
|
||||||
|
}
|
||||||
|
discharge_export_slots = {
|
||||||
|
t for t, s in enumerate(slots) if s.allow_discharge_export
|
||||||
|
}
|
||||||
|
late_replan_solver_relax = (
|
||||||
|
late_replan_strict_active
|
||||||
|
and not relaxed_solver_masks
|
||||||
|
)
|
||||||
neg_sell_phases_en = (
|
neg_sell_phases_en = (
|
||||||
om == "AUTO"
|
om == "AUTO"
|
||||||
and not purchase_fixed_pre
|
and not purchase_fixed_pre
|
||||||
and _neg_sell_phases_enabled(battery)
|
and _neg_sell_phases_enabled(battery)
|
||||||
|
and not late_replan_strict_active
|
||||||
)
|
)
|
||||||
neg_sell_phase_by_t: list[str] = ["none"] * T
|
neg_sell_phase_by_t: list[str] = ["none"] * T
|
||||||
neg_sell_soc_target_by_t: list[Optional[float]] = [None] * T
|
neg_sell_soc_target_by_t: list[Optional[float]] = [None] * T
|
||||||
@@ -2963,6 +3066,7 @@ def solve_dispatch(
|
|||||||
and not purchase_fixed_pre
|
and not purchase_fixed_pre
|
||||||
and neg_sell_phases_en
|
and neg_sell_phases_en
|
||||||
and not relaxed_neg_prep_window
|
and not relaxed_neg_prep_window
|
||||||
|
and not late_replan_strict_active
|
||||||
)
|
)
|
||||||
neg_evening_discharge_active = neg_evening_bundle_strict or future_neg_buy_discharge_en
|
neg_evening_discharge_active = neg_evening_bundle_strict or future_neg_buy_discharge_en
|
||||||
if neg_evening_bundle_strict:
|
if neg_evening_bundle_strict:
|
||||||
@@ -3151,7 +3255,9 @@ def solve_dispatch(
|
|||||||
purchase_fixed=purchase_fixed_pre,
|
purchase_fixed=purchase_fixed_pre,
|
||||||
)
|
)
|
||||||
# Tvrdý ge_bat push vypnout jen při neg_sell fallback (ne při prep relax — v64).
|
# Tvrdý ge_bat push vypnout jen při neg_sell fallback (ne při prep relax — v64).
|
||||||
evening_push_hard_suppressed = bool(neg_sell_phases_fallback)
|
evening_push_hard_suppressed = bool(
|
||||||
|
neg_sell_phases_fallback or late_replan_strict_active
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
evening_push_hard_suppressed = False
|
evening_push_hard_suppressed = False
|
||||||
last_pos_sell_pre_neg_buy = _last_non_negative_sell_before_neg_buy(
|
last_pos_sell_pre_neg_buy = _last_non_negative_sell_before_neg_buy(
|
||||||
@@ -3169,6 +3275,8 @@ def solve_dispatch(
|
|||||||
fixed_tariff=fixed_tariff_like_pre,
|
fixed_tariff=fixed_tariff_like_pre,
|
||||||
future_neg_buy_discharge_en=future_neg_buy_discharge_en,
|
future_neg_buy_discharge_en=future_neg_buy_discharge_en,
|
||||||
)
|
)
|
||||||
|
if strict_late_replan_evening_ts:
|
||||||
|
pos_sell_pre_neg_buy_ge_exempt_ts |= strict_late_replan_evening_ts
|
||||||
pre_neg_buy_empty_ts = _pre_neg_buy_empty_discharge_indices(
|
pre_neg_buy_empty_ts = _pre_neg_buy_empty_discharge_indices(
|
||||||
slots, first_neg_buy_idx, last_pos_sell_pre_neg_buy
|
slots, first_neg_buy_idx, last_pos_sell_pre_neg_buy
|
||||||
)
|
)
|
||||||
@@ -3199,6 +3307,13 @@ def solve_dispatch(
|
|||||||
slots, evening_push_ts
|
slots, evening_push_ts
|
||||||
)
|
)
|
||||||
night_self_consume_discourage_ts |= post_evening_push_night_ts
|
night_self_consume_discourage_ts |= post_evening_push_night_ts
|
||||||
|
if not relaxed_solver_masks and strict_late_replan_evening_ts:
|
||||||
|
strict_late_replan_night_ts = _strict_late_replan_night_self_consume_indices(
|
||||||
|
slots,
|
||||||
|
evening_export_ts=strict_late_replan_evening_ts,
|
||||||
|
)
|
||||||
|
night_self_consume_discourage_ts |= strict_late_replan_night_ts
|
||||||
|
post_evening_push_night_ts |= strict_late_replan_night_ts
|
||||||
battery_export_defer_pv_ts = {
|
battery_export_defer_pv_ts = {
|
||||||
t for t in range(T) if _battery_export_push_defer_to_pv(slots[t])
|
t for t in range(T) if _battery_export_push_defer_to_pv(slots[t])
|
||||||
}
|
}
|
||||||
@@ -3343,6 +3458,7 @@ def solve_dispatch(
|
|||||||
relaxed_neg_buy_charge
|
relaxed_neg_buy_charge
|
||||||
or relaxed_neg_prep_window
|
or relaxed_neg_prep_window
|
||||||
or neg_sell_phases_fallback
|
or neg_sell_phases_fallback
|
||||||
|
or late_replan_solver_relax
|
||||||
):
|
):
|
||||||
commitment_for_solve = None
|
commitment_for_solve = None
|
||||||
if commitment_for_solve is not None and len(commitment_for_solve) == T:
|
if commitment_for_solve is not None and len(commitment_for_solve) == T:
|
||||||
@@ -3422,7 +3538,7 @@ def solve_dispatch(
|
|||||||
deg_cap,
|
deg_cap,
|
||||||
)
|
)
|
||||||
degraded_evening_export_shortfall.append((t_deg, sf_deg, deg_cap))
|
degraded_evening_export_shortfall.append((t_deg, sf_deg, deg_cap))
|
||||||
if not relaxed_neg_buy_charge:
|
if not (relaxed_neg_buy_charge or late_replan_solver_relax):
|
||||||
neg_buy_slot_indices = [
|
neg_buy_slot_indices = [
|
||||||
t for t, s in enumerate(slots) if float(s.buy_price) < 0.0
|
t for t, s in enumerate(slots) if float(s.buy_price) < 0.0
|
||||||
]
|
]
|
||||||
@@ -3533,7 +3649,7 @@ def solve_dispatch(
|
|||||||
cap_w = float(min(pv_surplus_w, battery.max_charge_power_w))
|
cap_w = float(min(pv_surplus_w, battery.max_charge_power_w))
|
||||||
sf_pv = pulp.LpVariable(f"post_neg_pv_shortfall_{t}", 0, cap_w)
|
sf_pv = pulp.LpVariable(f"post_neg_pv_shortfall_{t}", 0, cap_w)
|
||||||
pv_charge_shortfall.append((t, sf_pv, cap_w))
|
pv_charge_shortfall.append((t, sf_pv, cap_w))
|
||||||
if neg_sell_phases_en and not prep_hold_relaxed:
|
if neg_sell_phases_en and not (prep_hold_relaxed or late_replan_strict_active):
|
||||||
for t_ns in range(T):
|
for t_ns in range(T):
|
||||||
phase_ns = neg_sell_phase_by_t[t_ns]
|
phase_ns = neg_sell_phase_by_t[t_ns]
|
||||||
tgt_ns = neg_sell_soc_target_by_t[t_ns]
|
tgt_ns = neg_sell_soc_target_by_t[t_ns]
|
||||||
@@ -3552,7 +3668,7 @@ def solve_dispatch(
|
|||||||
continue
|
continue
|
||||||
tail_last_by_day[_prague_calendar_date(st_ln)] = t_ln
|
tail_last_by_day[_prague_calendar_date(st_ln)] = t_ln
|
||||||
for t_tail_last in tail_last_by_day.values():
|
for t_tail_last in tail_last_by_day.values():
|
||||||
if t_tail_last in charge_slots or relaxed_neg_buy_charge:
|
if t_tail_last in charge_slots or relaxed_neg_buy_charge or late_replan_solver_relax:
|
||||||
us_tail = pulp.LpVariable(
|
us_tail = pulp.LpVariable(
|
||||||
f"neg_sell_tail_soc_{t_tail_last}",
|
f"neg_sell_tail_soc_{t_tail_last}",
|
||||||
0,
|
0,
|
||||||
@@ -3561,7 +3677,7 @@ def solve_dispatch(
|
|||||||
neg_sell_soc_underfill.append(
|
neg_sell_soc_underfill.append(
|
||||||
(t_tail_last, us_tail, float(battery.soc_max_wh))
|
(t_tail_last, us_tail, float(battery.soc_max_wh))
|
||||||
)
|
)
|
||||||
if not prep_hold_relaxed:
|
if not (prep_hold_relaxed or late_replan_strict_active):
|
||||||
for t_ph in range(T):
|
for t_ph in range(T):
|
||||||
if neg_sell_phase_by_t[t_ph] != "prep":
|
if neg_sell_phase_by_t[t_ph] != "prep":
|
||||||
continue
|
continue
|
||||||
@@ -3577,7 +3693,7 @@ def solve_dispatch(
|
|||||||
prep_hold_curtail_shortfall.append((t_ph, sf_ca, cap_ca))
|
prep_hold_curtail_shortfall.append((t_ph, sf_ca, cap_ca))
|
||||||
elif len(neg_buy_slot_indices_pre) >= 2:
|
elif len(neg_buy_slot_indices_pre) >= 2:
|
||||||
t_nb_last = max(neg_buy_slot_indices_pre)
|
t_nb_last = max(neg_buy_slot_indices_pre)
|
||||||
if t_nb_last in charge_slots or relaxed_neg_buy_charge:
|
if t_nb_last in charge_slots or relaxed_neg_buy_charge or late_replan_solver_relax:
|
||||||
us = pulp.LpVariable(
|
us = pulp.LpVariable(
|
||||||
f"neg_buy_soc_under_{t_nb_last}",
|
f"neg_buy_soc_under_{t_nb_last}",
|
||||||
0,
|
0,
|
||||||
@@ -4584,13 +4700,13 @@ def solve_dispatch(
|
|||||||
or force_night_self_consume
|
or force_night_self_consume
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if relaxed_expensive_import and not night_self_consume_slot:
|
if (relaxed_expensive_import or late_replan_solver_relax) and not night_self_consume_slot:
|
||||||
prob += gi[t] <= ev_cap_t + hp[t] + float(s.load_baseline_w)
|
prob += gi[t] <= ev_cap_t + hp[t] + float(s.load_baseline_w)
|
||||||
else:
|
else:
|
||||||
prob += gi[t] <= ev_cap_t + hp[t]
|
prob += gi[t] <= ev_cap_t + hp[t]
|
||||||
if (
|
if (
|
||||||
force_night_self_consume
|
force_night_self_consume
|
||||||
or (not relaxed_expensive_import or night_self_consume_slot)
|
or (not (relaxed_expensive_import or late_replan_solver_relax) or night_self_consume_slot)
|
||||||
) and om == "AUTO":
|
) and om == "AUTO":
|
||||||
prob += (
|
prob += (
|
||||||
bd[t] + pv_ld[t]
|
bd[t] + pv_ld[t]
|
||||||
@@ -5272,6 +5388,16 @@ def solve_dispatch(
|
|||||||
slots[i].interval_start.isoformat()
|
slots[i].interval_start.isoformat()
|
||||||
for i in sorted(degraded_evening_export_ts)
|
for i in sorted(degraded_evening_export_ts)
|
||||||
],
|
],
|
||||||
|
"strict_late_replan_evening_ts": [
|
||||||
|
slots[i].interval_start.isoformat()
|
||||||
|
for i in sorted(strict_late_replan_evening_ts)
|
||||||
|
],
|
||||||
|
"strict_late_replan_night_ts": [
|
||||||
|
slots[i].interval_start.isoformat()
|
||||||
|
for i in sorted(strict_late_replan_night_ts)
|
||||||
|
],
|
||||||
|
"late_replan_strict_active": bool(late_replan_strict_active),
|
||||||
|
"late_replan_solver_relax": bool(late_replan_solver_relax),
|
||||||
},
|
},
|
||||||
"masks": masks_snap,
|
"masks": masks_snap,
|
||||||
"soc_bounds": soc_bounds_snap,
|
"soc_bounds": soc_bounds_snap,
|
||||||
|
|||||||
@@ -3695,6 +3695,11 @@ class ChargeAcquisitionArbitrageTests(unittest.TestCase):
|
|||||||
50.0,
|
50.0,
|
||||||
operating_mode="AUTO",
|
operating_mode="AUTO",
|
||||||
)
|
)
|
||||||
|
self.assertEqual(snap["inputs"].get("relax_chain"), ["strict"])
|
||||||
|
self.assertNotIn(
|
||||||
|
"relaxed_solver_masks",
|
||||||
|
snap["inputs"].get("relax_chain") or [],
|
||||||
|
)
|
||||||
self.assertLess(results[0].grid_setpoint_w, -500)
|
self.assertLess(results[0].grid_setpoint_w, -500)
|
||||||
self.assertLess(results[0].battery_soc_target, 70.0)
|
self.assertLess(results[0].battery_soc_target, 70.0)
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,15 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
|||||||
- Nový retry **`relaxed_pos_sell_ge_block`** (+ **`relaxed_solver_masks`** nouzový) v `SOLVER_RELAX_STEPS`.
|
- Nový retry **`relaxed_pos_sell_ge_block`** (+ **`relaxed_solver_masks`** nouzový) v `SOLVER_RELAX_STEPS`.
|
||||||
- **v2:** two-pass pass2 dědí všechny relax flagy; při pass2 Infeasible fallback na pass1; override push zrušen už od `relaxed_neg_prep_hold_only`.
|
- **v2:** two-pass pass2 dědí všechny relax flagy; při pass2 Infeasible fallback na pass1; override push zrušen už od `relaxed_neg_prep_hold_only`.
|
||||||
- **v3 (`degraded-night-guard-v3`):** v **`relaxed_solver_masks`** — spot **bez nočního/večerního `ge_bat` exportu**; **`_degraded_relaxed_night_self_consume_indices`** + tvrdý expensive-import guard (dům z baterie až **`min_soc`**, ne import za ~4 Kč); exportní podlaha SoC ≥ **`reserve_soc`**.
|
- **v3 (`degraded-night-guard-v3`):** v **`relaxed_solver_masks`** — spot **bez nočního/večerního `ge_bat` exportu**; **`_degraded_relaxed_night_self_consume_indices`** + tvrdý expensive-import guard (dům z baterie až **`min_soc`**, ne import za ~4 Kč); exportní podlaha SoC ≥ **`reserve_soc`**.
|
||||||
- **v4 (`degraded-night-guard-v4`):** oprava v3 — `charge_slots=all` obcházela expensive-import guard → import za ~4 Kč. **Večer D0 (17–22h):** vývoz k **`reserve_soc`** (`degraded_evening_export_ts` + shortfall). **Po 22h / půlnoc:** tvrdý **`bd ≥ load`** (`force_night_self_consume`) i když `t ∈ charge_slots`. Večerní export sloty **ne** sahají do 23h+ (jinak blokují noc).
|
- **v4 (`degraded-night-guard-v4`):** oprava v3 — nouzový režim pro `relaxed_solver_masks` (viz výše).
|
||||||
|
- **v5 (`strict-late-replan-v5`):** **strict solve** bez relax chainu při pozdním replanu večer před `buy<0` dnem — `late_replan_strict_active`: večer 17–22h vývoz k **reserve**, noc self-consume (discourage import), vypnutí neg-evening bundle + prep fází + tvrdého evening push; snapshot `strict_late_replan_*_ts`, `late_replan_solver_relax`.
|
||||||
|
|
||||||
**Soubory:** `planning_engine.py`, `scripts/repro_home01_23840.py`, testy `test_home01_late_replan_high_soc_realistic_masks`, `test_degraded_relaxed_solver_evening_to_reserve_and_night_self_consume`.
|
**Soubory:** `planning_engine.py`, `scripts/repro_home01_23840.py`, testy `test_home01_late_replan_high_soc_realistic_masks`, `test_degraded_relaxed_solver_evening_to_reserve_and_night_self_consume`.
|
||||||
|
|
||||||
**Ověření:**
|
**Ověření:**
|
||||||
- `PYTHONPATH=backend python3 scripts/repro_home01_23840.py` → `OK two_pass`
|
- `PYTHONPATH=backend python3 scripts/repro_home01_23840.py` → `OK two_pass`
|
||||||
- `pytest backend/tests/test_planning_dispatch_milp.py -k "home01_late_replan or degraded_relaxed"`
|
- `pytest backend/tests/test_planning_dispatch_milp.py -k "home01_late_replan or degraded_relaxed"`
|
||||||
- Po deployi: aktivní run `planner_build_tag` končí **`degraded-night-guard-v4`**; při `relax_chain` obsahujícím `relaxed_solver_masks`: večer vývoz k ~**20 %**, noc **bez** importu pro baseload, ráno headroom na FVE + **`buy<0`**.
|
- Po deployi v5: `relax_chain = ["strict"]`, `late_replan_strict_active = true` — **bez** `relaxed_solver_masks`; večer export, noc bez drahého importu baseloadu.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user