Initial commit
Made-with: Cursor
This commit is contained in:
137
db/routines/R__fn_cop_estimate.sql
Normal file
137
db/routines/R__fn_cop_estimate.sql
Normal file
@@ -0,0 +1,137 @@
|
||||
-- =============================================================
|
||||
-- R__fn_cop_estimate.sql
|
||||
-- EMS Platform – odhad COP tepelného čerpadla dle venkovní teploty
|
||||
-- Repeatable migration
|
||||
-- =============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_cop_estimate(
|
||||
p_heat_pump_id INT,
|
||||
p_outdoor_temp_c NUMERIC
|
||||
)
|
||||
RETURNS NUMERIC(4,2)
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_cop_rated NUMERIC;
|
||||
v_cop_ref_temp NUMERIC;
|
||||
v_cop_estimated NUMERIC;
|
||||
BEGIN
|
||||
-- Načíst referenční COP a teplotu z konfigurace čerpadla
|
||||
SELECT cop_rated, cop_temp_reference_c
|
||||
INTO v_cop_rated, v_cop_ref_temp
|
||||
FROM ems.asset_heat_pump
|
||||
WHERE id = p_heat_pump_id;
|
||||
|
||||
IF v_cop_rated IS NULL OR v_cop_ref_temp IS NULL THEN
|
||||
-- Fallback: obecný odhad pro vzduch-voda TČ bez konfigurace
|
||||
-- Zdroj: přibližná lineární závislost COP na venkovní teplotě
|
||||
-- COP ≈ 2.0 při -10°C, COP ≈ 4.5 při +15°C
|
||||
v_cop_estimated := 2.0 + (p_outdoor_temp_c + 10.0) * (4.5 - 2.0) / 25.0;
|
||||
ELSE
|
||||
-- Lineární interpolace od referenčního bodu
|
||||
-- COP klesá přibližně o 0.10 na každý stupeň poklesu venkovní teploty
|
||||
-- Toto je zjednodušený model – zpřesnit dle skutečných dat z tepelky
|
||||
v_cop_estimated := v_cop_rated + (p_outdoor_temp_c - v_cop_ref_temp) * 0.10;
|
||||
END IF;
|
||||
|
||||
-- Omezit na rozumné hodnoty (COP vzduch-voda reálně 1.5–6.0)
|
||||
v_cop_estimated := GREATEST(1.5, LEAST(6.0, v_cop_estimated));
|
||||
|
||||
RETURN ROUND(v_cop_estimated, 2);
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_cop_estimate(INT, NUMERIC) IS
|
||||
'Odhadne COP tepelného čerpadla pro danou venkovní teplotu.
|
||||
Používá lineární model od referenčního bodu (cop_rated při cop_temp_reference_c).
|
||||
Výstup slouží k rozhodnutí zda je výhodné spustit TČ v daném intervalu.
|
||||
Přesnost modelu zlepšit kalibrací na historická data (cop_actual z telemetrie).
|
||||
Výsledek omezen na rozsah 1.5–6.0.';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_heat_pump_cost_per_kwh_heat(
|
||||
p_heat_pump_id INT,
|
||||
p_outdoor_temp_c NUMERIC,
|
||||
p_buy_price_czk_kwh NUMERIC
|
||||
)
|
||||
RETURNS NUMERIC(8,4)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
AS $$
|
||||
-- Cena za 1 kWh tepla = cena elektřiny / COP
|
||||
-- Čím vyšší COP, tím levnější teplo
|
||||
SELECT ROUND(
|
||||
p_buy_price_czk_kwh / NULLIF(ems.fn_cop_estimate(p_heat_pump_id, p_outdoor_temp_c), 0),
|
||||
4
|
||||
);
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_heat_pump_cost_per_kwh_heat(INT, NUMERIC, NUMERIC) IS
|
||||
'Vypočte efektivní cenu za 1 kWh dodaného tepla v Kč.
|
||||
Vstup: ID tepelného čerpadla, venkovní teplota, nákupní cena elektřiny.
|
||||
Výstup slouží k porovnání výhodnosti ohřevu v různých časových intervalech.
|
||||
Nižší hodnota = výhodnější čas pro provoz TČ.';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_heat_pump_should_run(
|
||||
p_heat_pump_id INT,
|
||||
p_interval_start TIMESTAMPTZ,
|
||||
p_outdoor_temp_c NUMERIC,
|
||||
p_tuv_tank_temp_c NUMERIC,
|
||||
p_buy_price_czk_kwh NUMERIC,
|
||||
p_max_cost_threshold NUMERIC DEFAULT 3.0 -- Kč/kWh tepla – maximální akceptovatelná cena
|
||||
)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_hp ems.asset_heat_pump%ROWTYPE;
|
||||
v_cost_per_kwh NUMERIC;
|
||||
v_override BOOLEAN;
|
||||
BEGIN
|
||||
SELECT * INTO v_hp FROM ems.asset_heat_pump WHERE id = p_heat_pump_id;
|
||||
|
||||
-- Kontrola override (blokování TČ)
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM ems.site_override
|
||||
WHERE site_id = v_hp.site_id
|
||||
AND override_type = 'block_heat_pump'
|
||||
AND valid_from <= p_interval_start
|
||||
AND (valid_to IS NULL OR valid_to > p_interval_start)
|
||||
) INTO v_override;
|
||||
|
||||
IF v_override THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
-- Povinný ohřev: teplota pod minimem
|
||||
IF p_tuv_tank_temp_c IS NOT NULL AND p_tuv_tank_temp_c < v_hp.tuv_min_temp_c THEN
|
||||
RETURN true;
|
||||
END IF;
|
||||
|
||||
-- Zásobník je plný
|
||||
IF p_tuv_tank_temp_c IS NOT NULL AND p_tuv_tank_temp_c >= v_hp.tuv_max_temp_c THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
-- Ekonomické rozhodnutí: spustit pokud cena tepla je pod prahem
|
||||
v_cost_per_kwh := ems.fn_heat_pump_cost_per_kwh_heat(
|
||||
p_heat_pump_id, p_outdoor_temp_c, p_buy_price_czk_kwh
|
||||
);
|
||||
|
||||
RETURN v_cost_per_kwh <= p_max_cost_threshold;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_heat_pump_should_run(INT, TIMESTAMPTZ, NUMERIC, NUMERIC, NUMERIC, NUMERIC) IS
|
||||
'Rozhodne zda má tepelné čerpadlo v daném intervalu běžet.
|
||||
Logika priorit:
|
||||
1. Pokud existuje override block_heat_pump → false.
|
||||
2. Pokud teplota zásobníku pod tuv_min_temp_c → true (povinný ohřev).
|
||||
3. Pokud zásobník nad tuv_max_temp_c → false.
|
||||
4. Jinak ekonomické rozhodnutí: spustit pokud cena tepla ≤ p_max_cost_threshold Kč/kWh.
|
||||
Výhodné časy jsou přes poledne v chladných měsících (vyšší venkovní teplota = lepší COP).';
|
||||
61
db/routines/R__fn_effective_price.sql
Normal file
61
db/routines/R__fn_effective_price.sql
Normal file
@@ -0,0 +1,61 @@
|
||||
-- =============================================================
|
||||
-- R__fn_effective_price.sql
|
||||
-- EMS Platform – funkce pro výpočet efektivní ceny per site
|
||||
-- Repeatable migration – nasazuje se při každé změně
|
||||
-- =============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_effective_buy_price(
|
||||
p_site_id INT,
|
||||
p_interval_start TIMESTAMPTZ
|
||||
)
|
||||
RETURNS NUMERIC(10,6)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
AS $$
|
||||
SELECT
|
||||
mip.buy_raw_price_czk_kwh
|
||||
+ smc.buy_margin_fixed_czk
|
||||
+ (mip.buy_raw_price_czk_kwh * smc.buy_margin_percent / 100.0)
|
||||
FROM ems.market_interval_price mip
|
||||
CROSS JOIN ems.site_market_config smc
|
||||
WHERE mip.market_source = 'OTE_CZ'
|
||||
AND mip.interval_start = p_interval_start
|
||||
AND smc.site_id = p_site_id
|
||||
AND smc.valid_from <= p_interval_start
|
||||
AND (smc.valid_to IS NULL OR smc.valid_to > p_interval_start)
|
||||
ORDER BY smc.valid_from DESC
|
||||
LIMIT 1;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_effective_buy_price(INT, TIMESTAMPTZ) IS
|
||||
'Vrátí efektivní nákupní cenu elektřiny v Kč/kWh pro danou lokalitu a 15min interval.
|
||||
Přičítá fixní a procentní nákupní marži dle aktuálně platné site_market_config.';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_effective_sell_price(
|
||||
p_site_id INT,
|
||||
p_interval_start TIMESTAMPTZ
|
||||
)
|
||||
RETURNS NUMERIC(10,6)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
AS $$
|
||||
SELECT
|
||||
mip.sell_raw_price_czk_kwh
|
||||
+ smc.sell_margin_fixed_czk
|
||||
+ (mip.sell_raw_price_czk_kwh * smc.sell_margin_percent / 100.0)
|
||||
FROM ems.market_interval_price mip
|
||||
CROSS JOIN ems.site_market_config smc
|
||||
WHERE mip.market_source = 'OTE_CZ'
|
||||
AND mip.interval_start = p_interval_start
|
||||
AND smc.site_id = p_site_id
|
||||
AND smc.valid_from <= p_interval_start
|
||||
AND (smc.valid_to IS NULL OR smc.valid_to > p_interval_start)
|
||||
ORDER BY smc.valid_from DESC
|
||||
LIMIT 1;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_effective_sell_price(INT, TIMESTAMPTZ) IS
|
||||
'Vrátí efektivní prodejní cenu elektřiny v Kč/kWh pro danou lokalitu a 15min interval.
|
||||
Aplikuje fixní a procentní prodejní marži (záporná marže = srážka z prodejní ceny).';
|
||||
171
db/routines/R__fn_fill_audit_interval.sql
Normal file
171
db/routines/R__fn_fill_audit_interval.sql
Normal file
@@ -0,0 +1,171 @@
|
||||
-- =============================================================
|
||||
-- R__fn_fill_audit_interval.sql
|
||||
-- EMS Platform – plnění audit_interval ze skutečné telemetrie
|
||||
-- Repeatable migration
|
||||
-- =============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_fill_audit_interval(
|
||||
p_site_id INT,
|
||||
p_interval_start TIMESTAMPTZ
|
||||
)
|
||||
RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_interval_end TIMESTAMPTZ := p_interval_start + INTERVAL '15 minutes';
|
||||
v_run_id INT;
|
||||
v_avg_pv_power_w INT;
|
||||
v_avg_battery_power_w INT;
|
||||
v_avg_grid_power_w INT;
|
||||
v_avg_load_power_w INT;
|
||||
v_last_soc NUMERIC(5,2);
|
||||
v_sum_ev_power_w INT;
|
||||
v_avg_hp_power_w INT;
|
||||
v_plan ems.planning_interval%ROWTYPE;
|
||||
v_buy_price NUMERIC;
|
||||
v_sell_price NUMERIC;
|
||||
v_actual_cost NUMERIC := NULL;
|
||||
BEGIN
|
||||
-- Najít aktivní plán pro tento interval
|
||||
SELECT pi.* INTO v_plan
|
||||
FROM ems.planning_interval pi
|
||||
JOIN ems.planning_run pr ON pr.id = pi.run_id
|
||||
WHERE pr.site_id = p_site_id
|
||||
AND pi.interval_start = p_interval_start
|
||||
AND pr.status IN ('active', 'superseded')
|
||||
ORDER BY pr.created_at DESC
|
||||
LIMIT 1;
|
||||
|
||||
v_run_id := v_plan.run_id;
|
||||
|
||||
-- Agregovat telemetrii střídače (průměr za 15min; agregace bez GROUP BY vrací vždy 1 řádek)
|
||||
SELECT
|
||||
AVG(pv_power_w)::INT,
|
||||
AVG(battery_power_w)::INT,
|
||||
AVG(grid_power_w)::INT,
|
||||
AVG(load_power_w)::INT,
|
||||
LAST(battery_soc_percent, measured_at)
|
||||
INTO
|
||||
v_avg_pv_power_w,
|
||||
v_avg_battery_power_w,
|
||||
v_avg_grid_power_w,
|
||||
v_avg_load_power_w,
|
||||
v_last_soc
|
||||
FROM ems.telemetry_inverter
|
||||
WHERE site_id = p_site_id
|
||||
AND measured_at >= p_interval_start
|
||||
AND measured_at < v_interval_end;
|
||||
|
||||
-- Agregovat EV nabíječky (součet průměrů po charger_id)
|
||||
SELECT COALESCE(SUM(avg_power), 0)::INT
|
||||
INTO v_sum_ev_power_w
|
||||
FROM (
|
||||
SELECT AVG(power_w) AS avg_power
|
||||
FROM ems.telemetry_ev_charger
|
||||
WHERE site_id = p_site_id
|
||||
AND measured_at >= p_interval_start
|
||||
AND measured_at < v_interval_end
|
||||
GROUP BY charger_id
|
||||
) sub;
|
||||
|
||||
-- Agregovat tepelné čerpadlo
|
||||
SELECT AVG(power_w)::INT
|
||||
INTO v_avg_hp_power_w
|
||||
FROM ems.telemetry_heat_pump
|
||||
WHERE site_id = p_site_id
|
||||
AND measured_at >= p_interval_start
|
||||
AND measured_at < v_interval_end;
|
||||
|
||||
-- Efektivní cena pro výpočet skutečných nákladů
|
||||
v_buy_price := ems.fn_effective_buy_price(p_site_id, p_interval_start);
|
||||
v_sell_price := ems.fn_effective_sell_price(p_site_id, p_interval_start);
|
||||
|
||||
-- Skutečné náklady (kladný grid = nákup, záporný = prodej)
|
||||
IF v_avg_grid_power_w IS NOT NULL THEN
|
||||
v_actual_cost := (v_avg_grid_power_w::NUMERIC / 1000.0 / 4.0)
|
||||
* CASE WHEN v_avg_grid_power_w >= 0
|
||||
THEN COALESCE(v_buy_price, 0)
|
||||
ELSE COALESCE(v_sell_price, 0) END;
|
||||
END IF;
|
||||
|
||||
-- Upsert do audit_interval
|
||||
INSERT INTO ems.audit_interval (
|
||||
site_id, interval_start, planning_run_id,
|
||||
actual_pv_power_w, actual_battery_power_w,
|
||||
actual_grid_power_w, actual_load_power_w,
|
||||
actual_battery_soc_pct,
|
||||
actual_ev_power_w,
|
||||
actual_heat_pump_power_w,
|
||||
actual_cost_czk,
|
||||
deviation_grid_w,
|
||||
deviation_cost_czk
|
||||
) VALUES (
|
||||
p_site_id, p_interval_start, v_run_id,
|
||||
v_avg_pv_power_w,
|
||||
v_avg_battery_power_w,
|
||||
v_avg_grid_power_w,
|
||||
v_avg_load_power_w,
|
||||
v_last_soc,
|
||||
v_sum_ev_power_w,
|
||||
v_avg_hp_power_w,
|
||||
ROUND(v_actual_cost, 4),
|
||||
CASE WHEN v_plan.run_id IS NOT NULL
|
||||
THEN v_avg_grid_power_w - v_plan.grid_setpoint_w
|
||||
ELSE NULL END,
|
||||
CASE WHEN v_plan.run_id IS NOT NULL
|
||||
THEN ROUND(v_actual_cost - COALESCE(v_plan.expected_cost_czk, 0), 4)
|
||||
ELSE NULL END
|
||||
)
|
||||
ON CONFLICT (site_id, interval_start) DO UPDATE SET
|
||||
planning_run_id = EXCLUDED.planning_run_id,
|
||||
actual_pv_power_w = EXCLUDED.actual_pv_power_w,
|
||||
actual_battery_power_w = EXCLUDED.actual_battery_power_w,
|
||||
actual_grid_power_w = EXCLUDED.actual_grid_power_w,
|
||||
actual_load_power_w = EXCLUDED.actual_load_power_w,
|
||||
actual_battery_soc_pct = EXCLUDED.actual_battery_soc_pct,
|
||||
actual_ev_power_w = EXCLUDED.actual_ev_power_w,
|
||||
actual_heat_pump_power_w = EXCLUDED.actual_heat_pump_power_w,
|
||||
actual_cost_czk = EXCLUDED.actual_cost_czk,
|
||||
deviation_grid_w = EXCLUDED.deviation_grid_w,
|
||||
deviation_cost_czk = EXCLUDED.deviation_cost_czk;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_fill_audit_interval(INT, TIMESTAMPTZ) IS
|
||||
'Naplní nebo aktualizuje jeden řádek v audit_interval pro danou lokalitu a 15min interval.
|
||||
Agreguje průměry z telemetrie (střídač, EV, TČ), porovná se skutečným plánem a spočítá odchylky.
|
||||
Volat každých 15 minut pro interval který právě skončil.';
|
||||
|
||||
-- ============================================================
|
||||
-- Hromadné plnění auditu za historické období
|
||||
-- ============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_fill_audit_range(
|
||||
p_site_id INT,
|
||||
p_from TIMESTAMPTZ,
|
||||
p_to TIMESTAMPTZ
|
||||
)
|
||||
RETURNS INT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_slot TIMESTAMPTZ;
|
||||
v_count INT := 0;
|
||||
BEGIN
|
||||
v_slot := date_trunc('hour', p_from)
|
||||
+ INTERVAL '15 min' * FLOOR(EXTRACT(MINUTE FROM p_from) / 15);
|
||||
|
||||
WHILE v_slot < p_to LOOP
|
||||
PERFORM ems.fn_fill_audit_interval(p_site_id, v_slot);
|
||||
v_slot := v_slot + INTERVAL '15 minutes';
|
||||
v_count := v_count + 1;
|
||||
END LOOP;
|
||||
|
||||
RETURN v_count;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_fill_audit_range(INT, TIMESTAMPTZ, TIMESTAMPTZ) IS
|
||||
'Hromadně naplní audit_interval pro celé historické období.
|
||||
Volá fn_fill_audit_interval pro každý 15min slot v rozsahu p_from–p_to.
|
||||
Vrátí počet zpracovaných intervalů. Použít pro backfill po výpadku nebo prvním nasazení.';
|
||||
160
db/routines/R__fn_set_mode.sql
Normal file
160
db/routines/R__fn_set_mode.sql
Normal file
@@ -0,0 +1,160 @@
|
||||
-- =============================================================
|
||||
-- R__fn_set_mode.sql
|
||||
-- EMS Platform – přepínání provozních režimů
|
||||
-- Repeatable migration
|
||||
-- =============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_set_mode(
|
||||
p_site_id INT,
|
||||
p_mode_code TEXT,
|
||||
p_activated_by TEXT DEFAULT 'system',
|
||||
p_valid_until TIMESTAMPTZ DEFAULT NULL,
|
||||
p_notes TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_current_mode TEXT;
|
||||
v_mode_exists BOOLEAN;
|
||||
BEGIN
|
||||
-- Ověřit že režim existuje
|
||||
SELECT EXISTS(SELECT 1 FROM ems.operating_mode_def WHERE code = p_mode_code)
|
||||
INTO v_mode_exists;
|
||||
|
||||
IF NOT v_mode_exists THEN
|
||||
RAISE EXCEPTION 'Neznámý provozní režim: %', p_mode_code;
|
||||
END IF;
|
||||
|
||||
-- Zjistit aktuální režim (pro log a previous_mode)
|
||||
SELECT mode_code INTO v_current_mode
|
||||
FROM ems.site_operating_mode
|
||||
WHERE site_id = p_site_id;
|
||||
|
||||
-- Pokud se režim nemění, nic nedělat
|
||||
IF v_current_mode = p_mode_code THEN
|
||||
RETURN p_mode_code;
|
||||
END IF;
|
||||
|
||||
-- Uzavřít předchozí záznam v logu
|
||||
UPDATE ems.site_operating_mode_log
|
||||
SET deactivated_at = now()
|
||||
WHERE site_id = p_site_id
|
||||
AND deactivated_at IS NULL;
|
||||
|
||||
-- Upsert aktivního režimu
|
||||
INSERT INTO ems.site_operating_mode
|
||||
(site_id, mode_code, activated_at, activated_by, valid_until, previous_mode, notes)
|
||||
VALUES
|
||||
(p_site_id, p_mode_code, now(), p_activated_by, p_valid_until, v_current_mode, p_notes)
|
||||
ON CONFLICT (site_id) DO UPDATE SET
|
||||
mode_code = EXCLUDED.mode_code,
|
||||
activated_at = EXCLUDED.activated_at,
|
||||
activated_by = EXCLUDED.activated_by,
|
||||
valid_until = EXCLUDED.valid_until,
|
||||
previous_mode = EXCLUDED.previous_mode,
|
||||
notes = EXCLUDED.notes;
|
||||
|
||||
-- Přidat záznam do logu
|
||||
INSERT INTO ems.site_operating_mode_log
|
||||
(site_id, mode_code, activated_at, activated_by, notes)
|
||||
VALUES
|
||||
(p_site_id, p_mode_code, now(), p_activated_by, p_notes);
|
||||
|
||||
RETURN p_mode_code;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_set_mode(INT, TEXT, TEXT, TIMESTAMPTZ, TEXT) IS
|
||||
'Přepne provozní režim lokality. Atomicky aktualizuje site_operating_mode a zapíše do audit logu.
|
||||
Ignoruje přepnutí na stejný režim. Vyhodí výjimku pro neznámý kód režimu.
|
||||
Příklad: SELECT ems.fn_set_mode(1, ''SELF_SUSTAIN'', ''user:jan'', NULL, ''Odjezd na dovolenou'');';
|
||||
|
||||
-- ============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_restore_previous_mode(
|
||||
p_site_id INT,
|
||||
p_activated_by TEXT DEFAULT 'system'
|
||||
)
|
||||
RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_previous TEXT;
|
||||
BEGIN
|
||||
SELECT previous_mode INTO v_previous
|
||||
FROM ems.site_operating_mode
|
||||
WHERE site_id = p_site_id;
|
||||
|
||||
IF v_previous IS NULL THEN
|
||||
-- Fallback na AUTO pokud není předchozí režim
|
||||
v_previous := 'AUTO';
|
||||
END IF;
|
||||
|
||||
RETURN ems.fn_set_mode(p_site_id, v_previous, p_activated_by, NULL, 'Obnova předchozího režimu');
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_restore_previous_mode(INT, TEXT) IS
|
||||
'Přepne lokalitu zpět na předchozí provozní režim (uložený v previous_mode).
|
||||
Pokud předchozí režim neexistuje, přepne na AUTO. Používat po skončení dočasného přepisu.';
|
||||
|
||||
-- ============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_expire_modes()
|
||||
RETURNS INT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_count INT := 0;
|
||||
v_rec RECORD;
|
||||
BEGIN
|
||||
-- Najít lokality kde vypršel valid_until a přepnout na AUTO
|
||||
FOR v_rec IN
|
||||
SELECT site_id, previous_mode
|
||||
FROM ems.site_operating_mode
|
||||
WHERE valid_until IS NOT NULL
|
||||
AND valid_until <= now()
|
||||
AND mode_code <> 'AUTO'
|
||||
LOOP
|
||||
PERFORM ems.fn_set_mode(
|
||||
v_rec.site_id,
|
||||
COALESCE(v_rec.previous_mode, 'AUTO'),
|
||||
'system:expiry',
|
||||
NULL,
|
||||
'Automatické vypršení dočasného režimu'
|
||||
);
|
||||
v_count := v_count + 1;
|
||||
END LOOP;
|
||||
|
||||
RETURN v_count;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_expire_modes() IS
|
||||
'Zkontroluje všechny lokality s dočasným režimem (valid_until IS NOT NULL) a přepne zpět ty s prosahlým časem.
|
||||
Volat každou minutu jako scheduled task. Vrátí počet přepnutých lokalit.';
|
||||
|
||||
-- ============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_update_heartbeat(
|
||||
p_site_id INT,
|
||||
p_status TEXT DEFAULT 'ok',
|
||||
p_ems_version TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS VOID
|
||||
LANGUAGE sql
|
||||
AS $$
|
||||
INSERT INTO ems.site_heartbeat (site_id, last_seen, status, ems_version)
|
||||
VALUES (p_site_id, now(), p_status, p_ems_version)
|
||||
ON CONFLICT (site_id) DO UPDATE SET
|
||||
last_seen = now(),
|
||||
status = EXCLUDED.status,
|
||||
ems_version = COALESCE(EXCLUDED.ems_version, ems.site_heartbeat.ems_version);
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_update_heartbeat(INT, TEXT, TEXT) IS
|
||||
'Aktualizuje informační heartbeat záznam EMS pro danou lokalitu.
|
||||
Volat každou minutu z backend service po úspěšném odeslání pulzu do Loxone.
|
||||
Slouží pouze pro EMS dashboard – Loxone watchdog nezávisí na této tabulce,
|
||||
sleduje HTTP pulzy přímo a nezávisle na dostupnosti DB.';
|
||||
Reference in New Issue
Block a user