x
This commit is contained in:
@@ -73,8 +73,34 @@ SELECT create_hypertable(
|
||||
-- ============================================================
|
||||
-- Kompresní politiky pro staré chunky
|
||||
-- Telemetrie starší 30 dní komprimovat (čtení stačí)
|
||||
-- Nutné nejdřív zapnout kompresi na hypertable (TimescaleDB 2.x+ / Tiger Data),
|
||||
-- jinak add_compression_policy hlásí chybu o columnstore / compression.
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE ems.telemetry_inverter SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_orderby = 'measured_at DESC',
|
||||
timescaledb.compress_segmentby = 'site_id, inverter_id'
|
||||
);
|
||||
|
||||
ALTER TABLE ems.telemetry_ev_charger SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_orderby = 'measured_at DESC',
|
||||
timescaledb.compress_segmentby = 'site_id, charger_id, connector_id'
|
||||
);
|
||||
|
||||
ALTER TABLE ems.telemetry_heat_pump SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_orderby = 'measured_at DESC',
|
||||
timescaledb.compress_segmentby = 'site_id, heat_pump_id'
|
||||
);
|
||||
|
||||
ALTER TABLE ems.market_interval_price SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_orderby = 'interval_start DESC',
|
||||
timescaledb.compress_segmentby = 'market_source'
|
||||
);
|
||||
|
||||
SELECT add_compression_policy('ems.telemetry_inverter', INTERVAL '30 days', if_not_exists => TRUE);
|
||||
SELECT add_compression_policy('ems.telemetry_ev_charger', INTERVAL '30 days', if_not_exists => TRUE);
|
||||
SELECT add_compression_policy('ems.telemetry_heat_pump', INTERVAL '30 days', if_not_exists => TRUE);
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
-- =============================================================
|
||||
-- V003__seed_site_home01.sql
|
||||
-- EMS Platform – seed data první lokality home-01
|
||||
-- Doplnit: latitude, longitude, IP adresy, azimuty FVE polí
|
||||
--
|
||||
-- Deye Modbus (holding, SUN-20K) – viz docs/04-modules/telemetry.md:
|
||||
-- 0x0215 PV W, 0x0103 SoC %, 0x0105 bat W, 0x0101 bat V×0.1,
|
||||
-- 0x0169 grid W, 0x016F grid L1 V×0.1, 0x0213 load W,
|
||||
-- 0x0220 inv temp ×0.1, 0x0168 mode, 0x0180 fault
|
||||
-- Teltonika / Samsung registry: TODO – doplnit z dokumentace / Loxone šablony
|
||||
-- =============================================================
|
||||
|
||||
-- ============================================================
|
||||
@@ -13,8 +18,8 @@ VALUES (
|
||||
'home-01',
|
||||
'Hlavní objekt',
|
||||
'Europe/Prague',
|
||||
NULL, -- TODO: doplnit GPS
|
||||
NULL, -- TODO: doplnit GPS
|
||||
49.24466967511591,
|
||||
17.40658656876068,
|
||||
true,
|
||||
'První instalace. Deye 20kW + 64kWh baterie + 2x Teltonika EV + Samsung TČ.'
|
||||
);
|
||||
@@ -27,13 +32,11 @@ VALUES (
|
||||
INSERT INTO ems.site_endpoint (site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes)
|
||||
SELECT id, 'modbus_tcp', '192.168.1.100', 502, 'modbus_tcp', 1, true, 'Waveshare WS-ETH pro Deye SUN-20K. Unit ID dle DIP přepínače.'
|
||||
FROM ems.site WHERE code = 'home-01';
|
||||
-- TODO: doplnit skutečnou IP adresy Waveshare
|
||||
|
||||
-- Teltonika EV nabíječka 1 přes Waveshare
|
||||
INSERT INTO ems.site_endpoint (site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes)
|
||||
SELECT id, 'modbus_tcp', '192.168.1.101', 502, 'modbus_tcp', 1, true, 'Waveshare pro Teltonika TeltoCharge #1.'
|
||||
FROM ems.site WHERE code = 'home-01';
|
||||
-- TODO: doplnit IP a unit_id
|
||||
|
||||
-- Teltonika EV nabíječka 2 přes Waveshare
|
||||
INSERT INTO ems.site_endpoint (site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes)
|
||||
@@ -49,7 +52,6 @@ FROM ems.site WHERE code = 'home-01';
|
||||
INSERT INTO ems.site_endpoint (site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes)
|
||||
SELECT id, 'loxone_http', '192.168.1.10', 80, 'http', NULL, true, 'Loxone Miniserver – příjem setpointů přes Virtual HTTP Inputs.'
|
||||
FROM ems.site WHERE code = 'home-01';
|
||||
-- TODO: doplnit IP Loxone
|
||||
|
||||
-- ============================================================
|
||||
-- SÍŤOVÉ PŘIPOJENÍ
|
||||
@@ -126,12 +128,12 @@ INSERT INTO ems.asset_pv_array (site_id, inverter_id, code, name, nominal_power_
|
||||
SELECT
|
||||
s.id, inv.id, 'pv-a', 'FVE pole A',
|
||||
10000, -- 10 kWp
|
||||
NULL, -- TODO: doplnit azimut (0=jih)
|
||||
NULL, -- TODO: doplnit sklon (stupně)
|
||||
184,
|
||||
35, -- sklon odhad; upřesnit dle střechy
|
||||
NULL,
|
||||
1.0,
|
||||
true,
|
||||
'Hlavní FVE pole řízené Deye střídačem. Doplnit azimut a sklon.'
|
||||
'Hlavní FVE pole řízené Deye střídačem.'
|
||||
FROM ems.site s
|
||||
JOIN ems.asset_inverter inv ON inv.site_id = s.id AND inv.code = 'deye-main'
|
||||
WHERE s.code = 'home-01';
|
||||
@@ -141,8 +143,8 @@ INSERT INTO ems.asset_pv_array (site_id, inverter_id, code, name, nominal_power_
|
||||
SELECT
|
||||
s.id, inv.id, 'pv-b', 'FVE pole B (ongrid)',
|
||||
10000,
|
||||
NULL, -- TODO: doplnit azimut
|
||||
NULL, -- TODO: doplnit sklon
|
||||
184,
|
||||
35,
|
||||
NULL,
|
||||
1.0,
|
||||
false,
|
||||
@@ -187,15 +189,15 @@ INSERT INTO ems.asset_heat_pump (
|
||||
tuv_temp_sensor_ref, schedulable, notes
|
||||
)
|
||||
SELECT
|
||||
s.id, 'hp-samsung', 'Samsung', NULL, -- TODO: doplnit model
|
||||
s.id, 'hp-samsung', 'Samsung', 'EHS Mono (placeholder)',
|
||||
ep.id,
|
||||
NULL, -- TODO: doplnit jmenovitý výkon W
|
||||
NULL, -- TODO: doplnit COP rated
|
||||
12000, -- jmenovitý topný výkon W – upřesnit z datasheetu
|
||||
3.20, -- COP @ 7 °C – upřesnit
|
||||
7.0, -- referenční teplota A7/W35
|
||||
30, 15,
|
||||
NULL, -- TODO: doplnit objem zásobníku
|
||||
200, -- objem TUV zásobníku (l) – upřesnit
|
||||
45, 60, 55,
|
||||
NULL, -- TODO: doplnit odkaz na teplotní čidlo
|
||||
'TODO: Loxone / Modbus čidlo TUV',
|
||||
true,
|
||||
'Samsung tepelné čerpadlo s Modbus modulem. Řídit dle COP a venkovní teploty (výhodné kolem poledne v chladných měsících).'
|
||||
FROM ems.site s
|
||||
|
||||
5
db/migration/V008__asset_inverter_active.sql
Normal file
5
db/migration/V008__asset_inverter_active.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Zapnutí/vypnutí střídače pro sběr telemetrie a plánování (JOIN s endpointem zůstává nutný).
|
||||
ALTER TABLE ems.asset_inverter
|
||||
ADD COLUMN IF NOT EXISTS active BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
COMMENT ON COLUMN ems.asset_inverter.active IS 'Pokud false, střídač se přeskočí při sběru telemetrie a plánování.';
|
||||
26
db/migration/V009__postgrest_roles.sql
Normal file
26
db/migration/V009__postgrest_roles.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- Role pro PostgREST anonymní přístup (read-only).
|
||||
-- GRANT na views je v db/views/R__z_postgrest_ems_anon_grants.sql (Flyway je aplikuje až po R__vw_*).
|
||||
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'ems_anon') THEN
|
||||
CREATE ROLE ems_anon NOLOGIN;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
GRANT USAGE ON SCHEMA ems TO ems_anon;
|
||||
|
||||
-- Read-only na tabulky (existují po V001–V008)
|
||||
GRANT SELECT ON ems.market_interval_price TO ems_anon;
|
||||
GRANT SELECT ON ems.planning_run TO ems_anon;
|
||||
GRANT SELECT ON ems.planning_interval TO ems_anon;
|
||||
GRANT SELECT ON ems.forecast_pv_interval TO ems_anon;
|
||||
GRANT SELECT ON ems.forecast_pv_run TO ems_anon;
|
||||
GRANT SELECT ON ems.operating_mode_def TO ems_anon;
|
||||
GRANT SELECT ON ems.site_operating_mode TO ems_anon;
|
||||
GRANT SELECT ON ems.site_operating_mode_log TO ems_anon;
|
||||
GRANT SELECT ON ems.ev_session TO ems_anon;
|
||||
GRANT SELECT ON ems.asset_vehicle TO ems_anon;
|
||||
|
||||
COMMENT ON ROLE ems_anon IS
|
||||
'Anonymní role pro PostgREST. Read-only přístup na views a vybrané tabulky.
|
||||
Zápisy jdou výhradně přes FastAPI backend který má vlastní DB connection.';
|
||||
40
db/migration/V010__indexes.sql
Normal file
40
db/migration/V010__indexes.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- =============================================================
|
||||
-- V010__indexes.sql
|
||||
-- B-tree indexy pro časté dotazy (plán, telemetrie, ceny, audit, EV, režimy).
|
||||
-- Pozn.: idx_ev_session_active na (charger_id, session_end) je ve V006;
|
||||
-- zde idx_ev_session_site_active doplňuje vyhledávání aktivní session podle site.
|
||||
-- =============================================================
|
||||
|
||||
-- Planning (control exporter hledá aktivní plán pro aktuální slot)
|
||||
CREATE INDEX IF NOT EXISTS idx_planning_run_site_status
|
||||
ON ems.planning_run (site_id, status, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_planning_interval_run_start
|
||||
ON ems.planning_interval (run_id, interval_start);
|
||||
|
||||
-- Telemetrie (dashboard čte poslední hodnoty)
|
||||
CREATE INDEX IF NOT EXISTS idx_telemetry_inverter_site_time
|
||||
ON ems.telemetry_inverter (site_id, measured_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_telemetry_ev_site_time
|
||||
ON ems.telemetry_ev_charger (site_id, measured_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_telemetry_hp_site_time
|
||||
ON ems.telemetry_heat_pump (site_id, measured_at DESC);
|
||||
|
||||
-- Market prices (forecast + planning čte ceny pro horizont)
|
||||
CREATE INDEX IF NOT EXISTS idx_market_price_source_start
|
||||
ON ems.market_interval_price (market_source, interval_start);
|
||||
|
||||
-- Audit (dashboard čte dnešní data)
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_interval_site_start
|
||||
ON ems.audit_interval (site_id, interval_start DESC);
|
||||
|
||||
-- EV session (control exporter + UI hledá aktivní session podle lokality)
|
||||
CREATE INDEX IF NOT EXISTS idx_ev_session_site_active
|
||||
ON ems.ev_session (site_id, session_end)
|
||||
WHERE session_end IS NULL;
|
||||
|
||||
-- Operating mode log
|
||||
CREATE INDEX IF NOT EXISTS idx_mode_log_site_time
|
||||
ON ems.site_operating_mode_log (site_id, activated_at DESC);
|
||||
58
db/migration/V011__indexes_and_aggregates.sql
Normal file
58
db/migration/V011__indexes_and_aggregates.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- ============================================================
|
||||
-- V011__indexes_and_aggregates.sql
|
||||
-- Doplňuje V010__indexes.sql: indexy na forecast + hourly CA telemetrie.
|
||||
-- (Indexy planning_run, planning_interval, market_price, audit, mode_log,
|
||||
-- ev_session jsou již ve V010 – zde se neopakují, aby nevznikly duplicitní B-stromy.)
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- Indexy pro výkon (forecast – nové oproti V010)
|
||||
-- ============================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_forecast_run_site_array
|
||||
ON ems.forecast_pv_run (site_id, pv_array_id, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_forecast_interval_run_start
|
||||
ON ems.forecast_pv_interval (run_id, interval_start);
|
||||
|
||||
-- ============================================================
|
||||
-- TimescaleDB Continuous Aggregates pro dashboard výkon
|
||||
-- ============================================================
|
||||
|
||||
-- Hodinové agregáty telemetrie střídače (pro graf posledních 7 dní)
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS ems.telemetry_inverter_hourly
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket('1 hour', measured_at) AS hour,
|
||||
site_id,
|
||||
AVG(pv_power_w)::INT AS avg_pv_w,
|
||||
AVG(battery_power_w)::INT AS avg_battery_w,
|
||||
AVG(grid_power_w)::INT AS avg_grid_w,
|
||||
AVG(load_power_w)::INT AS avg_load_w,
|
||||
LAST(battery_soc_percent, measured_at) AS last_soc_pct,
|
||||
COUNT(*) AS sample_count
|
||||
FROM ems.telemetry_inverter
|
||||
GROUP BY hour, site_id
|
||||
WITH NO DATA;
|
||||
|
||||
-- Refresh policy: každých 15 minut. Okno musí pokrývat ≥2× time_bucket (1h) → min. šířka >2h.
|
||||
SELECT add_continuous_aggregate_policy(
|
||||
'ems.telemetry_inverter_hourly',
|
||||
start_offset => INTERVAL '2 hours 15 minutes',
|
||||
end_offset => INTERVAL '1 minute',
|
||||
schedule_interval => INTERVAL '15 minutes'
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- View pro použití v dashboardu (7 dní zpět)
|
||||
-- ============================================================
|
||||
|
||||
CREATE OR REPLACE VIEW ems.vw_telemetry_hourly_7d AS
|
||||
SELECT *
|
||||
FROM ems.telemetry_inverter_hourly
|
||||
WHERE hour >= now() - INTERVAL '7 days'
|
||||
ORDER BY hour DESC;
|
||||
|
||||
COMMENT ON VIEW ems.telemetry_inverter_hourly IS
|
||||
'Hodinové agregáty telemetrie střídače. TimescaleDB continuous aggregate.
|
||||
Refresh každých 15 minut. Používat pro grafy delší než 1 den.';
|
||||
Reference in New Issue
Block a user