-- Kontrola plán vs. audit (síť) po uzavření slotu: pravidla v DB, dedup insert, výstup pro Discord z Pythonu.

create or replace function ems.fn_plan_actual_slot_guard_site(
  p_site_id int,
  p_now timestamptz default now()
)
returns jsonb
language sql
volatile
as $fn$
  with v_code as (
    select s.code as site_code
    from ems.site s
    where s.id = p_site_id
  ),
  slots as (
    select distinct unnest(array[
      ems.fn_planning_slot_boundary_prague(-1, p_now),
      ems.fn_planning_slot_boundary_prague(-2, p_now)
    ]) as interval_start
  ),
  base as (
    select
      s.interval_start,
      ai.actual_grid_power_w,
      ai.deviation_grid_w,
      pi.grid_setpoint_w as plan_grid_w,
      pi.effective_sell_price as plan_sell_czk
    from slots s
    inner join ems.audit_interval ai
      on ai.site_id = p_site_id
     and ai.interval_start = s.interval_start
    left join ems.planning_interval pi
      on pi.run_id = ai.planning_run_id
     and pi.interval_start = ai.interval_start
  ),
  cls as (
    select
      b.interval_start,
      b.plan_grid_w,
      coalesce(b.actual_grid_power_w, 0) as actual_grid_w,
      b.deviation_grid_w,
      case
        when b.plan_grid_w is null or b.deviation_grid_w is null then null::text
        when coalesce(
            b.plan_sell_czk,
            ems.fn_effective_sell_price(p_site_id, b.interval_start)
          ) < 0
         and coalesce(b.actual_grid_power_w, 0) < -4000
          then 'NEG_SELL_EXPORT'
        when b.plan_grid_w < -2000 and coalesce(b.actual_grid_power_w, 0) > 2500
          then 'GRID_IMPORT_VS_EXPORT_PLAN'
        when b.plan_grid_w <> 0
         and coalesce(b.actual_grid_power_w, 0) <> 0
         and (b.plan_grid_w > 0)
         <> (coalesce(b.actual_grid_power_w, 0) > 0)
         and least(
           abs(b.plan_grid_w),
           abs(coalesce(b.actual_grid_power_w, 0))
         ) >= 400
          then 'GRID_SIGN_MISMATCH'
        when b.plan_grid_w > -1000 and coalesce(b.actual_grid_power_w, 0) < -4000
          then 'GRID_EXPORT_SPIKE'
        when abs(b.deviation_grid_w) >= 10000 and abs(b.plan_grid_w) <= 2500
          then 'GRID_LARGE_DEVIATION'
        else null::text
      end as reason_code,
      case
        when b.plan_grid_w is null or b.deviation_grid_w is null then null::text
        when coalesce(
            b.plan_sell_czk,
            ems.fn_effective_sell_price(p_site_id, b.interval_start)
          ) < 0
         and coalesce(b.actual_grid_power_w, 0) < -4000
          then format(
            'záporná vykupní %s Kč/kWh, skutečnost síť %s W (vývoz nad práh 4 kW)',
            round(
              coalesce(
                b.plan_sell_czk,
                ems.fn_effective_sell_price(p_site_id, b.interval_start)
              )::numeric,
              4
            ),
            coalesce(b.actual_grid_power_w, 0)
          )
        when b.plan_grid_w < -2000 and coalesce(b.actual_grid_power_w, 0) > 2500
          then format(
            'plán síť %s W vs skutečnost %s W (plán vývoz, skutečnost silný odběr)',
            b.plan_grid_w,
            coalesce(b.actual_grid_power_w, 0)
          )
        when b.plan_grid_w <> 0
         and coalesce(b.actual_grid_power_w, 0) <> 0
         and (b.plan_grid_w > 0)
         <> (coalesce(b.actual_grid_power_w, 0) > 0)
         and least(
           abs(b.plan_grid_w),
           abs(coalesce(b.actual_grid_power_w, 0))
         ) >= 400
          then format(
            'plán síť %s W vs skutečnost %s W (opačný směr import/export)',
            b.plan_grid_w,
            coalesce(b.actual_grid_power_w, 0)
          )
        when b.plan_grid_w > -1000 and coalesce(b.actual_grid_power_w, 0) < -4000
          then format(
            'plán síť %s W vs skutečnost %s W (neočekávaný silný vývoz)',
            b.plan_grid_w,
            coalesce(b.actual_grid_power_w, 0)
          )
        when abs(b.deviation_grid_w) >= 10000 and abs(b.plan_grid_w) <= 2500
          then format(
            'odchylka výkonu sítě %s W (plán %s W, skutečnost %s W)',
            b.deviation_grid_w,
            b.plan_grid_w,
            coalesce(b.actual_grid_power_w, 0)
          )
        else null::text
      end as detail_cs
    from base b
  ),
  ins as (
    insert into ems.plan_fatal_deviation_sent (site_id, interval_start, reason_code)
    select
      p_site_id,
      c.interval_start,
      c.reason_code
    from cls c
    where c.reason_code is not null
    on conflict (site_id, interval_start) do nothing
    returning interval_start, reason_code
  ),
  notified as (
    select
      c.interval_start,
      c.reason_code,
      c.detail_cs,
      c.plan_grid_w,
      c.actual_grid_w,
      c.deviation_grid_w
    from cls c
    inner join ins i
      on i.interval_start = c.interval_start
     and i.reason_code = c.reason_code
  )
  select
    case
      when not exists (select 1 from v_code) then
        jsonb_build_object('error', 'unknown_site', 'site_id', p_site_id)
      else
        jsonb_build_object(
          'site_id', p_site_id,
          'site_code', (select vc.site_code from v_code vc),
          'alerts', coalesce(
            (
              select coalesce(
                jsonb_agg(
                  jsonb_build_object(
                    'interval_start', n.interval_start,
                    'reason_code', n.reason_code,
                    'detail', n.detail_cs,
                    'plan_grid_w', n.plan_grid_w,
                    'actual_grid_w', n.actual_grid_w,
                    'deviation_grid_w', n.deviation_grid_w,
                    'notify', true
                  )
                  order by n.interval_start
                ),
                '[]'::jsonb
              )
              from notified n
            ),
            '[]'::jsonb
          )
        )
    end;
$fn$;

comment on function ems.fn_plan_actual_slot_guard_site(int, timestamptz) is
'Poslední 2 uzavřené 15min sloty: fatální odchylka síť plán vs. audit (včetně NEG_SELL_EXPORT při sell<0 a vývozu >4 kW) → insert plan_fatal_deviation_sent (dedup); JSON alerts pro Discord.';

create or replace function ems.fn_plan_actual_slot_guard_all_active(
  p_now timestamptz default now()
)
returns jsonb
language sql
volatile
as $fn$
  select coalesce(
    jsonb_agg(
      ems.fn_plan_actual_slot_guard_site((elem->>'id')::int, p_now)
      order by (elem->>'id')::int
    ),
    '[]'::jsonb
  )
  from jsonb_array_elements(ems.fn_vw_site_directory_active()) as t(elem);
$fn$;

comment on function ems.fn_plan_actual_slot_guard_all_active(timestamptz) is
'Projde aktivní lokality (fn_vw_site_directory_active) a zavolá fn_plan_actual_slot_guard_site; pole výsledků pro scheduler.';
