141 lines
4.5 KiB
PL/PgSQL
141 lines
4.5 KiB
PL/PgSQL
-- =============================================================
|
||
-- 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
|
||
);';
|