oprava
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_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||||
PRE_NEG_BATT_EXPORT_MIN_SELL_CZK_KWH = 1.0
|
||||
PLANNER_BUILD_TAG = "2026-05-29-neg-prep-observed-soc-v40"
|
||||
PLANNER_BUILD_TAG = "2026-05-29-neg-prep-infeasible-relax-v40b"
|
||||
# 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).
|
||||
@@ -1300,12 +1300,20 @@ def _neg_evening_before_neg_push_indices(
|
||||
*,
|
||||
export_budget_wh: float,
|
||||
per_slot_discharge_wh: float,
|
||||
discharge_export_ok: set[int] | None = None,
|
||||
) -> set[int]:
|
||||
"""Nejdražší kladné-sell sloty v kandidátech, dokud budget z pozorovaného SoC."""
|
||||
if export_budget_wh < per_slot_discharge_wh * 0.5 or not candidate_ts:
|
||||
return set()
|
||||
eligible = {
|
||||
t
|
||||
for t in candidate_ts
|
||||
if discharge_export_ok is None or t in discharge_export_ok
|
||||
}
|
||||
if not eligible:
|
||||
return set()
|
||||
ranked = sorted(
|
||||
candidate_ts,
|
||||
eligible,
|
||||
key=lambda t: (float(slots[t].sell_price), -t),
|
||||
reverse=True,
|
||||
)
|
||||
@@ -1974,6 +1982,8 @@ def solve_dispatch(
|
||||
planner_version: str | None = None,
|
||||
relaxed_expensive_import: bool = False,
|
||||
relaxed_neg_buy_charge: bool = False,
|
||||
relaxed_neg_prep_window: bool = False,
|
||||
neg_sell_phases_fallback: bool = False,
|
||||
evening_push_ts_override: Optional[set[int]] = None,
|
||||
) -> tuple[list[DispatchResult], int, dict[str, Any]]:
|
||||
"""
|
||||
@@ -1981,6 +1991,7 @@ def solve_dispatch(
|
||||
Vrátí (výsledky, solver_duration_ms, solver_debug_snapshot).
|
||||
relaxed_expensive_import: nouzový režim po Infeasible — síť smí krmit baseload v drahých slotech.
|
||||
relaxed_neg_buy_charge: druhý nouzový retry bez neg_buy charge shortfall.
|
||||
relaxed_neg_prep_window: třetí retry — bez tvrdého večerního push/kotvy a prep hold binárek (sell<0 okno).
|
||||
"""
|
||||
T = len(slots)
|
||||
if T < 1:
|
||||
@@ -2256,7 +2267,12 @@ def solve_dispatch(
|
||||
neg_evening_push_ts: set[int] = set()
|
||||
neg_evening_export_budget_wh: float | None = None
|
||||
neg_evening_reserve_anchors: list[tuple[int, float]] = []
|
||||
if om == "AUTO" and not purchase_fixed_pre and neg_sell_phases_en:
|
||||
if (
|
||||
om == "AUTO"
|
||||
and not purchase_fixed_pre
|
||||
and neg_sell_phases_en
|
||||
and not relaxed_neg_prep_window
|
||||
):
|
||||
pre_neg_pv_export_ts, pre_neg_cushion_by_day = _pre_neg_pv_export_bundle(
|
||||
slots,
|
||||
battery,
|
||||
@@ -2298,6 +2314,7 @@ def solve_dispatch(
|
||||
neg_evening_before_neg_ts,
|
||||
export_budget_wh=float(neg_evening_export_budget_wh),
|
||||
per_slot_discharge_wh=per_slot_neg_eve_wh,
|
||||
discharge_export_ok=discharge_export_slots,
|
||||
)
|
||||
elif om == "AUTO" and not purchase_fixed_pre:
|
||||
legacy_ok = bool(
|
||||
@@ -2678,6 +2695,7 @@ def solve_dispatch(
|
||||
neg_sell_soc_underfill.append(
|
||||
(t_tail_last, us_tail, float(battery.soc_max_wh))
|
||||
)
|
||||
if not relaxed_neg_prep_window:
|
||||
for t_ph in range(T):
|
||||
if neg_sell_phase_by_t[t_ph] != "prep":
|
||||
continue
|
||||
@@ -3643,6 +3661,7 @@ def solve_dispatch(
|
||||
charge_commitment_prev_w=charge_commitment_prev_w,
|
||||
planner_version=planner_version,
|
||||
relaxed_expensive_import=True,
|
||||
evening_push_ts_override=evening_push_ts_override,
|
||||
)
|
||||
if not relaxed_neg_buy_charge:
|
||||
logger.warning(
|
||||
@@ -3664,6 +3683,60 @@ def solve_dispatch(
|
||||
relaxed_expensive_import=True,
|
||||
relaxed_neg_buy_charge=True,
|
||||
)
|
||||
if not relaxed_neg_prep_window:
|
||||
logger.warning(
|
||||
"solve_dispatch still Infeasible, retry with relaxed_neg_prep_window "
|
||||
"(skip evening push/anchors and prep hold hard constraints)"
|
||||
)
|
||||
return solve_dispatch(
|
||||
slots,
|
||||
battery,
|
||||
heat_pump,
|
||||
grid,
|
||||
ev_sessions,
|
||||
vehicles,
|
||||
current_soc_wh,
|
||||
current_tuv_temp_c,
|
||||
tuv_delta_stats=tuv_delta_stats,
|
||||
operating_mode=operating_mode,
|
||||
charge_commitment_prev_w=charge_commitment_prev_w,
|
||||
planner_version=planner_version,
|
||||
relaxed_expensive_import=True,
|
||||
relaxed_neg_buy_charge=True,
|
||||
relaxed_neg_prep_window=True,
|
||||
neg_sell_phases_fallback=neg_sell_phases_fallback,
|
||||
evening_push_ts_override=evening_push_ts_override,
|
||||
)
|
||||
if not neg_sell_phases_fallback:
|
||||
logger.warning(
|
||||
"solve_dispatch still Infeasible, retry with neg_sell phases disabled "
|
||||
"(prep_soc_percent=100)"
|
||||
)
|
||||
battery_no_phases = SimpleNamespace(
|
||||
**{
|
||||
**vars(battery),
|
||||
"planner_neg_sell_prep_soc_percent": 100.0,
|
||||
}
|
||||
)
|
||||
return solve_dispatch(
|
||||
slots,
|
||||
battery_no_phases,
|
||||
heat_pump,
|
||||
grid,
|
||||
ev_sessions,
|
||||
vehicles,
|
||||
current_soc_wh,
|
||||
current_tuv_temp_c,
|
||||
tuv_delta_stats=tuv_delta_stats,
|
||||
operating_mode=operating_mode,
|
||||
charge_commitment_prev_w=charge_commitment_prev_w,
|
||||
planner_version=planner_version,
|
||||
relaxed_expensive_import=True,
|
||||
relaxed_neg_buy_charge=True,
|
||||
relaxed_neg_prep_window=True,
|
||||
neg_sell_phases_fallback=True,
|
||||
evening_push_ts_override=evening_push_ts_override,
|
||||
)
|
||||
raise RuntimeError(f"Solver: {pulp.LpStatus[status]}")
|
||||
|
||||
# --- Post-processing ---
|
||||
@@ -3988,6 +4061,9 @@ def solve_dispatch(
|
||||
),
|
||||
"load_first_enabled": om == "AUTO",
|
||||
"relaxed_expensive_import": relaxed_expensive_import,
|
||||
"relaxed_neg_buy_charge": relaxed_neg_buy_charge,
|
||||
"relaxed_neg_prep_window": relaxed_neg_prep_window,
|
||||
"neg_sell_phases_fallback": neg_sell_phases_fallback,
|
||||
"charge_acquisition_buy_czk_kwh": charge_acquisition_czk_kwh,
|
||||
"charge_acquisition_cutoff_at": (
|
||||
slots[0].charge_acquisition_cutoff_at.isoformat()
|
||||
@@ -4208,11 +4284,24 @@ async def run_rolling_replan(
|
||||
planner_version=planner_version_resolved,
|
||||
)
|
||||
|
||||
logger.info(f"[site={site_id}] Rolling replan from {replan_from} → {horizon_to}")
|
||||
logger.info(
|
||||
"[site=%s] Rolling replan from %s → %s (tag=%s)",
|
||||
site_id,
|
||||
replan_from,
|
||||
horizon_to,
|
||||
PLANNER_BUILD_TAG,
|
||||
)
|
||||
|
||||
battery, hp, grid, vehicles, ev_sessions, soc_wh, tuv_temp, operating_mode, tuv_stats = (
|
||||
await _load_site_context(site_id, db)
|
||||
)
|
||||
if operating_mode != "AUTO":
|
||||
logger.info(
|
||||
"[site=%s] Rolling replan skipped: operating_mode=%s (not AUTO)",
|
||||
site_id,
|
||||
operating_mode,
|
||||
)
|
||||
return None, None
|
||||
|
||||
slots = await _load_slots(site_id, replan_from, horizon_to, db, soc_wh=soc_wh)
|
||||
# PV forecast korekce je kanonicky v DB (delta + rolling faktor + decay) a do LP vstupuje přes
|
||||
|
||||
@@ -5,6 +5,16 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-29 — Infeasible rolling: relax neg-prep okno (v40b)
|
||||
|
||||
**Problém:** Po načtení OTE na **30. 5.** (neg sell) rolling/home-01 končil `Solver: Infeasible` od ~13:15; ruční replan stejně. Plán zůstal na runu z 13:00 (horizont jen do 22:00). Log často prázdný — výjimka se loguje na `WARNING`, scheduler ji polyká.
|
||||
|
||||
**Změna (v40b):** Třetí retry `relaxed_neg_prep_window` (bez večerního push/kotvy + prep hold binárek); čtvrtý retry s `planner_neg_sell_prep_soc_percent=100` (fáze sell<0 vypnuté). Večerní push jen sloty s `allow_discharge_export`. Rolling v **MANUAL** se přeskočí (log INFO). Tag **`2026-05-29-neg-prep-infeasible-relax-v40b`**.
|
||||
|
||||
**Ověření:** po deployi `POST …/plan/run?type=rolling` v AUTO; `solver_params.inputs.relaxed_neg_prep_window` nebo `neg_sell_phases_fallback`; log: `docker compose -f deploy/docker-compose.yml logs backend --since 2h 2>&1 | rg -i infeasible`.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-29 — Neg-prep z pozorovaného SoC (Plan 5, v40)
|
||||
|
||||
**Problém:** Strategie „místo na zítřejší FVE + sell<0“ a večerní výboj před neg dnem počítaly z **modelového** SoC (řetězení `soc_target` mezi dny v `_pre_neg_pv_export_bundle`). BMS měl často **~15 %** více → předčasné zastavení výboje, „mrtvé“ kWh přes noc, méně ranního pre-neg exportu.
|
||||
|
||||
Reference in New Issue
Block a user