job ev_presence_notify + fn_ev_presence_nudge_due (SQL-first rozhodnutí+dedup); asset_vehicle.presence_nudge_enabled default false=inertní (V110). Worktree agent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
463 lines
20 KiB
Markdown
463 lines
20 KiB
Markdown
# Modul: EV Nabíjení
|
||
|
||
## Přehled vozidel na home-01
|
||
|
||
| Vozidlo | Nabíječka | Max výkon | Řízení | API |
|
||
|---|---|---|---|---|
|
||
| Tesla | ev-charger-1 (Teltonika 22kW) | 22 kW | WB proud limit + Tesla API | Zatím nerozhodnuto (Tessie nebo přímé) |
|
||
| Renault Zoe | ev-charger-2 (Teltonika 22kW) | 22 kW (Zoe max 22kW) | WB proud limit (Zoe respektuje) | Žádné – Zoe jako fixní zátěž při připojení |
|
||
|
||
---
|
||
|
||
## Klíčové principy
|
||
|
||
### 1. Přímé FVE nabíjení preferováno před průchodem přes baterii
|
||
|
||
Energie která jde FVE → baterie → EV má round-trip ztráty:
|
||
```
|
||
η_round_trip = η_charge × η_discharge ≈ 0.95 × 0.95 ≈ 0.90
|
||
```
|
||
|
||
Přímé napájení FVE → EV (nebo síť → EV) je ~10 % efektivnější.
|
||
Solver to vidí přes vyšší efektivní cenu energie procházející baterií (degradation_cost + round-trip loss).
|
||
|
||
### 2. Deadline charging
|
||
|
||
Každé vozidlo může mít nastaven:
|
||
- **cílový SoC** (%)
|
||
- **deadline** (do kdy musí být dosažen)
|
||
|
||
Solver garantuje dosažení SoC do deadline jako hard constraint.
|
||
Ekonomická optimalizace probíhá v rámci tohoto omezení.
|
||
|
||
### 3. Zoe – řízení přes WB proud limit
|
||
|
||
Zoe respektuje maximální proud nastavený na WB (Teltonika Modbus).
|
||
Solver nastaví `current_limit_a` pro daný slot.
|
||
Zoe vždy nabíjí pokud je připojena a proud > 6A.
|
||
|
||
Scheduler v Zoe se nepoužívá – WB proud limit je jediný řídicí prvek.
|
||
|
||
### 4. Tesla – WB + volitelně Tesla API
|
||
|
||
V první fázi stejný přístup jako Zoe – proud limit přes WB.
|
||
Tesla API (Tessie nebo přímé) přidáme ve fázi 2 pro:
|
||
- čtení aktuálního SoC bez dotazování WB
|
||
- čtení stavu připojení
|
||
- případné spuštění/zastavení nabíjení přímo v autě
|
||
|
||
---
|
||
|
||
## DB rozšíření – EV session a deadline
|
||
|
||
### Tabulka `ems.ev_session`
|
||
|
||
```sql
|
||
CREATE TABLE ems.ev_session (
|
||
id SERIAL PRIMARY KEY,
|
||
site_id INT NOT NULL REFERENCES ems.site(id),
|
||
charger_id INT NOT NULL REFERENCES ems.asset_ev_charger(id),
|
||
vehicle_id INT REFERENCES ems.asset_vehicle(id),
|
||
session_start TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
session_end TIMESTAMPTZ,
|
||
-- Stav při připojení
|
||
soc_at_connect_pct NUMERIC(5,2),
|
||
-- Deadline požadavek (nastavuje uživatel nebo API)
|
||
target_soc_pct NUMERIC(5,2),
|
||
target_deadline TIMESTAMPTZ,
|
||
-- Výsledek
|
||
soc_at_disconnect_pct NUMERIC(5,2),
|
||
energy_delivered_kwh NUMERIC(10,3),
|
||
cost_czk NUMERIC(10,4)
|
||
);
|
||
```
|
||
|
||
### Tabulka `ems.asset_vehicle`
|
||
|
||
```sql
|
||
CREATE TABLE ems.asset_vehicle (
|
||
id SERIAL PRIMARY KEY,
|
||
site_id INT NOT NULL REFERENCES ems.site(id),
|
||
code TEXT NOT NULL,
|
||
name TEXT,
|
||
make TEXT, -- 'Tesla', 'Renault'
|
||
model TEXT, -- 'Model Y', 'Zoe'
|
||
battery_capacity_kwh NUMERIC(6,2), -- Tesla ~58, Zoe ~22
|
||
max_charge_power_w INT, -- max přijímaný výkon vozidla
|
||
default_charger_id INT REFERENCES ems.asset_ev_charger(id),
|
||
api_type TEXT, -- 'tesla', 'none'
|
||
api_reference TEXT, -- odkaz na credentials v env
|
||
default_target_soc_pct NUMERIC(5,2) DEFAULT 80,
|
||
default_deadline_hour INT DEFAULT 7 -- 7:00 ráno jako výchozí deadline
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## Solver rozšíření – EV s round-trip a deadline
|
||
|
||
### Nové proměnné pro každý slot t a každé EV e
|
||
|
||
```python
|
||
ev_direct[e][t] # W – přímé napájení EV z FVE nebo sítě (bez průchodu baterií)
|
||
ev_via_bat[e][t] # W – napájení EV přes baterii (vyšší efektivní cena)
|
||
|
||
# Celkový výkon EV (co jde do auta)
|
||
ev_charge[e][t] = ev_direct[e][t] + ev_via_bat[e][t]
|
||
|
||
# Co ev_via_bat stojí energeticky navíc:
|
||
# ev_via_bat musí být "nakoupeno" z baterie s round-trip ztrátou
|
||
# solver to vidí přes účelovou funkci – viz níže
|
||
```
|
||
|
||
### Energetická bilance rozšířená o přímé EV
|
||
|
||
```python
|
||
# Zdroje = Spotřeba
|
||
pv_a_net[t] + pv_b[t] + grid_import[t] + batt_discharge[t]
|
||
== load_baseline[t]
|
||
+ Σ_e ev_direct[e][t] # přímá spotřeba EV
|
||
+ Σ_e ev_via_bat[e][t] # EV přes baterii (z discharge)
|
||
+ heat_pump[t]
|
||
+ batt_charge[t]
|
||
+ grid_export[t]
|
||
|
||
# Vazba: ev_via_bat[e][t] musí pokrýt batt_discharge[t]
|
||
# (solver to vyřeší sám – discharge jde buď do ev_via_bat nebo do load)
|
||
```
|
||
|
||
### Účelová funkce – efektivní cena EV přes baterii
|
||
|
||
```python
|
||
# Nabíjení přes baterii je dražší o round-trip ztrátu a degradaci:
|
||
EV_VIA_BAT_COST_FACTOR = 1.0 / (charge_efficiency * discharge_efficiency)
|
||
# ≈ 1.0 / (0.95 * 0.95) ≈ 1.108
|
||
|
||
# V objective function:
|
||
+ ev_via_bat[e][t] * buy_price[t] * EV_VIA_BAT_COST_FACTOR * H / 1000
|
||
+ ev_direct[e][t] * buy_price[t] * H / 1000 # přímé – bez navýšení
|
||
|
||
# Solver přirozeně preferuje přímé nabíjení kde je to možné
|
||
```
|
||
|
||
### Deadline constraint
|
||
|
||
```python
|
||
# Pro každé EV e s nastaveným deadline:
|
||
if ev_session[e].target_deadline is not None:
|
||
|
||
# Kolik energie ještě potřebujeme dodat
|
||
energy_needed_wh = (
|
||
(ev_session[e].target_soc_pct - ev_session[e].current_soc_pct)
|
||
/ 100.0 * vehicle[e].battery_capacity_kwh * 1000
|
||
)
|
||
|
||
# Deadline slot index
|
||
t_deadline = slot_index_for(ev_session[e].target_deadline)
|
||
|
||
# Hard constraint: součet dodané energie do deadline musí být >= potřebná
|
||
prob += pulp.lpSum(
|
||
ev_charge[e][t] * H # Wh za 15min slot
|
||
for t in range(t_deadline + 1)
|
||
if ev_connected[e][t] # jen sloty kdy je auto připojeno
|
||
) >= energy_needed_wh
|
||
|
||
# Zoe má tvrdší deadline (menší baterie, kritičtější)
|
||
# Tesla může mít měkčí deadline nebo vyšší flexibility okno
|
||
```
|
||
|
||
### Připojení EV – vstupní podmínka
|
||
|
||
```python
|
||
# ev_connected[e][t] = True/False
|
||
# Pokud auto není připojeno → ev_charge[e][t] = 0
|
||
|
||
for t in range(T):
|
||
if not ev_connected[e][t]:
|
||
prob += ev_charge[e][t] == 0
|
||
prob += ev_direct[e][t] == 0
|
||
prob += ev_via_bat[e][t] == 0
|
||
```
|
||
|
||
---
|
||
|
||
## Jak solver rozhoduje (příklady)
|
||
|
||
### Přebytek FVE přes poledne, Zoe připojena, baterie poloprázdná
|
||
|
||
```
|
||
Solver volí:
|
||
ev_direct[zoe][t] = max(min(surplus_w, zoe_max_w), 0) ← přímé z FVE
|
||
batt_charge[t] = zbývající surplus ← do baterie až pak
|
||
|
||
Protože přímé nabíjení Zoe je levnější než FVE → baterie → Zoe.
|
||
```
|
||
|
||
### Noc, Zoe má deadline 7:00 s SoC 20% (potřeba 30 kWh)
|
||
|
||
```
|
||
Solver:
|
||
- Rozloží nabíjení do nejlevnějších nočních slotů
|
||
- Garantuje dodání 30 kWh do 7:00 (hard constraint)
|
||
- Pokud jsou sloty se zápornou cenou → nabíjí naplno v těch slotech
|
||
- Vyhýbá se nabíjení přes baterii pokud není přebytek
|
||
```
|
||
|
||
### Tesla připojena, SoC 70%, deadline není nastaven
|
||
|
||
```
|
||
Solver:
|
||
- Tesla je "oportunistická" – nabíjí jen při přebytku FVE nebo levné ceně
|
||
- Bez deadline = měkká optimalizace, ne hard constraint
|
||
- Nastavit default_target_soc = 80% s default_deadline = zítra 7:00
|
||
(konfigurovatelné v asset_vehicle)
|
||
```
|
||
|
||
---
|
||
|
||
## Zjištění stavu připojení
|
||
|
||
### Teltonika WB (oba vozy)
|
||
|
||
Modbus registr stavu konektoru (status):
|
||
- `available` = žádné auto
|
||
- `preparing` / `charging` = auto připojeno
|
||
|
||
Polling každou minutu z `telemetry_ev_charger.status`.
|
||
|
||
### Tesla API (fáze 2)
|
||
|
||
Přes Tessie nebo přímé Tesla API:
|
||
- SoC baterie auta
|
||
- Stav připojení (plugged_in)
|
||
- Nabíjecí stav (charging / stopped)
|
||
|
||
Uložit do `ev_session` při připojení/odpojení.
|
||
|
||
### Renault Zoe
|
||
|
||
Žádné API. Stav připojení čteme výhradně z WB Modbus (`status != 'available'`).
|
||
SoC Zoe neznáme přesně – použijeme energii dodanou v session.
|
||
|
||
### Živé SoC během session (needed_wh, fix 2026-06-14)
|
||
|
||
`fn_ev_session_planning_json` (R__038) počítá `energy_needed_wh` i `headroom_wh` z
|
||
**živého SoC** = `soc_at_connect + dodaná_energie/kapacita`, clamp 99 % (finální taper
|
||
ignorujeme) — ne ze zamrzlého `soc_at_connect`. Dodaná energie je **time-weighted integrál
|
||
`power_w`** (`ems.fn_ev_session_delivered_wh`, dt cap 120 s), NE counter `energy_kwh`:
|
||
ten je na TeltoCharge (Telto reg 39) **rozbitý** — neakumuluje (ověřeno: 17.4 kWh nabito,
|
||
counter 0.18). Bez toho byl `energy_delivered_wh` trvale 0 → needed_wh konstantní →
|
||
plánovač slepý k pokroku → phantom 11 kW okna i u plného auta. Funguje pro Teslu i Zoe
|
||
(power-based, bez API). Pozn.: reg 39 rozbitý ⇒ i EV audit/ekonomika z něj jede naslepo.
|
||
|
||
**Tolerance „dost dobré" (V107):** `energy_needed_wh = 0` když
|
||
`live_soc >= least(target, 99) − asset_vehicle.charge_done_tolerance_pct` (default
|
||
3 p.b.). Effective target je zastropovaný na 99 (= clamp live_soc), takže se nehoní
|
||
poslední taper k 100 % (jinak věčné mini-dobíjení → cyklování nabíječky / Tesla
|
||
notifikace). `charge_done_tolerance_pct = 0` → tvrdě na target.
|
||
|
||
### Geofence arrival trigger (V109, default-off)
|
||
|
||
Příjezd domů se dnes zaznamenává jen z wallboxu (plug-in). `ev_vehicle_obs.trigger`
|
||
nově povoluje **`geofence_arrival`**: presence cesta (`telemetry_collector`, z Tesla
|
||
polohy bez buzení) při přechodu pryč→domů u **nepíchnutého** auta zapíše obs (SoC,
|
||
odometr) → `fn_ev_build_trips` (R__096) ji spáruje jako příjezd → spotřební forecast
|
||
ví o jízdě i bez píchnutí. Za env flagem `EV_GEOFENCE_ARRIVAL_OBS_ENABLED` (default
|
||
OFF), debounce 2 vzorky, dedup s wallbox arrival (plugged = wallbox autoritativní).
|
||
|
||
### Proaktivní notifikace „píchni auto" (V110, default-off)
|
||
|
||
Job `ev_presence_notify` (~25 min) pošle Discord nudge, když je auto **doma +
|
||
nepíchnuté + (SoC neznámé nebo < target − tolerance) + (efektivní sell ≤ 0 nebo buy
|
||
pod prahem)**. SQL-first: rozhodnutí + dedup v `ems.fn_ev_presence_nudge_due` (dedup
|
||
přes `ev_presence_nudge_sent`, 1 nudge na epizodu doma+odpojeno). Per-vozidlo flag
|
||
`asset_vehicle.presence_nudge_enabled` (default **false** = inertní).
|
||
|
||
---
|
||
|
||
## Statistika příjezdů
|
||
|
||
### Tabulka `ems.ev_arrival_stats`
|
||
|
||
Agregace podle `site_id`, `charger_id`, `day_of_week` (0 = neděle … 6 = sobota) a `arrival_hour` (0–23). Čas příjezdu se počítá v **Europe/Prague**. Unikátní klíč `(site_id, charger_id, day_of_week, arrival_hour)`; sloupec `sample_count` roste s každým zaznamenaným příjezdem.
|
||
|
||
Účel: po několika týdnech dat odhadnout typickou hodinu připojení vozidla na danou wallbox — pro notifikace („obvykle přijíždíš kolem 17–18h“) a později jako měkký vstup do plánovače.
|
||
|
||
### `ems.fn_update_ev_arrival_stats(site_id, charger_id, vehicle_id, arrived_at)`
|
||
|
||
Inkrementuje statistiku pro příslušný bucket (INSERT nebo `ON CONFLICT` +1). Volá se při **detekci nového příjezdu** v `telemetry_collector`: přechod telemetrie z `available` na stav připojení (`preparing`, `charging`, …).
|
||
|
||
### `ems.fn_ev_expected_arrival(site_id, charger_id, for_date)`
|
||
|
||
Vrátí až **3 řádky**: nejčastější hodiny příjezdu pro den v týdnu odpovídající kalendářnímu datu `for_date` (typicky „zítřek“ v časové zóně lokality z backendu). Filtr `sample_count >= 2`; `confidence_pct` = podíl dané hodiny na součtu vzorků pro stejný `day_of_week` u té nabíječky.
|
||
|
||
### API
|
||
|
||
`GET /api/v1/sites/{site_id}/ev/arrival-prediction` vrátí pro každou nabíječku (klíč = `asset_ev_charger.code`) pole `tomorrow` s `{ hour, confidence_pct, samples }`. Pokud je na site méně než **5** záznamů v `ev_session` celkem, odpověď má `insufficient_data: true` (predikce se může vracet prázdné nebo řídké).
|
||
|
||
### Provozní poznámka
|
||
|
||
Historie v `ev_arrival_stats` se **nemaže** — jde o dlouhodobou agregaci. Po **4+ týdnech** reálných příjezdů má smysl UI notifikace a experimentální zapojení do solveru (soft constraint).
|
||
|
||
---
|
||
|
||
## Seed data – vozidla home-01
|
||
|
||
```sql
|
||
-- V006__vehicles.sql
|
||
|
||
INSERT INTO ems.asset_vehicle
|
||
(site_id, code, name, make, model, battery_capacity_kwh,
|
||
max_charge_power_w, default_charger_id, api_type,
|
||
default_target_soc_pct, default_deadline_hour)
|
||
SELECT
|
||
s.id, 'tesla-my', 'Tesla Model Y', 'Tesla', 'Model Y',
|
||
58.0, 11000, -- Tesla Model Y AC max ~11kW
|
||
ch.id, 'none', -- Tesla API fáze 2
|
||
80, 7
|
||
FROM ems.site s
|
||
JOIN ems.asset_ev_charger ch ON ch.site_id = s.id AND ch.code = 'ev-charger-1'
|
||
WHERE s.code = 'home-01';
|
||
|
||
INSERT INTO ems.asset_vehicle
|
||
(site_id, code, name, make, model, battery_capacity_kwh,
|
||
max_charge_power_w, default_charger_id, api_type,
|
||
default_target_soc_pct, default_deadline_hour)
|
||
SELECT
|
||
s.id, 'zoe-r135', 'Renault Zoe R135', 'Renault', 'Zoe R135',
|
||
22.0, 22000, -- Zoe max 22kW AC
|
||
ch.id, 'none',
|
||
90, 7 -- Zoe: vyšší target SoC (menší baterie, kritičtější)
|
||
FROM ems.site s
|
||
JOIN ems.asset_ev_charger ch ON ch.site_id = s.id AND ch.code = 'ev-charger-2'
|
||
WHERE s.code = 'home-01';
|
||
```
|
||
|
||
---
|
||
|
||
## Otevřené body
|
||
|
||
- [ ] Tesla API: Tessie vs přímé API – rozhodnout ve fázi 2
|
||
- [ ] Ověřit Zoe max nabíjecí výkon (7.4 kW nebo méně dle podmínek)
|
||
- [ ] Ověřit round-trip efficiency na reálných datech po prvních týdnech provozu
|
||
- [ ] UI pro nastavení deadline a target SoC uživatelem (před odjezdem)
|
||
- [ ] Notifikace pokud deadline nelze splnit (nedostatek kapacity WB nebo energie)
|
||
- [ ] Zoe SoC estimace z kumulativní energie session – přesnost ověřit
|
||
|
||
---
|
||
|
||
## EV spotřební forecast — týdenní rytmus vozidla (2026-06-12)
|
||
|
||
Cíl: target SoC a deadline session z reálného užívání místo fixních defaultů
|
||
(pondělí služebka ~150 km → skoro plná; konec týdne míň; víkendová session
|
||
s pondělním deadline → v2 nabije v levných víkendových slotech — vyplyne z cen).
|
||
|
||
**Sběr (bez buzení auta):** při příjezdu/odjezdu (auto vzhůru) Tesla API →
|
||
`ems.ev_vehicle_obs` (odometer km — API vrací MÍLE, převod v `tesla_client`;
|
||
SoC). Páry odjezd→příjezd → `ems.ev_trip` (km z odometru, kWh z ΔSoC ×
|
||
kapacita; nabíjení cestou → `charged_away`, kWh se nepočítá). Job 00:50
|
||
`fn_update_ev_usage_stats` → `ems.ev_usage_stats` per (vozidlo, DOW):
|
||
avg/stddev kWh, km, hodina prvního odjezdu.
|
||
|
||
**Použití:** `fn_ev_next_departure` (příští typický odjezd: DOW s ≥4 vzorky
|
||
a ≥3 km) + `fn_ev_required_soc` (P80 spotřeby dne + 10 p.b., clamp
|
||
[`min_target_soc_pct`, 100]) — od V098 zapojeno jako 2. stupeň kaskády
|
||
`fn_ev_session_defaults` (viz níže); ruční patch `fn_ev_session_apply_patch`
|
||
vždy vyhrává.
|
||
|
||
**Aktivace per vozidlo** (po ~měsíci dat):
|
||
`update ems.asset_vehicle set target_soc_forecast_enabled = true where code = 'tesla-my';`
|
||
|
||
Tesla napojení (SoC při příjezdu → `soc_at_connect_pct`): `docs/tesla-fleet-api.md`.
|
||
Registry wallboxu: `docs/04-modules/modbus-registers-teltocharge.md`.
|
||
|
||
## Týdenní požadavky + fn_ev_session_defaults (2026-06-12)
|
||
|
||
Explicitní týdenní rytmus „v pondělí v 7:00 chci 90 %" bez čekání na
|
||
naučený forecast: tabulka **`ems.ev_weekly_requirement`** (V098) —
|
||
max 1 řádek na (vozidlo, den): `dow` (**0 = pondělí .. 6 = neděle**, ISO
|
||
pořadí — POZOR, jiné než postgres `extract(dow)` v `ev_usage_stats`),
|
||
`target_soc_pct`, `deadline_hour` (Europe/Prague), `enabled`.
|
||
Seed: tesla-my (home-01) pondělí 07:00 → 90 %.
|
||
|
||
Defaulty nové session dává **`ems.fn_ev_session_defaults(vehicle_id,
|
||
arrival)`** (R__099) → jsonb `{target_soc_pct, deadline, source}`, kaskáda:
|
||
|
||
1. **weekly** — nejbližší budoucí výskyt enabled řádku
|
||
`ev_weekly_requirement` do **48 h** od příjezdu (deadline = den `dow`
|
||
v `deadline_hour`, Europe/Prague). Páteční příjezd tedy pondělní
|
||
požadavek NEvyzvedne (>48 h) — nedělní večer už ano; dřívější nabití
|
||
na pondělí zajistí levné víkendové sloty samy (v2 + oportunismus),
|
||
explicitně jde vybrat „pondělí ráno 7:00" v Discordu.
|
||
2. **forecast** — `fn_ev_next_departure` + `fn_ev_required_soc`, jen při
|
||
`asset_vehicle.target_soc_forecast_enabled` (chování V089 beze změny).
|
||
3. **default** — `default_target_soc_pct`; deadline = příští výskyt
|
||
`default_deadline_hour` (Europe/Prague; dnešní, pokud je ještě před ní).
|
||
|
||
Volá ji `fn_ev_session_transition` při založení session (SQL-first; Python
|
||
nic nepřepočítává). Ruční přepis (Discord selecty / UI →
|
||
`fn_ev_session_apply_patch`) má vždy přednost — defaulty se aplikují jen
|
||
při vzniku session.
|
||
|
||
## Discord notifikace po příjezdu (2026-06-12, dev)
|
||
|
||
Po detekci příjezdu + Tesla SoC + replanu odejde na site webhook souhrn:
|
||
stav baterie auta → cíl (+kWh), deadline, plánovaná nabíjecí okna s ø cenou
|
||
(`_notify_ev_arrival_plan` v telemetry_collector). Interaktivní fáze B
|
||
(tlačítka „odjíždím za 2 h" → patch session + replan): `docs/discord-ev-interaction.md`.
|
||
|
||
## Měkký cíl — oportunistické nabíjení nad target (2026-06-12, dev)
|
||
|
||
Tvrdý cíl (deadline) = „bez tohohle neodjedu"; měkký cíl = „klidně doplň
|
||
do 100 %, když je energie skoro zadarmo". Implementace: dekompozice
|
||
Σ(EV energie) == needed − unmet + opp; `opp ∈ [0, headroom]`.
|
||
**Headroom = (100 − max(target, soc_at_connect)) % kapacity** (fix paradoxu
|
||
„nižší target → větší headroom": auto fyzicky bere jen energii nad svým
|
||
aktuálním SoC). **Hodnota kWh:** `coalesce(ev_session.opportunistic_value_czk_kwh,
|
||
asset_vehicle.opportunistic_value_czk_kwh)` — V099 přidal per-session override
|
||
(NULL = zdědit z vozidla, 0 = vypnout pro session ⇒ headroom_wh = 0; patch
|
||
klíčem `opportunistic_value_czk_kwh` ve `fn_ev_session_apply_patch`, validace ≥ 0).
|
||
Default vozidla 1 Kč/kWh, 0 = vypnuto.
|
||
Hodnota = ušetřené BUDOUCÍ nabíjení (auto neumí zpět — žádný noční prodej),
|
||
proto nízká → uplatní se při záporných cenách / plné domácí baterce
|
||
(lepší než curtail), běžné ceny ji nezaplatí. Víkendový vzor „pátek
|
||
nemusím do plna, víkend doplní zadarmo" z toho plyne sám. Dekompozice
|
||
zároveň stropuje celkovou energii do auta (dřív při buy<0 chyběl strop) —
|
||
a **bez session je EV == 0** (stop-session nevypíná jen tvrdý cíl, ale i
|
||
oportunismus). Session zůstává v plánu i po dosažení targetu, dokud má headroom;
|
||
**oportunistická vrstva není omezená deadline** (auto bývá doma dál, odjezd
|
||
řeší rolling replan — rozhodnutí 2026-06-12).
|
||
|
||
### Session se NEvyřazuje při needed_wh=0 (fix 2026-06-13)
|
||
|
||
Dřív `fn_planning_site_context` vracela `ev_sessions[e] = null`, když
|
||
`needed_wh = 0` (auto už nad targetem) **a** oportunismus byl vypnutý/headroom
|
||
nulový — a navíc úplně, když `target_deadline is null`. Druhá past byla v
|
||
Pythonu: `_ev_session_from_json` zahazovala session bez deadline. Důsledek
|
||
incidentu: aktivní plán měl `ev_sessions:0`, ač session běžela; **plánovač
|
||
neviděl ~6 kW zátěž auta** a špatně rozvrhl baterii (zbytečný večerní import).
|
||
|
||
Oprava (R__038 `ems.fn_ev_session_planning_json` + `db_io._ev_session_from_json`):
|
||
|
||
- Session se vyřadí (`null`) **jen** bez tvrdých dat — neznámá kapacita vozidla
|
||
nebo `soc_at_connect_pct` (nelze spočítat Wh). Jinak vždy objekt.
|
||
- **`target_deadline` smí být NULL** (žádný tvrdý cíl) — solver_v2 hard
|
||
deadline constraint aplikuje jen při `energy_needed_wh > 0`; oportunistická
|
||
vrstva běží i bez deadline. Auto nad targetem nebo bez cíle tak zůstává v
|
||
plánu jako známá zátěž i s headroomem k případnému levnému doplnění.
|
||
- `energy_needed_wh` = 0 bez deadline / cíle; headroom a opportunistic_value
|
||
beze změny (coalesce session → vozidlo).
|
||
|
||
### Min. výkon wallboxu a účtování via-bat (2026-06-12, dev)
|
||
|
||
- **`asset_ev_charger.min_power_w`** (1380 W = 6 A IEC 61851) jde přes
|
||
`fn_planning_site_context` do solver_v2: binárka `ev_on[e][t]`,
|
||
`setpoint ∈ {0} ∪ [min_power_w, max]` — žádné nevykonatelné 400–900 W.
|
||
- **Tvrdý cíl** sčítá jen sloty **před** deadline (slot začínající v deadline
|
||
už nepatří „do deadline" — oprava off-by-one).
|
||
- **`ev_direct ≤ gi + PV`** (fyzikální split; via_bat kryje vybíjení baterie).
|
||
- **Reporting:** kWh do EV z baterie (via_bat) neplatí slotový buy; solver_v2
|
||
je oceňuje oportunitní cenou v `planning_interval.battery_arbitrage_czk`
|
||
(min sell exportního slotu téhož pražského dne, jinak terminal value) a
|
||
`fn_plan_current_bundle.intervals` nese `ev1/ev2_via_bat_w` pro UI.
|