# 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-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–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`.

---

## 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**). **📋 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`. |

---

## 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 (v32–v35):**

| Období v B | Chování (v35) |
|------------|----------------|
| Začátek okna | Nabít podle **rampy SoC** (`soc_need`) zpět z PV B od tail |
| Střed okna | Od **t_detach**: měkké omezení `bc_pv`; hold/curtail při `soc_prev ≥ soc_target[t]` |
| Tail (posledních N slotů) | Rampa z `soc_need[tail_start]` → 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–v35)

### 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 | **v32 legacy** — od v35 se v LP neřídí (rampa z B). **100** = vypnutí fází (`_neg_sell_phases_enabled`). |
| `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.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`

- `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`.

### 4.4 v35 — rampa SoC z PV B, bod T, přebytek ✅

**Tag:** `2026-05-28-neg-sell-b-ramp-v35` (bod T opraven v **v36** — viz níže).

**Kód:** `_neg_sell_pv_b_charge_wh`, `_neg_sell_day_phases` (rampa), `_neg_sell_e_surplus_after_t_wh`, `_neg_sell_day_pv_b_usable_wh` (cushion v33).

- Zpětná projekce `soc_need` jen z PV B; prep `soc_target[t] = soc_need[t]` (ne fixních 80 %).
- **t_detach** = první prep slot kde `soc_need[t] ≤ soc_need[tail_start]`; **E_surplus_after_t** od T do konce okna.
- Prep hold: `soc_prev ≥ soc_target[t]`; po T: `NEG_SELL_POST_DETACH_BCPV_DISCOURAGE` na `bc_pv`.
- `solver_params.inputs`: `neg_sell_b_ramp_v35`, `t_detach_idx`, `e_surplus_after_t_wh`, `neg_sell_day_meta`.

**Ověření:** `NegSellSocPhaseTests::test_b_ramp_t_detach_and_surplus_meta`, MCP `solver_params`.

### 4.5 v36 — přípravné okno neg dne ✅

**Tag:** `2026-05-28-neg-prep-window-v36`

| Problém v35 | Oprava v36 |
|-------------|------------|
| **T** hned na 1. `sell<0` → celý den curtail A | `t_detach` až `soc_need[t] ≥ 85 % soc_max` + suffix B ≥ zbytek do 100 % |
| Ráno 2. neg dne nabíjí místo exportu | **Pre-neg per den** + cushion **A+B**; `pre_neg_pv_export_slots` pro každý pražský den zvlášť |
| Večer nevybije před zítřejším neg | `neg_evening_before_neg_slots` — výboj večer **D−1** |

**Cílová časová osa (např. 27. 5.):**

```text
07–09:30  sell ≥ 0   → export FVE (pre-neg, cushion OK)
09:45+    sell < 0   → nabíjení A+B po rampě
~11–13    bod T      → uvolnění / curtail A, B do domu nebo export
večer 26.5           → vybít bat před neg 27.5 (headroom)
```

**Ověření:** `NegSellPrepWindowV36Tests`, `solver_params.inputs.pre_neg_cushion_by_day`, `neg_evening_before_neg_slots`.

### 4.6 v40 — pozorované SoC pro neg-prep (Plan 5) ✅

**Tag:** `2026-05-29-neg-prep-observed-soc-v40`

| Problém v36 | Oprava v40 |
|-------------|------------|
| Cushion / večerní výboj z **modelového** SoC (řetězení cílů mezi dny) | **`observed_soc_wh`** z telemetrie; žádné `soc_est := soc_target[first_neg]` |
| BMS výš → plán „už mám headroom“ nevidí | Cushion OK pokud `observed_soc ≥ soc_target[first_neg]` |
| Večerní výboj pod exportuje | Rozpočet `max(0, observed − reserve − night_baseload_buffer)` → `neg_evening_push_slots` |

**Kód:** `_pre_neg_pv_export_bundle`, `_neg_evening_discharge_budget_wh`, `_neg_evening_before_neg_push_indices` v `planning_engine.py`.

**Ověření:** `ObservedSocNegPrepTests`; MCP `solver_params.inputs.observed_soc_wh`, `neg_evening_export_budget_wh`, `neg_evening_push_slots`.

---

## 5. Specifikace rampy (v35 — reference)

### 5.0 Rozhodnutí produktu (home-01, 2026-05)

| Téma | Rozhodnutí |
|------|------------|
| Rampa / **T** | Odvozené z PV B; **bez** řízení fixním `planner_neg_sell_prep_soc_percent` v LP pro home-01. |
| TČ v pre-neg | **Zákaz** plánovaného topení. |
| Bazén | Min. 4 h filtrace/den, dynamicky navýšit; Shelly; přitop ručně / později. |
| Spirála | Loxone; v38. |
| UI flex | Workshop **před** v37 — viz § 9.1. |

### 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`) — v36

**Definice (implementováno v36):** první prep slot `t`, kde současně:

```text
soc_need[t] ≥ max(0,85 × soc_max, 0,92 × soc_need[tail_start])
Σ charge_b[t..konec] ≥ (soc_max − soc_need[t]) × 1,05
```

**Zrušeno (chyba v35):** `soc_need[t] ≤ soc_need[tail_start]` — platilo vždy na začátku okna.

**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 Parametry termiky (rozhodnutí + otevřeno)

| Parametr | Stav | Hodnota / poznámka |
|----------|------|---------------------|
| `hp_no_run_pre_neg_export` | **Rozhodnuto** | `true` — v `pre_neg_pv_export_ts` **netopit** (raději export FVE). |
| `tuv_comfort_temp_c` | Otevřeno | Např. 50–52 °C — doplnit do konfigurace site. |
| `tuv_preheat_temp_c` | Otevřeno | Např. 55–58 °C — jen v bodu **T**, pokud `E_surplus_after_t` stačí. |
| `tuv_evening_topup_hour` | **Rozhodnuto** | **19:00** Europe/Prague — večerní doklep TUV (implementace v36). |
| Spirála | **Rozhodnuto** | Ovládání **Loxone**; model v EMS až v38. |

---

## 7. Bazén — filtrace a přitop (📋)

### 7.1 Provozní záměr (rozhodnutí home-01)

- **Filtrace ~1 kW** — min. **4 h/den**; **více hodin**, pokud `E_surplus_after_t` a přebytek dovolí (marginalní náklad ≈ 0).
- **Kdy:** přes den ve **slunečných** slotech (`is_daytime_pv_surplus_slot` nebo obdobné); **dynamicky** dle cen / přebytku, ne pevné okno 09–17.
- **Proč ve dni:** cirkulace promíchá prohřátou hladinu.
- **Priorita:** po rampě bat / od bodu **T**, před exportem B za `sell < 0`.
- **Přitop vody:** **mimo** první verzi plánovače; začátek sezóny **ručně**; automatika později.
- **Exekuce:** **Shelly** — ovládání z EMS po implementaci assetu (v37).

### 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.

### 9.1 Vizualizace flexibilních zátěží — probrat před implementací (📋)

**Stav:** produktové rozhodnutí **není** — **neimplementovat** bazén / rozšířené TČ v UI ani v LP sinku, dokud není schválený návrh. Workshop mezi **v35** a **v37**.

**Proč:** flexibilní zátěže (TČ, bazén, spirála, EV) sdílí stejnou časovou osu jako energie (**T**, `E_surplus_after_t`, fáze sell&lt;0). Bez přehledného UI bude provoz těžko kontrolovatelný.

**Návrhy k diskusi** (nic z toho není závazná implementace):

| Nápad | Co ukázat |
|-------|-----------|
| **Pásma dne** | V grafu plánu: pre-neg export \| sell&lt;0 prep \| od **T** \| tail \| večerní export bat. |
| **Bod T** | Svislá značka + tooltip: `t_detach`, `e_surplus_after_t_wh`, odhad hodin bazénu. |
| **Rozpočet bazénu** | „Dnes 2/4 h filtrace naplánováno“ + zbývající Wh přebytku. |
| **Slot detail** | Kromě bat/síť/FVE: **TČ** (`heat_pump_setpoint_w`), **EV**, (budoucí) **bazén ON**, badge **flex sink**. |
| **Srovnání běhů** | Před/po v35: rampa SoC, méně fiktivního grid importu, curtail A. |
| **Živě vs plán** | Volitelně: telemetrie TUV / Shelly pool vs plánovaný stav (až bude data). |

**Výstup workshopu:** krátký mock / seznam widgetů v `Planning.tsx` + které sloupce ukládat do `planning_interval` / `solver_params`.

**Otevřené otázky UI:** viz [`docs/06-open-questions.md`](../06-open-questions.md).

---

## 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

| Pořadí | Fáze | Tag / doc | Obsah | Blokátor |
|--------|------|-----------|--------|----------|
| 1 | **v35** ✅ | `neg-sell-b-ramp-v35` | Rampa `soc_need` z B, **T**, `E_surplus_after_t`, uvolnění A | — |
| 2 | **UI workshop** | — | Vizualizace flex. zátěží — § 9.1; schválený návrh widgetů | **Před v37** |
| 3 | **v36** | `termika-v36` | Blok TČ pre-neg; TUV v `sell<0` po **T**; večerní doklep **19:00** Prague | v35 |
| 4 | **v37** | `pool-v37` | Bazén: Shelly, min 4 h/den, LP sink | UI workshop |
| 5 | **v38** | `spiral-v38` | Spirála (Loxone) + volba TČ vs spirála | v37 |

Každá implementační fáze: migrace (pokud DB), `planning_engine.py`, testy MILP, `planning-changelog.md`, ověření MCP na home-01.

---

## 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í

Živý seznam: [`docs/06-open-questions.md`](../06-open-questions.md) — sekce **Plánování — neg sell, termika, flexibilní zátěže**.

Zbývá hlavně: **čas večerního doklepu TUV** (~19h?), **návrh UI flex zátěží** (workshop před v37).
