-- Řízení bazénového čerpadla (Phase 1, bez solveru): denní runtime budget z -- fn_pool_daily_runtime_min (teplotní nebo statický fallback) rozvržený do -- NEJLEVNĚJŠÍHO souvislého okna dne (efektivní nákupní cena), + dump-load overlay -- (záporná/nulová výkupní cena → absorbuj přebytek místo exportu se ztrátou). -- Výstup řídí Shelly relé přes signál POOL_PUMP_ON (fn_signal_enqueue_bool → -- signal_service). Žádné Modbus. Bazál (R__003) bazén odečítá → s tímto řízením -- se odečet stává správným (řízená + plánovaná zátěž). -- Rozhodnutí ON/OFF pro daný 15min slot. create or replace function ems.fn_pool_schedule_slot( p_pump_id int, p_slot_start timestamptz ) returns boolean language sql stable as $fn$ with cfg as ( select pp.id, pp.site_id, pp.schedulable, greatest(0, coalesce( (ems.fn_pool_daily_runtime_min(pp.id) ->> 'runtime_min')::int, 0 )) as runtime_min from ems.asset_pool_pump pp where pp.id = p_pump_id ), win as ( select c.site_id, ceil(c.runtime_min::numeric / 15.0)::int as w from cfg c ), -- sloty kalendářního dne slotu (Europe/Prague) s efektivní cenou day_slots as ( select ep.interval_start, ep.effective_buy_price_czk_kwh as buy, ep.effective_sell_price_czk_kwh as sell, row_number() over (order by ep.interval_start) as rn from ems.vw_site_effective_price ep join cfg c on c.site_id = ep.site_id where (ep.interval_start at time zone 'Europe/Prague')::date = (p_slot_start at time zone 'Europe/Prague')::date ), -- nejlevnější souvislé okno délky w slotů (self-join, ~96×w řádků = triviální) best as ( select s1.rn as start_rn from day_slots s1 join day_slots s2 on s2.rn >= s1.rn and s2.rn < s1.rn + (select w from win) where (select w from win) > 0 group by s1.rn having count(*) = (select w from win) order by sum(s2.buy) asc, s1.rn asc limit 1 ) select coalesce((select schedulable from cfg), false) and coalesce( -- v nejlevnějším souvislém okně budgetu exists ( select 1 from day_slots ds cross join best b where ds.interval_start = p_slot_start and ds.rn >= b.start_rn and ds.rn < b.start_rn + (select w from win) ) -- NEBO dump-load: záporná/nulová výkupní cena ⇒ raději zkonzumuj než exportuj se ztrátou or exists ( select 1 from day_slots ds where ds.interval_start = p_slot_start and ds.sell <= 0 ), false ); $fn$; comment on function ems.fn_pool_schedule_slot is 'Pool ON/OFF pro 15min slot (Phase 1, bez solveru): nejlevnější souvislé okno délky = daily runtime budget (fn_pool_daily_runtime_min) z vw_site_effective_price, NEBO dump-load při sell<=0. false když pump není schedulable / není cena pro den.'; -- Control tick: pro každý aktivní řiditelný bazén spočti stav slotu a zařaď signál -- POOL_PUMP_ON (idempotentně). Volá control smyčka každých 15 min (hranice slotu). -- Enqueue jen když existuje signal_route (jinak bezpečně nic — route se seeduje provozně). create or replace function ems.fn_pool_control_tick( p_now timestamptz default now() ) returns table( pump_id int, site_id int, desired_on boolean, runtime_min int, has_route boolean, enqueued int ) language plpgsql as $fn$ declare v_slot timestamptz; r record; v_on boolean; v_route boolean; begin v_slot := date_bin(interval '15 minutes', p_now, timestamptz '1970-01-01T00:00:00Z'); for r in select pp.id as pid, pp.site_id as sid, greatest(0, coalesce( (ems.fn_pool_daily_runtime_min(pp.id) ->> 'runtime_min')::int, 0 )) as rt from ems.asset_pool_pump pp join ems.site s on s.id = pp.site_id where s.active = true and pp.schedulable = true loop v_on := coalesce(ems.fn_pool_schedule_slot(r.pid, v_slot), false); v_route := exists ( select 1 from ems.signal_route sr where sr.site_id = r.sid and sr.signal_code = 'POOL_PUMP_ON' ); pump_id := r.pid; site_id := r.sid; desired_on := v_on; runtime_min := r.rt; has_route := v_route; enqueued := case when v_route then ems.fn_signal_enqueue_bool(r.sid, 'POOL_PUMP_ON', v_on) else 0 end; return next; end loop; end; $fn$; comment on function ems.fn_pool_control_tick is 'Control tick bazénu (každých 15 min): pro aktivní řiditelné pumpy spočte fn_pool_schedule_slot a zařadí POOL_PUMP_ON (jen když existuje signal_route). Shelly relé pak řídí signal_service. Bez Modbus.';