Files
ems/CLAUDE.md

17 KiB
Raw Blame History

CLAUDE.md EMS Platform (Cursor Agent)

Čti před každou implementační změnou. Stručná orientace; detail v docs/ a SQL v db/.


1. Co to je

Multi-site Energy Management System: optimalizuje FVE, baterii a flexibilní zátěž (EV, TČ) podle spotových cen OTE CZ a předpovědí; výstupy řídí zařízení (Modbus) a informuje Loxone jako exekutora. Referenční lokalita v seedu: home-01 (Deye, baterie, 2× EV Teltonika, Samsung TČ).


2. Technologický stack

Vrstva Technologie
DB PostgreSQL 16 + TimescaleDB
Migrace Flyway (db/migration, db/routines, db/views)
API PostgREST (REST ze schématu ems) + FastAPI (logika, joby plán v docs)
Frontend React + TypeScript + Vite (očekáváno u kořene / Docker)
Pole / zařízení Modbus TCP (pymodbus), HTTP (Loxone, případně API vozidel)
Solver PuLP + HiGHS (HiGHS_CMD)
Runtime Docker Compose

3. Adresářová struktura

Cesta Účel
CLAUDE.md, .env.example, docker-compose.yml Kořen: pravidla, env šablona, compose
docs/ Produktová a technická specifikace (overview, architektura, datový model, integrace)
docs/04-modules/ Modulové specifikace (ceny, forecast, spotřeba, TČ, telemetrie, řízení, plánování, režimy, EV)
docs/loxone-integration.md Loxone watchdog, heartbeat, role exekutora
docs/06-open-questions.md Nedokončené rozhodnutí doplňovat místo hádání
db/migration/ Flyway versioned migrace V00x__*.sql (schéma, seed, alter)
db/routines/ Repeatable SQL: funkce ems.fn_*
db/views/ Repeatable SQL: view ems.vw_*
backend/services/ Python služby (v repozitáři zatím hlavně plánování)

4. Pravidla NIKDY neporušovat

  1. 15min logika pro plán/ceny/baseline/audit/forecast intervaly. Časové řady v těchto doménách = 15min sloty. Telemetrie zařízení je 1min (hypertables) agregace do 15min přes SQL/job, ne ukládat „hodinové“ řádky jako primární plánovací záznam.

  2. Všechny doménové záznamy vázat na site_id (telemetrie, plány, audit, konfigurace aktiv, session, …). Výjimka: market_interval_price je globální pro zdroj/trh; vazba na site je přes konfiguraci a view.

  3. Raw ceny ≠ efektivní ceny. ems.market_interval_price = bez marží. Efektivní nákup/prodej jen přes ems.vw_site_effective_price (join na platnou site_market_config).

  4. Loxone = exekutor + autonomní fallback, ne optimalizátor. Logika a plán v EMS. Watchdog v Loxone nesmí záviset na čtení DB (site_heartbeat je jen pro EMS UI/diagnostiku).

  5. FVE pole B (controllable = false, typicky ongrid GEN) žádný curtailment. Curtailment jen pole A (Deye). Solver smí omezovat jen pv_a; pole B může mít zelený bonus na asset_pv_array (green_bonus_*), audit pv_b_production_wh / green_bonus_czk.

  6. Záporná prodejní cena → grid_export == 0 v LP (hard constraint).

  7. Záporná nákupní cena → omezit import na realistický horní strop (viz solve_dispatch v planning_engine.py nesmí „nekonečný“ import).

  8. PuLP + HiGHS pro dispatch; žádný návrat k greedy fn_plan_day jako primárnímu řešení (SQL wrapper může zůstat pro uložení výsledků dle docs).

  9. Zelený bonus je na asset_pv_array (sloupce green_bonus_*), nikdy v site_market_config. Výpočet přes fn_green_bonus_revenue(). Bonus se nepočítá v solveru pouze v audit_filler (fn_fill_audit_interval).

  10. Deye Modbus: čtení i zápis (setpointy). RS485→Waveshare→TCP, knihovna pymodbus.

  11. Přepínání provozního režimu přes DB API / ems.fn_set_mode držet konzistenci s operating_mode_def a Loxone loxone_mode_value.

Provozní režimy (operating_mode)

  • Pět hodnot mode_code v ems.site_operating_mode: AUTO, SELF_SUSTAIN, CHARGE_CHEAP, PRESERVE, MANUAL.
  • Režim se načítá v planning_engine._load_site_context(); dodatečné LP constraints podle režimu jsou v solve_dispatch() (žádný export / limit importu / zákaz nabíjení nebo vybíjení baterie podle módu).
  • Fyzické režimy Deye (PASSIVE / SELL / CHARGE) se odvozují v control_exporter.get_deye_mode a zapisují v write_inverter_setpoints.
  • lock_battery=True u ControlSetpoints (PRESERVE): registry 108/109 = 0 Deye baterii nepoužívá. Výjimka oproti obecnému pravidlu max A ve PASSIVE/SELL.
  1. forecast_pv_run a forecast_pv_interval se NESMÍ mazat historické běhy zůstávají v DB pro tracking přesnosti (forecast_accuracy, fn_fill_forecast_accuracy).

  2. Endpoint GET …/forecast/pv vrací DISTINCT ON (interval_start, pv_array_id) seřazené podle nejnovějšího forecast_pv_run.created_at, aby UI nemělo duplikáty slotů; plná historie běhů zůstává v tabulkách.

  3. Příchod a odjezd EV detekuje telemetry_collector z telemetrie nabíječky: přechod availablepreparing / charging (resp. jakýkoli stav ≠ available) znamená příjezd; přechod na available uzavře ev_session. Tabulka ev_arrival_stats se při příjezdu doplňuje přes fn_update_ev_arrival_stats a nemá se mazat (dlouhodobá historická statistika).

  4. Bazální spotřeba = load_power_w minus řízené zátěže (součet EV z telemetry_ev_charger, TČ z telemetry_heat_pump). Tabulka consumption_baseline_stats se plní denně (APScheduler 00:30) přes fn_update_baseline_stats. Solver načítá průměr bazálu z consumption_baseline_stats (DOW + hodina v Europe/Prague), ne z consumption_baseline_interval.

  5. Rozšířený horizont plánování (96h): denní plán pokrývá 96h od začátku aktuálního 15min slotu. Sloty v prvních 36h používají přesné efektivní ceny z vw_site_effective_price (OTE). Sloty 3696h doplňuje predikovaná cena z market_price_stats přes fn_get_predicted_price (prodejní strana hrubý faktor 0,85 vs. nákupní predikce). V účelové funkci LP se uplatní váhy nejistoty podle vzdálenosti od začátku okna: 1,0 (036h), 0,7 (3672h), 0,4 (7296h). Statistiky cen plní fn_update_market_price_stats (job 14:45), TUV delta fn_update_tuv_usage_stats (job 00:45). Detail: docs/04-modules/planning-extended-horizon.md.

  6. Modbus zápis = journal. Každý zápis do zařízení přes control exporter se loguje do ems.modbus_command. Verifikační job běží každé 2 minuty a ověřuje nedávno zápis (written → čtení registru). Při mismatch po max. 3 pokusech o zápis → přepnutí na SELF_SUSTAIN (fn_set_mode, system:mismatch) + Discord alert, pokud je DISCORD_WEBHOOK_URL. Detail: docs/04-modules/modbus-command-journal.md.

  7. Deye zápis registrů 60499: pouze FC 0x10 (write_registers), nikdy FC 0x06 pro tento rozsah; execute_modbus_commands slučuje souvislé adresy do jednoho FC 0x10. Fyzické režimy střídače jsou tři: PASSIVE, SELL, CHARGE (mapování z plánu / politik EMS v control_exporter.get_deye_mode). V PASSIVE a SELL jsou reg 108 / 109 obvykle na maximum z DB (výjimka PRESERVE: lock_battery=True0 / 0). Omezování pod maximum jinak brání Deye reagovat na nepředvídatelnou spotřebu a přebytky FVE. Řízení: time points blok 1 = začátek aktuálního 15min slotu + plán pro tento slot, blok 2 = začátek následujícího slotu + plán pro něj (current_slot_hhmm / next_slot_hhmm); bloky 36 neaktivní 2355 (ne 23:59 kvůli firmware), zápis nejednou častěji než 1× denně (Europe/Prague) + při změně podpisu (deye_tou_inactive_signature: HHMM|min_soc|reserve_soc|tp_discharge_w, V028 meta + V029 komentář); reg 166+ u TP: SELL = reserve_soc_percent, PASSIVE / řádky 36 = min_soc_percent. 108 / 109 / 141 (0) / 142 (0 = selling first jen ve SELL, jinak 1) / 178 (pevně 32 ve SELL, 48 v PASSIVE a CHARGE bez read-modify-write) / 143 (export limit W z DB) z aktuálního setpointu. Reg 191 EMS nezapisuje. Čas 6264: před zařazením do fronty čtení 6264; zápis jen při driftu > 60 s, nebo NULL deye_last_system_time_sync_at, nebo uplynulých 24 h od posledního zápisu času (deye_last_system_time_sync_at se mění jen při zápisu); při chybě čtení se čas zapisuje; reg 64 se zapisuje s sekundami 0; verifikace journalu pro souvislý blok 6264 je toleranční (odchylka dekódovaného času až 120 s). SELL: grid_setpoint_w < 200. CHARGE: battery_w > 500 a grid_setpoint_w > 200. PASSIVE: ostatní (včetně battery_w=None u SELF_SUSTAIN → plné limity 108/109). Detail: docs/04-modules/modbus-registers.md, režimy: docs/04-modules/operating-modes.md.

  8. Baterie export v LP: V solve_dispatch binárka z_export[t]: pokud grid_export v daném slotu ≥ 1 W, platí koncové soc[t] ≥ arb_base_wh (ekonomická rezerva z DB, ne časová řada arb_floor_series). Bez exportu může plán jít k min_soc_percent (provozní podlaha; u paralelních packů často 1112 %, migrace V029 + komentář sloupce).


5. Schéma ems tabulky (jedna věta)

Tabulka Popis
site Lokalita (časová zóna, GPS, aktivita).
site_endpoint Endpointy: Modbus, Loxone HTTP, atd.
site_market_config Marže, režimy cenění; časová platnost (zelený bonus není zde viz asset_pv_array).
site_grid_connection Limity import/export, no_export, rezervovaný výkon.
site_override Manuální přepisy nad plánem (JSON + platnost).
site_operating_mode Aktuální provozní režim na site (1 řádek/site).
site_operating_mode_log Historie přepnutí režimů.
site_heartbeat Poslední EMS heartbeat pro dashboard (ne pro Loxone watchdog).
operating_mode_def Číselník režimů (baterie/síť/EV/TČ, hodnota pro Loxone).
asset_inverter Střídač (výkony, endpoint, zda řiditelný).
asset_battery Baterie vázaná na střídač (SoC limity, účinnosti, degradace).
asset_pv_array FVE pole (Wp, orientace, curtailable vs ne; volitelně green_bonus_* pro dotované pole).
asset_ev_charger Nabíječka EV (výkony, fáze, endpoint).
asset_heat_pump TČ (výkon, COP ref, limity běhu, TUV parametry).
asset_vehicle Vozidlo (kapacita, max AC výkon, default target SoC/deadline).
market_interval_price Raw spot OTE (15min), bez marží.
telemetry_inverter 1min telemetrie střídače (Timescale).
telemetry_ev_charger 1min telemetrie nabíječky (Timescale).
telemetry_heat_pump 1min telemetrie TČ (Timescale).
forecast_pv_run Metadata běhu predikce FVE.
forecast_pv_interval Predikovaný výkon FVE po 15min (Timescale).
forecast_accuracy Řádky přesnosti predikce vs telemetrie po 15min (per run); doplňuje fn_fill_forecast_accuracy.
forecast_weather_interval Počasí 15min pro site (Timescale).
forecast_correction_log Log korekcí forecastu vs skutečnost při rolling replanu.
planning_run Jeden běh plánovače (daily/rolling/manual, stav, parametry solveru).
planning_interval Výstup solveru po 15min (baterie, síť, EV, TČ, curtailment A).
audit_interval Skutečnost vs plán po 15min (náklady, odchylky, bonus pole B).
consumption_baseline_interval Bazální spotřeba actual/forecast 15min (Timescale).
consumption_baseline_stats Historické průměry bazálu per DOW+hodina (EMA z telemetrie); vstup solveru.
market_price_stats Historické průměry raw OTE ceny per DOW+hodina; predikce cen za horizont OTE (fn_get_predicted_price).
tuv_usage_stats Průměrná změna teploty TUV zásobníku per DOW+hodina (telemetrie TČ); vstup TUV look-ahead ve solveru.
ev_session Nabíjecí session na WB (deadline, energie, náklady).
ev_arrival_stats Agregované počty příjezdů EV podle dne v týdnu a hodiny (Europe/Prague); plní se z detekce příjezdu v telemetrii.
modbus_command Journal Modbus zápisů (pending → written → verified / mismatch / failed); retry a vazba na planning_run; u Deye exportu deye_physical_mode (PASSIVE/SELL/CHARGE).
cutoff_switch_log Log přepnutí cut-off přepínačů (mikroinvertory); edge trigger, důvod a cena.

View / funkce (nejsou tabulky): vw_site_effective_price, vw_latest_telemetry, vw_audit_summary, vw_operating_mode, vw_forecast_accuracy_by_lead_time, vw_forecast_accuracy_daily; fn_effective_price, fn_green_bonus_revenue, fn_cop_estimate, fn_fill_audit_interval, fn_fill_forecast_accuracy, fn_set_mode, fn_update_ev_arrival_stats, fn_ev_expected_arrival, fn_update_baseline_stats, fn_get_baseline_forecast, fn_update_market_price_stats, fn_update_tuv_usage_stats, fn_get_predicted_price.


6. Periodické úlohy backendu (APScheduler / smyčky)

Specifikace z docs/02-architecture.md, modulových docs a komentářů v planning_engine.py. V gitu je zatím rozpracovaný backend joby mají být v backend/app/main.py (zatím často chybí).

Úloha Frekvence Poznámka
telemetry_collector každých 60 s Smyčka polling Modbus (Deye, EV×2, TČ) viz docs/04-modules/telemetry.md
price_importer 14:00 denně + 00:05 kontrola docs/04-modules/market-prices.md (časy CET v dokumentaci)
forecast_service 14:30 + 06:00 denně docs/04-modules/forecast.md
run_daily_plan 15:00 denně backend/services/planning_engine.py (horizont 96 h, váhy slotů 1,0 / 0,7 / 0,4)
run_rolling_replan každých 15 min (*/15) planning_engine.py přepočet od aktuálního slotu
control_exporter každých 15 min (slot boundary) docs/04-modules/control.md
verify_modbus každé 2 min Ověření modbus_command ve stavu written (posledních 10 min); viz docs/04-modules/modbus-command-journal.md
audit_filler / fn_fill_audit_interval každých 15 min docs/02-architecture.md, DB fn_fill_audit_interval
forecast_accuracy / fn_fill_forecast_accuracy každých 15 min (min. 2,17,32,47) Po audit filleru; doplní actual z telemetrie do forecast_accuracy
fn_update_baseline_stats 00:30 denně Aktualizace consumption_baseline_stats z telemetrie (30d lookback)
fn_update_market_price_stats 14:45 denně Po importu OTE a forecastu; market_price_stats (90d lookback)
fn_update_tuv_usage_stats 00:45 denně Po baseline jobu; tuv_usage_stats (30d lookback)

7. Kde hledat co

Chci… Kam
Pochopit systém end-to-end docs/01-overview.md, docs/02-architecture.md
Tabulky, vazby, jednotky docs/03-data-model.md
OTE ceny, marže, efektivní cena docs/04-modules/market-prices.md, db/views/R__vw_site_effective_price.sql
FVE forecast, počasí docs/04-modules/forecast.md
Bazální spotřeba docs/04-modules/consumption.md
TČ, COP, TUV docs/04-modules/heat-pump.md, db/routines/R__fn_cop_estimate.sql
Modbus, telemetrie, agregace docs/04-modules/telemetry.md
Modbus journal, verifikace, Discord docs/04-modules/modbus-command-journal.md
Deye registry (FC 0x10, 108/109/141/142/178/143) 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, horizont 96h docs/04-modules/planning.md, docs/04-modules/planning-extended-horizon.md, planning_engine.py
Provozní režimy AUTO / SELF_SUSTAIN / … docs/04-modules/operating-modes.md, db/migration/V004__operating_modes.sql, R__fn_set_mode.sql
EV, session, deadline charging docs/04-modules/ev-charging.md, db/migration/V006__vehicles.sql
Curtailment A, zelený bonus B db/migration/V005__planning_curtailment.sql
Rolling plán, forecast log db/migration/V007__rolling_replanning.sql
Audit 15min db/routines/R__fn_fill_audit_interval.sql, docs/04-modules/telemetry.md
Nové sloupce / tabulky nový db/migration/V00x__*.sql + případně db/routines / db/views
Nespecifikované chování docs/06-open-questions.md (přidat otázku, neimpl. naslepo)

Konvence (krátce)

  • Python: snake_case, type hints, Pydantic pro API modely.
  • SQL: snake_case, explicitní FK; Flyway pořadí V###__ / repeatable R__.
  • Výkon W, energie Wh, ceny Kč/kWh; čas v DB TIMESTAMPTZ (UTC).
  • NIKDY neupravuj existující V__ migrační soubory po jejich aplikaci na DB.
  • Pokud je potřeba opravit chybu ve verzované migraci, vytvoř novou V{N+1} migraci.