Merge branch 'worktree-agent-a288972b643cdefcc' into dev
All checks were successful
CI and deploy / migration-check (push) Successful in 22s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-06-12 19:17:01 +02:00
8 changed files with 520 additions and 109 deletions

View File

@@ -327,8 +327,9 @@ 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]) `fn_ev_session_transition` při příjezdu
(fallback defaulty; ruční patch `fn_ev_session_apply_patch` vždy vyhrává).
[`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';`
@@ -336,6 +337,34 @@ a ≥3 km) + `fn_ev_required_soc` (P80 spotřeby dne + 10 p.b., clamp
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:

View File

@@ -15,34 +15,59 @@ Plán nabíjení: 11:3013:45; 02:1504:30 — 34.2 kWh, ø 1.85 Kč/kWh
Implementace: `_notify_ev_arrival_plan` v `telemetry_collector.py` (sloty
`ev*_setpoint_w > 0` z aktivního plánu shlukované do oken).
## Fáze B — zpětná vazba tlačítkem — ✅ IMPLEMENTOVÁNO (2026-06-12)
## Fáze B — zpětná vazba dvěma výběry — ✅ IMPLEMENTOVÁNO (2026-06-12)
**Architektura: Discord BOT přes gateway** — spojení jde Z backendu VEN
(websocket), žádný veřejný endpoint do EMS (na rozdíl od interactions
webhooku). Knihovna `discord.py`, token v `/opt/ems-deploy/.env`.
Zpráva z fáze A dostane tlačítka:
`[Odjezd za 2 h] [za 4 h] [Ráno (typicky)] [Do plna hned] [Nenabíjet]`
Zpráva z fáze A dostane **dva selecty** (místo dřívější řady tlačítek):
Callback tlačítka:
1. `fn_ev_session_apply_patch(site, session, {"target_deadline": now+2h, …})`
(„Do plna hned" navíc `target_soc_pct=100`; „Nenabíjet" `target_soc_pct=soc`),
2. okamžitý `run_rolling_replan` + `export_setpoints` (vzor ev_arrival),
3. bot **edituje původní zprávu** novým plánem (žádný spam).
```
🔌 Tesla Model Y připojeno
Baterie auta: 55 % → cíl 90 % (~26 kWh)
Deadline: po 15.06. 07:00
Plán nabíjení: 11:3013:45; 02:1504:30 — 26.2 kWh, ø 1.85 Kč/kWh
[ 🕑 Kdy odjíždíš? ▾ ] za 2 h | za 4 h | dnes večer 18:00 |
zítra ráno 7:00 | zítra poledne 12:00 |
pondělí ráno 7:00
[ 🔋 Kolik potřebuješ? ▾ ] 30 % | 50 % | 70 % | 100 % | Nenabíjet
```
**Sémantika:**
- Každý výběr **okamžitě** PATCHne otevřenou session přes
`fn_ev_session_apply_patch` — ale jen ve SVÉ dimenzi: výběr 1 nastaví
`target_deadline` (absolutní čas Europe/Prague; pevné volby = nejbližší
budoucí výskyt, „pondělí ráno" z pátku je validní i přes 48 h),
výběr 2 nastaví `target_soc_pct`. Druhý rozměr zůstává beze změny
(default z `fn_ev_session_defaults`, případně dřívější výběr).
- „Nenabíjet" = stop akce (target = SoC při připojení → solver nic neplánuje).
- **No-click = pohotovostní režim:** bez kliknutí jede session na defaultech
z `ems.fn_ev_session_defaults` (týdenní požadavek `ev_weekly_requirement`
→ forecast z rytmu → defaulty vozidla; viz `docs/04-modules/ev-charging.md`).
S `min_target_soc_pct` 30 % to znamená: drž aspoň ~30 % pro pohotovost
a zbytek doplňuj oportunisticky v levných/záporných slotech
(`opportunistic_value_czk_kwh` — měkký cíl).
Po každém výběru: patch session → okamžitý `run_rolling_replan` +
`export_setpoints` (vzor ev_arrival) → bot **edituje původní zprávu**
přepočteným plánem (`build_ev_plan_summary`) + potvrzením
`_(nastaveno: odjezd dnes večer 18:00)_` (žádný spam).
Bezpečnost: bot reaguje jen na whitelisted user ID (majitel), akce omezené
na patch session + replan (žádné režimy/registry). Tlačítka expirují
s koncem session.
na patch session + replan (žádné režimy/registry). Selecty expirují
s koncem session (uzavřená session → ephemeral „Session už není otevřená").
**Implementace:** `services/discord_bot.py` (lifespan task; discord.py
gateway), `services/ev_notify.py` (sdílený souhrn plánu; bot-first, webhook
fallback). custom_id `ev:<site>:<charger>:<akce>` — tlačítka přežijí restart.
Env: `DISCORD_BOT_TOKEN`, `DISCORD_EV_CHANNEL_ID`, `DISCORD_ALLOWED_USER_IDS`
(čárkami; prázdné = bot vypnut, jede fáze A webhook). Akce: h2/h4 (deadline
teď+N), morning (další default_deadline_hour vozidla, Prague), full (100 % +
deadline za 1 h → max tempo), stop (target = SoC při připojení). Po akci:
patch session → okamžitý replan + export → bot zedituje zprávu novým plánem.
Testy: tests/test_discord_bot.py (parse, patch akcí).
fallback). custom_id `ev:<site>:<charger>:dep` / `:tgt` — obsluha přes
`on_interaction` + regex (persistent vzor), selecty přežijí restart backendu.
Legacy tlačítka `h2|h4|morning|full|stop` ze starších zpráv zůstávají
obsloužená (`action_to_patch`). Env: `DISCORD_BOT_TOKEN`,
`DISCORD_EV_CHANNEL_ID`, `DISCORD_ALLOWED_USER_IDS` (čárkami; prázdné =
bot vypnut, jede fáze A webhook).
Testy: tests/test_discord_bot.py (parse, výběr→patch, absolutní deadline
z voleb vč. půlnoci a pondělí z pátku, legacy akce).
## Výhled (fáze C)
Stejný bot = kanál pro ranní triáž s dotazy („proč jsi v 19:00 nabíjel?" →