zdokumentovani noveho pohleud na planovani nabijeni
This commit is contained in:
@@ -73,6 +73,7 @@ Krátce a v pořadí:
|
||||
- Záporná **prodejní** cena → export do sítě v LP **neekonomický** / u části instalací **tvrdě 0**; přebytek → nabíjení / curtailment **A** / GEN cutoff (viz `solve_dispatch` v `backend/services/planning_engine.py`).
|
||||
- **Pole B** je v modelu **nekontrolovatelné** — nelze ho `pv_a_curtailed` omezit.
|
||||
- **Zelený bonus** není v účelové funkci LP; počítá se v auditu (`fn_green_bonus_revenue`) — viz `docs/04-modules/planning.md`.
|
||||
- **~60 % SoC ve slunci (BA81/KV1)** nebo **ranní export před sell<0 (home-01):** často **v58** (`bc_pv=0` při `sell > min+0,20`) nebo **v33 pre-neg cushion** — plánovaná náhrada: `docs/04-modules/planning-charge-slot-budget.md` (zatím ne v produkci).
|
||||
4. **Mezery modelu** (upozornit jednou větu, když je to relevantní):
|
||||
- LP používá horní strop **`max_charge_power_w`** bez závislosti na SoC → u vysokého SoC může reálný proud být nižší než plán.
|
||||
|
||||
|
||||
@@ -202,6 +202,7 @@ Specifikace z `docs/02-architecture.md`, modulových docs a komentářů v `plan
|
||||
| Deye registry (FC 0x10, 108/109/141/142/178/143/145/340) | `docs/04-modules/modbus-registers.md` |
|
||||
| Export setpointů, Loxone HTTP | `docs/04-modules/control.md`, `docs/loxone-integration.md` |
|
||||
| LP solver, rolling replan, korekce FVE, dynamický OTE horizont | `docs/04-modules/planning.md`, `docs/04-modules/planning-extended-horizon.md`, `docs/planning-changelog.md`, `planning_engine.py` |
|
||||
| Rozpočet nabíjecích slotů (Wh × ceny × forecast; plánováno) | `docs/04-modules/planning-charge-slot-budget.md` — náhrada v58 + pre-neg cushion |
|
||||
| Záporný výkup, bod T, termika, bazén (home-01 strategie) | `docs/04-modules/planning-neg-sell-strategy.md` |
|
||||
| Arbitráž baterie (mezi sloty ≠ buy/sell v jednom 15min) | `docs/04-modules/planning-arbitrage-accounting.md` |
|
||||
| Provozní režimy AUTO / SELF_SUSTAIN / … | `docs/04-modules/operating-modes.md`, `db/migration/V004__operating_modes.sql`, `db/routines/R__044_fn_set_mode.sql` |
|
||||
|
||||
@@ -147,4 +147,18 @@ Očekávání: SoC před večerem **70–90 %** po levném pásmu; večer **expo
|
||||
|
||||
---
|
||||
|
||||
*Poslední aktualizace: 2026-05-27 — self-konzistentní grid maska B (v12), ekonomické sloupce v `planning_interval`, `economics_summary` v explain bundle. Po deployi: `PLANNER_BUILD_TAG=2026-05-27-self-consistent-grid-mask-v12`, `solver_params.objective_terms[].grid_charge_suppressed_reason`.*
|
||||
## 6. Plánováno: výběr nabíjecích slotů podle Wh (charge-slot-budget)
|
||||
|
||||
**Stav:** neimplementováno — plná specifikace v [`planning-charge-slot-budget.md`](planning-charge-slot-budget.md).
|
||||
|
||||
Shrnutí vztahu k arbitráži:
|
||||
|
||||
- **`charge_acquisition`** má vycházet z **vybrané fronty** slotů (`allow_charge` / `allow_grid_charge`), ne z jednoho `min(buy)` ani prahu `sell > min + ε`.
|
||||
- **Počet slotů** nabíjení má odpovídat **potřebným Wh** (`soc_max − current`, případně `soc_need[first_neg] − observed` před neg oknem), s `min(pv_surplus, P_max) × 0,25` per slot.
|
||||
- **Export FVE** v drahém slotu je správný **až po** vyčerpání levnější fronty — ne tvrdý `bc_pv = 0` (v58) nezávisle na rozpočtu.
|
||||
|
||||
Tím se sjednotí fixní tarif (řazení `sell ASC`) a spot (řazení `buy ASC` + pre-neg `pre_window_wh`).
|
||||
|
||||
---
|
||||
|
||||
*Poslední aktualizace: 2026-06-01 — přidán odkaz na plánovaný charge-slot-budget; dříve 2026-05-27 self-konzistentní grid maska B (v12).*
|
||||
|
||||
340
docs/04-modules/planning-charge-slot-budget.md
Normal file
340
docs/04-modules/planning-charge-slot-budget.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Plánování: rozpočet nabíjecích slotů (Wh × ceny × forecast)
|
||||
|
||||
**Stav:** návrh k implementaci (2026-06) — **zatím neimplementováno** v produkčním kódu.
|
||||
**Účel:** nahradit tvrdé prahy typu `sell > min_sell + 0,20 → bc_pv = 0` (v58) a binární pre-neg „cushion“ (v33) jednotným **energetickým rozpočtem** ve `fn_load_planning_slots_full`, který pokryje fixní tarify (BA81, KV1), spot (home-01) i zkracující se okna `sell < 0` (zima).
|
||||
|
||||
**Související:**
|
||||
|
||||
| Dokument | Vztah |
|
||||
|----------|--------|
|
||||
| [`planning.md`](planning.md) | LP, masky, současné v58–v62 |
|
||||
| [`planning-arbitrage-accounting.md`](planning-arbitrage-accounting.md) | proč ne `sell < buy` v jednom slotu |
|
||||
| [`planning-neg-sell-strategy.md`](planning-neg-sell-strategy.md) | rampa, tail, pole B — cílové SoC v okně |
|
||||
| [`R__063_fn_load_planning_slots_full.sql`](../../db/routines/R__063_fn_load_planning_slots_full.sql) | místo implementace masek |
|
||||
| [`planning_engine.py`](../../backend/services/planning_engine.py) | LP jen respektuje masky + objective |
|
||||
|
||||
**Changelog (plánované):** [`docs/planning-changelog.md`](../planning-changelog.md) — sekce *Plánováno: charge-slot-budget*.
|
||||
|
||||
---
|
||||
|
||||
## 1. Problém, který řešíme
|
||||
|
||||
### 1.1 Fixní tarif (BA81, KV1) — slunečný den ~60 % SoC
|
||||
|
||||
**Symptom:** přes den nabíjení ze slunce jen na ~60–66 %, zbytek FVE jde do sítě při výkupu 2–3 Kč/kWh; nabíjení jen v 2–4 slotech u minima výkupu (~1,45 Kč).
|
||||
|
||||
**Příčina (současný kód):**
|
||||
|
||||
- `R__063` už počítá `v_energy_to_fill` a vrstvu A (PV) s kumulací Wh, ale u fixního tarifu řadí PV vrstvu podle **`store_score`** (future sell − sell).
|
||||
- **`planning_engine` v58:** tvrdě `bc_pv = 0` (a často i `bc_gi = 0`) když `sell > min(sell≥0) + 0,20` — LP **nesmí** nabíjet, i když SQL nastaví `allow_charge = true`.
|
||||
|
||||
→ Rozpor mezi maskou a LP; ekonomicky „správný“ export v 10:00 blokuje doplnění baterie, i když večerní arbitráž to nevyžaduje.
|
||||
|
||||
### 1.2 home-01 — velká baterie, kratší / slabší okno `sell < 0`
|
||||
|
||||
**Symptom:** ráno export FVE před `sell < 0` i při výkupu ~2–3 Kč, zatímco v záporném okně by energie mohla být potřeba víc (krátké okno, slabší zimní FVE).
|
||||
|
||||
**Příčina (současný kód):**
|
||||
|
||||
- **v33:** `_pre_neg_pv_export_forecast_cushion_ok` — **binární** rozhodnutí: pokud forecast PV v celém denním okně `sell < 0` pokryje deficit do `soc_target[first_neg]` × 1,15 → **všechny** pre-neg sloty s přebytkem → export (`bc_pv = 0`, push `ge_pv`).
|
||||
- Neptá se: *kolik Wh musím nabít **před** oknem*, když *uvnitř* okna forecast nestačí.
|
||||
- **v44:** na neg den **žádný grid** před 1. `sell < 0` — při nedostatku FVE v okně chybí páka levného NT před oknem.
|
||||
|
||||
### 1.3 Společná chyba modelu
|
||||
|
||||
| Špatně | Správně |
|
||||
|--------|---------|
|
||||
| Prah `sell > min + 0,20` | **Kolik Wh** chybí do cíle a **které sloty** je nejlevněji doplní |
|
||||
| Binární cushion OK / fail | **pre_window_wh** = max(0, deficit − in_window_wh) |
|
||||
| `store_score` u fixního buy | U fixního tarifu řadit **`sell ASC`** (výkup = příležitostní cena uložení) |
|
||||
| LP přepisuje SQL masky (v58) | LP **jen** `bc_*` kde `allow_charge`; export kde maska nepřidělila charge budget |
|
||||
|
||||
---
|
||||
|
||||
## 2. Principy návrhu
|
||||
|
||||
1. **SQL-first:** výběr nabíjecích slotů = **`ems.fn_load_planning_slots_full`** (rozšíření `R__063`), ne nové tvrdé větve v `solve_dispatch` kromě stávajících bezpečnostních guardů (load-first, večerní push, neg fáze).
|
||||
2. **Energetický rozpočet (Wh), ne Kč prah:** ceny řadí **prioritu slotů**; zastavení kumulace je **`cum_wh ≥ target_wh`** (± `charge_slot_buffer`).
|
||||
3. **Forecast v každém slotu:** `pv_surplus_w` = max(0, pv_a + pv_b − load_baseline − …) už ve work tabulce; nabíjecí příspěvek slotu = `min(pv_surplus_w, max_charge_w) × charge_eff × 0,25` (+ volitelně grid `per_slot_charge_wh`).
|
||||
4. **Oddělení nákupního a výprodejního okna** — viz [`planning-arbitrage-accounting.md`](planning-arbitrage-accounting.md); `charge_acquisition` z vybraných slotů, ne `min(buy)` celého horizontu.
|
||||
5. **Jeden algoritmus, více režimů:** stejná kostra pro spot i fixed; liší se **řazení** (buy vs sell), **cíl SoC** a **výjimky** (neg den, block_export).
|
||||
|
||||
---
|
||||
|
||||
## 3. Slovník
|
||||
|
||||
| Symbol / pole | Význam |
|
||||
|---------------|--------|
|
||||
| `energy_to_fill_wh` | `soc_max_wh − current_soc_wh` (základní deficit do plné baterie) |
|
||||
| `charge_target_wh` | Cíl pro výběr slotů — může být `< soc_max` (rezerva neg okna, safety, večerní export) |
|
||||
| `in_window_wh` | Odhad energie do baterie **uvnitř** speciálního okna (např. všechny sloty `sell < 0` téhož pražského dne), z forecastu A+B (+ volitelně grid v okně) |
|
||||
| `pre_window_wh` | `max(0, charge_target_wh − in_window_wh × reliability_factor)` — kolik Wh je třeba doplnit **před** oknem |
|
||||
| `slot_charge_wh[t]` | Wh, které lze v `t` reálně natočit (PV surplus cap + výkon baterie) |
|
||||
| `allow_charge` | SQL maska: LP smí `bc_pv` / `bc_gi` |
|
||||
| `allow_grid_charge` | podmnožina: smí `bc_gi` |
|
||||
| `charge_slot_reason` | debug: `grid_layer_b`, `pv_layer_a`, `pre_neg_fill`, `neg_window`, `buy_negative`, … |
|
||||
|
||||
---
|
||||
|
||||
## 4. Jádro algoritmu (společné)
|
||||
|
||||
Pro každý běh plánovače (site, horizont, `current_soc_wh`):
|
||||
|
||||
### Krok 1 — Cíl energie
|
||||
|
||||
```text
|
||||
charge_target_wh := min(
|
||||
soc_max_wh − current_soc_wh,
|
||||
optional_cap_from_neg_strategy, -- viz §6
|
||||
optional_safety_soc_target_wh
|
||||
)
|
||||
```
|
||||
|
||||
- Výchozí: doplnit do **`soc_max_wh`** (s `charge_slot_buffer` z `asset_battery` jako dnes).
|
||||
- U **home-01** s neg dnem: cíl na vstupu do okna = **`soc_need[first_neg_sell]`** z rampy (v35/v36), ne fixních 80 %.
|
||||
- Rezerva pro okno `sell < 0`: stávající logika `v_pv_layer_cap_wh -= neg_window_pv_surplus_wh` v `R__063` zůstane jako **snížení** `charge_target_wh` před oknem, ne jako samostatný binární export.
|
||||
|
||||
### Krok 2 — Dodávka uvnitř speciálního okna (volitelná větev)
|
||||
|
||||
Pro každý pražský den s alespoň jedním `sell < 0`:
|
||||
|
||||
```text
|
||||
in_window_wh := sum over slots t in neg_window(day):
|
||||
min(pv_surplus_w[t], max_charge_w) * charge_eff * 0.25
|
||||
+ (optional) grid_wh if allow_grid in neg window (v45)
|
||||
```
|
||||
|
||||
`reliability_factor` ∈ (0, 1] — např. **0,85** zimní / krátké okno, **1,0** letní dlouhé okno; nebo odvozené z počtu slotů `sell < 0` (≤ 4 sloty → nižší faktor).
|
||||
|
||||
### Krok 3 — Deficit před oknem
|
||||
|
||||
```text
|
||||
pre_window_wh := max(0, charge_target_at_neg_entry − in_window_wh * reliability_factor)
|
||||
```
|
||||
|
||||
Kde `charge_target_at_neg_entry` = SoC potřeba na `first_neg_sell` (z rampy / `soc_need`), minus `current_soc` (pozorované), přepočteno na Wh.
|
||||
|
||||
### Krok 4 — Fronta slotů (řazení + kumulace)
|
||||
|
||||
Kandidáti = sloty s `slot_charge_wh > 0` **nebo** (pro grid vrstvu) sloty bez PV, kde smí síť.
|
||||
|
||||
**Řazení (priorita):**
|
||||
|
||||
| Režim | Primární klíč | Sekundární |
|
||||
|-------|----------------|------------|
|
||||
| Spot (`purchase_pricing_mode ≠ fixed`) | `buy_price ASC` | den plánu, před exportním oknem, `slot_ord` |
|
||||
| Fixní tarif | `sell_price ASC` | stejné geografické priority jako dnes v R__063 |
|
||||
|
||||
**Kumulace:**
|
||||
|
||||
```text
|
||||
cum := 0
|
||||
for slot in candidates ordered:
|
||||
if cum >= budget_wh: break
|
||||
allow_charge[slot] := true
|
||||
if grid_layer: allow_grid_charge[slot] := true
|
||||
cum += slot_charge_wh[slot] -- u grid vrstvy min(cum increment, per_slot_charge_wh)
|
||||
```
|
||||
|
||||
**Budgety (vrstvy, po sobě):**
|
||||
|
||||
1. **Pre-neg / před oknem** — `budget = pre_window_wh` (jen sloty `t < first_neg_sell` téhož dne).
|
||||
2. **Grid B** — `budget = grid_target_wh` (AM/PM 50/50 jako dnes); spot: nejlevnější `buy`; fixed: nejlevnější `sell` u slotů splňujících marži.
|
||||
3. **PV A — zbytek** — `budget = max(0, charge_target_wh − grid_filled_wh − pre_neg_filled_wh)`.
|
||||
|
||||
Po výběru: sloty **mimo** vybranou frontu nemusí mít `allow_charge` — LP pak může exportovat FVE, pokud objective dává smysl (bez v58 zákazu).
|
||||
|
||||
### Krok 5 — Export před oknem (home-01)
|
||||
|
||||
**Nahradit** v33 binární cushion:
|
||||
|
||||
```text
|
||||
export_allowed_pre_neg[t] :=
|
||||
t in pre_neg_calendar_window
|
||||
AND pv_surplus_w[t] > threshold
|
||||
AND NOT allow_charge[t] -- přebytek po naplnění pre_window_wh
|
||||
AND (optional) sell[t] >= sell_export_floor[t] -- ne dump pod ranním pásmem (R__063 morning zone)
|
||||
```
|
||||
|
||||
V Pythonu: `pre_neg_pv_export_ts` = sloty s exportem **jen pokud** nejsou v charge frontě; případně měkká penalizace místo `bc_pv = 0` na celé pásmo.
|
||||
|
||||
---
|
||||
|
||||
## 5. Fixní tarif (BA81, KV1)
|
||||
|
||||
### 5.1 Změny oproti dnešku
|
||||
|
||||
| Dnes (v58–v59) | Plán |
|
||||
|----------------|------|
|
||||
| `bc_pv = 0` if `sell > min + 0,20` | **Zrušit** v `planning_engine.py` |
|
||||
| PV vrstva A: `store_score DESC` | Fixed: **`sell_price ASC`** + kumulace `pv_surplus` Wh |
|
||||
| Grid: min sell sloty (v59) | Zachovat, sladit s jednotnou frontou (grid = vrstva B) |
|
||||
| Večer push: `bc_* = 0` | Zachovat (exportní okno) |
|
||||
|
||||
### 5.2 Ekonomická interpretace
|
||||
|
||||
- **Buy** je konstantní → rozhoduje **výkup v slotu** (opportunity cost uložení do baterie).
|
||||
- Nabíjet v pořadí **nejnižší sell**, dokud `cum < charge_target_wh`.
|
||||
- Export v slotu s vyšším sell je OK **až poté**, co je rozpočet Wh vyčerpán v levnějších slotech (včetně pozdějších levných sellů v pořadí času — viz pořadí: nejdřív globálně nejlevnější sell v rámci dne / AM-PM, viz níže).
|
||||
|
||||
### 5.3 AM/PM a pořadí v čase
|
||||
|
||||
Zachovat stávající **AM/PM rozpočet** `grid_target_wh` (50/50, přeliv AM→PM). Uvnitř segmentu:
|
||||
|
||||
- fixed grid: `sell ASC` + filtr `sell ≤ min(sell≥0) + degrad + ε` (jako v59) **nebo** čistě kumulace Wh bez ε, pokud Wh budget stačí — **rozhodnutí při implementaci:** preferovat **Wh kumulaci**; ε jen jako failsafe proti grid nabíjení za 6 Kč v noci (KV1).
|
||||
|
||||
### 5.4 Večerní špička a profitable export
|
||||
|
||||
Beze změny principu: `allow_discharge_export` + `evening_push_ts`; v push slotech **`allow_charge = false`**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Spot — home-01 a negativní výkup
|
||||
|
||||
### 6.1 Běžný spot (bez neg dne v horizontu)
|
||||
|
||||
Stejná kostra jako §4:
|
||||
|
||||
- Grid vrstva: **`buy ASC`** (stávající spot loop + self-konzistentní filtr `pv_charge_wh_ahead`).
|
||||
- PV vrstva: **`store_score DESC`** nebo hybrid — **po** grid; budget = zbytek `charge_target_wh`.
|
||||
|
||||
### 6.2 Den s `sell < 0` (neg strategie)
|
||||
|
||||
Integrace s [`planning-neg-sell-strategy.md`](planning-neg-sell-strategy.md):
|
||||
|
||||
| Komponenta | Role v charge-slot budget |
|
||||
|------------|---------------------------|
|
||||
| `soc_need[t]` rampa (v35/v36) | `charge_target_at_neg_entry` |
|
||||
| `in_window_wh` z A+B v neg sloty | sníží `pre_window_wh` |
|
||||
| `pre_window_wh > 0` | **nabíjecí fronta před `first_neg_sell`** (PV + případně grid) |
|
||||
| Tail / T / curtail | **beze změny** v LP (fáze neg okna) |
|
||||
| v44 `neg_day_no_grid_before_neg_sell` | **Změkčit:** povolit grid v N nejlevnějších `buy` slotech před oknem, pokud `pre_window_wh > in_window_wh × factor` a `buy < 0` nebo `buy ≤ ref_buy_am + degrad` |
|
||||
|
||||
### 6.3 Nahrazení v33 cushion
|
||||
|
||||
| v33 (dnes) | charge-slot budget |
|
||||
|------------|-------------------|
|
||||
| `cushion_ok` → export vše pre-neg | `pre_window_wh` malé → více `allow_charge` pre-neg |
|
||||
| `cushion_fail` → nabíjet | `pre_window_wh` velké → fronta nabíjení |
|
||||
| `bc_pv = 0` v celém `pre_neg_pv_export_ts` | Jen sloty, kde **není** `allow_charge` a export je ekonomicky výhodný |
|
||||
|
||||
**Zima / krátké okno:** málo slotů `sell < 0` → malé `in_window_wh` → velké `pre_window_wh` → **více nabíjení před oknem**, i při sell 2–3 Kč, pokud jsou to nejlevnější dostupné sloty (ne plošný export).
|
||||
|
||||
### 6.4 Velká baterie (64 kWh)
|
||||
|
||||
- `charge_target_wh` může být **desítky kWh** — fronta musí počítat **skutečné** `slot_charge_wh`, ne jen cap 6 slotů / segment, pokud budget vyžaduje více (rozšířit `grid_charge_cap_*` odvozeně od `ceil(budget / per_slot_charge_wh)` — částečně už je).
|
||||
- `charge_acquisition` = vážený buy ve vybraných `allow_grid_charge` slotech (stávající two-pass v Pythonu).
|
||||
|
||||
---
|
||||
|
||||
## 7. Role LP po změně masek
|
||||
|
||||
` solve_dispatch` **nesmí** přepisovat energetický výběr prahy typu v58.
|
||||
|
||||
| Oblast | LP chování |
|
||||
|--------|------------|
|
||||
| Nabíjení | `bc_pv`, `bc_gi` jen kde `allow_charge` / `allow_grid_charge` |
|
||||
| Export FVE | objective `−ge_pv×sell`; **bez** `bc_pv=0` jen kvůli sell |
|
||||
| Export bat | `allow_discharge_export`, `charge_acquisition`, večerní push |
|
||||
| Guard | `ge_pv=0` if `sell < charge_acquisition − degrad` (spot) — **měkká** hranice hodnoty uložené energie |
|
||||
| Spot grid | v61: `bc_gi=0` if `buy > charge_acquisition + degrad` |
|
||||
| Neg fáze | rampa, tail, T — beze změny |
|
||||
|
||||
---
|
||||
|
||||
## 8. Návrh rozšíření `fn_load_planning_slots_full`
|
||||
|
||||
Nové / rozšířené sloupce ve výstupu (nebo JSON v meta — preferováno sloupce pro debug):
|
||||
|
||||
| Sloupec | Typ | Popis |
|
||||
|---------|-----|--------|
|
||||
| `charge_budget_wh` | numeric | celkový cíl pro tento běh |
|
||||
| `charge_slot_wh` | numeric | odhad Wh v daném slotu |
|
||||
| `charge_cum_wh` | numeric | kumulativa po řazení (audit) |
|
||||
| `charge_layer` | text | `pre_neg` / `grid_am` / `grid_pm` / `pv_a` / … |
|
||||
| `pre_window_wh` | numeric | jen informativní per běh (nebo per den v `solver_params`) |
|
||||
|
||||
V `planning_run.solver_params` (commit meta):
|
||||
|
||||
```json
|
||||
{
|
||||
"charge_slot_budget": {
|
||||
"charge_target_wh": 42000,
|
||||
"pre_window_wh_by_day": {"2026-06-02": 18000},
|
||||
"in_window_wh_by_day": {"2026-06-02": 12000},
|
||||
"reliability_factor": 0.85,
|
||||
"planner_build_tag": "…-charge-slot-budget-v1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Co zrušit / neimplementovat znovu
|
||||
|
||||
| Položka | Akce |
|
||||
|---------|------|
|
||||
| v58 `fixed_high_sell_no_pv_charge` | **odstranit** po nasazení budget |
|
||||
| v58 `FIXED_PV_CHARGE_ONLY_NEAR_MIN_SELL_CZK_KWH` | **odstranit** (konstanta) |
|
||||
| v59 `fixed_grid_charge_unprofitable` část `sell > min + 0,20` | nahradit: grid jen ve vybrané frontě; případně ponechat `sell < buy` guard pro grid |
|
||||
| v60 `sell < buy` → `bc_gi=0` (spot) | **neobnovovat** (záměrně zrušeno v61) |
|
||||
| v33 binární cushion jako hlavní páka | nahradit §4 krok 2–3; cushion ponechat jako **audit** / `solver_params.inputs.pre_neg_cushion_legacy_ok` |
|
||||
|
||||
---
|
||||
|
||||
## 10. Fáze implementace (doporučené pořadí)
|
||||
|
||||
1. **Dokumentace + testy scénářů** (tento soubor, pytest fixtures s umělými sloty).
|
||||
2. **`R__063`:** fixed PV vrstva `sell ASC` + Wh kumulace; sloupce debug; bez změny Pythonu → ověřit, že v58 stále blokuje (regrese).
|
||||
3. **Python:** odstranit v58/v59 `bc_pv`/`bc_gi` fixed větve; spoléhat na masky.
|
||||
4. **`R__063` + Python:** pre-neg `pre_window_wh` pro spot; zúžit `pre_neg_pv_export_ts`.
|
||||
5. **v44 změkčení:** grid před neg jen když `pre_window_wh` > práh.
|
||||
6. **Dokumentace + changelog** tag `charge-slot-budget-v1`; MCP ověření home-01 / KV1 / BA81.
|
||||
|
||||
---
|
||||
|
||||
## 11. Ověření
|
||||
|
||||
### 11.1 Automatické testy (pytest)
|
||||
|
||||
| Scénář | Očekávání |
|
||||
|--------|-----------|
|
||||
| Fixed, slunečný den, nízký ranní SoC | `allow_charge` v mnoha PV slotech; max SoC v plánu → blízko `soc_max` |
|
||||
| Fixed, min sell odpoledne | dřívější sloty s vyšším sell **bez** `allow_charge`, pokud budget vyčerpán v levnějších |
|
||||
| Spot, krátké neg okno (2–4 sloty), slabá FVE | `pre_window_wh > 0`; nabíjení před `first_neg_sell`; **ne** plošný pre-neg export |
|
||||
| Spot, dlouhé neg okno, silná FVE | po naplnění `pre_window_wh` může export pre-neg v drahých sell |
|
||||
| home-01 velká baterie | kumulace ≥ desítky kWh přes více slotů |
|
||||
|
||||
### 11.2 SQL / MCP
|
||||
|
||||
```sql
|
||||
select interval_start, allow_charge, allow_grid_charge,
|
||||
charge_layer, charge_slot_wh, charge_cum_wh,
|
||||
sell_price, pv_surplus_w
|
||||
from ems.fn_load_planning_slots_full(<site_id>, <from>, <to>, <soc_wh>)
|
||||
where allow_charge
|
||||
order by interval_start;
|
||||
```
|
||||
|
||||
Po deployi: aktivní `planning_run.solver_params->'charge_slot_budget'`; u home-01 `pre_neg_pv_export_slots` ⊆ sloty bez `allow_charge`.
|
||||
|
||||
### 11.3 Regrese
|
||||
|
||||
- Večerní push (v57), spot v61, KV1 noc v62 — **nesmí** rozpadnout.
|
||||
- Neg rampa v35/v36 — SoC v okně `sell < 0` stejné cíle, mění se jen **příprava před oknem**.
|
||||
|
||||
---
|
||||
|
||||
## 12. Otevřené body (rozhodnutí před kódem)
|
||||
|
||||
1. **`reliability_factor`:** fixní 0,85 vs funkce počtu neg slotů?
|
||||
2. **Fixed řazení:** globální `sell ASC` přes den vs AM/PM segmenty (doporučení: AM/PM budget + `sell ASC` v segmentu).
|
||||
3. **Večer před neg dnem:** zda `charge_target_wh` zahrnuje večerní výboj D−1 (`neg_evening_before_neg`) — ano, přes `soc_need`, ne duplicitní budget.
|
||||
4. **Multi-day horizont:** každý pražský den vlastní `pre_window_wh` (jako v36 bundle) — **ano**.
|
||||
5. **UI:** badge „charge budget“ ve frontendu — volitelné, až budou sloupce v API.
|
||||
|
||||
---
|
||||
|
||||
## 13. Shrnutí pro produkt
|
||||
|
||||
Jednou větou: **místo prahu „sell nad minimum + 20 haléřů“ spočítáme, kolik Wh chybí do cíle, kolik jich dodá záporné výkupní okno z forecastu, a zbytek nabijeme v nejlevnějších slotech (buy nebo sell podle tarifu) s ohledem na PV přebytek a spotřebu — u home-01 tím nahradíme plošný ranní export před `sell < 0`, u KV1/BA81 doplníme baterii ve slunečný den nad ~60 %.**
|
||||
@@ -1,8 +1,8 @@
|
||||
# Strategie záporného výkupu, FVE A/B, termika a flexibilní zátěže (home-01)
|
||||
|
||||
Navazuje na [`planning.md`](planning.md), [`planning-arbitrage-accounting.md`](planning-arbitrage-accounting.md), [`planning-changelog.md`](../planning-changelog.md), [`heat-pump.md`](heat-pump.md), [`ev-charging.md`](ev-charging.md).
|
||||
Navazuje na [`planning.md`](planning.md), [`planning-arbitrage-accounting.md`](planning-arbitrage-accounting.md), [`planning-charge-slot-budget.md`](planning-charge-slot-budget.md) (plánovaná náhrada pre-neg cushion), [`planning-changelog.md`](../planning-changelog.md), [`heat-pump.md`](heat-pump.md), [`ev-charging.md`](ev-charging.md).
|
||||
|
||||
**Stav:** část je **implementovaná** (v32–v35), část je **návrh** (v36+ termika, bazén, spirála). V textu je označeno `✅ hotovo` vs `📋 návrh`.
|
||||
**Stav:** část je **implementovaná** (v32–v40), část je **návrh** (termika, bazén, spirála; **charge-slot-budget** — viz níže). V textu je označeno `✅ hotovo` vs `📋 návrh`.
|
||||
|
||||
---
|
||||
|
||||
@@ -29,7 +29,7 @@ Navazuje na [`planning.md`](planning.md), [`planning-arbitrage-accounting.md`](p
|
||||
| **Prep (v32)** | Všechny `sell < 0` sloty **před** tail. Dnes: plochý cíl **`planner_neg_sell_prep_soc_percent`** (default **80 %**). |
|
||||
| **Bod T** (`t_detach`) 📋 | První slot (od tail zpět), od kdy **forecast pole B** (po loadu, s limitem nabíjení) **sám** dožene zbytek SoC na 100 %. Nahrazuje fixních 80 %. |
|
||||
| **`E_surplus_after_t`** 📋 | Integrál plánovaného přebytku FVE (typ. od **T** do `last_sell<0`), který by jinak šel do sítě / curtail — budget pro TČ předehřát, bazén, spirálu. |
|
||||
| **Pre-neg export (v33)** | Kladné `sell` **před** prvním `sell < 0`: export FVE jen pokud forecast v celém `sell < 0` okně pokryje dobítí na prep cíl (× margin **1,15**). |
|
||||
| **Pre-neg export (v33)** | Kladné `sell` **před** prvním `sell < 0`: export FVE jen pokud forecast v celém `sell < 0` okně pokryje dobítí na prep cíl (× margin **1,15**). **📋 Plánovaná náhrada:** `pre_window_wh` v [`planning-charge-slot-budget.md`](planning-charge-slot-budget.md) §6. |
|
||||
| **Load-first (v34)** | Dům z `pv_ld`; při dostatečné FVE žádný fiktivní `grid_import = load` v plánu. |
|
||||
| **Rampa B + bod T (v35)** | `soc_need` zpět od tail jen z PV B; **t_detach**; `E_surplus_after_t`; uvolnění A po T (měkké). |
|
||||
| **Reg 340** | Deye *max solar power* ≈ `pv_a_forecast_solver_w − pv_a_curtailed_w`. |
|
||||
@@ -119,6 +119,32 @@ Navazuje na [`planning.md`](planning.md), [`planning-arbitrage-accounting.md`](p
|
||||
|
||||
**Ověření:** `PreNegPvExportForecastTests`, `solver_params.inputs.pre_neg_pv_export_forecast_ok`.
|
||||
|
||||
### 4.2b 📋 Plánováno — pre-neg jako energetický rozpočet (charge-slot-budget)
|
||||
|
||||
**Stav:** neimplementováno (specifikace 2026-06).
|
||||
|
||||
**Problém v33 při zimě / krátkém okně `sell < 0`:** binární cushion často **projde** (optimistický forecast v okně × 1,15) → ranní export FVE i při sell ~2–3 Kč, přestože **uvnitř** okna energie nestačí na rampu / 100 % tail — velká baterie (home-01) pak přijde do neg okna podnabitá.
|
||||
|
||||
**Záměr (souhrn):**
|
||||
|
||||
```text
|
||||
charge_target_at_neg := soc_need[first_neg] (rampa v35/v36, observed SoC)
|
||||
in_window_wh := sum forecast PV (A+B) v sell<0 sloty dne × η
|
||||
pre_window_wh := max(0, charge_target_at_neg − in_window_wh × reliability)
|
||||
|
||||
Před first_neg: allow_charge v nejlevnějších slotech (buy ASC) + PV surplus,
|
||||
dokud cum_wh < pre_window_wh
|
||||
Export pre-neg: jen sloty s PV přebytkem, které NEJSOU v charge frontě
|
||||
```
|
||||
|
||||
**Vazby:**
|
||||
|
||||
- Rampa / tail / T / curtail A — **beze změny** v LP.
|
||||
- **v44** (`neg_day_no_grid_before_neg_sell`): plánované **změkčení** — grid před oknem povolen v N nejlevnějších `buy` slotech, pokud `pre_window_wh` výrazně převyšuje `in_window_wh`.
|
||||
- **v36 per-den bundle** zůstává; `pre_window_wh` se počítá **per pražský den**, ne globálně.
|
||||
|
||||
**Detail:** [`planning-charge-slot-budget.md`](planning-charge-slot-budget.md) §4–§6, changelog *Plánováno*.
|
||||
|
||||
### 4.3 v34 — tvrdý load-first ✅
|
||||
|
||||
**Tag:** `2026-05-28-load-first-hard-v34`
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
- **Tvrdé výkonové limity site/baterie:** `gi ≤ site_grid_connection.max_import_power_w` (breaker); **`bc_pv + bc_gi ≤ asset_battery.max_charge_power_w`**; **`ge ≤ max_export_power_w`** (proměnná `ge`, platí `ge = ge_pv + ge_bat`); **`bd + ge_bat ≤ asset_battery.max_discharge_power_w`** (vybíjení do domu + export z baterie nesmí současně překročit BMS). Dříve LP dovoloval import+nabíjení a dvojnásobné nabíjení; u prodeje hrozilo současné `bd` a `ge_bat` až 2× max discharge — viz `SitePowerCapTests`.
|
||||
- **Hodnota FVE (PV store value):** tvrdé `ge_pv = 0` jen pokud `sell < future_sell_opportunity − degradation` **a** `sell < 0` (spot), nebo u fixního tarifu dle `fixed_pv_b_export_cap`. Při **`sell ≥ 0` (spot home-01, KV1):** `ge_pv` **neblokuje** pv_store — solver volí export vs. `bc_pv` podle `−ge_pv×sell` a degradace; **baterii** na večerní peak drží `ge_bat` (`evening_early` / push), ne curtail FVE. **v31:** při `sell ≥ 0` + PV přebytek **není** plný `ge_bat` push z `pre_neg_buy_discharge` / ranních shortfallů (export cap pro FVE). **Před prvním `sell < 0`:** `allow_pre_neg_pv_export`. Tag `2026-05-28-morning-pv-export-priority-v31`. Testy `Home01PvStoreValueTests`, `PreNegativeSellExportTests`.
|
||||
- **BA81 úsvit + MI (v51):** `fixed_pv_b_export_cap` (`ge_pv ≤ pv_b`) jen pokud **`pv_a_forecast ≥ 1500 W`** (`DAWN_LOW_PV_NO_CURTAIL_W`); při slabším A + přebytku → `fixed_mi_low_pv_surplus_export` (bez pv_store bloku). Exporter: při `forecast < 1500` a bez curtail A → **bez reg 340** (`setpoints.py`). Tag `2026-05-31-ba81-dawn-no-micro-curtail-v51`. Test `test_ba81_dawn_low_pv_no_full_curtail_for_mi_cap`.
|
||||
- **Fixní tarif — PV export vs. nabíjení (v58–v59):** v58: při **`sell > min_sell + 0,20`** a PV přebytku → **`bc_pv = 0`**, export FVE; profitable noc **`sell > buy`** mimo `evening_early`. v59: **`bc_gi = 0`** i bez FVE při **`sell < buy`** nebo **`sell > min_sell + 0,20`**; večerní push bez nabíjení; **`R__063`** grid maska podle nejnižšího sell (`sell ASC`), ne `slot_ord`. Tagy `…-v58`, `…-v59`.
|
||||
- **Fixní tarif — PV export vs. nabíjení (v58–v59, dočasné):** v58: při **`sell > min_sell + 0,20`** a PV přebytku → **`bc_pv = 0`**, export FVE; profitable noc **`sell > buy`** mimo `evening_early`. v59: **`bc_gi = 0`** i bez FVE při **`sell < buy`** nebo **`sell > min_sell + 0,20`**; večerní push bez nabíjení; **`R__063`** grid maska podle nejnižšího sell (`sell ASC`), ne `slot_ord`. Tagy `…-v58`, `…-v59`. **Plánovaná náhrada:** energetický rozpočet Wh + řazení výkupů/nákupů — **[`planning-charge-slot-budget.md`](planning-charge-slot-budget.md)** (zrušení v58 v LP, rozšíření `R__063`).
|
||||
- **Drahý nákup → vlastní spotřeba z baterie:** mimo `allow_charge` platí `bd + pv_ld ≥ load_baseline + hp[t]` a `gi ≤ EV + hp[t]` (ne `hp_rated`). **Spot:** drahý slot = `buy > min(buy≥0) + degradace`. **Fixní nákup (DB `purchase_pricing_mode=fixed` nebo heuristika rozptylu buy < 0,25):** navíc `buy > charge_acquisition + degradace`. Na spotu **nesmí** `charge_acquisition` (~0,9 Kč) označit všechny sloty jako drahé → Infeasible (home-01). Při **Infeasible** solver jednou opakuje s `relaxed_expensive_import` (síť smí krmit baseload v drahých slotech; v `solver_params.inputs.relaxed_expensive_import=true`). Testy `AutoPassiveSelfConsumptionTests`, `test_spot_low_acquisition_does_not_mark_all_slots_expensive`, `test_negative_buy_in_horizon_does_not_block_all_grid_import`.
|
||||
- **Záporný výkup (`sell < 0`) bez exportu:** `block_export_on_negative_sell` (KV1) **nebo** `purchase_pricing_mode=fixed` (BA81). **Spot (home-01):** `ge_pv=0` dokud není plná baterie; při plné jen ventil pole B (`ge_pv ≤ pv_b`, `w_pv_b_vent_neg`); výboj baterie při `sell<0` jen **12 slotů** před `buy ≤ planner_extreme_buy_threshold` (default −2), pokud spread do budoucna dává smysl — tag `2026-05-26-neg-sell-bat-dump-extreme-buy-v11`. Večerní discharge maska u spotu: denní peak ≥17:00 (ne `sell > ref_buy` v slotu). **v50:** u **KV1** při `sell≥0` a PV přebytku >500 W i **po** 1. `sell<0` → `ge_pv` (PV_SURPLUS), ne tvrdý `ge_bat` z večerního peak/push.
|
||||
- **Pole B při sell<0 (home-01):** pokud `block_export_on_negative_sell = false`, LP nesmí vynutit `ge_pv = 0` (přebytek neriťitelného PV B). KV1 s `block_export = true` jen curtail A / nabíjení.
|
||||
@@ -53,7 +53,7 @@
|
||||
**Planner tag v23:** v22b + **výboj baterie do sítě** před `buy<0` (`_pre_neg_buy_discharge_indices`, sell≥1 Kč/kWh, push `ge_bat` z DB limitů). Viz changelog v23.
|
||||
V `solve_dispatch` (AUTO): **`charge_slots`** = `allow_charge` z DB + **`buy < 0`** + všechny sloty **`sell < 0`** s PV přebytkem > 500 W (i bez `block_export_on_negative_sell`, BA81). **`pv_charge_shortfall`** / **`NEG_SELL_CURTAIL_PENALTY`** platí v těchto slotech. Při **`sell < 0`** (legacy): safety deficit cílí **`soc_max_wh`**; po posledním **`sell < 0`**: **`post_neg_pv_topup`**. **Planner tag v32:** fázované SoC — viz níže.
|
||||
- **Záporný výkup — strategie home-01 (v32–v40 prep hotovo):** **[`planning-neg-sell-strategy.md`](planning-neg-sell-strategy.md)**. **v35:** rampa B. **v36 prep:** oprava **T**, pre-neg per den (cushion A+B), večer D−1. **v40:** cushion a večerní výboj z **`observed_soc_wh`** (telemetrie), rozpočet `neg_evening_export_budget_wh` (`2026-05-29-neg-prep-observed-soc-v40`). **v36 termika** (TČ/TUV) — otevřeno.
|
||||
- **Před sell<0 — export FVE s forecast pojistkou (v33):** `_pre_neg_pv_export_forecast_cushion_ok` — export FVE v kladných slotech před prvním `sell<0` jen pokud součet predikovaného PV přebytku v sell<0 okně (týž pražský den) pokryje dobítí na prep SoC (× **1,15**). Jinak LP raději nabíjí z FVE (riziko deště). Při splněné pojistce: `bc_pv=0` v `pre_neg_pv_export_ts`, shortfall na `ge_pv`, penalizace `bc_pv`. `solver_params.inputs.pre_neg_pv_export_forecast_ok`, `pre_neg_pv_export_slots`. Testy `PreNegPvExportForecastTests`. U **fixního tarifu** s polem B: **`ge_pv ≤ pv_b`** (ne pv_store **`ge_pv = 0`**). Při **`deye_gen_microinverter_cutoff_enabled`**: **`ge == 0` jen** pokud **`block_export_on_negative_sell`** (KV1), ne kvůli samotnému `z_gen_cutoff` (BA81 musí moci exportovat B při plné baterii). Vstupní **`soc_wh`** z telemetrie se před MILP omezí přes **`_planner_soc_for_solver`** (rezerva ~650 Wh pod `soc_max`, jinak Infeasible při 100 % SoC a dlouhém záporném výkupu). **`planner_build_tag`** v `solver_params`. Changelog: [`docs/planning-changelog.md`](../planning-changelog.md).
|
||||
- **Před sell<0 — export FVE s forecast pojistkou (v33, dočasné):** `_pre_neg_pv_export_forecast_cushion_ok` — export FVE v kladných slotech před prvním `sell<0` jen pokud součet predikovaného PV přebytku v sell<0 okně (týž pražský den) pokryje dobítí na prep SoC (× **1,15**). Jinak LP raději nabíjí z FVE (riziko deště). Při splněné pojistce: `bc_pv=0` v `pre_neg_pv_export_ts`, shortfall na `ge_pv`, penalizace `bc_pv`. `solver_params.inputs.pre_neg_pv_export_forecast_ok`, `pre_neg_pv_export_slots`. Testy `PreNegPvExportForecastTests`. **Plánovaná náhrada:** `pre_window_wh` + nabíjecí fronta — **[`planning-charge-slot-budget.md`](planning-charge-slot-budget.md)** §6. U **fixního tarifu** s polem B: **`ge_pv ≤ pv_b`** (ne pv_store **`ge_pv = 0`**). Při **`deye_gen_microinverter_cutoff_enabled`**: **`ge == 0` jen** pokud **`block_export_on_negative_sell`** (KV1), ne kvůli samotnému `z_gen_cutoff` (BA81 musí moci exportovat B při plné baterii). Vstupní **`soc_wh`** z telemetrie se před MILP omezí přes **`_planner_soc_for_solver`** (rezerva ~650 Wh pod `soc_max`, jinak Infeasible při 100 % SoC a dlouhém záporném výkupu). **`planner_build_tag`** v `solver_params`. Changelog: [`docs/planning-changelog.md`](../planning-changelog.md).
|
||||
- **Záporná nákupní cena:**
|
||||
- horní mez `grid_import` zahrnuje `load_baseline_w` + nabíjení/EV/TČ (bez nekonečného importu).
|
||||
- **Záporná prodejní cena → tvrdý zákaz vývozu (`ge = 0`)** (`planning_engine.solve_dispatch`): platí ve slotu kde `sell_price < 0`, pokud lokality zapne některou z opcí —
|
||||
@@ -142,6 +142,10 @@ flowchart TD
|
||||
|
||||
**Funkce:** … home-01 **v61**; BA81/KV1 fixed **v59** (+ `R__063`).
|
||||
|
||||
### Rozpočet nabíjecích slotů (plánováno, 2026-06)
|
||||
|
||||
Náhrada tvrdých prahů v58 a binárního pre-neg cushion (v33): **deficit Wh**, forecast v okně `sell < 0`, fronta nejlevnějších slotů (buy/sell) s `pv_surplus`. Pokrývá BA81/KV1 (slunečný den nad ~60 % SoC) i home-01 (krátké zimní neg okno → nabíjení před oknem). **Specifikace:** [`planning-charge-slot-budget.md`](planning-charge-slot-budget.md). **Stav:** neimplementováno — viz changelog *Plánováno*.
|
||||
|
||||
### Arbitráž baterie — účtování mezi sloty (povinné čtení)
|
||||
|
||||
**Detail:** [`planning-arbitrage-accounting.md`](planning-arbitrage-accounting.md).
|
||||
|
||||
@@ -5,6 +5,27 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
||||
|
||||
---
|
||||
|
||||
## Plánováno — rozpočet nabíjecích slotů (charge-slot-budget, neimplementováno)
|
||||
|
||||
**Stav:** pouze dokumentace (2026-06); implementace později.
|
||||
|
||||
**Motivace:**
|
||||
|
||||
- **BA81/KV1:** v58 `sell > min_sell + 0,20 → bc_pv = 0` drží denní SoC ~60 % při slunci — konflikt s `R__063` vrstvou A.
|
||||
- **home-01:** v33 binární pre-neg cushion exportuje FVE před `sell < 0` i při středním sell; při kratším zimním okně `sell < 0` / slabší FVE chybí nabíjení **před** oknem.
|
||||
|
||||
**Záměr:**
|
||||
|
||||
1. **`fn_load_planning_slots_full`:** `charge_target_wh`, `pre_window_wh` (deficit − forecast v neg okně), fronta slotů řazená **`sell ASC`** (fixed) / **`buy ASC`** (spot), kumulace `pv_surplus` Wh → `allow_charge`.
|
||||
2. **Python:** zrušit v58 (a související fixed `bc_pv`/`bc_gi` prahy); pre-neg export jen v pre-neg slotech **bez** `allow_charge`.
|
||||
3. **Neg den:** změkčit v44 grid před `sell < 0`, pokud `pre_window_wh` > dostupná FVE v okně.
|
||||
|
||||
**Specifikace:** [`docs/04-modules/planning-charge-slot-budget.md`](04-modules/planning-charge-slot-budget.md).
|
||||
|
||||
**Plánovaný tag:** `…-charge-slot-budget-v1` (po implementaci).
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-01 — KV1: noc z baterie, ne import za 6,35 Kč (v62)
|
||||
|
||||
**Problém:** Po večerním vývozu (~32 % SoC) plán **22:00–06:00** krmil dům ze **sítě** (`grid ~260 W`, `bat 0`) místo z baterie. Fixní **buy ≈ charge_acquisition ≈ 6,35** → `expensive_import_slot` nikdy true → neplatilo `bd ≥ load` ani noční penalizace importu (`buy > acq` je false).
|
||||
|
||||
Reference in New Issue
Block a user