fix letni /zimni cas OTE
All checks were successful
deploy / deploy (push) Successful in 19s
test / smoke-test (push) Successful in 5s

This commit is contained in:
Dusan Vojacek
2026-04-12 21:57:37 +02:00
parent 5919b6caf3
commit f0dfcefd54
4 changed files with 62 additions and 40 deletions

View File

@@ -19,6 +19,7 @@ AS $$
DECLARE
v_date_text text;
v_market_date date;
v_anchor timestamptz;
v_dl jsonb;
v_npts int;
BEGIN
@@ -39,6 +40,10 @@ BEGIN
in_payload #>> '{graph,title}';
END IF;
v_market_date := to_date(v_date_text, 'DD.MM.YYYY');
-- Začátek obchodního dne v Praze jako absolutní okamžik; pak +15 min v UTC.
-- Starý vzor (timestamp + interval) AT TIME ZONE při přechodu na letní čas
-- slučoval různé sloty → duplicitní interval_start a chyba ON CONFLICT.
v_anchor := v_market_date::timestamp AT TIME ZONE 'Europe/Prague';
-- OTE mění strukturu: novější data = 15min série (tooltip flash_chart_01_y_15m_*),
-- starší / jiná odpověď = jen 24 hodinových bodů s tooltip "Cena".
@@ -60,7 +65,15 @@ BEGIN
SELECT t.dl INTO v_dl
FROM jsonb_array_elements(in_payload #> '{data,dataLine}') AS t(dl)
WHERE dl ->> 'tooltip' = 'Cena'
AND jsonb_array_length(dl -> 'point') BETWEEN 20 AND 28
AND jsonb_array_length(dl -> 'point') >= 90
LIMIT 1;
END IF;
IF v_dl IS NULL THEN
SELECT t.dl INTO v_dl
FROM jsonb_array_elements(in_payload #> '{data,dataLine}') AS t(dl)
WHERE dl ->> 'tooltip' = 'Cena'
AND jsonb_array_length(dl -> 'point') BETWEEN 2 AND 28
LIMIT 1;
END IF;
@@ -68,7 +81,7 @@ BEGIN
SELECT t.dl INTO v_dl
FROM jsonb_array_elements(in_payload #> '{data,dataLine}') AS t(dl)
WHERE dl ->> 'tooltip' = 'flash_chart_01_y_15m_price_tooltip'
AND jsonb_array_length(dl -> 'point') BETWEEN 20 AND 28
AND jsonb_array_length(dl -> 'point') BETWEEN 2 AND 28
LIMIT 1;
END IF;
@@ -83,17 +96,13 @@ BEGIN
v_npts := jsonb_array_length(v_dl -> 'point');
IF v_npts >= 90 THEN
-- x = 1..N = 15min bloky (typicky 96; 92/100 při přechodu letní/zimní čas)
-- x = 1..N = 15min bloky (96 běžně; 92 při zkráceném dni při přechodu na letní čas)
RETURN QUERY
SELECT s.interval_start, s.interval_end, s.raw_price_czk_kwh
FROM (
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,
(v_anchor + ((block_no - 1) * INTERVAL '15 minutes')) AS interval_start,
(v_anchor + (block_no * INTERVAL '15 minutes')) AS interval_end,
ROUND(
(price_eur_mwh * in_czk_per_eur / 1000.0)::numeric, 6
)::numeric(10,6) AS raw_price_czk_kwh,
@@ -106,19 +115,17 @@ BEGIN
) pts
) AS s
ORDER BY s.block_no;
ELSE
-- x = 1..24 = hodiny dne; každou hodinovou cenu rozvineme na 4× 15min slot
ELSIF v_npts BETWEEN 2 AND 28 THEN
-- x = 1..H = hodiny (ne nutně celý den); každou hodinovou cenu 4× 15min slot
RETURN QUERY
SELECT s.interval_start, s.interval_end, s.raw_price_czk_kwh
FROM (
SELECT
((v_market_date::timestamp
+ (((hour_no - 1) * 4 + qix) * INTERVAL '15 minutes'))
AT TIME ZONE 'Europe/Prague') AS interval_start,
((v_market_date::timestamp
+ (((hour_no - 1) * 4 + qix) * INTERVAL '15 minutes')
(v_anchor + (((hour_no - 1) * 4 + qix) * INTERVAL '15 minutes'))
AS interval_start,
(v_anchor + (((hour_no - 1) * 4 + qix) * INTERVAL '15 minutes')
+ INTERVAL '15 minutes')
AT TIME ZONE 'Europe/Prague') AS interval_end,
AS interval_end,
ROUND(
(price_eur_mwh * in_czk_per_eur / 1000.0)::numeric, 6
)::numeric(10,6) AS raw_price_czk_kwh,
@@ -130,6 +137,11 @@ BEGIN
CROSS JOIN (VALUES (0), (1), (2), (3)) AS q(qix)
) AS s
ORDER BY s.sort_key;
ELSE
RAISE EXCEPTION
'OTE price series: unexpected point count % (tooltip %)',
v_npts,
v_dl ->> 'tooltip';
END IF;
IF NOT FOUND THEN
@@ -140,10 +152,9 @@ $$;
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).
Výběr série: (1) flash_chart_01_y_15m_price_tooltip s ≥90 body, (2) flash_chart_01_y_60m_price_tooltip s ≥90,
(3) tooltip Cena s 2028 body (hodinovka → 4× 15min stejná EUR/MWh), (4) legacy 15m tooltip s 2028 body.
EUR/MWh → Kč/kWh přes kurz. Výstup: řádky po 15 min (typicky 96).
Datum z graph.title (DD.MM.YYYY). Sloty: kotva půlnoci Europe/Prague jako timestamptz + násobky 15 min (bez DST duplicit).
Výběr série: flash_15m ≥90, flash_60m ≥90, Cena ≥90, Cena 228 h, flash_15m 228 h.
EUR/MWh → Kč/kWh. Typicky 96 řádků, 92 při zkráceném dni (letní čas).
Testování přímo v DB:
SELECT * FROM ems.fn_ote_parse_15m_price_json(pg_read_file(''/tmp/ote.json'')::jsonb, 25.0) LIMIT 5;';
@@ -167,7 +178,7 @@ BEGIN
currency,
imported_at
)
SELECT
SELECT DISTINCT ON (p.interval_start)
'OTE_CZ',
p.interval_start,
p.interval_end,
@@ -176,6 +187,7 @@ BEGIN
'CZK',
now()
FROM ems.fn_ote_parse_15m_price_json(in_payload, in_czk_per_eur) AS p
ORDER BY p.interval_start, p.interval_end DESC
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,