Files
ems/db/routines/R__082_fn_ote_day_signals_prague.sql
Dusan Vojacek 6074535d96
Some checks failed
CI and deploy / migration-check (push) Failing after 25s
CI and deploy / deploy (push) Has been skipped
OTE informatin discord
2026-04-29 14:17:24 +02:00

170 lines
7.0 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- OTE CZ: signály pro "briefing" dalšího dne (TZ Europe/Prague)
--
-- Cíl: vrátit pár srozumitelných signálů (negativní/okolo nuly/špička),
-- které se dají posílat do Discordu bez logiky v Pythonu.
create or replace function ems.fn_ote_day_signals_prague(
p_day date,
p_lookback_days int default 14
)
returns jsonb
language sql
stable
as $fn$
with params as (
select
greatest(1, least(coalesce(p_lookback_days, 14), 60))::int as lookback_days,
0.25::numeric as zeroish_abs_czk_kwh,
4::numeric as spike_interesting_czk_kwh,
6::numeric as spike_extreme_czk_kwh
),
day_slots as (
select
mip.interval_start,
(mip.interval_start at time zone 'Europe/Prague') as local_ts,
extract(hour from mip.interval_start at time zone 'Europe/Prague')::int as local_hour,
mip.buy_raw_price_czk_kwh as price
from ems.market_interval_price mip
where mip.market_source = 'OTE_CZ'
and (mip.interval_start at time zone 'Europe/Prague')::date = p_day
),
day_agg as (
select
count(*)::int as slot_count,
min(price) as min_price,
max(price) as max_price,
avg(price) as avg_price,
count(*) filter (where price < 0)::int as neg_slots,
count(*) filter (where abs(price) <= (select zeroish_abs_czk_kwh from params))::int as zeroish_slots,
min(price) filter (where local_hour between 10 and 14) as noon_min_price,
avg(price) filter (where local_hour between 10 and 14) as noon_avg_price,
max(price) filter (where local_hour between 6 and 9) as morning_max_price,
max(price) filter (where local_hour between 17 and 21) as evening_max_price,
max(price) filter (where local_hour between 17 and 21)
- avg(price) filter (where local_hour between 10 and 14) as evening_minus_noon
from day_slots
),
hist_hours as (
select
(mip.interval_start at time zone 'Europe/Prague')::date as d,
extract(hour from mip.interval_start at time zone 'Europe/Prague')::int as h,
avg(mip.buy_raw_price_czk_kwh) as avg_price
from ems.market_interval_price mip
cross join params p
where mip.market_source = 'OTE_CZ'
and (mip.interval_start at time zone 'Europe/Prague')::date < p_day
and (mip.interval_start at time zone 'Europe/Prague')::date >= p_day - (p.lookback_days || ' days')::interval
group by 1, 2
),
hist_windows as (
select
avg(avg_price) filter (where h between 17 and 21) as avg_evening,
avg(avg_price) filter (where h between 6 and 9) as avg_morning,
avg(avg_price) filter (where h between 10 and 14) as avg_noon
from hist_hours
),
signals as (
select jsonb_agg(s order by (s ->> 'severity') desc, (s ->> 'code')) as arr
from (
-- negativní
select jsonb_build_object(
'code', 'NEG_EXTREME',
'severity', 3,
'title', 'extrémně záporné ceny',
'detail', format('min %.3f Kč/kWh, záporné sloty %s', (select min_price from day_agg), (select neg_slots from day_agg))
) s
where (select min_price from day_agg) <= -0.50
or (select neg_slots from day_agg) >= 8
union all
select jsonb_build_object(
'code', 'NEG_PRESENT',
'severity', 2,
'title', 'záporné ceny',
'detail', format('min %.3f Kč/kWh, záporné sloty %s', (select min_price from day_agg), (select neg_slots from day_agg))
) s
where (select min_price from day_agg) < 0
and not (
(select min_price from day_agg) <= -0.50
or (select neg_slots from day_agg) >= 8
)
-- okolo nuly přes den
union all
select jsonb_build_object(
'code', 'NOON_ZEROISH',
'severity', 2,
'title', 'poledne okolo nuly',
'detail', format('polední průměr %.3f Kč/kWh (1014)', (select noon_avg_price from day_agg))
) s
where coalesce((select noon_avg_price from day_agg), 999) <= (select zeroish_abs_czk_kwh from params)
union all
select jsonb_build_object(
'code', 'MANY_ZEROISH',
'severity', 1,
'title', 'hodně slotů okolo nuly',
'detail', format('okolo nuly slotů %s (|p| ≤ %.2f Kč/kWh)', (select zeroish_slots from day_agg), (select zeroish_abs_czk_kwh from params))
) s
where (select zeroish_slots from day_agg) >= 16
-- špička večer (hard)
union all
select jsonb_build_object(
'code', 'EVENING_SPIKE_EXTREME',
'severity', 3,
'title', 'večer extrémně drahý',
'detail', format('max večer %.3f Kč/kWh (1721)', (select evening_max_price from day_agg))
) s
where coalesce((select evening_max_price from day_agg), 0) >= (select spike_extreme_czk_kwh from params)
union all
select jsonb_build_object(
'code', 'EVENING_SPIKE_INTERESTING',
'severity', 2,
'title', 'večer drahý',
'detail', format('max večer %.3f Kč/kWh (1721)', (select evening_max_price from day_agg))
) s
where coalesce((select evening_max_price from day_agg), 0) >= (select spike_interesting_czk_kwh from params)
and coalesce((select evening_max_price from day_agg), 0) < (select spike_extreme_czk_kwh from params)
-- špička večer (relativní vůči posledním N dnům)
union all
select jsonb_build_object(
'code', 'EVENING_SPIKE_REL',
'severity', 2,
'title', 'večer nadprůměrná špička',
'detail', format(
'max večer %.3f vs. průměr %.3f (lookback %s dní)',
(select evening_max_price from day_agg),
(select avg_evening from hist_windows),
(select lookback_days from params)
)
) s
where (select avg_evening from hist_windows) is not null
and coalesce((select evening_max_price from day_agg), 0) >= (select avg_evening from hist_windows) + 1.5
) t
)
select jsonb_build_object(
'day', p_day,
'lookback_days', (select lookback_days from params),
'metrics', jsonb_build_object(
'slot_count', (select slot_count from day_agg),
'min_price', round(coalesce((select min_price from day_agg), 0)::numeric, 6),
'max_price', round(coalesce((select max_price from day_agg), 0)::numeric, 6),
'avg_price', round(coalesce((select avg_price from day_agg), 0)::numeric, 6),
'neg_slots', (select neg_slots from day_agg),
'zeroish_slots', (select zeroish_slots from day_agg),
'noon_avg_price', round(coalesce((select noon_avg_price from day_agg), 0)::numeric, 6),
'evening_max_price', round(coalesce((select evening_max_price from day_agg), 0)::numeric, 6),
'avg_evening_last_n', round(coalesce((select avg_evening from hist_windows), 0)::numeric, 6),
'avg_noon_last_n', round(coalesce((select avg_noon from hist_windows), 0)::numeric, 6)
),
'signals', coalesce((select arr from signals), '[]'::jsonb)
);
$fn$;
comment on function ems.fn_ote_day_signals_prague(date, int) is
'Signály pro briefing OTE_CZ cen pro p_day (negativní/okolo nuly/špička), včetně relativní špičky proti posledním N dnům.';