From 743b74d78819f6f5d2e925170dd0f63cc135f108 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Sun, 5 Apr 2026 03:06:19 +0200 Subject: [PATCH] opravy chybejicich contraints --- backend/services/price_importer.py | 6 +- ...032__market_interval_price_primary_key.sql | 27 +++++++++ ...033__telemetry_hypertable_primary_keys.sql | 58 +++++++++++++++++++ db/routines/R__fn_ote_import.sql | 55 ++++++++++-------- 4 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 db/migration/V032__market_interval_price_primary_key.sql create mode 100644 db/migration/V033__telemetry_hypertable_primary_keys.sql diff --git a/backend/services/price_importer.py b/backend/services/price_importer.py index fe2eb48..fac41f3 100644 --- a/backend/services/price_importer.py +++ b/backend/services/price_importer.py @@ -186,5 +186,7 @@ async def import_ote_prices( ) return int(n), date_str, float(first_price or 0.0), None except Exception as e: - logger.error("OTE import DB error: %s", e) - return -1, date_str, 0.0, f"db_import:{e.__class__.__name__}" + detail = str(e).strip() or e.__class__.__name__ + logger.error("OTE import DB error: %s", detail, exc_info=True) + short = detail[:200] if len(detail) > 200 else detail + return -1, date_str, 0.0, f"db_import:{e.__class__.__name__}: {short}" diff --git a/db/migration/V032__market_interval_price_primary_key.sql b/db/migration/V032__market_interval_price_primary_key.sql new file mode 100644 index 0000000..021ac5b --- /dev/null +++ b/db/migration/V032__market_interval_price_primary_key.sql @@ -0,0 +1,27 @@ +-- OTE import (fn_ote_import_from_json) používá ON CONFLICT (market_source, interval_start). +-- Bez odpovídajícího UNIQUE/PK PostgreSQL hlásí: +-- "there is no unique or exclusion constraint matching the ON CONFLICT specification" +-- Některé instalace (ruční DB, nestandardní pořadí migrací, restore) mohly zůstat bez PK. + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_catalog.pg_class r + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' + AND r.relname = 'market_interval_price' + AND r.relkind = 'r' + ) AND NOT EXISTS ( + SELECT 1 + FROM pg_catalog.pg_constraint c + JOIN pg_catalog.pg_class r ON r.oid = c.conrelid + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' + AND r.relname = 'market_interval_price' + AND c.contype = 'p' + ) THEN + ALTER TABLE ems.market_interval_price + ADD CONSTRAINT market_interval_price_pkey PRIMARY KEY (market_source, interval_start); + END IF; +END $$; diff --git a/db/migration/V033__telemetry_hypertable_primary_keys.sql b/db/migration/V033__telemetry_hypertable_primary_keys.sql new file mode 100644 index 0000000..0fc187b --- /dev/null +++ b/db/migration/V033__telemetry_hypertable_primary_keys.sql @@ -0,0 +1,58 @@ +-- telemetry_collector: INSERT … ON CONFLICT vyžaduje UNIQUE/PK odpovídající cíli konfliktu. +-- Stejná třída problému jako u market_interval_price (V032): DB bez primárních klíčů z V001. + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_catalog.pg_class r + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' AND r.relname = 'telemetry_inverter' AND r.relkind = 'r' + ) AND NOT EXISTS ( + SELECT 1 FROM pg_catalog.pg_constraint c + JOIN pg_catalog.pg_class r ON r.oid = c.conrelid + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' AND r.relname = 'telemetry_inverter' AND c.contype = 'p' + ) THEN + ALTER TABLE ems.telemetry_inverter + ADD CONSTRAINT telemetry_inverter_pkey PRIMARY KEY (inverter_id, measured_at); + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_catalog.pg_class r + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' AND r.relname = 'telemetry_ev_charger' AND r.relkind = 'r' + ) AND NOT EXISTS ( + SELECT 1 FROM pg_catalog.pg_constraint c + JOIN pg_catalog.pg_class r ON r.oid = c.conrelid + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' AND r.relname = 'telemetry_ev_charger' AND c.contype = 'p' + ) THEN + ALTER TABLE ems.telemetry_ev_charger + ADD CONSTRAINT telemetry_ev_charger_pkey PRIMARY KEY (charger_id, connector_id, measured_at); + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_catalog.pg_class r + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' AND r.relname = 'telemetry_heat_pump' AND r.relkind = 'r' + ) AND NOT EXISTS ( + SELECT 1 FROM pg_catalog.pg_constraint c + JOIN pg_catalog.pg_class r ON r.oid = c.conrelid + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = 'ems' AND r.relname = 'telemetry_heat_pump' AND c.contype = 'p' + ) THEN + ALTER TABLE ems.telemetry_heat_pump + ADD CONSTRAINT telemetry_heat_pump_pkey PRIMARY KEY (heat_pump_id, measured_at); + END IF; +END $$; + +-- Jeden otevřený řádek session na nabíječku (V020); bez indexu spadne ON CONFLICT v telemetry_collector. +CREATE UNIQUE INDEX IF NOT EXISTS uidx_ev_session_charger_open + ON ems.ev_session (charger_id) + WHERE session_end IS NULL; diff --git a/db/routines/R__fn_ote_import.sql b/db/routines/R__fn_ote_import.sql index 9edf374..2606a8c 100644 --- a/db/routines/R__fn_ote_import.sql +++ b/db/routines/R__fn_ote_import.sql @@ -38,33 +38,38 @@ BEGIN END IF; v_market_date := to_date(v_date_text, 'DD.MM.YYYY'); + -- Řazení přes poddotaz: některé verze PG/rozšíření hlásí 42P10 při ORDER BY + -- přímo v RETURN QUERY set-returning funkce volané z INSERT ... SELECT. RETURN QUERY - WITH price_line AS ( - -- Správná série: 15min ceny (tooltip rozlišuje od Množství a 60min) - SELECT dl - FROM jsonb_array_elements(in_payload #> '{data,dataLine}') AS t(dl) - WHERE dl ->> 'tooltip' = 'flash_chart_01_y_15m_price_tooltip' - LIMIT 1 - ), - points AS ( + SELECT s.interval_start, s.interval_end, s.raw_price_czk_kwh + FROM ( + WITH price_line AS ( + SELECT dl + FROM jsonb_array_elements(in_payload #> '{data,dataLine}') AS t(dl) + WHERE dl ->> 'tooltip' = 'flash_chart_01_y_15m_price_tooltip' + LIMIT 1 + ), + points AS ( + SELECT + (p ->> 'x')::int AS block_no, + (p ->> 'y')::numeric AS price_eur_mwh + FROM price_line pl + CROSS JOIN LATERAL jsonb_array_elements(pl.dl -> 'point') AS p + ) SELECT - (p ->> 'x')::int AS block_no, - (p ->> 'y')::numeric AS price_eur_mwh - FROM price_line pl - CROSS JOIN LATERAL jsonb_array_elements(pl.dl -> 'point') AS p - ) - SELECT - ((v_market_date::timestamp - + ((block_no - 1) * INTERVAL '15 minutes')) - AT TIME ZONE 'Europe/Prague') AS interval_start, - ((v_market_date::timestamp - + (block_no * INTERVAL '15 minutes')) - AT TIME ZONE 'Europe/Prague') AS interval_end, - ROUND( - (price_eur_mwh * in_czk_per_eur / 1000.0)::numeric, 6 - )::numeric(10,6) AS raw_price_czk_kwh - FROM points - ORDER BY block_no; + ((v_market_date::timestamp + + ((block_no - 1) * INTERVAL '15 minutes')) + AT TIME ZONE 'Europe/Prague') AS interval_start, + ((v_market_date::timestamp + + (block_no * INTERVAL '15 minutes')) + AT TIME ZONE 'Europe/Prague') AS interval_end, + ROUND( + (price_eur_mwh * in_czk_per_eur / 1000.0)::numeric, 6 + )::numeric(10,6) AS raw_price_czk_kwh, + block_no + FROM points + ) AS s + ORDER BY s.block_no; IF NOT FOUND THEN RAISE EXCEPTION