-- ============================================================= -- V001__init_schema.sql -- EMS Platform – inicializace schématu a všech tabulek -- ============================================================= CREATE SCHEMA IF NOT EXISTS ems; -- ============================================================ -- LOKALITY -- ============================================================ CREATE TABLE ems.site ( id SERIAL PRIMARY KEY, code TEXT UNIQUE NOT NULL, name TEXT NOT NULL, timezone TEXT NOT NULL DEFAULT 'Europe/Prague', latitude NUMERIC(9,6), longitude NUMERIC(9,6), active BOOLEAN NOT NULL DEFAULT true, notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); COMMENT ON TABLE ems.site IS 'Lokalita / jeden objekt v systému EMS.'; COMMENT ON COLUMN ems.site.id IS 'Primární klíč lokality.'; COMMENT ON COLUMN ems.site.code IS 'Krátký unikátní kód lokality. Příklad: home-01.'; COMMENT ON COLUMN ems.site.name IS 'Lidsky čitelný název lokality.'; COMMENT ON COLUMN ems.site.timezone IS 'Časová zóna IANA. Výchozí Europe/Prague.'; COMMENT ON COLUMN ems.site.latitude IS 'Zeměpisná šířka – vstup pro weather API a výpočty irradiance.'; COMMENT ON COLUMN ems.site.longitude IS 'Zeměpisná délka – vstup pro weather API a výpočty irradiance.'; COMMENT ON COLUMN ems.site.active IS 'Pokud false, lokalita se přeskočí při plánování a sběru dat.'; COMMENT ON COLUMN ems.site.notes IS 'Volné poznámky.'; COMMENT ON COLUMN ems.site.created_at IS 'Čas vytvoření záznamu.'; -- ------------------------------------------------------------ CREATE TABLE ems.site_endpoint ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), endpoint_type TEXT NOT NULL, host TEXT NOT NULL, port INT, protocol TEXT, unit_id INT, auth_reference TEXT, enabled BOOLEAN NOT NULL DEFAULT true, notes TEXT ); COMMENT ON TABLE ems.site_endpoint IS 'Komunikační endpointy lokality. Jedna lokalita může mít více endpointů různých typů.'; COMMENT ON COLUMN ems.site_endpoint.id IS 'Primární klíč endpointu.'; COMMENT ON COLUMN ems.site_endpoint.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.site_endpoint.endpoint_type IS 'Typ endpointu: modbus_tcp, loxone_http, http_api.'; COMMENT ON COLUMN ems.site_endpoint.host IS 'IP adresa nebo hostname cílového zařízení (Waveshare, Loxone).'; COMMENT ON COLUMN ems.site_endpoint.port IS 'TCP port. Modbus TCP typicky 502, Loxone 80.'; COMMENT ON COLUMN ems.site_endpoint.protocol IS 'Protokol: modbus_tcp, http, https.'; COMMENT ON COLUMN ems.site_endpoint.unit_id IS 'Modbus Unit ID (slave address). Relevantní pro modbus_tcp.'; COMMENT ON COLUMN ems.site_endpoint.auth_reference IS 'Název env proměnné nebo secret s přihlašovacími údaji.'; COMMENT ON COLUMN ems.site_endpoint.enabled IS 'Pokud false, endpoint se nepoužívá.'; COMMENT ON COLUMN ems.site_endpoint.notes IS 'Volné poznámky.'; -- ------------------------------------------------------------ CREATE TABLE ems.site_market_config ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), purchase_pricing_mode TEXT NOT NULL DEFAULT 'spot', sale_pricing_mode TEXT NOT NULL DEFAULT 'spot', buy_margin_fixed_czk NUMERIC(10,4) NOT NULL DEFAULT 0, buy_margin_percent NUMERIC(6,4) NOT NULL DEFAULT 0, sell_margin_fixed_czk NUMERIC(10,4) NOT NULL DEFAULT 0, sell_margin_percent NUMERIC(6,4) NOT NULL DEFAULT 0, currency TEXT NOT NULL DEFAULT 'CZK', valid_from TIMESTAMPTZ NOT NULL, valid_to TIMESTAMPTZ, notes TEXT ); COMMENT ON TABLE ems.site_market_config IS 'Obchodní konfigurace lokality s maržemi. valid_from/valid_to umožňuje historii změn.'; COMMENT ON COLUMN ems.site_market_config.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.site_market_config.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.site_market_config.purchase_pricing_mode IS 'Režim nákupní ceny: spot, fixed, hybrid.'; COMMENT ON COLUMN ems.site_market_config.sale_pricing_mode IS 'Režim prodejní ceny: spot, fixed, hybrid.'; COMMENT ON COLUMN ems.site_market_config.buy_margin_fixed_czk IS 'Fixní nákupní marže Kč/kWh přičítaná k raw ceně.'; COMMENT ON COLUMN ems.site_market_config.buy_margin_percent IS 'Procentní nákupní marže aplikovaná na raw cenu.'; COMMENT ON COLUMN ems.site_market_config.sell_margin_fixed_czk IS 'Fixní prodejní marže Kč/kWh. Záporná = srážka z prodejní ceny.'; COMMENT ON COLUMN ems.site_market_config.sell_margin_percent IS 'Procentní prodejní marže.'; COMMENT ON COLUMN ems.site_market_config.currency IS 'Měna konfigurace.'; COMMENT ON COLUMN ems.site_market_config.valid_from IS 'Začátek platnosti konfigurace.'; COMMENT ON COLUMN ems.site_market_config.valid_to IS 'Konec platnosti. NULL = aktuálně platný záznam.'; COMMENT ON COLUMN ems.site_market_config.notes IS 'Volné poznámky.'; -- ------------------------------------------------------------ CREATE TABLE ems.site_grid_connection ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id) UNIQUE, max_import_power_w INT NOT NULL, max_export_power_w INT NOT NULL DEFAULT 0, no_export BOOLEAN NOT NULL DEFAULT false, reserved_capacity_w INT NOT NULL DEFAULT 0, notes TEXT ); COMMENT ON TABLE ems.site_grid_connection IS 'Síťová omezení připojení lokality k distribuční síti.'; COMMENT ON COLUMN ems.site_grid_connection.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.site_grid_connection.site_id IS 'Vazba na lokalitu. Každá lokalita má nejvýše jeden záznam.'; COMMENT ON COLUMN ems.site_grid_connection.max_import_power_w IS 'Maximální povolený odběr ze sítě v W dle jističe/smlouvy.'; COMMENT ON COLUMN ems.site_grid_connection.max_export_power_w IS 'Maximální povolený export do sítě v W. 0 = export zakázán.'; COMMENT ON COLUMN ems.site_grid_connection.no_export IS 'Pokud true, export do sítě je zakázán bez ohledu na max_export_power_w.'; COMMENT ON COLUMN ems.site_grid_connection.reserved_capacity_w IS 'Výkon rezervovaný pro interní potřeby, odečítá se z dostupné kapacity.'; COMMENT ON COLUMN ems.site_grid_connection.notes IS 'Volné poznámky.'; -- ============================================================ -- AKTIVA -- ============================================================ CREATE TABLE ems.asset_inverter ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), code TEXT NOT NULL, manufacturer TEXT, model TEXT, endpoint_id INT REFERENCES ems.site_endpoint(id), max_charge_power_w INT, max_discharge_power_w INT, max_export_power_w INT, controllable BOOLEAN NOT NULL DEFAULT true, notes TEXT ); COMMENT ON TABLE ems.asset_inverter IS 'Střídač / hybridní měnič na lokalitě.'; COMMENT ON COLUMN ems.asset_inverter.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.asset_inverter.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.asset_inverter.code IS 'Kód aktiva, unikátní v rámci lokality.'; COMMENT ON COLUMN ems.asset_inverter.manufacturer IS 'Výrobce. Příklad: Deye.'; COMMENT ON COLUMN ems.asset_inverter.model IS 'Model. Příklad: SUN-20K-SG01LP1-EU.'; COMMENT ON COLUMN ems.asset_inverter.endpoint_id IS 'Modbus TCP endpoint přes Waveshare.'; COMMENT ON COLUMN ems.asset_inverter.max_charge_power_w IS 'Maximální nabíjecí výkon baterie v W.'; COMMENT ON COLUMN ems.asset_inverter.max_discharge_power_w IS 'Maximální vybíjecí výkon baterie v W.'; COMMENT ON COLUMN ems.asset_inverter.max_export_power_w IS 'Maximální výkon exportu do sítě v W.'; COMMENT ON COLUMN ems.asset_inverter.controllable IS 'Pokud false, střídač není řízen EMS (ongridový na GEN portu).'; COMMENT ON COLUMN ems.asset_inverter.notes IS 'Volné poznámky.'; -- ------------------------------------------------------------ CREATE TABLE ems.asset_battery ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), inverter_id INT NOT NULL REFERENCES ems.asset_inverter(id), code TEXT NOT NULL, usable_capacity_wh INT NOT NULL, min_soc_percent NUMERIC(5,2) NOT NULL DEFAULT 10, reserve_soc_percent NUMERIC(5,2) NOT NULL DEFAULT 20, max_soc_percent NUMERIC(5,2) NOT NULL DEFAULT 95, charge_efficiency NUMERIC(5,4) NOT NULL DEFAULT 0.95, discharge_efficiency NUMERIC(5,4) NOT NULL DEFAULT 0.95, degradation_cost_czk_kwh NUMERIC(8,4) NOT NULL DEFAULT 0.5 ); COMMENT ON TABLE ems.asset_battery IS 'Bateriový systém připojený ke střídači.'; COMMENT ON COLUMN ems.asset_battery.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.asset_battery.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.asset_battery.inverter_id IS 'Střídač ke kterému je baterie připojena.'; COMMENT ON COLUMN ems.asset_battery.code IS 'Kód aktiva.'; COMMENT ON COLUMN ems.asset_battery.usable_capacity_wh IS 'Použitelná kapacita baterie v Wh bez rezerv výrobce.'; COMMENT ON COLUMN ems.asset_battery.min_soc_percent IS 'Minimální SoC v % – absolutní spodní limit, nikdy nepřekročit.'; COMMENT ON COLUMN ems.asset_battery.reserve_soc_percent IS 'Rezervní SoC v % – zachován pro výpadky sítě, nevyužívat v běžném plánu.'; COMMENT ON COLUMN ems.asset_battery.max_soc_percent IS 'Maximální SoC v % – horní limit pro denní provoz.'; COMMENT ON COLUMN ems.asset_battery.charge_efficiency IS 'Účinnost nabíjení (0–1). Typicky 0.95.'; COMMENT ON COLUMN ems.asset_battery.discharge_efficiency IS 'Účinnost vybíjení (0–1). Typicky 0.95.'; COMMENT ON COLUMN ems.asset_battery.degradation_cost_czk_kwh IS 'Odhadovaný náklad degradace v Kč/kWh cyklu. Vstupuje do optimalizace jako cena za použití baterie.'; -- ------------------------------------------------------------ CREATE TABLE ems.asset_pv_array ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), inverter_id INT REFERENCES ems.asset_inverter(id), code TEXT NOT NULL, name TEXT, nominal_power_wp INT NOT NULL, azimuth_deg NUMERIC(6,2), tilt_deg NUMERIC(5,2), module_count INT, shading_factor NUMERIC(4,3) NOT NULL DEFAULT 1.0, controllable BOOLEAN NOT NULL DEFAULT false, notes TEXT ); COMMENT ON TABLE ems.asset_pv_array IS 'FVE pole. Každé pole jako samostatný záznam – klíčové pro přesnou predikci výroby.'; COMMENT ON COLUMN ems.asset_pv_array.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.asset_pv_array.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.asset_pv_array.inverter_id IS 'Střídač ke kterému je pole připojeno.'; COMMENT ON COLUMN ems.asset_pv_array.code IS 'Kód pole, unikátní v rámci lokality. Příklad: pv-a, pv-b.'; COMMENT ON COLUMN ems.asset_pv_array.name IS 'Popis pole. Příklad: Jižní střecha.'; COMMENT ON COLUMN ems.asset_pv_array.nominal_power_wp IS 'Nominální výkon pole v Wp (součet výkonů panelů).'; COMMENT ON COLUMN ems.asset_pv_array.azimuth_deg IS 'Azimut panelů ve stupních. 0=jih, 90=západ, -90=východ.'; COMMENT ON COLUMN ems.asset_pv_array.tilt_deg IS 'Sklon panelů od horizontály ve stupních.'; COMMENT ON COLUMN ems.asset_pv_array.module_count IS 'Počet panelů v poli.'; COMMENT ON COLUMN ems.asset_pv_array.shading_factor IS 'Koeficient stínění (0–1). 1.0 = bez stínění.'; COMMENT ON COLUMN ems.asset_pv_array.controllable IS 'Pokud false, pole není řízeno EMS (ongridový autonomní střídač na GEN portu).'; COMMENT ON COLUMN ems.asset_pv_array.notes IS 'Volné poznámky.'; -- ------------------------------------------------------------ CREATE TABLE ems.asset_ev_charger ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), code TEXT NOT NULL, manufacturer TEXT, model TEXT, endpoint_id INT REFERENCES ems.site_endpoint(id), max_power_w INT NOT NULL, min_power_w INT NOT NULL DEFAULT 1380, phases INT NOT NULL DEFAULT 3, connector_count INT NOT NULL DEFAULT 1, schedulable BOOLEAN NOT NULL DEFAULT true, notes TEXT ); COMMENT ON TABLE ems.asset_ev_charger IS 'EV nabíjecí stanice. Komunikace přes Modbus TCP (Waveshare převodník).'; COMMENT ON COLUMN ems.asset_ev_charger.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.asset_ev_charger.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.asset_ev_charger.code IS 'Kód aktiva, unikátní v rámci lokality.'; COMMENT ON COLUMN ems.asset_ev_charger.manufacturer IS 'Výrobce nabíječky. Příklad: Teltonika.'; COMMENT ON COLUMN ems.asset_ev_charger.model IS 'Model nabíječky. Příklad: TeltoCharge 22kW.'; COMMENT ON COLUMN ems.asset_ev_charger.endpoint_id IS 'Modbus TCP endpoint přes Waveshare převodník.'; COMMENT ON COLUMN ems.asset_ev_charger.max_power_w IS 'Maximální výkon nabíječky v W.'; COMMENT ON COLUMN ems.asset_ev_charger.min_power_w IS 'Minimální výkon nabíjení v W. 1380 W = 6A jednofázové minimum IEC 61851.'; COMMENT ON COLUMN ems.asset_ev_charger.phases IS 'Počet fází nabíjení.'; COMMENT ON COLUMN ems.asset_ev_charger.connector_count IS 'Počet konektorů na nabíječce.'; COMMENT ON COLUMN ems.asset_ev_charger.schedulable IS 'Pokud true, nabíječka je zapojená do plánování EMS.'; COMMENT ON COLUMN ems.asset_ev_charger.notes IS 'Volné poznámky.'; -- ------------------------------------------------------------ CREATE TABLE ems.asset_heat_pump ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), code TEXT NOT NULL, manufacturer TEXT, model TEXT, endpoint_id INT REFERENCES ems.site_endpoint(id), rated_heating_power_w INT NOT NULL, cop_rated NUMERIC(4,2), cop_temp_reference_c NUMERIC(5,2), min_run_duration_min INT NOT NULL DEFAULT 30, min_stop_duration_min INT NOT NULL DEFAULT 15, tuv_tank_volume_l INT, tuv_min_temp_c NUMERIC(5,2) NOT NULL DEFAULT 45, tuv_max_temp_c NUMERIC(5,2) NOT NULL DEFAULT 60, tuv_target_temp_c NUMERIC(5,2) NOT NULL DEFAULT 55, tuv_temp_sensor_ref TEXT, schedulable BOOLEAN NOT NULL DEFAULT true, notes TEXT ); COMMENT ON TABLE ems.asset_heat_pump IS 'Tepelné čerpadlo s Modbus řízením (Samsung). Řízeno na základě COP, venkovní teploty a stavu zásobníku TUV.'; COMMENT ON COLUMN ems.asset_heat_pump.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.asset_heat_pump.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.asset_heat_pump.code IS 'Kód aktiva, unikátní v rámci lokality.'; COMMENT ON COLUMN ems.asset_heat_pump.manufacturer IS 'Výrobce tepelného čerpadla. Příklad: Samsung.'; COMMENT ON COLUMN ems.asset_heat_pump.model IS 'Model tepelného čerpadla.'; COMMENT ON COLUMN ems.asset_heat_pump.endpoint_id IS 'Modbus TCP endpoint přes Waveshare převodník.'; COMMENT ON COLUMN ems.asset_heat_pump.rated_heating_power_w IS 'Jmenovitý topný výkon v W při referenčních podmínkách.'; COMMENT ON COLUMN ems.asset_heat_pump.cop_rated IS 'Jmenovitý COP při referenční teplotě cop_temp_reference_c.'; COMMENT ON COLUMN ems.asset_heat_pump.cop_temp_reference_c IS 'Referenční venkovní teplota pro jmenovitý COP. Typicky 7°C (norma A7/W35).'; COMMENT ON COLUMN ems.asset_heat_pump.min_run_duration_min IS 'Minimální nepřerušená doba běhu v minutách. Chrání kompresor před krátkými cykly.'; COMMENT ON COLUMN ems.asset_heat_pump.min_stop_duration_min IS 'Minimální doba stání po vypnutí v minutách. Ochrana kompresoru při restartu.'; COMMENT ON COLUMN ems.asset_heat_pump.tuv_tank_volume_l IS 'Objem zásobníku TUV v litrech. Slouží k výpočtu doby ohřevu.'; COMMENT ON COLUMN ems.asset_heat_pump.tuv_min_temp_c IS 'Minimální teplota TUV zásobníku v °C. Pod touto hodnotou se ohřev spustí bez ohledu na cenu.'; COMMENT ON COLUMN ems.asset_heat_pump.tuv_max_temp_c IS 'Maximální teplota TUV zásobníku v °C. Nad touto hodnotou se ohřev zastaví.'; COMMENT ON COLUMN ems.asset_heat_pump.tuv_target_temp_c IS 'Cílová teplota TUV pro normální plánovaný provoz.'; COMMENT ON COLUMN ems.asset_heat_pump.tuv_temp_sensor_ref IS 'Název nebo kód čidla teploty zásobníku (Modbus registr nebo Loxone Virtual Output).'; COMMENT ON COLUMN ems.asset_heat_pump.schedulable IS 'Pokud true, tepelné čerpadlo je zapojeno do plánování EMS.'; COMMENT ON COLUMN ems.asset_heat_pump.notes IS 'Volné poznámky.'; -- ============================================================ -- TRŽNÍ DATA -- ============================================================ CREATE TABLE ems.market_interval_price ( market_source TEXT NOT NULL DEFAULT 'OTE_CZ', interval_start TIMESTAMPTZ NOT NULL, interval_end TIMESTAMPTZ NOT NULL, buy_raw_price_czk_kwh NUMERIC(10,6) NOT NULL, sell_raw_price_czk_kwh NUMERIC(10,6) NOT NULL, currency TEXT NOT NULL DEFAULT 'CZK', imported_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (market_source, interval_start) ); COMMENT ON TABLE ems.market_interval_price IS 'Raw spotové ceny elektřiny OTE CZ. Sdílené pro všechny lokality, bez marží. Granularita 15 minut.'; COMMENT ON COLUMN ems.market_interval_price.market_source IS 'Zdroj tržních dat. Příklad: OTE_CZ.'; COMMENT ON COLUMN ems.market_interval_price.interval_start IS 'Začátek 15minutového intervalu (UTC).'; COMMENT ON COLUMN ems.market_interval_price.interval_end IS 'Konec 15minutového intervalu (UTC).'; COMMENT ON COLUMN ems.market_interval_price.buy_raw_price_czk_kwh IS 'Raw nákupní cena v Kč/kWh bez marží.'; COMMENT ON COLUMN ems.market_interval_price.sell_raw_price_czk_kwh IS 'Raw prodejní referenční cena v Kč/kWh bez marží. Pro OTE CZ = DAM cena.'; COMMENT ON COLUMN ems.market_interval_price.currency IS 'Měna cen záznamu.'; COMMENT ON COLUMN ems.market_interval_price.imported_at IS 'Čas importu záznamu do DB. Slouží pro audit importů.'; -- ============================================================ -- TELEMETRIE -- ============================================================ CREATE TABLE ems.telemetry_inverter ( site_id INT NOT NULL REFERENCES ems.site(id), inverter_id INT NOT NULL REFERENCES ems.asset_inverter(id), measured_at TIMESTAMPTZ NOT NULL, pv_power_w INT, battery_soc_percent NUMERIC(5,2), battery_power_w INT, battery_voltage_v NUMERIC(7,3), grid_power_w INT, grid_voltage_v NUMERIC(7,3), load_power_w INT, inverter_temp_c NUMERIC(5,2), operating_mode TEXT, fault_code INT, PRIMARY KEY (inverter_id, measured_at) ); COMMENT ON TABLE ems.telemetry_inverter IS 'Telemetrie ze střídače Deye čtená přes Modbus TCP. 1min granularita. TimescaleDB hypertable.'; COMMENT ON COLUMN ems.telemetry_inverter.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.telemetry_inverter.inverter_id IS 'Vazba na střídač.'; COMMENT ON COLUMN ems.telemetry_inverter.measured_at IS 'Čas měření (UTC).'; COMMENT ON COLUMN ems.telemetry_inverter.pv_power_w IS 'Celkový okamžitý výkon FVE v W (součet všech stringů čtených tímto střídačem, včetně GEN portu).'; COMMENT ON COLUMN ems.telemetry_inverter.battery_soc_percent IS 'Aktuální stav nabití baterie v %.'; COMMENT ON COLUMN ems.telemetry_inverter.battery_power_w IS 'Výkon baterie v W. Kladné = nabíjení, záporné = vybíjení.'; COMMENT ON COLUMN ems.telemetry_inverter.battery_voltage_v IS 'Napětí bateriového systému v V.'; COMMENT ON COLUMN ems.telemetry_inverter.grid_power_w IS 'Výkon přenosu se sítí v W. Kladné = import ze sítě, záporné = export do sítě.'; COMMENT ON COLUMN ems.telemetry_inverter.grid_voltage_v IS 'Napětí sítě v V.'; COMMENT ON COLUMN ems.telemetry_inverter.load_power_w IS 'Celková spotřeba objektu v W (vše za AC výstupem střídače, včetně EV a TUV).'; COMMENT ON COLUMN ems.telemetry_inverter.inverter_temp_c IS 'Teplota střídače v °C.'; COMMENT ON COLUMN ems.telemetry_inverter.operating_mode IS 'Provozní režim dle Modbus registru (raw hodnota pro ladění).'; COMMENT ON COLUMN ems.telemetry_inverter.fault_code IS 'Kód chyby z Modbus registru. 0 = bez chyby.'; -- ------------------------------------------------------------ CREATE TABLE ems.telemetry_ev_charger ( site_id INT NOT NULL REFERENCES ems.site(id), charger_id INT NOT NULL REFERENCES ems.asset_ev_charger(id), measured_at TIMESTAMPTZ NOT NULL, connector_id INT NOT NULL DEFAULT 1, status TEXT, power_w INT, energy_kwh NUMERIC(10,3), current_a NUMERIC(7,3), voltage_v NUMERIC(7,3), session_id TEXT, error_code TEXT, PRIMARY KEY (charger_id, connector_id, measured_at) ); COMMENT ON TABLE ems.telemetry_ev_charger IS 'Telemetrie EV nabíječky Teltonika čtená přes Modbus TCP (Waveshare). 1min granularita. TimescaleDB hypertable.'; COMMENT ON COLUMN ems.telemetry_ev_charger.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.telemetry_ev_charger.charger_id IS 'Vazba na EV nabíječku.'; COMMENT ON COLUMN ems.telemetry_ev_charger.measured_at IS 'Čas měření (UTC).'; COMMENT ON COLUMN ems.telemetry_ev_charger.connector_id IS 'Číslo konektoru (1-based). Teltonika TeltoCharge má 1 konektor.'; COMMENT ON COLUMN ems.telemetry_ev_charger.status IS 'Stav konektoru dle OCPP: available, preparing, charging, suspended_ev, suspended_evse, finishing, faulted.'; COMMENT ON COLUMN ems.telemetry_ev_charger.power_w IS 'Aktuální nabíjecí výkon v W.'; COMMENT ON COLUMN ems.telemetry_ev_charger.energy_kwh IS 'Kumulativní energie aktuální session v kWh. Resetuje se při nové session.'; COMMENT ON COLUMN ems.telemetry_ev_charger.current_a IS 'Nabíjecí proud v A.'; COMMENT ON COLUMN ems.telemetry_ev_charger.voltage_v IS 'Napětí v bodě připojení v V.'; COMMENT ON COLUMN ems.telemetry_ev_charger.session_id IS 'Identifikátor aktuální nabíjecí session z Modbus registru.'; COMMENT ON COLUMN ems.telemetry_ev_charger.error_code IS 'Kód chyby z Modbus registru nabíječky.'; -- ------------------------------------------------------------ CREATE TABLE ems.telemetry_heat_pump ( site_id INT NOT NULL REFERENCES ems.site(id), heat_pump_id INT NOT NULL REFERENCES ems.asset_heat_pump(id), measured_at TIMESTAMPTZ NOT NULL, outdoor_temp_c NUMERIC(5,2), water_inlet_temp_c NUMERIC(5,2), water_outlet_temp_c NUMERIC(5,2), tuv_tank_temp_c NUMERIC(5,2), power_w INT, operating_mode TEXT, cop_actual NUMERIC(4,2), defrost_active BOOLEAN, alarm_code INT, PRIMARY KEY (heat_pump_id, measured_at) ); COMMENT ON TABLE ems.telemetry_heat_pump IS 'Telemetrie tepelného čerpadla Samsung čtená přes Modbus TCP (Waveshare). 1min granularita. TimescaleDB hypertable.'; COMMENT ON COLUMN ems.telemetry_heat_pump.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.telemetry_heat_pump.heat_pump_id IS 'Vazba na tepelné čerpadlo.'; COMMENT ON COLUMN ems.telemetry_heat_pump.measured_at IS 'Čas měření (UTC).'; COMMENT ON COLUMN ems.telemetry_heat_pump.outdoor_temp_c IS 'Venkovní teplota naměřená čerpadlem v °C. Klíčový vstup pro výpočet COP a rozhodnutí o ohřevu.'; COMMENT ON COLUMN ems.telemetry_heat_pump.water_inlet_temp_c IS 'Teplota vody na vstupu do výměníku čerpadla v °C.'; COMMENT ON COLUMN ems.telemetry_heat_pump.water_outlet_temp_c IS 'Teplota vody na výstupu z čerpadla v °C.'; COMMENT ON COLUMN ems.telemetry_heat_pump.tuv_tank_temp_c IS 'Teplota TUV zásobníku v °C. Klíčový vstup pro rozhodnutí kdy ohřívat.'; COMMENT ON COLUMN ems.telemetry_heat_pump.power_w IS 'Aktuální příkon čerpadla v W.'; COMMENT ON COLUMN ems.telemetry_heat_pump.operating_mode IS 'Provozní režim: heating, cooling, dhw (domestic hot water), standby.'; COMMENT ON COLUMN ems.telemetry_heat_pump.cop_actual IS 'Aktuálně vypočtený COP pokud je dostupný z Modbus registru.'; COMMENT ON COLUMN ems.telemetry_heat_pump.defrost_active IS 'Příznak aktivního odmrazovacího cyklu. Při defrostu je skutečný příkon vyšší.'; COMMENT ON COLUMN ems.telemetry_heat_pump.alarm_code IS 'Kód alarmu z Modbus registru. 0 = bez alarmu.'; -- ============================================================ -- PREDIKCE -- ============================================================ CREATE TABLE ems.forecast_pv_run ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), pv_array_id INT REFERENCES ems.asset_pv_array(id), forecast_source TEXT NOT NULL DEFAULT 'open_meteo', model_params JSONB, horizon_start TIMESTAMPTZ NOT NULL, horizon_end TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), status TEXT NOT NULL DEFAULT 'ok' ); COMMENT ON TABLE ems.forecast_pv_run IS 'Metadata jednoho běhu predikce výroby FVE.'; COMMENT ON COLUMN ems.forecast_pv_run.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.forecast_pv_run.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.forecast_pv_run.pv_array_id IS 'Konkrétní FVE pole. NULL = agregovaná predikce celé lokality.'; COMMENT ON COLUMN ems.forecast_pv_run.forecast_source IS 'Zdroj meteorologických dat: open_meteo, solcast, manual.'; COMMENT ON COLUMN ems.forecast_pv_run.model_params IS 'Parametry predikčního modelu použité při tomto běhu (JSON).'; COMMENT ON COLUMN ems.forecast_pv_run.horizon_start IS 'Začátek predikčního horizontu.'; COMMENT ON COLUMN ems.forecast_pv_run.horizon_end IS 'Konec predikčního horizontu.'; COMMENT ON COLUMN ems.forecast_pv_run.created_at IS 'Čas spuštění predikce.'; COMMENT ON COLUMN ems.forecast_pv_run.status IS 'Stav běhu: ok, partial, failed.'; -- ------------------------------------------------------------ CREATE TABLE ems.forecast_pv_interval ( run_id INT NOT NULL REFERENCES ems.forecast_pv_run(id), pv_array_id INT NOT NULL REFERENCES ems.asset_pv_array(id), interval_start TIMESTAMPTZ NOT NULL, power_w INT NOT NULL, irradiance_wm2 NUMERIC(8,2), temp_c NUMERIC(5,2), PRIMARY KEY (run_id, pv_array_id, interval_start) ); COMMENT ON TABLE ems.forecast_pv_interval IS 'Predikovaný výkon FVE po 15min intervalech. TimescaleDB hypertable.'; COMMENT ON COLUMN ems.forecast_pv_interval.run_id IS 'Vazba na běh predikce.'; COMMENT ON COLUMN ems.forecast_pv_interval.pv_array_id IS 'Konkrétní FVE pole.'; COMMENT ON COLUMN ems.forecast_pv_interval.interval_start IS 'Začátek 15min intervalu (UTC).'; COMMENT ON COLUMN ems.forecast_pv_interval.power_w IS 'Predikovaný výkon FVE pole v W.'; COMMENT ON COLUMN ems.forecast_pv_interval.irradiance_wm2 IS 'GHI irradiance ze weather service v W/m² použitá při výpočtu.'; COMMENT ON COLUMN ems.forecast_pv_interval.temp_c IS 'Predikovaná venkovní teplota v °C použitá při výpočtu.'; -- ------------------------------------------------------------ CREATE TABLE ems.forecast_weather_interval ( site_id INT NOT NULL REFERENCES ems.site(id), forecast_source TEXT NOT NULL DEFAULT 'open_meteo', interval_start TIMESTAMPTZ NOT NULL, outdoor_temp_c NUMERIC(5,2), irradiance_wm2 NUMERIC(8,2), cloud_cover_pct NUMERIC(5,2), wind_speed_ms NUMERIC(6,2), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (site_id, forecast_source, interval_start) ); COMMENT ON TABLE ems.forecast_weather_interval IS 'Predikce počasí per lokalita, 15min granularita. Sdílený vstup pro FVE predikci i COP odhad tepelného čerpadla.'; COMMENT ON COLUMN ems.forecast_weather_interval.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.forecast_weather_interval.forecast_source IS 'Zdroj predikce počasí.'; COMMENT ON COLUMN ems.forecast_weather_interval.interval_start IS 'Začátek 15min intervalu (UTC).'; COMMENT ON COLUMN ems.forecast_weather_interval.outdoor_temp_c IS 'Predikovaná venkovní teplota v °C. Klíčový vstup pro COP odhad tepelného čerpadla.'; COMMENT ON COLUMN ems.forecast_weather_interval.irradiance_wm2 IS 'Predikovaná irradiance GHI v W/m².'; COMMENT ON COLUMN ems.forecast_weather_interval.cloud_cover_pct IS 'Predikovaná oblačnost v %.'; COMMENT ON COLUMN ems.forecast_weather_interval.wind_speed_ms IS 'Predikovaná rychlost větru v m/s.'; COMMENT ON COLUMN ems.forecast_weather_interval.created_at IS 'Čas importu predikce do DB.'; -- ============================================================ -- PLÁNOVÁNÍ -- ============================================================ CREATE TABLE ems.planning_run ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), horizon_start TIMESTAMPTZ NOT NULL, horizon_end TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), status TEXT NOT NULL DEFAULT 'draft', solver_params JSONB, notes TEXT ); COMMENT ON TABLE ems.planning_run IS 'Jeden plánovací běh pro konkrétní lokalitu a horizont.'; COMMENT ON COLUMN ems.planning_run.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.planning_run.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.planning_run.horizon_start IS 'Začátek plánovaného horizontu (typicky dnešní půlnoc).'; COMMENT ON COLUMN ems.planning_run.horizon_end IS 'Konec plánovaného horizontu (typicky zítřejší půlnoc nebo +48h).'; COMMENT ON COLUMN ems.planning_run.created_at IS 'Čas vytvoření plánu.'; COMMENT ON COLUMN ems.planning_run.status IS 'Stav plánu: draft (sestavován), approved (schválen), active (aktuálně platný), superseded (nahrazen novějším).'; COMMENT ON COLUMN ems.planning_run.solver_params IS 'Parametry optimalizačního solveru použité při tomto běhu (JSON).'; COMMENT ON COLUMN ems.planning_run.notes IS 'Volné poznámky k plánu.'; -- ------------------------------------------------------------ CREATE TABLE ems.planning_interval ( run_id INT NOT NULL REFERENCES ems.planning_run(id), interval_start TIMESTAMPTZ NOT NULL, battery_setpoint_w INT, battery_soc_target_pct NUMERIC(5,2), grid_setpoint_w INT, ev_charge_power_w INT, heat_pump_enabled BOOLEAN, heat_pump_setpoint_w INT, expected_cost_czk NUMERIC(10,4), effective_buy_price NUMERIC(10,6), effective_sell_price NUMERIC(10,6), PRIMARY KEY (run_id, interval_start) ); COMMENT ON TABLE ems.planning_interval IS 'Výstup optimalizace – jeden řádek = jeden 15min slot plánu.'; COMMENT ON COLUMN ems.planning_interval.run_id IS 'Vazba na plánovací běh.'; COMMENT ON COLUMN ems.planning_interval.interval_start IS 'Začátek 15min intervalu (UTC).'; COMMENT ON COLUMN ems.planning_interval.battery_setpoint_w IS 'Plánovaný výkon baterie v W. Kladné = nabíjení, záporné = vybíjení.'; COMMENT ON COLUMN ems.planning_interval.battery_soc_target_pct IS 'Cílový SoC baterie na konci intervalu v %.'; COMMENT ON COLUMN ems.planning_interval.grid_setpoint_w IS 'Plánovaný výkon se sítí v W. Kladné = import, záporné = export.'; COMMENT ON COLUMN ems.planning_interval.ev_charge_power_w IS 'Plánovaný agregovaný výkon nabíjení EV v W (součet všech nabíječek na site).'; COMMENT ON COLUMN ems.planning_interval.heat_pump_enabled IS 'Zda má tepelné čerpadlo v tomto intervalu běžet.'; COMMENT ON COLUMN ems.planning_interval.heat_pump_setpoint_w IS 'Plánovaný výkon tepelného čerpadla v W.'; COMMENT ON COLUMN ems.planning_interval.expected_cost_czk IS 'Očekávané náklady intervalu v Kč. Záporné = příjem z prodeje.'; COMMENT ON COLUMN ems.planning_interval.effective_buy_price IS 'Efektivní nákupní cena použitá při plánování v Kč/kWh.'; COMMENT ON COLUMN ems.planning_interval.effective_sell_price IS 'Efektivní prodejní cena použitá při plánování v Kč/kWh.'; -- ============================================================ -- AUDIT -- ============================================================ CREATE TABLE ems.audit_interval ( site_id INT NOT NULL REFERENCES ems.site(id), interval_start TIMESTAMPTZ NOT NULL, planning_run_id INT REFERENCES ems.planning_run(id), actual_pv_power_w INT, actual_battery_power_w INT, actual_grid_power_w INT, actual_load_power_w INT, actual_battery_soc_pct NUMERIC(5,2), actual_ev_power_w INT, actual_heat_pump_power_w INT, actual_cost_czk NUMERIC(10,4), deviation_grid_w INT, deviation_cost_czk NUMERIC(10,4), PRIMARY KEY (site_id, interval_start) ); COMMENT ON TABLE ems.audit_interval IS 'Skutečnost vs plán po 15min intervalech. Plněno zpětně funkcí ems.fn_fill_audit_interval().'; COMMENT ON COLUMN ems.audit_interval.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.audit_interval.interval_start IS 'Začátek 15min intervalu (UTC).'; COMMENT ON COLUMN ems.audit_interval.planning_run_id IS 'Plán který byl v tomto intervalu aktivní.'; COMMENT ON COLUMN ems.audit_interval.actual_pv_power_w IS 'Skutečný výkon FVE v W (průměr za 15min z telemetrie).'; COMMENT ON COLUMN ems.audit_interval.actual_battery_power_w IS 'Skutečný výkon baterie v W (průměr za 15min).'; COMMENT ON COLUMN ems.audit_interval.actual_grid_power_w IS 'Skutečný výkon přenosu se sítí v W (průměr za 15min).'; COMMENT ON COLUMN ems.audit_interval.actual_load_power_w IS 'Skutečná celková spotřeba v W (průměr za 15min).'; COMMENT ON COLUMN ems.audit_interval.actual_battery_soc_pct IS 'Skutečný SoC baterie na konci intervalu v %.'; COMMENT ON COLUMN ems.audit_interval.actual_ev_power_w IS 'Skutečný výkon nabíjení EV v W (suma všech nabíječek, průměr za 15min).'; COMMENT ON COLUMN ems.audit_interval.actual_heat_pump_power_w IS 'Skutečný příkon tepelného čerpadla v W (průměr za 15min).'; COMMENT ON COLUMN ems.audit_interval.actual_cost_czk IS 'Skutečné náklady intervalu v Kč vypočtené ze skutečného grid_power a efektivní ceny.'; COMMENT ON COLUMN ems.audit_interval.deviation_grid_w IS 'Odchylka skutečný - plánovaný výkon sítě v W.'; COMMENT ON COLUMN ems.audit_interval.deviation_cost_czk IS 'Odchylka skutečných nákladů od plánovaných v Kč.'; -- ============================================================ -- SPOTŘEBA -- ============================================================ CREATE TABLE ems.consumption_baseline_interval ( site_id INT NOT NULL REFERENCES ems.site(id), interval_start TIMESTAMPTZ NOT NULL, data_type TEXT NOT NULL, power_w INT NOT NULL, source TEXT, PRIMARY KEY (site_id, data_type, interval_start) ); COMMENT ON TABLE ems.consumption_baseline_interval IS 'Bazální (neflexibilní) spotřeba po 15min intervalech. Historická skutečnost i predikce.'; COMMENT ON COLUMN ems.consumption_baseline_interval.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.consumption_baseline_interval.interval_start IS 'Začátek 15min intervalu (UTC).'; COMMENT ON COLUMN ems.consumption_baseline_interval.data_type IS 'Typ záznamu: actual (historická skutečnost), forecast (predikce pro plánování).'; COMMENT ON COLUMN ems.consumption_baseline_interval.power_w IS 'Bazální spotřeba v W = celková spotřeba mínus měřitelná flexibilní zařízení.'; COMMENT ON COLUMN ems.consumption_baseline_interval.source IS 'Metoda výpočtu: measured (z telemetrie), model_v1 (statistický model).'; -- ============================================================ -- OVERRIDE -- ============================================================ CREATE TABLE ems.site_override ( id SERIAL PRIMARY KEY, site_id INT NOT NULL REFERENCES ems.site(id), override_type TEXT NOT NULL, value_json JSONB, valid_from TIMESTAMPTZ NOT NULL, valid_to TIMESTAMPTZ, reason TEXT, created_by TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); COMMENT ON TABLE ems.site_override IS 'Manuální přepisy provozních stavů. Mají přednost před automatickým plánem.'; COMMENT ON COLUMN ems.site_override.id IS 'Primární klíč.'; COMMENT ON COLUMN ems.site_override.site_id IS 'Vazba na lokalitu.'; COMMENT ON COLUMN ems.site_override.override_type IS 'Typ přepisu: force_charge, force_discharge, block_export, manual_setpoint, block_heat_pump.'; COMMENT ON COLUMN ems.site_override.value_json IS 'Parametry přepisu jako JSON. Obsah závisí na override_type.'; COMMENT ON COLUMN ems.site_override.valid_from IS 'Začátek platnosti přepisu.'; COMMENT ON COLUMN ems.site_override.valid_to IS 'Konec platnosti. NULL = platí do odvolání.'; COMMENT ON COLUMN ems.site_override.reason IS 'Důvod přepisu pro auditní účely.'; COMMENT ON COLUMN ems.site_override.created_by IS 'Uživatel nebo systémová komponenta která přepis vytvořila.'; COMMENT ON COLUMN ems.site_override.created_at IS 'Čas vytvoření záznamu.';