Compare commits
3 Commits
7036bcfdb8
...
095676e3b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
095676e3b1 | ||
|
|
67d34aba41 | ||
|
|
b46da6b2dc |
@@ -50,10 +50,6 @@ DEFAULT_PLANNER_DISCHARGE_RELAX_PREWINDOW_SLOTS = 8
|
||||
# Penalizace je v Kč/Wh (např. 0.20 = 200 Kč/kWh). Musí být dost velká, aby přebila
|
||||
# bezpečnostní SoC buffer + terminal shadow cenu a solver skutečně „dovylil“ před sell<0.
|
||||
PRENEG_SELL_SOC_ANCHOR_SLACK_PENALTY_CZK_PER_WH = 0.20
|
||||
# Penalita za překročení SoC capu před prvním buy<0 slotem. Musí být VÝRAZNĚ VYŠŠÍ
|
||||
# než marginal arbitrage (peak_sell − avg_neg_buy ~ 5 Kč/kWh), aby LP cap dodržoval
|
||||
# i přes alternativní cesty.
|
||||
PRE_NEG_BUY_SOC_SLACK_PENALTY_CZK_PER_WH = 0.05
|
||||
PEAK_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||||
# Měkký tlak: v okně sell<0 + block_export využít PV přebytek do baterie (ne curtail).
|
||||
PV_CHARGE_SHORTFALL_PENALTY_CZK_KWH = 120.0
|
||||
@@ -68,7 +64,7 @@ NEG_SELL_PV_B_VENT_PENALTY_CZK_KWH = 4.0
|
||||
# Výboj baterie při sell<0 jen těsně před extrémně záporným buy (round-trip arbitráž).
|
||||
EXTREME_BUY_DUMP_PREWINDOW_SLOTS = 12
|
||||
NEG_SELL_BAT_DUMP_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||||
PLANNER_BUILD_TAG = "2026-05-27-hard-pv-mask-v15"
|
||||
PLANNER_BUILD_TAG = "2026-05-27-simple-buy-neg-window-v16"
|
||||
CORRECTION_WINDOW_H = 1 # hodina zpět pro výpočet korekčního faktoru
|
||||
CORRECTION_MIN_CLAMP = 0.5 # spodní limit korekčního faktoru
|
||||
CORRECTION_MAX_CLAMP = 1.5 # horní limit korekčního faktoru
|
||||
@@ -1204,33 +1200,28 @@ def solve_dispatch(
|
||||
discharge_export_slots = {
|
||||
t for t, s in enumerate(slots) if s.allow_discharge_export
|
||||
}
|
||||
# Pokud je v horizontu buy<0 okno a startovní SoC je nad cap (rezervační target),
|
||||
# potřebujeme cestu jak baterii do okna vybít — rozšířit discharge_export okno o
|
||||
# vysoké-sell sloty před prvním buy<0. R__063 v noci typicky nepovoluje export
|
||||
# (allow_discharge_export=false), ale tady chceme zaplatit za vyklízení baterky,
|
||||
# abychom potom v buy<0 okně nasáli max z OTE záporných cen.
|
||||
_neg_buy_indices_pre = [
|
||||
t for t, s in enumerate(slots) if float(s.buy_price) < 0
|
||||
]
|
||||
_first_neg_buy_idx_pre = _neg_buy_indices_pre[0] if _neg_buy_indices_pre else None
|
||||
_usable_cap_pre = float(battery.soc_max_wh) - float(min_soc_wh)
|
||||
_cap_target_raw_wh_pre = float(min_soc_wh) + 0.15 * _usable_cap_pre
|
||||
if (
|
||||
_first_neg_buy_idx_pre is not None
|
||||
and _first_neg_buy_idx_pre > 0
|
||||
and float(current_soc_wh) > _cap_target_raw_wh_pre
|
||||
):
|
||||
# Práh sell: alespoň 80 % maxima sell v okně před buy<0 (peak hours).
|
||||
_pre_sells = [
|
||||
float(slots[t].sell_price) for t in range(_first_neg_buy_idx_pre)
|
||||
if float(slots[t].sell_price) > 0
|
||||
# Vybití baterie před `buy<0` oknem: pokud je v horizontu buy<0, můžeme baterii
|
||||
# vybít teď za `sell` a v buy<0 okně ji nabít za záporný buy (= příjem).
|
||||
# Ekonomicky výhodné dokud: sell_t > avg_buy_neg + degradation
|
||||
# (vybíjet ztratíme ~discharge_eff loss, nabíjení v buy<0 nás platí; marže ~ sell − buy_neg − degrad).
|
||||
# Cílem je obejít to, že R__063 v noci dává allow_discharge_export=false a LP
|
||||
# by jinak nemohl baterku vyklidit přes ge_bat.
|
||||
_neg_buy_for_disch = next(
|
||||
(t for t, s in enumerate(slots) if float(s.buy_price) < 0), None
|
||||
)
|
||||
if _neg_buy_for_disch is not None and _neg_buy_for_disch > 0:
|
||||
_neg_buy_prices = [
|
||||
float(slots[t].buy_price)
|
||||
for t in range(_neg_buy_for_disch, T)
|
||||
if float(slots[t].buy_price) < 0
|
||||
]
|
||||
if _pre_sells:
|
||||
_sell_peak_pre = max(_pre_sells)
|
||||
_sell_thr_pre = 0.8 * _sell_peak_pre
|
||||
for t in range(_first_neg_buy_idx_pre):
|
||||
if float(slots[t].sell_price) >= _sell_thr_pre:
|
||||
discharge_export_slots.add(t)
|
||||
_avg_neg_buy = sum(_neg_buy_prices) / len(_neg_buy_prices) if _neg_buy_prices else 0.0
|
||||
# Práh = avg buy<0 + degradation cycle overhead; default fallback 0.1 Kč/kWh
|
||||
# když z nějakého důvodu neumíme spočítat (ochrana proti vybití do mínusu).
|
||||
_disch_sell_thr = max(_avg_neg_buy + float(degradation_cost_effective), 0.1)
|
||||
for t in range(_neg_buy_for_disch):
|
||||
if float(slots[t].sell_price) >= _disch_sell_thr:
|
||||
discharge_export_slots.add(t)
|
||||
# SELF_SUSTAIN dřív vynucoval ge[t] == 0, což umí udělat MILP infeasible v okamžiku, kdy:
|
||||
# - baterie je na max SoC (nelze nabíjet),
|
||||
# - PV pole B není curtailable,
|
||||
@@ -1287,26 +1278,6 @@ def solve_dispatch(
|
||||
# Kotva: poslední slot před prvním sell<0 by měl končit u planner floor (pokud relaxace existuje).
|
||||
# Slack penalizujeme v objective; samotné omezení přidáme až po definici soc.
|
||||
first_neg_sell_idx, pre_neg_export_last_t = _pre_negative_sell_export_window(slots)
|
||||
|
||||
# buy<0 okno: pokud je v horizontu, baterie musí dorazit DO něj s dostatečnou volnou
|
||||
# kapacitou, aby tam mohla maximálně nasát z OTE záporné cenny. LP samo to nevidí
|
||||
# (acquisition je vstupní konstanta, ne endogenní funkce slotů) — přidáme tvrdý
|
||||
# constraint na SoC v posledním slotu před prvním buy<0.
|
||||
neg_buy_indices = [t for t, s in enumerate(slots) if float(s.buy_price) < 0]
|
||||
first_neg_buy_idx = neg_buy_indices[0] if neg_buy_indices else None
|
||||
pre_neg_buy_soc_cap_wh: float | None = None
|
||||
if first_neg_buy_idx is not None and first_neg_buy_idx > 0:
|
||||
# Cap = min_soc + 15 % usable kapacity (~25 % SoC celkem pro home-01).
|
||||
# Strukturální hodnota: do buy<0 okna LP musí dorazit s prakticky prázdnou
|
||||
# baterií, aby tam mohl maximálně nasát z OTE záporných cen + PV (+ následné
|
||||
# sell<0 sloty taky doplní PV → odpolední přebytek se nevyhodí v curtail).
|
||||
usable_capacity = float(battery.soc_max_wh) - float(min_soc_wh)
|
||||
pre_neg_buy_soc_cap_wh = float(min_soc_wh) + 0.15 * usable_capacity
|
||||
# Pokud start SoC je už nad capem (zdědili jsme „plnou" baterii od minulého
|
||||
# plánu / replanu), zvedneme cap na current SoC. Constraint je pak „nepřibývat"
|
||||
# místo „prudce klesnout" — LP není infeasible / megapenalty když nemá kam vybít.
|
||||
if float(current_soc_wh) > pre_neg_buy_soc_cap_wh:
|
||||
pre_neg_buy_soc_cap_wh = float(current_soc_wh)
|
||||
last_neg_sell_by_prague_date: dict[object, int] = {}
|
||||
for t_ln, st_ln in enumerate(slots):
|
||||
if float(st_ln.sell_price) < 0:
|
||||
@@ -1363,23 +1334,6 @@ def solve_dispatch(
|
||||
t_anchor = first_neg_sell_idx - 1
|
||||
soc_anchor_slack = pulp.LpVariable("soc_anchor_slack_wh", 0, float(battery.usable_capacity_wh))
|
||||
|
||||
# Tvrdý cap SoC před prvním buy<0 slotem: rezervovat volnou kapacitu, aby LP
|
||||
# nasál maximum levné OTE záporné ceny + PV (acquisition v LP je konstanta,
|
||||
# bez tohoto omezení LP nepreferuje buy<0 sloty před PV nabíjením).
|
||||
pre_neg_buy_anchor_slack: pulp.LpVariable | None = None
|
||||
if (
|
||||
om == "AUTO"
|
||||
and first_neg_buy_idx is not None
|
||||
and first_neg_buy_idx > 0
|
||||
and pre_neg_buy_soc_cap_wh is not None
|
||||
):
|
||||
# Měkký constraint přes slack — nesmí dělat LP infeasible v patologických případech
|
||||
# (např. startovní SoC = 100 % a krátký horizont k buy<0).
|
||||
pre_neg_buy_anchor_slack = pulp.LpVariable(
|
||||
"pre_neg_buy_soc_slack_wh", 0, float(battery.usable_capacity_wh)
|
||||
)
|
||||
prob += soc[first_neg_buy_idx - 1] <= float(pre_neg_buy_soc_cap_wh) + pre_neg_buy_anchor_slack
|
||||
|
||||
daytime_en = bool(getattr(battery, "planner_daytime_charge_target_enabled", True))
|
||||
safety_pen_czk_per_wh: list[float] = []
|
||||
safety_vars: list[Optional[pulp.LpVariable]] = []
|
||||
@@ -1610,11 +1564,6 @@ def solve_dispatch(
|
||||
if soc_anchor_slack is not None
|
||||
else 0
|
||||
)
|
||||
+ (
|
||||
pre_neg_buy_anchor_slack * PRE_NEG_BUY_SOC_SLACK_PENALTY_CZK_PER_WH
|
||||
if pre_neg_buy_anchor_slack is not None
|
||||
else 0
|
||||
)
|
||||
+ pulp.lpSum(
|
||||
safety_vars[t] * safety_pen_czk_per_wh[t]
|
||||
for t in range(T)
|
||||
@@ -1986,6 +1935,16 @@ def solve_dispatch(
|
||||
prob += bd[t] == 0
|
||||
|
||||
# Slot pre-selection (z DB fn_load_planning_slots_full → allow_*)
|
||||
# PŘED prvním buy<0 slotem v horizontu (= rezervační okno):
|
||||
# - sell ≥ 0 → bc_pv = 0 (PV poteče do gridu / curtail, baterka se nenabíjí z PV
|
||||
# protože v buy<0 okně bude akvizice levnější — záporná).
|
||||
# - sell < 0 → slot je v charge_slots (R__063), bc_pv ≤ pv_surplus (= nemůžeme
|
||||
# pole A vyhodit do mínusu, raději nabít baterii).
|
||||
# JINDY (po buy<0 okně, nebo žádné buy<0 v horizontu): původní permissive
|
||||
# bc_pv ≤ pv_surplus aby nedošlo k regresi normálních dnů.
|
||||
_neg_buy_idx_main = next(
|
||||
(t for t, s in enumerate(slots) if float(s.buy_price) < 0), None
|
||||
)
|
||||
if om == "AUTO":
|
||||
for t in range(T):
|
||||
if t not in charge_slots:
|
||||
@@ -1996,11 +1955,17 @@ def solve_dispatch(
|
||||
+ int(s.pv_b_forecast_w)
|
||||
- int(s.load_baseline_w),
|
||||
)
|
||||
# Mimo grid-charge masku: jen PV přebytek; výjimka záporný buy (spot arbitráž).
|
||||
if float(s.buy_price) >= 0.0:
|
||||
prob += bc_gi[t] == 0
|
||||
in_pre_neg_buy_window = (
|
||||
_neg_buy_idx_main is not None and t < _neg_buy_idx_main
|
||||
)
|
||||
if pv_surplus_w <= 0:
|
||||
prob += bc_pv[t] == 0
|
||||
elif in_pre_neg_buy_window:
|
||||
# Strukturální preference: PV jde do gridu (sell≥0) nebo curtail,
|
||||
# ne do baterie — kapacitu si šetříme na buy<0 okno.
|
||||
prob += bc_pv[t] == 0
|
||||
else:
|
||||
prob += bc_pv[t] <= float(pv_surplus_w)
|
||||
if t not in discharge_export_slots and t not in neg_sell_bat_dump_slots:
|
||||
@@ -2411,15 +2376,6 @@ def solve_dispatch(
|
||||
if slots[0].charge_acquisition_cutoff_at is not None
|
||||
else None
|
||||
),
|
||||
"pre_neg_buy_soc_cap_wh": (
|
||||
float(pre_neg_buy_soc_cap_wh)
|
||||
if pre_neg_buy_soc_cap_wh is not None else None
|
||||
),
|
||||
"pre_neg_buy_soc_slack_wh": (
|
||||
float(pulp.value(pre_neg_buy_anchor_slack) or 0)
|
||||
if pre_neg_buy_anchor_slack is not None else None
|
||||
),
|
||||
"first_neg_buy_idx": first_neg_buy_idx,
|
||||
},
|
||||
"masks": masks_snap,
|
||||
"soc_bounds": soc_bounds_snap,
|
||||
|
||||
@@ -1230,7 +1230,7 @@ class NegativeSellPvChargeTests(unittest.TestCase):
|
||||
50.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-27-hard-pv-mask-v15")
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-27-simple-buy-neg-window-v16")
|
||||
self.assertGreater(
|
||||
results[0].battery_setpoint_w,
|
||||
5_500,
|
||||
@@ -1380,7 +1380,7 @@ class NegativeSellPvChargeTests(unittest.TestCase):
|
||||
50.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-27-hard-pv-mask-v15")
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-27-simple-buy-neg-window-v16")
|
||||
self.assertEqual(len(results), len(slots))
|
||||
|
||||
def test_gen_cutoff_full_soc_neg_sell_with_pv_b_feasible(self) -> None:
|
||||
@@ -1444,7 +1444,7 @@ class NegativeSellPvChargeTests(unittest.TestCase):
|
||||
55.0,
|
||||
operating_mode="AUTO",
|
||||
)
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-27-hard-pv-mask-v15")
|
||||
self.assertEqual(snap.get("planner_build_tag"), "2026-05-27-simple-buy-neg-window-v16")
|
||||
self.assertEqual(len(results), len(slots))
|
||||
|
||||
def test_fixed_tariff_neg_sell_no_grid_export(self) -> None:
|
||||
|
||||
@@ -5,43 +5,28 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-27 (e) — pre-`buy<0` discharge_export window + SoC cap (v15)
|
||||
## 2026-05-27 (f) — zjednodušená strategie pro buy<0 okno (v16, revert v14+v15)
|
||||
|
||||
**Problém (home-01 run 16636, tag v14):** SoC cap 47 % spočítán správně (`pre_neg_buy_soc_cap_wh=29800`), ale LP **necilí** — 12:45 SoC = 98,4 %, slack 33 kWh × penalty 5 Kč/kWh = 165 Kč LP přijímá. Důvod: startovní SoC 58 % > cap 47 %, a R__063 v noci nepovoluje `allow_discharge_export=true` → **LP nemá cestu jak baterii vybít** kromě malé self-consumption. Tvrdý `bc_pv[t] = 0` mimo charge_slots by vyřešil PV pumping, ale rozbije bilanci v testech kde `pv_surplus > 0 + sell < pv_store_val` (export blokován v Pythonu).
|
||||
**Problém v14/v15 (run 16622, 16636, 16642):** Vrstvy soft penalty (cap+slack, PV charge suppressed penalty) LP **nedonutily** vybít baterii ani omezit PV pumping. LP přijímal sloupec slack 24 kWh × 50 Kč/kWh = 1190 Kč a baterii nabíjel z ranního PV (10:30 SoC=95 %), pak v `buy<0` okně (13:00–14:45) curtail pole A 5–9 kW + export pole A do mínusu.
|
||||
|
||||
**Oprava (tag `2026-05-27-hard-pv-mask-v15`):**
|
||||
**Strukturální root cause (3 vrstvy):**
|
||||
1. R__063 `allow_charge=false` ze SQL Pythonský `solve_dispatch` ignoruje pro PV charging (`bc_pv ≤ pv_surplus` i pro `t not in charge_slots`).
|
||||
2. `discharge_export_slots` v noci `false` (R__063) → LP nemá cestu jak baterii vybít přes ge_bat.
|
||||
3. `acquisition` v LP je vstupní konstanta — LP nevidí, že buy<0 okno je „lepší cesta" než ranní PV pumping.
|
||||
|
||||
- **Cap snížen na `min_soc + 0.15 × usable`** (~25 % SoC) místo dynamického z `neg_window_capacity`.
|
||||
- **Penalty zvýšena 10×** na 0,05 Kč/Wh (= 50 Kč/kWh), nad marginal arbitrage (~5 Kč/kWh).
|
||||
- **Když start SoC > cap_raw**, rozšíříme `discharge_export_slots` o sloty před `first_neg_buy_idx` se `sell ≥ 0.8 × max(sell_pre_neg_buy)`. LP tak může vybít baterii **přes ge_bat** v noci/ráno za sell ~3 Kč/kWh; pak v buy<0 okně nasáje z OTE záporných cen.
|
||||
- Když start ≤ cap_raw, cap = current_soc (constraint „nepřibývat" místo „klesnout" → triviálně splněno, LP nikdy nedostane infeasible kvůli rezervaci).
|
||||
**Oprava (tag `2026-05-27-simple-buy-neg-window-v16`):** Reverted v14+v15, znovu postaveno **2 jednoduchá pravidla** podle business logiky:
|
||||
|
||||
**Ekonomická logika home-01 zítra:**
|
||||
- Vybít 20 kWh v noci za sell~3 Kč/kWh = ~60 Kč
|
||||
- Nabít 20 kWh v buy<0 okně za buy~−0.22 Kč/kWh = ~+4 Kč (acquisition záporný)
|
||||
- Vyprodat v peak za sell~4.4 Kč/kWh = ~88 Kč
|
||||
- Total alternative: ~152 Kč (vs current bez vybíjení ~132 Kč)
|
||||
1. **Tvrdé `bc_pv[t] = 0` pre-first_neg_buy_idx** (slots kde `t not in charge_slots`): PV poteče do gridu (sell≥0) nebo curtail, ne do baterie. R__063 už pro `sell<0+pv_surplus` přidává `allow_charge=true` (= `t in charge_slots`), takže pole A v `sell<0` slotech může nabíjet baterii (= nevyhodit do mínusu).
|
||||
2. **Rozšíření `discharge_export_slots`** o pre-`buy<0` sloty se **dynamickým prahem** `sell ≥ max(avg(buy<0) + degradation_cost, 0.1) Kč/kWh`. Pro home-01 (avg buy<0 ≈ −0,22, degrad ≈ 0,15) to dělá práh ~0,1 Kč → prakticky všechny noční sloty se `sell > 0`. Ekonomická logika: marže `sell_t − acquisition_in_neg_buy_window − degradation`, a pokud `acquisition ≈ záporný` (buy<0 v okně), je výhodné vybít a znovu nabít i za sell ~1 Kč/kWh.
|
||||
|
||||
**Ověření:** v `solver_params.inputs` nově: `first_neg_buy_idx`, `pre_neg_buy_soc_cap_wh`, `pre_neg_buy_soc_slack_wh`. Replan home-01 → SoC v 12:45 ≤ 25 % (nebo cap = start SoC, pokud bottleneck export). `bat_setpoint_w < 0` v některých nočních / ranních slotech s peak sell.
|
||||
**Business logika (od uživatele):**
|
||||
- Noc před `buy<0`: vybít baterii za sell ~3 Kč/kWh.
|
||||
- Ráno: minimální SoC.
|
||||
- `buy<0` okno: PV B necurtailovat (R__063 už řeší), nabíjet ze sítě (LP samo, buy záporný = `t in charge_slots`).
|
||||
- Po `sell>0`: baterie plná, max prodej.
|
||||
- Večer: prodat zbytek.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-27 (d) — pre-`buy<0` SoC cap v LP (v14)
|
||||
|
||||
**Problém (home-01 run 16622, tag v13):** Fix 1 (acquisition ≥ 0) i Fix 2 (R__063 `v_pv_layer_cap_wh` redukce) byly nasazené, ale plán pro 2026-05-25 zůstal stejný: 10:30 SoC = 95 %, 11:00 SoC = 98,3 %, 13:00–14:45 (buy<0, sell<0) baterka plná → export pole A do mínusu + curtail 5 kW pole A. Příčina:
|
||||
- LP v `solve_dispatch` má **bc_pv[t] ≤ pv_surplus_w** i pro sloty `t not in charge_slots` (Python obchází tvrdou masku R__063 pro PV charging) → R__063 Fix 2 (vrstva A cap = 0) nemá efekt.
|
||||
- `acquisition` je v LP **vstupní konstanta**, ne endogenní funkce slotů → LP nevidí per-slot opportunity cost (nabíjení v `buy<0` = záporná cena vs PV = 0) → LP rovnoměrně nabíjí kdekoliv má PV.
|
||||
- Marginal arbitrage = peak_sell (4,40) − avg_neg_buy (−0,22) = **4,62 Kč/kWh**, vs PV→bat (acquisition 0,757) = 3,64 Kč/kWh — rozdíl ~1 Kč/kWh × 32 kWh denně.
|
||||
|
||||
**Oprava (tag `2026-05-27-pre-neg-buy-soc-cap-v14`):** Tvrdý LP constraint na SoC v posledním slotu před prvním `buy<0`:
|
||||
- `neg_buy_indices = [t for t,s in slots if buy<0]`
|
||||
- `neg_window_capacity_wh = Σ min(max_charge_w, pv_surplus + grid_max_import) × 0.25 × eff` přes neg_buy sloty.
|
||||
- `pre_neg_buy_soc_cap_wh = max(min_soc_wh, soc_max_wh − min(neg_window, 0.9 × usable))`.
|
||||
- LP: `soc[first_neg_buy_idx − 1] ≤ pre_neg_buy_soc_cap_wh + slack`, slack penalizován 0,005 Kč/Wh (= 5 Kč/kWh, lehce nad marginal arb → LP cap dodrží, ale neinfeasible při krátkém horizontu / vysokém startovním SoC).
|
||||
|
||||
**Důsledek:** LP musí baterii do `buy<0` okna dorazit s volnou kapacitou — místo ranního PV nabíjení (sell≥0 sloty) export pole B (green bonus 7,135 Kč/kWh) a curtail pole A; v `buy<0` okně max import + max PV → baterka plná; večer max export.
|
||||
|
||||
**Ověření:** v `solver_params.inputs` nově: `first_neg_buy_idx`, `pre_neg_buy_soc_cap_wh`, `pre_neg_buy_soc_slack_wh`. Replan home-01 zítra (2026-05-25) → SoC v 12:45 ≤ cap (cca 10–15 %), 13:00–14:45 SoC stoupá z capu k 100 %, `pv_a_curtailed_w` v okně blíží 0.
|
||||
**Ověření:** `pytest backend/tests/test_planning_dispatch_milp.py tests/test_planning_charge_slot_selection.py` — 87 passed (1 pre-existing fail nesouvisí). Po deploy MCP: `select pr.solver_params->'planner_build_tag'` = `…-v16`, plán home-01 25.5.: SoC v 12:45 < 50 %, 13:00–14:45 SoC roste z capu k ~95 %, `pv_a_curtailed_w` blízko 0 v okně.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user