Initial commit

Made-with: Cursor
This commit is contained in:
Dusan Vojacek
2026-03-20 13:27:37 +01:00
commit 8b4af663d8
77 changed files with 13337 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
-- =============================================================
-- R__vw_audit_summary.sql
-- EMS Platform přehledové views pro audit a dashboard
-- Repeatable migration
-- =============================================================
-- Denní souhrn per lokalita
CREATE OR REPLACE VIEW ems.vw_audit_daily AS
SELECT
site_id,
date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague') AS day_local,
COUNT(*) AS interval_count,
-- Energie (kWh = W × 15min / 60 = W / 4 / 1000)
ROUND(SUM(GREATEST(actual_grid_power_w, 0))::NUMERIC / 4000, 3) AS import_kwh,
ROUND(SUM(ABS(LEAST(actual_grid_power_w, 0)))::NUMERIC / 4000, 3) AS export_kwh,
ROUND(SUM(GREATEST(actual_pv_power_w, 0))::NUMERIC / 4000, 3) AS pv_kwh,
ROUND(SUM(GREATEST(actual_load_power_w, 0))::NUMERIC / 4000, 3) AS load_kwh,
ROUND(SUM(GREATEST(actual_ev_power_w, 0))::NUMERIC / 4000, 3) AS ev_kwh,
ROUND(SUM(GREATEST(actual_heat_pump_power_w, 0))::NUMERIC / 4000, 3) AS hp_kwh,
-- Náklady
ROUND(SUM(actual_cost_czk), 2) AS actual_cost_czk,
ROUND(SUM(deviation_cost_czk), 2) AS total_deviation_czk,
-- Počet intervalů s velkými odchylkami (>1kW)
COUNT(*) FILTER (WHERE ABS(deviation_grid_w) > 1000) AS high_deviation_count
FROM ems.audit_interval
GROUP BY site_id, date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague');
COMMENT ON VIEW ems.vw_audit_daily IS
'Denní souhrn auditu per lokalita. Energie v kWh, náklady v Kč.
Používat pro dashboard denního přehledu a reporty.';
-- ============================================================
-- Týdenní souhrn
CREATE OR REPLACE VIEW ems.vw_audit_weekly AS
SELECT
site_id,
date_trunc('week', interval_start AT TIME ZONE 'Europe/Prague') AS week_local,
ROUND(SUM(GREATEST(actual_grid_power_w, 0))::NUMERIC / 4000, 1) AS import_kwh,
ROUND(SUM(ABS(LEAST(actual_grid_power_w, 0)))::NUMERIC / 4000, 1) AS export_kwh,
ROUND(SUM(GREATEST(actual_pv_power_w, 0))::NUMERIC / 4000, 1) AS pv_kwh,
ROUND(SUM(actual_cost_czk), 0) AS actual_cost_czk
FROM ems.audit_interval
GROUP BY site_id, date_trunc('week', interval_start AT TIME ZONE 'Europe/Prague');
COMMENT ON VIEW ems.vw_audit_weekly IS
'Týdenní souhrn auditu per lokalita.';
-- ============================================================
-- Aktuální den hourly breakdown pro dashboard graf
CREATE OR REPLACE VIEW ems.vw_audit_today_hourly AS
SELECT
site_id,
date_trunc('hour', interval_start AT TIME ZONE 'Europe/Prague') AS hour_local,
ROUND(AVG(actual_pv_power_w)::NUMERIC / 1000, 2) AS avg_pv_kw,
ROUND(AVG(actual_battery_power_w)::NUMERIC / 1000, 2) AS avg_battery_kw,
ROUND(AVG(actual_grid_power_w)::NUMERIC / 1000, 2) AS avg_grid_kw,
ROUND(AVG(actual_load_power_w)::NUMERIC / 1000, 2) AS avg_load_kw,
ROUND(AVG(actual_battery_soc_pct), 1) AS avg_soc_pct,
ROUND(SUM(actual_cost_czk), 2) AS cost_czk
FROM ems.audit_interval
WHERE interval_start >= date_trunc('day', now() AT TIME ZONE 'Europe/Prague')
AND interval_start < date_trunc('day', now() AT TIME ZONE 'Europe/Prague') + INTERVAL '1 day'
GROUP BY site_id, date_trunc('hour', interval_start AT TIME ZONE 'Europe/Prague')
ORDER BY hour_local;
COMMENT ON VIEW ems.vw_audit_today_hourly IS
'Hodinový přehled dnešního dne pro dashboard graf výkonů a nákladů.';

View File

@@ -0,0 +1,77 @@
-- =============================================================
-- R__vw_latest_telemetry.sql
-- EMS Platform aktuální stav všech zařízení per lokalita
-- Repeatable migration
-- =============================================================
CREATE OR REPLACE VIEW ems.vw_latest_inverter AS
SELECT DISTINCT ON (t.inverter_id)
t.site_id,
t.inverter_id,
inv.code AS inverter_code,
t.measured_at,
t.pv_power_w,
t.battery_soc_percent,
t.battery_power_w,
t.grid_power_w,
t.load_power_w,
t.inverter_temp_c,
t.operating_mode,
t.fault_code,
now() - t.measured_at AS data_age
FROM ems.telemetry_inverter t
JOIN ems.asset_inverter inv ON inv.id = t.inverter_id
ORDER BY t.inverter_id, t.measured_at DESC;
COMMENT ON VIEW ems.vw_latest_inverter IS
'Nejnovější telemetrická data pro každý střídač. Slouží pro real-time dashboard a health check.';
-- ------------------------------------------------------------
CREATE OR REPLACE VIEW ems.vw_latest_ev_charger AS
SELECT DISTINCT ON (t.charger_id, t.connector_id)
t.site_id,
t.charger_id,
ch.code AS charger_code,
t.connector_id,
t.measured_at,
t.status,
t.power_w,
t.energy_kwh,
t.current_a,
t.session_id,
t.error_code,
now() - t.measured_at AS data_age
FROM ems.telemetry_ev_charger t
JOIN ems.asset_ev_charger ch ON ch.id = t.charger_id
ORDER BY t.charger_id, t.connector_id, t.measured_at DESC;
COMMENT ON VIEW ems.vw_latest_ev_charger IS
'Nejnovější telemetrická data pro každý konektor EV nabíječky. Slouží pro dashboard a řízení nabíjení.';
-- ------------------------------------------------------------
CREATE OR REPLACE VIEW ems.vw_latest_heat_pump AS
SELECT DISTINCT ON (t.heat_pump_id)
t.site_id,
t.heat_pump_id,
hp.code AS heat_pump_code,
t.measured_at,
t.outdoor_temp_c,
t.tuv_tank_temp_c,
t.water_outlet_temp_c,
t.power_w,
t.operating_mode,
t.cop_actual,
t.defrost_active,
t.alarm_code,
-- Odhadovaný COP pro aktuální venkovní teplotu
ems.fn_cop_estimate(t.heat_pump_id, t.outdoor_temp_c) AS cop_estimated,
now() - t.measured_at AS data_age
FROM ems.telemetry_heat_pump t
JOIN ems.asset_heat_pump hp ON hp.id = t.heat_pump_id
ORDER BY t.heat_pump_id, t.measured_at DESC;
COMMENT ON VIEW ems.vw_latest_heat_pump IS
'Nejnovější telemetrická data pro každé tepelné čerpadlo včetně odhadovaného COP.
Slouží pro real-time dashboard a rozhodovací logiku plánování.';

View File

@@ -0,0 +1,75 @@
-- =============================================================
-- R__vw_operating_mode.sql
-- EMS Platform views pro provozní režimy a heartbeat
-- Repeatable migration
-- =============================================================
-- Aktuální stav všech lokalit (pro dashboard a PostgREST)
CREATE OR REPLACE VIEW ems.vw_site_status AS
SELECT
s.id AS site_id,
s.code AS site_code,
s.name AS site_name,
m.mode_code AS active_mode,
d.name AS mode_name,
d.description AS mode_description,
d.is_autonomous,
m.activated_at,
m.activated_by,
m.valid_until,
m.previous_mode,
m.notes AS mode_notes,
-- Heartbeat
hb.last_seen AS ems_last_seen,
hb.status AS ems_status,
EXTRACT(EPOCH FROM (now() - hb.last_seen))::INT AS ems_age_seconds,
-- Varování pokud EMS dlouho nepingoval
CASE
WHEN hb.last_seen IS NULL THEN 'never_seen'
WHEN now() - hb.last_seen > INTERVAL '5 minutes' THEN 'stale'
WHEN now() - hb.last_seen > INTERVAL '2 minutes' THEN 'delayed'
ELSE 'ok'
END AS ems_heartbeat_status,
-- Aktuální telemetrie (snapshot)
li.pv_power_w,
li.battery_soc_percent,
li.battery_power_w,
li.grid_power_w,
li.load_power_w,
li.measured_at AS telemetry_at
FROM ems.site s
LEFT JOIN ems.site_operating_mode m ON m.site_id = s.id
LEFT JOIN ems.operating_mode_def d ON d.code = m.mode_code
LEFT JOIN ems.site_heartbeat hb ON hb.site_id = s.id
LEFT JOIN ems.vw_latest_inverter li ON li.site_id = s.id
WHERE s.active = true;
COMMENT ON VIEW ems.vw_site_status IS
'Kompletní stavový přehled lokality: aktivní režim, heartbeat EMS, aktuální telemetrie.
Primární view pro dashboard a health check endpoint. Jeden řádek na aktivní lokalitu.
ems_heartbeat_status slouží pro EMS vlastní alerting Loxone watchdog tuto tabulku nečte,
sleduje HTTP pulzy přímo (viz docs/loxone-integration.md).';
-- ============================================================
-- Log přepnutí režimů (pro UI historii)
CREATE OR REPLACE VIEW ems.vw_mode_log_recent AS
SELECT
l.id,
l.site_id,
s.code AS site_code,
l.mode_code,
d.name AS mode_name,
l.activated_at,
l.deactivated_at,
EXTRACT(EPOCH FROM COALESCE(l.deactivated_at, now()) - l.activated_at)::INT AS duration_sec,
l.activated_by,
l.notes
FROM ems.site_operating_mode_log l
JOIN ems.site s ON s.id = l.site_id
JOIN ems.operating_mode_def d ON d.code = l.mode_code
WHERE l.activated_at >= now() - INTERVAL '7 days'
ORDER BY l.activated_at DESC;
COMMENT ON VIEW ems.vw_mode_log_recent IS
'Posledních 7 dní přepnutí provozních režimů. Slouží pro audit log v UI.';

View File

@@ -0,0 +1,42 @@
-- =============================================================
-- R__vw_site_effective_price.sql
-- EMS Platform view efektivních cen per site
-- Repeatable migration
-- =============================================================
CREATE OR REPLACE VIEW ems.vw_site_effective_price AS
SELECT
smc.site_id,
mip.interval_start,
mip.interval_end,
mip.market_source,
-- Raw ceny
mip.buy_raw_price_czk_kwh,
mip.sell_raw_price_czk_kwh,
-- Marže
smc.buy_margin_fixed_czk,
smc.buy_margin_percent,
smc.sell_margin_fixed_czk,
smc.sell_margin_percent,
-- Efektivní ceny
ROUND(
mip.buy_raw_price_czk_kwh
+ smc.buy_margin_fixed_czk
+ (mip.buy_raw_price_czk_kwh * smc.buy_margin_percent / 100.0),
6
) AS effective_buy_price_czk_kwh,
ROUND(
mip.sell_raw_price_czk_kwh
+ smc.sell_margin_fixed_czk
+ (mip.sell_raw_price_czk_kwh * smc.sell_margin_percent / 100.0),
6
) AS effective_sell_price_czk_kwh
FROM ems.market_interval_price mip
CROSS JOIN ems.site_market_config smc
WHERE smc.valid_from <= mip.interval_start
AND (smc.valid_to IS NULL OR smc.valid_to > mip.interval_start);
COMMENT ON VIEW ems.vw_site_effective_price IS
'Efektivní nákupní a prodejní ceny elektřiny per lokalita a 15min interval.
Dopočítává marže z site_market_config na raw ceny z market_interval_price.
Nezahrnuje data bez platné market_config. Používat pro plánování a audit.';