-- ============================================================= -- R__fn_ote_import.sql -- OTE CZ import – parser a import funkce -- Repeatable migration – při změně funkce stačí upravit tento soubor -- ============================================================= -- Parser: raw jsonb → 96 cenových řádků CREATE OR REPLACE FUNCTION ems.fn_ote_parse_15m_price_json( in_payload jsonb, in_czk_per_eur numeric DEFAULT 25.000 ) RETURNS TABLE ( interval_start timestamptz, interval_end timestamptz, raw_price_czk_kwh numeric(10,6) ) LANGUAGE plpgsql AS $$ DECLARE v_date_text text; v_market_date date; BEGIN IF in_payload IS NULL THEN RAISE EXCEPTION 'in_payload must not be null'; END IF; IF in_czk_per_eur IS NULL OR in_czk_per_eur <= 0 THEN RAISE EXCEPTION 'in_czk_per_eur must be > 0, got: %', in_czk_per_eur; END IF; -- Datum z graph.title ve formátu "... DD.MM.YYYY" v_date_text := substring( in_payload #>> '{graph,title}' FROM '([0-9]{2}\.[0-9]{2}\.[0-9]{4})' ); IF v_date_text IS NULL THEN RAISE EXCEPTION 'cannot parse date from graph.title: %', in_payload #>> '{graph,title}'; 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 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 ((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 'dataLine tooltip=flash_chart_01_y_15m_price_tooltip not found; ' 'dostupné tooltips: %', (SELECT jsonb_agg(dl ->> 'tooltip') FROM jsonb_array_elements(in_payload #> '{data,dataLine}') dl); END IF; END; $$; COMMENT ON FUNCTION ems.fn_ote_parse_15m_price_json(jsonb, numeric) IS 'Parsuje raw JSON z OTE @@chart-data?time_resolution=PT15M. Datum extrahuje z graph.title (DD.MM.YYYY). Série: flash_chart_01_y_15m_price_tooltip (EUR/MWh → Kč/kWh přes kurz). Výstup: 96 řádků, interval_start/end jako timestamptz (UTC), cena Kč/kWh. Testování přímo v DB: COPY tmp_ote FROM ''/tmp/ote.json''; SELECT * FROM ems.fn_ote_parse_15m_price_json(pg_read_file(''/tmp/ote.json'')::jsonb, 25.0) LIMIT 5;'; CREATE OR REPLACE FUNCTION ems.fn_ote_import_from_json( in_payload jsonb, in_czk_per_eur numeric DEFAULT 25.000 ) RETURNS integer LANGUAGE plpgsql AS $$ DECLARE v_rowcount integer; BEGIN INSERT INTO ems.market_interval_price ( market_source, interval_start, interval_end, buy_raw_price_czk_kwh, sell_raw_price_czk_kwh, currency, imported_at ) SELECT 'OTE_CZ', p.interval_start, p.interval_end, p.raw_price_czk_kwh, p.raw_price_czk_kwh, -- spot trh: buy = sell 'CZK', now() FROM ems.fn_ote_parse_15m_price_json(in_payload, in_czk_per_eur) AS p ON CONFLICT (market_source, interval_start) DO UPDATE SET interval_end = EXCLUDED.interval_end, buy_raw_price_czk_kwh = EXCLUDED.buy_raw_price_czk_kwh, sell_raw_price_czk_kwh = EXCLUDED.sell_raw_price_czk_kwh, imported_at = EXCLUDED.imported_at; GET DIAGNOSTICS v_rowcount = ROW_COUNT; RETURN v_rowcount; END; $$; COMMENT ON FUNCTION ems.fn_ote_import_from_json(jsonb, numeric) IS 'Uloží výstup fn_ote_parse_15m_price_json do ems.market_interval_price. Python předá raw jsonb z HTTP response + kurz EUR/CZK. Vrátí počet upsertnutých řádků (očekáváno 96). Testování přímo v DB: SELECT ems.fn_ote_import_from_json( pg_read_file(''/tmp/ote.json'')::jsonb, 25.0 );';