17 KiB
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
-
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.
-
Všechny doménové záznamy vázat na
site_id(telemetrie, plány, audit, konfigurace aktiv, session, …). Výjimka:market_interval_priceje globální pro zdroj/trh; vazba na site je přes konfiguraci a view. -
Raw ceny ≠ efektivní ceny.
ems.market_interval_price= bez marží. Efektivní nákup/prodej jen přesems.vw_site_effective_price(join na platnousite_market_config). -
Loxone = exekutor + autonomní fallback, ne optimalizátor. Logika a plán v EMS. Watchdog v Loxone nesmí záviset na čtení DB (
site_heartbeatje jen pro EMS UI/diagnostiku). -
FVE pole B (
controllable = false, typicky ongrid GEN) – žádný curtailment. Curtailment jen pole A (Deye). Solver smí omezovat jenpv_a; pole B může mít zelený bonus naasset_pv_array(green_bonus_*), auditpv_b_production_wh/green_bonus_czk. -
Záporná prodejní cena →
grid_export == 0v LP (hard constraint). -
Záporná nákupní cena → omezit import na realistický horní strop (viz
solve_dispatchvplanning_engine.py– nesmí „nekonečný“ import). -
PuLP + HiGHS pro dispatch; žádný návrat k greedy
fn_plan_dayjako primárnímu řešení (SQL wrapper může zůstat pro uložení výsledků – dle docs). -
Zelený bonus je na
asset_pv_array(sloupcegreen_bonus_*), nikdy vsite_market_config. Výpočet přesfn_green_bonus_revenue(). Bonus se nepočítá v solveru – pouze v audit_filler (fn_fill_audit_interval). -
Deye Modbus: čtení i zápis (setpointy). RS485→Waveshare→TCP, knihovna
pymodbus. -
Přepínání provozního režimu přes DB API /
ems.fn_set_mode– držet konzistenci soperating_mode_defa Loxoneloxone_mode_value.
Provozní režimy (operating_mode)
- Pět hodnot
mode_codevems.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 vsolve_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_modea zapisují vwrite_inverter_setpoints. lock_battery=TrueuControlSetpoints(PRESERVE): registry 108/109 = 0 – Deye baterii nepoužívá. Výjimka oproti obecnému pravidlu max A ve PASSIVE/SELL.
-
forecast_pv_runaforecast_pv_intervalse NESMÍ mazat – historické běhy zůstávají v DB pro tracking přesnosti (forecast_accuracy,fn_fill_forecast_accuracy). -
Endpoint
GET …/forecast/pvvracíDISTINCT ON (interval_start, pv_array_id)seřazené podle nejnovějšíhoforecast_pv_run.created_at, aby UI nemělo duplikáty slotů; plná historie běhů zůstává v tabulkách. -
Příchod a odjezd EV detekuje
telemetry_collectorz telemetrie nabíječky: přechodavailable→preparing/charging(resp. jakýkoli stav ≠available) znamená příjezd; přechod naavailableuzavřeev_session. Tabulkaev_arrival_statsse při příjezdu doplňuje přesfn_update_ev_arrival_statsa nemá se mazat (dlouhodobá historická statistika). -
Bazální spotřeba =
load_power_wminus řízené zátěže (součet EV ztelemetry_ev_charger, TČ ztelemetry_heat_pump). Tabulkaconsumption_baseline_statsse plní denně (APScheduler 00:30) přesfn_update_baseline_stats. Solver načítá průměr bazálu zconsumption_baseline_stats(DOW + hodina v Europe/Prague), ne zconsumption_baseline_interval. -
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 36–96h doplňuje predikovaná cena zmarket_price_statspřesfn_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 (0–36h), 0,7 (36–72h), 0,4 (72–96h). Statistiky cen plnífn_update_market_price_stats(job 14:45), TUV deltafn_update_tuv_usage_stats(job 00:45). Detail:docs/04-modules/planning-extended-horizon.md. -
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 jeDISCORD_WEBHOOK_URL. Detail:docs/04-modules/modbus-command-journal.md. -
Deye zápis registrů 60–499: pouze FC 0x10 (
write_registers), nikdy FC 0x06 pro tento rozsah;execute_modbus_commandssluč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 vcontrol_exporter.get_deye_mode). V PASSIVE a SELL jsou reg 108 / 109 obvykle na maximum z DB (výjimka PRESERVE:lock_battery=True→ 0 / 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 3–6 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 3–6 =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 62–64: před zařazením do fronty čtení 62–64; zápis jen při driftu > 60 s, nebo NULLdeye_last_system_time_sync_at, nebo uplynulých 24 h od posledního zápisu času (deye_last_system_time_sync_atse 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 62–64 je toleranční (odchylka dekódovaného času až 120 s). SELL:grid_setpoint_w< −200. CHARGE:battery_w> 500 agrid_setpoint_w> 200. PASSIVE: ostatní (včetněbattery_w=Noneu SELF_SUSTAIN → plné limity 108/109). Detail:docs/04-modules/modbus-registers.md, režimy:docs/04-modules/operating-modes.md. -
Baterie – export v LP: V
solve_dispatchbinárkaz_export[t]: pokudgrid_exportv daném slotu ≥ 1 W, platí koncovésoc[t] ≥ arb_base_wh(ekonomická rezerva z DB, ne časová řadaarb_floor_series). Bez exportu může plán jít kmin_soc_percent(provozní podlaha; u paralelních packů často 11–12 %, 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###__/ repeatableR__. - 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.