18 KiB
Strategie záporného výkupu, FVE A/B, termika a flexibilní zátěže (home-01)
Navazuje na planning.md, planning-arbitrage-accounting.md, planning-changelog.md, heat-pump.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)
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 vpre_neg_pv_export_tstlačí 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_tje 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/noneper slotprep_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 < 0jen pokud usable FVE v celémsell < 0dni ≥ 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:
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):
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:
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:
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_needv 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]:
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_wv bilanciload_site_expr. - TUV look-ahead:
tuv_usage_stats, nouz podtuv_min_temp_c, boost při poklesu podmin+5 °C. - Export TČ:
heat_pump_enabled/heat_pump_setpoint_wvplanning_interval; Modbus zápis — vizcontrol.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_slotzfn_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
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_poolnebo 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_pvz B při plné baterii a zapnutém bazénu
8. EV
- Typicky odpoledne — session z telemetrie /
ev_session. - LP: deadline constraint na
target_socktarget_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:
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
-- 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;
# 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 sekce Plánování — neg sell, termika, bazén — nutné doplnit před v36+.