Files
ems/CLAUDE.md
Dusan Vojacek 91a9bef3d7
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped
implementace co nejdrive dosazeni SOC na home-01 a umozneni plneho socu n slotu ped koncem sell < 0
2026-05-26 08:07:00 +02:00

27 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); výběr lokality comboboxem (SiteSelectionContext, GET /api/v1/me/sites, persist localStorage ems.selected_site_id)
Pole / zařízení Modbus TCP (pymodbus), HTTP (Loxone, případně API vozidel)
Solver PuLP + HiGHS (HiGHS_CMD)
Runtime Docker Compose
Živá DB přes MCP (Cursor) Server ID user-postgres-ems, nástroj query, { "sql": "…" } — viz docs/07-mcp-postgres-ems.md a pravidlo .cursor/rules/mcp-postgres-ems.mdc

2b. MCP — živá EMS databáze (read-only)

Když uživatel napíše „použij MCP“ nebo potřebuje aktuální řádky z Postgresu (plán, telemetrie, journal):

  1. Zavolej MCP nástroj query na serveru user-postgres-ems s argumentem {"sql": "<SELECT …>"}.
  2. Neodmlouvej bez pokusu (typ „nepřipojím se“, „MCP neexistuje“). Po chybě popiš skutečnou chybu a co zkontrolovat.
  3. Kanonický popis, příklady a bezpečnost: docs/07-mcp-postgres-ems.md.

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í
docs/07-mcp-postgres-ems.md MCP read-only SQL na EMS DB (server user-postgres-ems, nástroj query)
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 kde zapnuté): buď deye_gen_microinverter_cutoff_enabled na deye-main, nebo ems.site_grid_connection.block_export_on_negative_sell (default false). home-01 kvůli neriťitelnému PV B často bez druhého přepínače — přebytek pole B nesmí dělat PL infeasible; KV1 (bez pole B / fixní nákup) migrace V074 nastavuje block_export_on_negative_sell = true.

  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). Ekonomika slotů: masky + guardy v solve_dispatch — viz docs/04-modules/planning.md. Arbitráž baterie: neúčtovat buy[t]/sell[t] ve stejném 15min slotu jako nákup/prodej téže kWh; min(buy) horizontu ≠ cena nabití (home-01 nabíjí hodiny, ne jednu čtvrthodinu). Povinné: docs/04-modules/planning-arbitrage-accounting.md.

  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.

SQL-first a read-model (Python jen tenká orchestrace)

Projekt je SQL-first: doménová logika, agregace, joiny mezi tabulkami a stabilní čtecí rozhraní patří do PostgreSQL (ems.fn_*, případně ems.vw_*). Python (FastAPI, joby) volá DB; neskladá vlastní dotazy nad schématem mimo výjimky níže.

Formát SQL v repu (db/migration, db/routines, db/views): odsazení 2 mezery na úroveň vnoření; rezervovaná klíčová slova PostgreSQL vždy malými písmeny (create table, select, where, references, …). Identifikátory (ems.*, sloupce) snake_case; typy v deklaracích též malými (int, text, timestamptz, jsonb). Nový / upravený SQL v tomto stylu — nesmí se objevovat verzované migrace psané „ALL CAPS keywords“.

  • Preferuj: novou nebo rozšířenou ems.fn_*(…) s jasnými parametry; potřebuješ často stejné sloupce z více tabulek → ems.vw_* (view zapouzdřuje joiny a strukturu DB; z Pythonu je SELECT … FROM ems.vw_* v pořádku).
  • Nechtěné: skládání dotazů v Pythonu (vlastní JOIN / WITH / poddotazy nad ems.* tabulkami). Místo toho funkce nebo view v db/routines/ / db/views/ + jedno volání z aplikace.
  • Jediné SQL v backend/services/*.py a backend/app/routers/*.py: SELECT 1 / EXISTS; select ems.fn_*(…); SELECT … FROM ems.vw_* (read přes view); žádné jiné ad-hoc SELECT/INSERT/UPDATE. IO (Modbus, HTTP); PuLP; orchestrace scheduleru.
  • Health a Loxone po změně režimu: fn_health_summary, fn_health_detailed_db, fn_vw_site_directory_active, fn_site_economics_yesterday_notification, fn_site_mode_loxone_bundle v repeatable db/routines/R__073_fn_health_site_jobs_mode_bundle.sql; FastAPI je v app/main.py + joby v app/lifespan.py.

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 exporter_monolith.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.

13a. PV delta kalibrace: GET …/forecast/pv-delta-profile vrací JSON z fn_pv_forecast_delta_profile; GET …/configuration obsahuje pv_forecast_calibration z ems.site_pv_forecast_calibration; PATCH …/configuration/pv-forecast-calibration mění cutoff / policy / přepsání parametrů delty. Referenční dny špičkové produkce zpětně: tabulka ems.site_pv_forecast_reference_day (V076) + volitelně sloupec reference_day_weight_mult v kalibraci — v fn_pv_forecast_delta_profile zvednou váhu řádků forecast_accuracy těchto kalendářních dní (datum ve site.timezone jako u slotů); doplňovat lze ems.fn_pv_forecast_sync_reference_days. Provozní mazání uložené predikce za den (hranice Europe/Prague, ne TZ site): ems.fn_delete_forecast_pv_prague_calendar_day. Telemetrie telemetry_inverter.is_export_limited / pv_derating_flags (V058) řídí vyloučení slotu z učení v fn_fill_forecast_accuracy (telemetry_derating); telemetry_collector je plní čtením Deye reg 145 a 179 při poll střídače.

  1. 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).

  2. 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; bez EMA „ocasu“ přepočítáš smaž+hromadný update přes ems.fn_rebuild_consumption_baseline_stats(site_id, lookback) (site_id NULL → všechny lokality). Solver načítá průměr bazálu z consumption_baseline_stats (DOW + hodina v Europe/Prague), ne z consumption_baseline_interval.

  3. Dynamický horizont plánování (jen OTE): konec okna z ems.fn_planning_horizon_end(site_id, horizon_start) (min posledního OTE konce a start + 36 h, NULL pokud je známé OTE kratší než 1 h od startu rolling se přeskočí; denní plán při NULL použije 1 h fallback v Pythonu). Strop a práh měnit v SQL (defaultní argumenty funkce / repeatable migrace), ne přes env. Solver používá výhradně sloty s efektivní cenou z vw_site_effective_price (žádná predikce v LP). Účelová funkce má terminal SoC shadow price: (průměr buy v prvních 24 h slotů × planner_terminal_soc_value_factor / 1000) × soc[T1] (Kč; SoC v Wh), kde planner_terminal_soc_value_factor je ems.asset_battery.planner_terminal_soc_value_factor načtené přes ems.fn_planning_site_context (žádný skrytý faktor v Pythonu). Fázované SoC v okně sell < 0 (v32): planner_neg_sell_prep_soc_percent, planner_neg_sell_full_soc_tail_slots, planner_neg_sell_vent_min_sell_czk_kwh na asset_battery; curtail A → reg 340, plná baterie = solar sell off bez zápisu 340. market_price_stats / fn_get_predicted_price zůstávají pro statistiky; detail: docs/04-modules/planning.md, docs/04-modules/planning-extended-horizon.md.

  4. 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 → u běžných registrů přepnutí na SELF_SUSTAIN (run_fn_set_mode_with_discordfn_set_mode, activated_by = system:mismatch) + Discord při skutečné změně režimu. Výjimka: souvislý blok Deye 6264 (čas) → po 3 neúspěšných ověřeních bez změny režimu, kritický Discord (notify_modbus_clock_verify_exhausted). Obecně: při jakékoli změně mode_code z Pythonu (POST /api/v1/sites/{id}/mode, mismatch → SELF_SUSTAIN, fn_expire_modes) lze Discord zapnout přes DISCORD_WEBHOOK_URL. Detail: docs/04-modules/modbus-command-journal.md.

  5. 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žim Deye (PASSIVE / CHARGE / SELL): výhradně get_deye_mode v exporter_monolith.py (bez wattových prahů: SELL při battery_w < 0 a grid_setpoint_w < 0; CHARGE při obou > 0; jinak PASSIVE). PASSIVE (ZERO, AUTO): export_mode=PV_SURPLUS108=0, 109=max, 142=deye_zero_export_mode (ne selling first); jinak 108/109 dle deye_battery_charge_discharge_amps / _deye_zero_export_amps_for_passive (import bez vybíjení → 109=0); TOU SOC (reg 166+): PASSIVE = min_soc_percent, CHARGE = max_soc_percent (clamp 10100 z DB), SELL = reserve_soc_percent (_deye_passive_tou_battery_soc_pct, _deye_tou_params). SELL: 108=0, 109=max, 178=32 (peak shaving off), 143 omezeno podle |grid_setpoint_w|; 142/145/TOU jako v write_inverter_setpoints. Reg 340 (max solar power, W): jen pokud ems.fn_site_has_active_green_bonus_pv(site_id) a ems.fn_inverter_pv_a_max_w(inverter_id) > 0 (strop z deye_reg340_max_solar_w, typ. 32k home-01 / 65k jinde, ne součet Wp; min deye_reg340_min_solar_w, home-01 400); hodnota z plánu / curtailu (AUTO). Není v DEYE_CRITICAL_REGS_SELF_SUSTAIN — verify mismatch nečeká přepnutí do SELF_SUSTAIN. PRESERVE: lock_battery → 108/109=0. Čas 6264, bloky TOU 12 vs 36, verify, Discord: beze změny oproti dřívějšímu chování — plný popis docs/04-modules/modbus-registers.md a docs/04-modules/operating-modes.md.

  6. 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, block_export_on_negative_sell (LP při záporném sell), 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); volitelně is_export_limited, pv_derating_flags pro vyloučení slotu z učení delty.
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.
site_pv_forecast_calibration Per site: cutoff učení delty, policy škrcení, přepsání parametrů fn_pv_forecast_delta_profile.
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).
signal_def Katalog odchozích signálů (kód, typ hodnoty); seed EXPORT_BAN_ACTIVE.
signal_route Mapování signál → cíl (loxone_vi, http_rest) per site + endpoint_id + volitelný route_config_json / verify_config_json.
signal_outbound_journal Journal HTTP odeslání signálů (queuedsentverified / retry / abandoned).
signal_state Poslední požadovaná / odeslaná / ověřená hodnota na cíli (idempotence).
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_site_directory, vw_modbus_last_verified, vw_asset_inverter_modbus_poll, vw_asset_ev_charger_modbus_poll, vw_asset_heat_pump_modbus_poll, vw_latest_telemetry, vw_telemetry_hourly_7d, vw_telemetry_15m_7d (15min agregát pro dashboard sloty; repeatable R__071_vw_telemetry_15m_7d.sql), 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_delete_forecast_pv_prague_calendar_day, fn_pv_forecast_sync_reference_days, fn_set_mode, fn_expire_modes (vrací řádky přepnutí pro Discord), fn_restore_previous_mode, fn_update_ev_arrival_stats, fn_ev_expected_arrival, fn_update_baseline_stats, fn_rebuild_consumption_baseline_stats, fn_get_baseline_forecast, fn_update_market_price_stats, fn_update_tuv_usage_stats, fn_get_predicted_price, dále read-modely: fn_site_configuration, fn_site_full_status, fn_site_notifications_context, fn_plan_current_bundle, fn_planning_run_horizon, fn_planning_future_price_days, fn_economics_daily_month, fn_economics_monthly_chart, fn_economics_lock_day, fn_economics_unlock_day, fn_energy_flows_daily_month, fn_energy_flows_intervals_day, fn_forecast_pv_split, fn_ev_sessions_active, fn_ev_session_apply_patch, fn_ev_arrival_prediction_bundle, fn_ev_session_transition, fn_negative_price_predictions, fn_latest_ote_day_stats, fn_ote_day_slot_stats_prague, fn_ote_list_missing_days, fn_site_effective_prices_day_prague, fn_modbus_journal_list, fn_modbus_written_command_ids, fn_modbus_commands_by_ids, fn_inverter_modbus_caps_patch, fn_set_mode_with_context, fn_fill_audit_for_site_window, plánování: fn_load_planning_slots_full, fn_last_effective_ote, fn_planning_horizon_end, fn_planning_site_context, fn_pv_forecast_correction_factor, fn_planning_run_commit, fn_planning_slot_boundary_prague, fn_planning_interval_at_offset, fn_telemetry_inverter_sample, fn_telemetry_ev_charger_sample, fn_telemetry_heat_pump_sample, fn_battery_cycle_audit, Deye helpery: fn_deye_pack_system_time, fn_deye_clock_drift_sec, fn_deye_time_point_regs, fn_deye_tou_inactive_signature, fn_modbus_last_verified_map, fn_inverter_pv_a_max_w, fn_site_has_active_green_bonus_pv.


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 (scheduler) 13:30 / 14:00 / 00:05 Jeden globální zápis do market_interval_price za tick (ne cyklus per site); po importu obnova predikce záporných cen pro každou aktivní site. Viz docs/04-modules/market-prices.md
forecast_service 14:30 + 06:00 denně docs/04-modules/forecast.md
run_daily_plan 15:00 denně backend/services/planning_engine.py + ems.fn_planning_horizon_end (dynamický OTE horizont, terminal SoC)
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
signal_outbound_send / signal_outbound_verify každých 15 s services/signal_service.py — odeslání fronty signal_outbound_journal a readback verify (Loxone / HTTP REST).
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__061_vw_site_effective_price.sql, backend/services/price_importer.py
Multi-site UI (combobox), seznam aktivních lokalit GET /api/v1/me/sites v backend/app/main.py, frontend/src/context/SiteSelectionContext.tsx, useSiteStatus (filtr vw_site_status)
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__005_fn_cop_estimate.sql
Modbus, telemetrie, agregace docs/04-modules/telemetry.md
Dashboard přehled 15min grafy slotů, SoC vs. živá telemetrie docs/04-modules/telemetry.md (CA telemetry_inverter_15m, view vw_telemetry_15m_7d), frontend/src/hooks/useDashboardData.ts, frontend/src/components/charts/SocTuvChart.tsx
Modbus journal, verifikace, Discord docs/04-modules/modbus-command-journal.md
Deye registry (FC 0x10, 108/109/141/142/178/143/145/340) 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, dynamický OTE horizont docs/04-modules/planning.md, docs/04-modules/planning-extended-horizon.md, docs/planning-changelog.md, planning_engine.py
Arbitráž baterie (mezi sloty ≠ buy/sell v jednom 15min) docs/04-modules/planning-arbitrage-accounting.md
Provozní režimy AUTO / SELF_SUSTAIN / … docs/04-modules/operating-modes.md, db/migration/V004__operating_modes.sql, db/routines/R__044_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__019_fn_fill_audit_interval.sql, docs/04-modules/telemetry.md
Nové sloupce / tabulky nový db/migration/V00x__*.sql + případně db/routines / db/views
JSONB read-model (fn_*, fetch_json) docs/02-architecture.md sekce Read-model JSONB, app/db_json.py
Self-hosted deploy (Gitea, Caddy, /opt/ems-deploy) docs/deployment-self-hosted.md, deploy/deploy.sh
Reset DB / restore z dumpu (Docker volume, Timescale) docs/database-reset-and-restore.md, scripts/import_ems_db.sh
Nespecifikované chování docs/06-open-questions.md (přidat otázku, neimpl. naslepo)
MCP read-only SQL na EMS DB docs/07-mcp-postgres-ems.md — server ID user-postgres-ems, nástroj query, {"sql":"…"}. Pravidlo .cursor/rules/mcp-postgres-ems.mdc.

Konvence (krátce)

  • Python: snake_case, type hints, Pydantic pro API modely.
  • SQL: viz také odstavec Formát SQL u sekce SQL-first výše — 2 mezery odsazení, klíčová slova malými písmeny, snake_case identifikátory, explicitní FK; Flyway pořadí V###__ / repeatable R__NNN_*.sql (třímístný prefix = pořadí závislostí mezi fn/vw).
  • Timescale continuous aggregate (CA): komentář k objektu CA je COMMENT ON VIEW, ne COMMENT ON MATERIALIZED VIEW (PG hlásí 42809). Viz .cursor/rules/timescale-continuous-aggregate.mdc.
  • 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.
  • Deploy: flyway validate před migrate (deploy/deploy.sh). Lokálně ./scripts/flyway_validate_local.sh; CI viz docs/deployment-self-hosted.md a scripts/ci_check_migration_immutability.sh.