Faze 0A: battery guard carve-out — neblokovat import na nabiti pri zaporne cene
_apply_export_plan_guard / _build_setpoints: kdyz slot CHARGE / importuje na nabiti baterie (grid_sp>0 & bat>0), guard vrati sp beze zmeny a export_ban se nenastavi. Opravuje, ze se baterie nedobila v zapornych cenach (CHARGE+17kW prekloplen na PASSIVE -> Deye nenabijel ze site). Diagnoza: agent a599eecc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -125,11 +125,20 @@ def _build_setpoints(
|
||||
export_limit = 0
|
||||
elif export_limit <= 0 and grid_sp < 0:
|
||||
export_limit = abs(grid_sp)
|
||||
bat_w = int(pi["battery_setpoint_w"] or 0)
|
||||
# Záporný výkup sám o sobě neblokuje export, pokud plán export explicitně žádá.
|
||||
export_ban = sell_f is not None and float(sell_f) < 0 and grid_sp >= 0
|
||||
# A nesmí blokovat ani IMPORT na nabití baterie (CHARGE / grid>0 & bat>0) —
|
||||
# jinak MI cut-off (178) / 145=0 zbytečně odstaví pole B a Deye nenabije
|
||||
# ze sítě v záporných cenách (bug 2026-06-13). §6 blokuje jen export.
|
||||
is_grid_charge = pm == "CHARGE" or (grid_sp > 0 and bat_w > 0)
|
||||
export_ban = (
|
||||
sell_f is not None
|
||||
and float(sell_f) < 0
|
||||
and grid_sp >= 0
|
||||
and not is_grid_charge
|
||||
)
|
||||
gen_cutoff_raw = pi.get("deye_gen_cutoff_enabled")
|
||||
gen_cutoff = bool(gen_cutoff_raw) if gen_cutoff_raw is not None else False
|
||||
bat_w = int(pi["battery_setpoint_w"] or 0)
|
||||
pv_a_allowed: int | None = None
|
||||
if bool(reg340_pv_a_control_enabled) and int(pv_a_cap_w) > 0:
|
||||
forecast = int(pi.get("pv_a_forecast_solver_w") or 0)
|
||||
@@ -294,6 +303,16 @@ def _apply_export_plan_guard(
|
||||
)
|
||||
grid_sp = int(pi.get("grid_setpoint_w") or sp.grid_setpoint_w or 0)
|
||||
|
||||
# Carve-out: nabíjecí / importní slot NENÍ export. Guard řeší jen zákaz
|
||||
# exportu při sell<0 — když plán importuje na nabití baterie (CHARGE, nebo
|
||||
# grid_sp>0 & bat_sp>0), překlopení na PASSIVE by zařízlo grid charge
|
||||
# (bug 2026-06-13: baterie se nedobila v záporných cenách). §6 zakazuje
|
||||
# jen export, ne import (§7).
|
||||
pm = str(pi.get("deye_physical_mode") or "").strip().upper()
|
||||
bat_sp = int(pi.get("battery_setpoint_w") or 0)
|
||||
if pm == "CHARGE" or (grid_sp > 0 and bat_sp > 0):
|
||||
return sp
|
||||
|
||||
neg_sell = sell_f is not None and float(sell_f) < 0
|
||||
plan_no_export = export_mode == "NONE" and grid_sp >= 0
|
||||
if not neg_sell and not plan_no_export:
|
||||
|
||||
@@ -111,6 +111,29 @@ class ExportPlanGuardTests(unittest.TestCase):
|
||||
self.assertIs(out, sp)
|
||||
self.assertEqual(get_deye_mode(out), "SELL")
|
||||
|
||||
def test_neg_sell_grid_charge_not_blocked(self) -> None:
|
||||
# Záporný sell + IMPORT na nabití baterie (CHARGE / grid>0 & bat>0):
|
||||
# guard NESMÍ překlopit na PASSIVE — jinak Deye nenabije ze sítě
|
||||
# v záporných cenách (bug 2026-06-13).
|
||||
sp = _sp(
|
||||
grid_setpoint_w=17000,
|
||||
battery_w=17000,
|
||||
deye_physical_mode="CHARGE",
|
||||
export_mode="NONE",
|
||||
)
|
||||
pi = _DictRecord(
|
||||
{
|
||||
"grid_setpoint_w": 17000,
|
||||
"battery_setpoint_w": 17000,
|
||||
"deye_physical_mode": "CHARGE",
|
||||
"effective_sell_price": -1.2,
|
||||
"export_mode": "NONE",
|
||||
}
|
||||
)
|
||||
out = _apply_export_plan_guard(1, _auto_mode(), pi, sp)
|
||||
self.assertIs(out, sp)
|
||||
self.assertEqual(get_deye_mode(out), "CHARGE")
|
||||
|
||||
def test_non_auto_mode_skipped(self) -> None:
|
||||
sp = _sp()
|
||||
pi = _DictRecord({"effective_sell_price": -1.0, "export_mode": "NONE"})
|
||||
|
||||
224
docs/audits/planner-neg-buy-charge-not-executed-2026-06-13.md
Normal file
224
docs/audits/planner-neg-buy-charge-not-executed-2026-06-13.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Diagnóza: baterie se nenabila během záporných cen (home-01, 2026-06-13)
|
||||
|
||||
**Datum analýzy:** 2026-06-13 · **Site:** 2 (home-01) · **Autor:** agent (read-only MCP/psql)
|
||||
**Verdikt:** **Bug v EXEKUCI (control exporter), NE v plánovači ani forecastu.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Shrnutí (TL;DR)
|
||||
|
||||
Plánovač (v1 i shadow v2) **udělal vše správně**: během záporného nákupu 13:00–15:45
|
||||
naplánoval **CHARGE + grid_setpoint = +17 000 W** (import 17 kW ze sítě) a SoC cíl
|
||||
šplhal 41 % → ~96–100 % do 16:00. Realita: SoC se zastavil na **~71 %** v 15:00 a tam
|
||||
zůstal, protože **baterie se nabíjela JEN z FVE** (grid_w ≈ 0 celé odpoledne). Když
|
||||
PV po 15:00 spadlo (mraky: pv 14 kW → 3 kW), nabíjení skončilo.
|
||||
|
||||
**Příčina:** exekuční pojistka `_apply_export_plan_guard`
|
||||
(`backend/services/control/setpoints.py:272`) má **false positive**. Spustí se při
|
||||
**záporné výkupní ceně** (`sell < 0`) a `grid_setpoint_w >= 0`, což má detekovat „plán
|
||||
nechce exportovat". Jenže slot, kde plán **importuje 17 kW kvůli nabití baterie**, má
|
||||
také `grid_setpoint_w >= 0` (import je kladný) a `sell < 0`. Guard to splete s exportní
|
||||
situací a přepne `deye_physical_mode` z **CHARGE na PASSIVE** (`_passive_no_export_guard`,
|
||||
`setpoints.py:261`). V PASSIVE Deye nenabíjí ze sítě → 17 kW grid charge se nikdy nevykoná.
|
||||
|
||||
**Ekonomický dopad:** Baterie skončila ~71 % místo ~96 %. Večer (buy 3.8–4.7 Kč/kWh)
|
||||
se load nedokryl z baterie a **importoval ze sítě** (19:15: import **5.8 kW** při buy
|
||||
2.17 Kč/kWh) a v peaku exportoval jen z ~59 % SoC místo ~96 %. Cca **15–20 kWh** levné
|
||||
(záporné = placené ZA odběr) energie se nenakoupilo; místo toho se draho dokupovalo
|
||||
a méně prodávalo ve špičce.
|
||||
|
||||
---
|
||||
|
||||
## 2. Důkazy z DB (historické běhy + telemetrie)
|
||||
|
||||
### 2.1 Efektivní ceny (`vw_site_effective_price`, site 2, Praha)
|
||||
|
||||
| Okno | buy [Kč/kWh] | sell [Kč/kWh] |
|
||||
|------|-------------|---------------|
|
||||
| 13:00–15:45 | **−0.47 … −0.95** (záporný) | −0.94 … −1.63 (záporný) |
|
||||
| 16:00–18:30 | +0.45 … +1.42 | −0.33 … −0.93 (stále záporný) |
|
||||
| 18:45–21:00 | **+1.42 … +4.70** (večerní špička) | +0.40 … +2.40 |
|
||||
|
||||
### 2.2 Plán (executed first-slot, v1 superseded runs) — CO PLÁN CHTĚL
|
||||
|
||||
Plánovač každý rolling běh ordinoval CHARGE 17 kW po celou dobu záporného nákupu:
|
||||
|
||||
| run_t | run_id | slot | mode | grid_w | predicted | buy |
|
||||
|-------|--------|------|------|--------|-----------|-----|
|
||||
| 13:00 | 27081 | 13:00 | **CHARGE** | **17000** | f | −0.47 |
|
||||
| 13:30 | 27092 | 13:30 | **CHARGE** | **17000** | f | −0.87 |
|
||||
| 14:00 | 27102 | 14:00 | **CHARGE** | **17000** | f | −0.78 |
|
||||
| 15:00 | 27122 | 15:00 | **CHARGE** | **17000** | f | −0.51 |
|
||||
| 15:45 | 27144 | 15:45 | **CHARGE** | **17000** | f | −0.19 |
|
||||
| 16:00 | 27149 | 16:00 | PASSIVE | 1182 | f | +0.45 |
|
||||
|
||||
`is_predicted_price = f` všude → **cena NENÍ predikovaná**, takže `_apply_price_failsafe_guard`
|
||||
nehraje roli. Plán je reálný a chce nabíjet ze sítě.
|
||||
|
||||
Plán z 13:00 (run 27081) ukazoval kompletní trajektorii: SoC 41.7 % (13:00) → **100 %**
|
||||
(15:45) → večer export. v2 shadow (27082) totéž (~98 %). **Oba enginy chtěly plnou baterii.**
|
||||
|
||||
### 2.3 Realita (`telemetry_inverter`, 15min průměr)
|
||||
|
||||
| slot | SoC % | grid_w | batt_w | pv_w |
|
||||
|------|-------|--------|--------|------|
|
||||
| 13:30 | 45.3 | **2** | −13436 | 14846 |
|
||||
| 14:00 | 55.1 | **13** | −13129 | 14317 |
|
||||
| 14:30 | 64.3 | **−15** | −12700 | 13954 |
|
||||
| 15:00 | **71.0** | 63 | −1201 | 8058 |
|
||||
| 15:30 | 68.9 | 23 | +2236 | 6569 |
|
||||
| 16:00 | 70.2 | 2191 | −1320 | 7497 |
|
||||
| 19:15 | 59.0 | **+5825 (import!)** | −72 | 758 |
|
||||
| 20:45 | 49.7 | −13466 (export) | +15080 | — |
|
||||
|
||||
**Klíč:** `grid_w ≈ 0` po celé okno záporných cen. Plán chtěl import +17 000 W, reálně
|
||||
se neimportovalo nic. Baterie šla nahoru jen z PV (batt_w ≈ −13 kW = nabíjení z FVE).
|
||||
Jakmile PV spadlo (mraky kolem 15:00), SoC zamrzl na ~71 %.
|
||||
|
||||
### 2.4 Modbus journal (`modbus_command`, site 2 inverter, 13:00–15:45)
|
||||
|
||||
Zapsané registry: **pouze 148/149** (TOU časy), **109** (max discharge 350 A), **340**
|
||||
(solar cap). **Žádný zápis 108 (charge proud) / 142 / 143 / TOU power 154 / TOU grid-charge
|
||||
flag 172.** Sloupec `deye_physical_mode` = **PASSIVE** u všech.
|
||||
|
||||
→ Exekuce psala Deye v PASSIVE, ne CHARGE, navzdory plánu CHARGE. To je přesně otisk
|
||||
guardu, který stáhl CHARGE→PASSIVE: `get_deye_mode(setpoints_now)` v
|
||||
`write_inverter_setpoints` (inverter.py:81) vrátil PASSIVE, protože guard přepsal
|
||||
`deye_physical_mode` na "PASSIVE" ještě před zápisem.
|
||||
|
||||
---
|
||||
|
||||
## 3. Přesný mechanismus chyby (řetězec kódu)
|
||||
|
||||
1. `orchestrator.export_setpoints` (`orchestrator.py:101`) volá
|
||||
`_apply_export_plan_guard(site_id, mode, pi_now, sp_now)`.
|
||||
2. `_build_setpoints` (`setpoints.py:129`) už pro odpolední slot nastaví
|
||||
`export_ban = sell_f < 0 and grid_sp >= 0` → **True** (sell −0.8, grid +17000).
|
||||
Plán importuje (grid_sp kladný), ale `grid_sp >= 0` platí stejně jako u „neexportuji".
|
||||
3. `_apply_export_plan_guard` (`setpoints.py:297`):
|
||||
`neg_sell = sell_f is not None and float(sell_f) < 0` → **True**.
|
||||
Podmínka `if not neg_sell and not plan_no_export: return sp` **neprojde** → guard
|
||||
pokračuje a volá `_passive_no_export_guard(sp, hard_ban=True)`.
|
||||
4. `_passive_no_export_guard` (`setpoints.py:261`): vrací setpoints s
|
||||
**`deye_physical_mode="PASSIVE"`**, `export_mode="NONE"`, `export_ban=True`,
|
||||
`deye_gen_cutoff_enabled=True`. `battery_w` zůstane (line 248–250 jen ořízne záporné),
|
||||
ale **fyzický režim je PASSIVE** → `deye_battery_charge_discharge_amps` jde do PASSIVE
|
||||
větve, ne CHARGE; TOU grid-charge flag (reg 172) = 0. Baterie se neplní ze sítě.
|
||||
|
||||
**Kořen:** guard rozlišuje jen `sell < 0` a `grid_sp >= 0`, ale **nerozlišuje import
|
||||
(nabíjení) od „neexportuji"**. Záporný **prodej** legitimně zakazuje **export**, ale
|
||||
nesmí zakázat **import na nabití baterie** — to jsou dvě nezávislé věci na jednom
|
||||
elektroměru. Plán, který při `sell < 0` importuje kvůli levnému/zápornému **nákupu**,
|
||||
je zcela správný (pravidlo 6 zakazuje jen *export* při sell<0, ne import).
|
||||
|
||||
---
|
||||
|
||||
## 4. Vyloučení ostatních hypotéz
|
||||
|
||||
| Hypotéza | Verdikt | Důkaz |
|
||||
|----------|---------|-------|
|
||||
| Plánovač byl konzervativní (cílil 71 %) | **NE** | Plán cílil 96–100 %; `max_soc_percent=100`, `planner_max_soc_percent=100` — žádný strop pod 100 %. |
|
||||
| HW strop nabíjení bránil naplnění | **NE** | `max_charge_c_rate=0.28 → 17.9 kW`, `bms_max_charge_w=18000`. PV samo dávalo −13 kW; ze sítě bylo dost hlavy (breaker 17 kW). |
|
||||
| `is_predicted_price` failsafe guard | **NE** | `is_predicted_price = f` na všech executed slotech → `_apply_price_failsafe_guard` se nespustil. |
|
||||
| §7 import cap při buy<0 v solver_v2 stropoval import | **NE** | solver_v2 stropuje `gi[t] ≤ max_imp` (17 kW) — řádek 146; plán reálně ordinoval celých 17 kW. §7 řeší jen „nekonečný" import, ne tento případ. v1 (aktivní engine) navíc psal grid +17000. |
|
||||
| Terminal SoC shadow price podhodnocená záporným průměrem buy | **NE (není to příčina dnešního selhání)** | viz §6 — potenciální *sekundární* slabina, ale dnes plán plnou baterii CHTĚL, takže terminal value byla dost vysoká. Není to páka pro tento bug. |
|
||||
| Forecast PV přestřelil → plán nehedgoval | **NE** | Plán hedgoval importem 17 kW (právě proto, že se nespoléhal jen na PV). Forecast PV v 15:00–16:00 sice nadhodnotil (plán pv_a_fc ~3.4 kW, reálně PV spadlo), ale to by řešil ten import, který guard zařízl. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Páka a návrh fixu (po krocích)
|
||||
|
||||
### Páka
|
||||
Jedna funkce, jedna podmínka: `_apply_export_plan_guard` v
|
||||
`backend/services/control/setpoints.py` (a symetricky `export_ban` v `_build_setpoints`).
|
||||
Guard se nesmí spouštět, když plán **importuje / nabíjí baterii** (fyzický režim CHARGE,
|
||||
resp. `grid_setpoint_w > 0` / `battery_setpoint_w > 0`).
|
||||
|
||||
### Návrh (krok po kroku)
|
||||
|
||||
1. **`_apply_export_plan_guard` (setpoints.py:297):** přidat carve-out pro CHARGE/import.
|
||||
Guard má řešit jen *export*; pokud plán slot je CHARGE (`pi.deye_physical_mode == 'CHARGE'`)
|
||||
nebo importuje na nabití (`grid_setpoint_w > 0` a `battery_setpoint_w > 0`), **vrátit
|
||||
`sp` beze změny**. Konkrétně:
|
||||
```python
|
||||
pm = str(pi.get("deye_physical_mode") or "").strip().upper()
|
||||
grid_sp = int(pi.get("grid_setpoint_w") or sp.grid_setpoint_w or 0)
|
||||
bat_sp = int(pi.get("battery_setpoint_w") or 0)
|
||||
is_grid_charge = pm == "CHARGE" or (grid_sp > 0 and bat_sp > 0)
|
||||
if is_grid_charge:
|
||||
return sp # import na nabití baterie není export; sell<0 zakazuje jen export
|
||||
```
|
||||
(umístit hned po `if mode.mode_code != "AUTO" or pi is None: return sp`).
|
||||
|
||||
2. **`_build_setpoints` `export_ban` (setpoints.py:129):** zpřesnit, aby se nenastavil
|
||||
u nabíjecího slotu:
|
||||
```python
|
||||
export_ban = (
|
||||
sell_f is not None and float(sell_f) < 0
|
||||
and grid_sp >= 0
|
||||
and not (pm == "CHARGE" or (grid_sp > 0 and bat_w > 0))
|
||||
)
|
||||
```
|
||||
Tím se neaktivuje MI cut-off (reg 178) / 145=0 jen kvůli tomu, že slot importuje při
|
||||
záporném sell. Pole B přebytek při importu jde do baterie/zátěže, nikoli do sítě —
|
||||
`gen_cutoff` netřeba. (Pozor: zkontrolovat interakci s pravidlem 6 / BA81 — viz §7.)
|
||||
|
||||
3. **Bez tvrdé změny chování pro skutečný export:** Když plán slot je PASSIVE/SELL
|
||||
s `grid_setpoint_w >= 0` a `sell < 0` (tj. opravdu by mohl nechtěně exportovat),
|
||||
guard se chová jako dosud. Fix mění chování **jen** pro nabíjecí (CHARGE/import) sloty.
|
||||
|
||||
### Alternativa (defensivnější)
|
||||
Místo `deye_physical_mode`/setpoints rozhodovat čistě na `grid_setpoint_w > 0`
|
||||
(import) → guard přeskočit. Plus: nezávisí na tom, zda solver vyplnil `deye_physical_mode`.
|
||||
Minus: PASSIVE import bez nabíjení (`bat=0, grid>0`) by guard přeskočil i tam, kde to
|
||||
nevadí (žádný export beztak nehrozí, protože importujeme). Doporučuji kombinaci:
|
||||
`grid_sp > 0` (import) → return sp; tím je carve-out robustní i bez `battery_setpoint_w`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Sekundární zjištění (NE příčina dnešního bugu, ale stojí za sledování)
|
||||
|
||||
**Terminal SoC shadow price při dlouhém záporném okně.**
|
||||
`solver_v2._terminal_value_czk_per_wh` (solver_v2.py:90) = `max(0, avg_buy_24h) × factor / 1000`.
|
||||
Když je průměr buy prvních 24 h stažený dolů zápornými cenami (dnes 13:00–15:45 záporné),
|
||||
`avg_buy` klesá → terminal hodnota zbytkové energie na večer je nižší. Dnes to **nezpůsobilo
|
||||
podnabití** (plán plnou baterii chtěl, protože večerní špička 3.8–4.7 Kč/kWh přebila vše),
|
||||
ale na hraně (mělčí večerní špička) by podhodnocená terminal value mohla vést k tomu, že
|
||||
plán nebude motivován plně nabít. **Doporučení:** zvážit, zda terminal value nepočítat
|
||||
z *kladné* části budoucích buy (např. percentil/medián kladných buy ve zbytku horizontu),
|
||||
ne z prostého průměru, který záporné ceny umělaá stahují. **Toto je samostatná otázka,
|
||||
ne fix dnešního incidentu** — viz `docs/06-open-questions.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Co ověřit (před nasazením fixu)
|
||||
|
||||
1. **Regrese pravidla 6 / BA81 (2026-06-12):** carve-out se NESMÍ rozšířit na sloty, kde
|
||||
plán EXPORTuje při kladném sell, ani povolit export baterie/pole B při `sell < 0`.
|
||||
Fix se týká jen import/charge slotů (`grid_setpoint_w > 0`). Ověřit, že u BA81 (cutoff
|
||||
při sell +1.36) a KV1 (`block_export_on_negative_sell=true`) se chování nemění — tam
|
||||
guard pro export běží dál.
|
||||
2. **Golden gate (`backend/tests/golden/test_golden_replay.py`):** Fix je v control
|
||||
exporteru, **ne** v plánovači → plánovací výstupy (planning_interval) se nemění →
|
||||
golden replay by měl zůstat zelený. Spustit pro jistotu (`pytest backend/tests/golden`).
|
||||
3. **solver_v2_eval (`scripts/harness/solver_v2_eval.py`):** rovněž bez dopadu (control
|
||||
exporter mimo solver). Ověřit, že se eval čísla nezmění.
|
||||
4. **Unit test guardu:** přidat test `_apply_export_plan_guard`, že u
|
||||
`deye_physical_mode='CHARGE', grid_setpoint_w=17000, sell<0` **vrátí sp beze změny**
|
||||
(mode zůstane CHARGE), a u `PASSIVE, grid>=0, sell<0` dál vynutí PASSIVE no-export.
|
||||
5. **Běžný letní den bez záporných cen:** žádný slot nemá `sell < 0` → guard se beztak
|
||||
nespouští → fix nemá dopad. (Ověřeno logikou; carve-out aktivní jen při sell<0 + import.)
|
||||
6. **Po nasazení živě (MCP):** příští den se záporným nákupem zkontrolovat
|
||||
`modbus_command` — že u CHARGE slotů jsou zapsané reg **108** (charge proud) + TOU
|
||||
**154/172** (grid-charge flag=1) a `deye_physical_mode='CHARGE'`, a že `telemetry_inverter.grid_power_w`
|
||||
ukazuje reálný import během záporných cen.
|
||||
|
||||
---
|
||||
|
||||
## 8. Soubory (relevantní)
|
||||
|
||||
- `backend/services/control/setpoints.py` — `_apply_export_plan_guard` (272), `_passive_no_export_guard` (236), `_build_setpoints` export_ban (95/129). **Místo fixu.**
|
||||
- `backend/services/control/orchestrator.py:101` — volání guardu před zápisem.
|
||||
- `backend/services/control/inverter.py:81` — `get_deye_mode(setpoints_now)`; `:243` drop unchanged regs; `:290` journal `deye_physical_mode`.
|
||||
- `backend/services/planning/solver_v2.py:90` — terminal value (sekundární zjištění).
|
||||
- DB: `ems.planning_interval`, `ems.modbus_command`, `ems.telemetry_inverter`, `ems.vw_site_effective_price`.
|
||||
@@ -5,6 +5,14 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-13 — exekuce: baterie se nedobila v záporných cenách (guard carve-out)
|
||||
|
||||
- **Problém:** buy záporný 13:00–15:45 (−0.47…−0.95 Kč), plán ordinoval CHARGE +17 kW import, SoC cíl ~96 %, ale realita SoC 71 % (nabíjení jen z PV, grid≈0). Večer se dokupovalo ze sítě místo z plné baterie.
|
||||
- **Příčina:** `_apply_export_plan_guard` / `_build_setpoints` (setpoints.py) — false positive: `sell<0` & `grid_sp>=0` splete IMPORT na nabití s exportní situací a překlopí CHARGE→PASSIVE / nastaví export_ban. V PASSIVE Deye nenabíjí ze sítě.
|
||||
- **Mechanismus (fix):** carve-out — když slot je CHARGE (`pm=='CHARGE'`) nebo importuje na nabití (`grid_sp>0 & bat>0`), guard vrátí sp beze změny a export_ban se nenastaví. §6 zakazuje jen export, ne import (§7).
|
||||
- **Soubory:** backend/services/control/setpoints.py, test_control_export_plan_guard.py (test_neg_sell_grid_charge_not_blocked), docs/audits/planner-neg-buy-charge-not-executed-2026-06-13.md.
|
||||
- **Ověření:** guard testy 47 passed; živě — záporný den → grid_w roste, SoC k cíli. Mimo solver → golden gate / solver_v2_eval beze změny.
|
||||
|
||||
## 2026-06-13 — degradační cena dle skutečných cen packů (V103)
|
||||
|
||||
- **Problém:** seedy nesly default 0.50 Kč/kWh u KV1/BA81/HU1 — u malých packů zabíjel mělké arbitráže, u HU1 zkresloval studii spotové smlouvy.
|
||||
|
||||
Reference in New Issue
Block a user