From a53bcd0b81a9b211adc2433fe58010d819ea12be Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Tue, 26 May 2026 13:00:33 +0200 Subject: [PATCH] dokumentace planu --- CLAUDE.md | 1 + docs/04-modules/planning-neg-sell-strategy.md | 429 ++++++++++++++++++ docs/04-modules/planning.md | 2 +- docs/06-open-questions.md | 10 + docs/planning-changelog.md | 4 + 5 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 docs/04-modules/planning-neg-sell-strategy.md diff --git a/CLAUDE.md b/CLAUDE.md index 4c8feac..7af12f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` | +| 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` | | EV, session, deadline charging | `docs/04-modules/ev-charging.md`, `db/migration/V006__vehicles.sql` | diff --git a/docs/04-modules/planning-neg-sell-strategy.md b/docs/04-modules/planning-neg-sell-strategy.md new file mode 100644 index 0000000..b0a3159 --- /dev/null +++ b/docs/04-modules/planning-neg-sell-strategy.md @@ -0,0 +1,429 @@ +# 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). + +**Stav:** část je **implementovaná** (v32–v34), část je **návrh** (v35+ termika, bazén, spirála). V textu je označeno `✅ hotovo` vs `📋 návrh`. + +--- + +## 1. Cíl produktu (home-01) + +| Cíl | Popis | +|-----|--------| +| Baterie v okně `sell < 0` | Dojet na **100 %** (`soc_max`) do konce denního úseku záporného výkupu (Europe/Prague). | +| Pole B (zelený bonus) | Při záporném výkupu smí jít přebytek do sítě (ekonomika bonusu); není curtailable. | +| Pole A (Deye, curtailable) | Po dosažení plánované energetické pohody **nechat dostupné** pro dům a chybu forecastu — ne nutně „vždy škrtat v 80 %“. | +| Ranní kladný sell | Typicky **export celé FVE** do site — nekrást výkon TČ ani fiktivním nabíjením v plánu. | +| Termika | TUV komfort / předehřát / večerní doklep — **uvnitř** vhodných oken, ne v ranním exportním pásmu. | +| Flexibilní sink | Bazén (filtrace), později spirála — sežrat **plánovaný přebytek** místo exportu za záporný sell. | +| EV | Odpoledne; nabíjení po naplnění energetické rampy / v levných slotech. | + +--- + +## 2. Slovník + +| Pojem | Význam | +|-------|--------| +| **Okno `sell < 0`** | Souvislé 15min sloty téhož **kalendářního dne** (Prague), kde `effective_sell_price < 0`. | +| **Tail** | Posledních **N** slotů okna (`planner_neg_sell_full_soc_tail_slots`, default **4** = 1 h). Cíl SoC = **`soc_max` (100 %)**. | +| **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**). | +| **Load-first (v34)** | Dům z `pv_ld`; při dostatečné FVE žádný fiktivní `grid_import = load` v plánu. | +| **Reg 340** | Deye *max solar power* ≈ `pv_a_forecast_solver_w − pv_a_curtailed_w`. | + +--- + +## 3. Časová osa dne (referenční home-01) + +```text + Prague + |-----|-----|-----|-----|-----|-----|-----|-----| + 06 08 10 12 14 16 18 20 + + [ A: ranní sell ≥ 0 — export FVE (v33) ] + [ B: sell < 0 — nabíjení bat, T*, TČ, bazén ] + [ C: večerní peak sell — export bat (masky) ] + [ D: EV často odpoledne / večer ] +``` + +### 3.1 Fáze A — před prvním `sell < 0` (ranní export) + +- **Chování plánu (v33):** pokud `_pre_neg_pv_export_forecast_cushion_ok`, sloty v `pre_neg_pv_export_ts` tlačí **export** (`ge_pv`), **`bc_pv = 0`** (FVE ne do baterie). +- **Termika (📋):** **neplánovat** komfortní TČ/TUV v těchto slotech — výkon by kolidoval s exportní strategií (FVE má jít do site). +- **Deye:** load-first na zařízení — dům si vezme z FVE; plán ale může ukazovat export, ne „import pro load“ (viz v34). + +### 3.2 Fáze B — okno `sell < 0` + +**Energie (dnes v32, návrh v35):** + +| Období v B | Dnes (v32) | Návrh (v35) | +|------------|------------|-------------| +| Začátek okna | ASAP nabít na **80 %** z A+B | Nabít podle **rampy SoC** odvozené zpět z B od tail | +| Střed okna | Měkký **curtail A** při SoC ≥ 80 % na začátku slotu | Od **T**: A necpát do bat; B + přebytek | +| Tail (posledních N slotů) | Rampa 80 % → 100 % | Rampa z **T** / B → 100 % | + +**Termika (📋):** + +- TČ **primárně zde**, když je dost PV / po bodu **T**, ne v ranní fázi A. +- **Předehřát** v **T** jen pokud je **T** dostatečně brzy a `E_surplus_after_t` je velké. +- **Večerní doklep** TUV (1–2 h před sprchou) — samostatné pravidlo od `tuv_usage_stats`. + +**Bazén (📋):** + +- Jen **slunečné** hodiny v rámci B (a ideálně **po T**), **X hodin/den** — promíchání prohřáté hladiny. + +### 3.3 Den bez `sell < 0` (📋) + +- Přebytek FVE → prodej za **kladný sell** (ne „výmět“). +- **TČ:** topit v slotech, kde **COP × sell** dává smysl oproti prodeji kWh (viz `fn_cop_estimate`, `fn_heat_pump_cost_per_kwh_heat`). +- **Spirála:** spíš **nízká priorita** — každá kWh do spirály je kWh, kterou šlo prodat. +- **Bazén:** volitelně v nejlepších PV slotech, pokud export není ekonomicky nutný. + +--- + +## 4. Implementované vrstvy (v32–v34) + +### 4.1 v32 — fázované SoC a curtail A ✅ + +**DB:** `ems.asset_battery` — migrace **`V083__planner_neg_sell_phases.sql`** + +| Sloupec | Default | Význam | +|---------|---------|--------| +| `planner_neg_sell_prep_soc_percent` | 80 | Plochý cíl SoC v prep fázi (% `soc_max`). **100** = legacy (tlak na max až v tail). | +| `planner_neg_sell_full_soc_tail_slots` | 4 | Počet 15min slotů tail před koncem denního `sell < 0`. **0** = bez tail. | +| `planner_neg_sell_vent_min_sell_czk_kwh` | −1 (home-01) | V tail: ventil pole B (`ge_pv`) pokud `sell ≥` práh. **NULL** = jen při plné baterii. | + +**Kód:** `backend/services/planning_engine.py` + +- `_neg_sell_day_phases()` — `prep` / `tail` / `none` per slot +- `prep_soc_shortfall`, `prep_hold_met_binary`, měkké `prep_hold_curtail` / `prep_hold_bcpv` +- Výstup: `planning_interval.pv_a_curtailed_w`, `solver_params.masks[].neg_sell_phase` + +**Omezení v32 (důvod návrhu v35):** + +- **80 %** není odvozené z délky okna ani z forecastu B. +- Curtail A je **měkký** (penalizace ~1 Kč/kWh) — LP může v sousedním slotu znovu nabíjet. +- Hold: `soc_prev ≥ 80 %` na **začátku** slotu, ne dynamická rampa. + +**Ověření:** `NegSellSocPhaseTests`, MCP `planning_interval` + `solver_params->'masks'`. + +### 4.2 v33 — export FVE před `sell < 0` s forecast pojistkou ✅ + +**Kód:** `_pre_neg_pv_export_forecast_cushion_ok`, `_neg_sell_day_pv_usable_wh`, `pre_neg_pv_export_ts`. + +- Export v kladných slotech před prvním `sell < 0` **jen pokud** usable FVE v celém `sell < 0` dni ≥ potřebné Wh na prep (× **1,15**). +- Jinak LP raději nabíjí z FVE (déšť / slabý forecast v okně). + +**Ověření:** `PreNegPvExportForecastTests`, `solver_params.inputs.pre_neg_pv_export_forecast_ok`. + +### 4.3 v34 — tvrdý load-first ✅ + +**Tag:** `2026-05-28-load-first-hard-v34` + +- `gi ≤ bc_gi + max(0, max_load − pv_forecast)` — při vysoké FVE žádný fiktivní import = load. +- Při `pv_forecast ≥ max_load + 500 W`: `pv_ld ≥ load`. + +**Ověření:** `LoadFirstDispatchTests::test_neg_sell_prep_no_fictitious_grid_import_for_load`. + +--- + +## 5. Návrh v35 — energie: rampa z PV B, bod T, přebytek + +📋 **Není v produkci** — specifikace pro implementaci. + +### 5.1 Kotva vzadu (tail — beze změny konceptu) + +Pro každý pražský den s `sell < 0`: + +```text +indices = všechny sloty t kde sell[t] < 0, seřazené +last_neg = indices[-1] +tail_start = max(indices[0], last_neg - (N - 1)) # N = planner_neg_sell_full_soc_tail_slots +``` + +Pro `t ≥ tail_start`: cíl `soc_target[t] = soc_max` (případně rampa v tail mezi `soc_detach` a `soc_max` pokud `N > 1`). + +### 5.2 Zpětná projekce pouze z pole B + +Pro odhad **nabití z B** v slotu `t` (zjednodušený model, stejný styl jako `_neg_sell_day_pv_usable_wh`): + +```text +pv_surplus_b[t] = max(0, pv_b_forecast[t] - load_baseline[t] - rezerva_EV_HP) +charge_b[t] = min(pv_surplus_b[t], max_charge_power_w) × charge_efficiency × 0,25 h +``` + +Zpět od `tail_start`: + +```text +soc_need[last_neg] = soc_max +soc_need[t-1] = soc_need[t] - charge_b[t] # clamp ≥ min_soc_wh +``` + +Výsledkem je **`soc_need[t]`** — požadované SoC na **konci** slotu `t`, kdyby stačilo jen B. + +### 5.3 Bod T (`t_detach`) + +**Definice:** nejmenší `t` v prep části, kde: + +```text +soc_need[t] ≤ soc_detach_wh +``` + +kde `soc_detach_wh` může být: + +- konfigurovatelné % (náhrada za fixních 80 %), nebo +- odvozené z `soc_need` v okamžiku přechodu („natural“ detach). + +**Interpretace:** + +| Situace | Význam | +|---------|--------| +| **T** brzy po začátku `sell < 0` | Dlouhé okno, B stačí → od **T** uvolnit A pro dům / odchylku | +| **T** těsně před tail | Krátké okno → A potřebné déle, malý `E_surplus_after_t` | +| Aktuální SoC **pod** `soc_need[t]` při replanu | Ještě fáze „honit rampu“ (A+B) | +| Rampa z aktuálního SoC **nedosáhne** tail ani optimisticky | Slabý den — 100 % dnes nejspíš nevyjde | + +### 5.4 Plánovaný přebytek `E_surplus_after_t` + +Pro sloty `t ∈ [t_detach, last_neg]`: + +```text +E_surplus_after_t = Σ_t max(0, + pv_a_forecast[t] + pv_b_forecast[t] + - load_baseline[t] + - charge_to_battery_cap[t] +) +``` + +× `0,25 h` (případně jen část nad tím, co jde do `soc_need`). + +**Použití:** + +| Spotřebič | Pravidlo | +|-----------|----------| +| TČ předehřát v **T** | Jen pokud `E_surplus_after_t` > práh a **T** je dostatečně brzy | +| Bazén filtrace | Rozpočet hodin ≤ f(`E_surplus_after_t`), slunce) | +| Spirála (📋) | Až když TČ + bazén nestačí sežrat přebytek | +| Export B | Zbytek (zelený bonus) — lepší než -0,3 Kč/kWh, horší než vlastní spotřeba | + +### 5.5 Chování PV A po T (📋) + +**Ne** „tvrdě urazit A v 80 %“. + +| Režim | LP / plán | +|-------|-----------| +| `t < t_detach` | Plné nabíjení z A+B směrem k `soc_need[t]` | +| `t ≥ t_detach` | **Necpát A do baterie** (`bc_pv` z A minimálně); A dostupné pro `pv_ld` / dům | +| Curtail A | Měkké nebo jen při riziku zbytečného exportu A za `sell < 0` | + +**Deye:** reg **340** = forecast A − curtail; při plném plánu bez exportu EMS 340 nemusí zapisovat (`plan_skips_deye_reg340_write`). + +### 5.6 Výstupy do `solver_params` (📋) + +Navrhované klíče v `planning_run.solver_params.inputs`: + +| Klíč | Typ | Popis | +|------|-----|--------| +| `neg_sell_soc_ramp_wh` | pole | `soc_need[t]` per slot ISO | +| `t_detach_idx` | int | index slotu T | +| `e_surplus_after_t_wh` | float | integrál přebytku | +| `neg_sell_window_slots` | int | délka okna | +| `planner_build_tag` | string | např. `2026-05-28-neg-sell-b-ramp-v35` | + +--- + +## 6. Termika — TČ, TUV, spirála + +### 6.1 Co je dnes v solveru ✅ + +- Proměnná **`hp[t]`** 0…`rated_heating_power_w` v bilanci `load_site_expr`. +- **TUV look-ahead:** `tuv_usage_stats`, nouz pod `tuv_min_temp_c`, boost při poklesu pod `min+5 °C`. +- **Export TČ:** `heat_pump_enabled` / `heat_pump_setpoint_w` v `planning_interval`; Modbus zápis — viz [`control.md`](control.md) (často TODO). + +**Není v modelu:** spirála, bojler jako samostatná zátěž, teplotní stav zásobníku jako spojitá proměnná v každém slotu (jen zjednodušený `tuv_pred`). + +### 6.2 Pravidla podle typu dne (📋) + +#### Den **se** `sell < 0` + +| Kdy | TČ / TUV | +|-----|----------| +| Ranní pásma **před** `sell < 0` (pre-neg export) | **Netopit** (kromě nouze pod `tuv_min`) | +| Uvnitř `sell < 0`, `t < t_detach` | Minimum; priorita nabíjení bat | +| Uvnitř `sell < 0`, `t ≥ t_detach` | Komfort / předehřát dle `E_surplus_after_t` | +| Večer (sprcha) | **Doklep** na `tuv_comfort_temp_c` | + +#### Den **bez** `sell < 0` + +- TČ v slotech s **nízkým buy** a dobrým **COP** (poledne), ne v nejlepších **exportních** slotech FVE. +- Spirála: nízká priorita — preferovat prodej FVE. + +### 6.3 TČ vs spirála (📋) + +| Kritérium | Preferovat TČ | Preferovat spirálu | +|-----------|---------------|-------------------| +| Dlouhé `sell < 0`, B pokryje bat | Ano (COP) | Ne | +| Krátké okno, hodně FVE „na střeše“ | Částečně | Ano, pokud marginal cost ≈ 0 | +| Den bez `sell < 0` | Ano při dobrém COP | Spíš ne | + +Spirála vyžaduje **novou zátěž** v DB + LP (`flex_load_spiral[t]` nebo signál Loxone). + +### 6.4 Konfigurovatelné teploty (📋 — rozhodnutí) + +| Parametr | Navrh | Poznámka | +|----------|-------|----------| +| `tuv_comfort_temp_c` | např. 50–52 | Denní komfort | +| `tuv_preheat_temp_c` | např. 55–58 | V bodu **T**, podmíněně | +| `tuv_evening_topup_before_min` | např. 90 | Doklep před sprchou | +| `hp_no_run_pre_neg_export` | true | Blok TČ ve fázích A (v33 sloty) | + +--- + +## 7. Bazén — filtrace a přitop (📋) + +### 7.1 Provozní záměr + +- **Filtrace ~1 kW** — regulovatelný **denní rozpočet hodin** (např. 4–6 h). +- **Kdy:** jen ve **slunečných** hodinách (např. 09:00–17:00 Prague, nebo příznak `is_daytime_pv_surplus_slot` z `fn_load_planning_slots_full`). +- **Proč ve dni:** cirkulace promíchá prohřátou hladinu (uživatelský požadavek). +- **Priorita:** po naplnění bat rampy / od **T**, před exportem B za `sell < 0`. + +### 7.2 Napojení na `E_surplus_after_t` + +```text +pool_hours_max = min( + pool_filter_hours_per_day_config, + floor(E_surplus_after_t_wh / (1000 W × 0,25 h)) +) +``` + +Rozložit do slotů s `sell < 0` ∧ slunce ∧ `t ≥ t_detach`. + +### 7.3 Datový model (📋) + +Zatím **není** v `db/migration`. Návrh: + +- `ems.asset_pool` nebo rozšíření site config JSON +- sloupce: `filter_power_w`, `filter_hours_per_day`, `solar_window_start_hour`, `solar_window_end_hour` (Prague) + +### 7.4 LP (📋) + +- `pool_filter[t] ∈ [0, filter_power_w]` +- Zapnout jen pokud: `soc[t] ≥ soc_need[t]`, `sell[t] < 0`, slunce, zbývá denní rozpočet hodin +- Penalizovat `ge_pv` z B při plné baterii a zapnutém bazénu + +--- + +## 8. EV + +- Typicky **odpoledne** — session z telemetrie / `ev_session`. +- LP: deadline constraint na `target_soc` k `target_deadline`. +- Strategická vazba na v35: **po dosažení rampy** nebo v `allow_charge` + PV bohatých slotech — ne v ranním pre-neg exportu. +- Konflikt s večerním exportem bat řeší stávající masky `allow_discharge_export`. + +--- + +## 9. UI plánování — význam čísel + +Řádek v detailu slotu (**Planning.tsx**): + +**„Škrcení A / ≈ reg 340“** + +| Zobrazení | DB / výpočet | Význam | +|-----------|--------------|--------| +| **CURTAIL X W** | `pv_a_curtailed_w` | Kolik W z pole A plán **odebírá** (nechce využít). **0** = žádné škrcení. | +| **povoleno Y W** | `pv_a_forecast_solver_w − pv_a_curtailed_w` | Odhad **reg 340** (*max solar power*) pro pole A. | + +Příklad: forecast A = 4 654 W, curtail = 1 117 W → povoleno **3 537 W**. + +**Badge `sell− prep` / `sell− tail`:** z `solver_params.masks[].neg_sell_phase` (v32). + +**Bat. / síť / SoC:** `battery_setpoint_w` / `grid_setpoint_w` / `battery_soc_target_pct` — po v34 u vysoké FVE **grid ≈ 0**, ne fiktivní import = load. + +--- + +## 10. Priorita flexibilních spotřebičů (📋) + +Při `sell < 0` a plné / dostatečné baterii: + +```text +1. Bazální dům (load-first, pv_ld) +2. Nouz TUV (tuv_min) +3. EV deadline +4. TČ komfort / doklep / předehřát (dle fáze) +5. Bazén filtrace (slunce, rozpočet hodin) +6. Spirála (až bude v EMS) +7. Export pole B (zelený bonus) +8. Curtail A (poslední ventil) +``` + +--- + +## 11. Roadmap implementace + +| Fáze | Tag / doc | Obsah | Závislost | +|------|-----------|--------|-----------| +| **v35** | `neg-sell-b-ramp-v35` | Rampa `soc_need` z B, **T**, `E_surplus_after_t`, uvolnění A | V083 sloupce; náhrada plochých 80 % v LP | +| **v36** | termika-v36 | Blok TČ v pre-neg; TUV doklep; komfort v `sell<0` po **T** | v35 | +| **v37** | pool-v37 | Asset bazén, denní hodiny, LP sink | v35 | +| **v38** | spiral-v38 | Spirála + volba TČ vs spirála | Loxone/Modbus, v37 | + +Každá fáze: migrace (pokud DB), `planning_engine.py`, testy MILP, zápis do `planning-changelog.md`, ověření na home-01 přes MCP. + +--- + +## 12. Ověření v provozu + +```sql +-- aktivní běh +select id, solver_params->>'planner_build_tag' as tag, + solver_params->'inputs'->>'pre_neg_pv_export_forecast_ok' as pre_neg_ok, + solver_params->'inputs'->>'t_detach_idx' as t_detach, + solver_params->'inputs'->>'e_surplus_after_t_wh' as e_surplus +from ems.planning_run +where site_id = (select id from ems.site where code = 'home-01') + and status = 'active' +order by created_at desc +limit 1; + +-- sloty kolem poledne +select pi.interval_start at time zone 'Europe/Prague' as prague, + pi.battery_soc_target_pct, + pi.pv_a_curtailed_w, + pi.pv_a_forecast_solver_w, + pi.battery_setpoint_w, + pi.grid_setpoint_w, + pi.effective_sell_price +from ems.planning_interval pi +join ems.planning_run pr on pr.id = pi.run_id +where pr.site_id = (select id from ems.site where code = 'home-01') + and pr.status = 'active' + and (pi.interval_start at time zone 'Europe/Prague')::date = current_date +order by pi.interval_start; +``` + +```bash +# testy +cd backend && python3 -m pytest tests/test_planning_dispatch_milp.py -k "NegSell or PreNeg or LoadFirst" -q +``` + +--- + +## 13. Související soubory + +| Oblast | Cesta | +|--------|--------| +| Solver | `backend/services/planning_engine.py` — `_neg_sell_day_phases`, `_pre_neg_pv_export_*`, `solve_dispatch` | +| DB parametry | `db/migration/V083__planner_neg_sell_phases.sql` | +| Kontext site | `db/routines/R__039_fn_planning_site_context.sql` | +| FE plán | `frontend/src/pages/Planning.tsx` — `pvAAllowedW`, curtail badge | +| Deye 340 | `backend/services/control/setpoints.py` — `compute_pv_a_reg340_max_solar_w` | +| TUV stats | `ems.tuv_usage_stats`, `fn_update_tuv_usage_stats` | + +--- + +## 14. Otevřená rozhodnutí + +Přesunuta do [`docs/06-open-questions.md`](../06-open-questions.md) sekce **Plánování — neg sell, termika, bazén** — nutné doplnit před v36+. diff --git a/docs/04-modules/planning.md b/docs/04-modules/planning.md index ef09d3c..e1f19da 100644 --- a/docs/04-modules/planning.md +++ b/docs/04-modules/planning.md @@ -50,7 +50,7 @@ **Planner tag v26:** v25 + upřesnění večerního exportu — viz sekce **Večerní export z baterie** níže a changelog v26. **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 — fázované SoC (v32, `2026-05-28-neg-sell-soc-phases-v32`):** Parametry na **`ems.asset_battery`**: `planner_neg_sell_prep_soc_percent` (default **80**), `planner_neg_sell_full_soc_tail_slots` (default **4** = 1 h), `planner_neg_sell_vent_min_sell_czk_kwh` (default **−1** u home-01; **NULL** = ventil B jen při plné baterii). **`_neg_sell_day_phases`** (kalendářní den Prague): **prep** = ASAP nabít na prep %; **tail** = poslední N slotů rampa na `soc_max` + volitelný `ge_pv ≤ pv_b` pokud `sell ≥` práh; měkký curtail A (`pv_a_curtailed_w`) při SoC ≥ prep. Realizace na Deye: **reg 340** = forecast − curtail; při plné baterii bez curtailu v plánu EMS 340 **nezapisuje** (solar sell off). Legacy: `prep_soc_percent ≥ 100` nebo `tail_slots = 0`. KV1: seed `prep=100`. Ověření: `NegSellSocPhaseTests`, `solver_params.masks[].neg_sell_phase`. +- **Záporný výkup — strategie home-01 (v32–v34 hotovo, v35+ návrh):** Kompletní specifikace (rampa SoC z PV B, bod **T**, termika, bazén, UI curtail): **[`planning-neg-sell-strategy.md`](planning-neg-sell-strategy.md)**. Stručně — **v32:** `planner_neg_sell_prep_soc_percent` (80 %), `planner_neg_sell_full_soc_tail_slots` (4), `planner_neg_sell_vent_min_sell_czk_kwh`; fáze prep/tail, měkký curtail A. **v33:** export FVE před `sell<0` s forecast pojistkou. **v34:** tvrdý load-first. **v35 (návrh):** nahradit fixních 80 % rampou z B a bodem **T**. - **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). - **Záporná nákupní cena:** - horní mez `grid_import` zahrnuje `load_baseline_w` + nabíjení/EV/TČ (bez nekonečného importu). diff --git a/docs/06-open-questions.md b/docs/06-open-questions.md index 8c1607c..de8fce1 100644 --- a/docs/06-open-questions.md +++ b/docs/06-open-questions.md @@ -18,6 +18,16 @@ Tento soubor slouží jako živý seznam věcí které je potřeba rozhodnout p ## Důležité (neblokují, ale řeší se brzy) +### Plánování — neg sell, termika, bazén + +Kompletní návrh: [`docs/04-modules/planning-neg-sell-strategy.md`](04-modules/planning-neg-sell-strategy.md). Implementace v35+ čeká na doplnění: + +- [ ] **v35 — bod T a rampa SoC z PV B** — potvrdit, zda `soc_detach_wh` = odvozené z rampy, nebo ponechat konfigurovatelné % jako strop (náhrada 80 %). +- [ ] **TUV teploty** — `tuv_comfort_temp_c`, `tuv_preheat_temp_c` pro předehřát v bodu T; čas večerního doklepu před sprchou (fixní hodina vs. uživatelský profil). +- [ ] **Bazén** — `filter_hours_per_day` (kolik hodin filtrace), okno slunce (Prague 09–17?), jen filtrace 1 kW nebo i přitop TČ vody. +- [ ] **Spirála** — je ovladatelná z EMS/Loxone? Samostatný asset vs. signál; priorita oproti TČ v dnech bez `sell < 0`. +- [ ] **TČ v pre-neg exportu** — potvrdit zákaz plánovaného topení ve slotech `pre_neg_pv_export_ts` (v36). + - [x] **Arbitráž baterie — 1. vlna (před solve):** `charge_acquisition_buy_czk_kwh` + cutoff před 1. `allow_discharge_export`; LP `+ge_bat×acquisition` v exportních slotech. Zbývá iterace po solve a více charge slotů — [`planning-arbitrage-accounting.md`](04-modules/planning-arbitrage-accounting.md) §6, [`docs/05-todo.md`](05-todo.md). - [ ] **Dvě úrovně min SoC v DB** – Dnes jedno `min_soc_percent` (provozní podlaha pro LP i TOU PASSIVE). Budoucí oddělení „tvrdé BMS minimum“ vs „plánovací minimum“ by vyžadovalo nový sloupec nebo politiku per site. diff --git a/docs/planning-changelog.md b/docs/planning-changelog.md index a97281d..a791d0f 100644 --- a/docs/planning-changelog.md +++ b/docs/planning-changelog.md @@ -5,6 +5,10 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen --- +## 2026-05-28 — Dokumentace strategie sell<0 + termika + bazén + +**Soubor:** [`docs/04-modules/planning-neg-sell-strategy.md`](04-modules/planning-neg-sell-strategy.md) — cíle, slovník, časová osa dne, v32–v34 vs návrh v35+, TČ/TUV podle typu dne, bazén, UI curtail/reg 340, roadmap, SQL ověření. Otevřená rozhodnutí: [`docs/06-open-questions.md`](06-open-questions.md). + ## 2026-05-28 — Tvrdý load-first v LP (v34) **Problém:** V sell<0 prep plán ukazoval `grid_setpoint_w ≈ load_baseline` při FVE ≫ load — LP účetně posílal dům přes `gi`, zatímco Deye load-first krmit dům z FVE.