Files
ems/db/migration/V001__init_schema.sql
Dusan Vojacek 8b4af663d8 Initial commit
Made-with: Cursor
2026-03-20 13:27:44 +01:00

609 lines
39 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =============================================================
-- 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í (01). Typicky 0.95.';
COMMENT ON COLUMN ems.asset_battery.discharge_efficiency IS 'Účinnost vybíjení (01). 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í (01). 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.';