-- jeden EV session objekt pro LP (fn_planning_site_context).
-- Vrací jsonb objekt session na daném wallboxu, nebo null::jsonb pokud session
-- není nebo nemá použitelná data (kapacita vozidla, SoC při připojení).
--
-- KLÍČOVÝ ROZDÍL oproti dřívější inline logice (bug 2026-06-13): session se
-- NEVYŘAZUJE jen proto, že needed_wh = 0 (auto už nad targetem). Plánovač pak
-- neviděl ~6 kW zátěž auta a špatně rozvrhl baterii. Session zůstává v plánu,
-- dokud má oportunistický headroom (cena rozhodne, jestli se nabíjí) — měkký
-- cíl řeší solver dekompozicí Σ == needed − unmet + opp.
--
-- Vyřazení (null) jen když chybí tvrdá data:
--   - žádná otevřená session na wallboxu, nebo
--   - neznámá kapacita vozidla / SoC při připojení (nelze spočítat Wh).
-- target_deadline SMÍ být NULL (žádný tvrdý cíl) — solver to zvládá
--   (deadline constraint se aplikuje jen při needed_wh > 0).

drop function if exists ems.fn_ev_session_planning_json;

create or replace function ems.fn_ev_session_planning_json(
  p_site_id int,
  p_charger_code text
)
returns jsonb
language sql
stable
as $fn$
  select case
    when v.battery_capacity_kwh is null then null::jsonb
    when es.soc_at_connect_pct is null then null::jsonb
    else jsonb_build_object(
      -- tvrdý cíl: jen pokud je nastaven deadline I cílový SoC (jinak null →
      -- solver hard constraint vynechá, energy_needed_wh = 0).
      'target_deadline', case
        when coalesce(es.target_soc_pct, v.default_target_soc_pct) is null then null
        else es.target_deadline
      end,
      'energy_needed_wh', case
        when es.target_deadline is null then 0::numeric
        when coalesce(es.target_soc_pct, v.default_target_soc_pct) is null then 0::numeric
        else greatest(
          0,
          (coalesce(es.target_soc_pct, v.default_target_soc_pct)::numeric
            - es.soc_at_connect_pct::numeric) / 100.0
            * (v.battery_capacity_kwh * 1000)
          - coalesce(es.energy_delivered_wh, 0)::numeric
        )
      end,
      -- headroom do 100 % od max(target, SoC při připojení): „nenabíjet" (nízký
      -- target) nesmí ZVĚTŠIT oportunistickou vrstvu; auto fyzicky bere jen
      -- energii nad svým aktuálním SoC. Při vypnutém oportunismu (value <= 0)
      -- headroom = 0 — session zůstane v plánu, ale solver ji nebude doplňovat.
      'headroom_wh', case
        when coalesce(es.opportunistic_value_czk_kwh, v.opportunistic_value_czk_kwh, 0) > 0 then greatest(
          0,
          (100 - greatest(
            coalesce(es.target_soc_pct, v.default_target_soc_pct, es.soc_at_connect_pct)::numeric,
            es.soc_at_connect_pct::numeric
          )) / 100.0 * (v.battery_capacity_kwh * 1000)
        )
        else 0
      end,
      'opportunistic_value_czk_kwh',
        coalesce(es.opportunistic_value_czk_kwh, v.opportunistic_value_czk_kwh, 0)
    )
  end
  from ems.ev_session es
  join ems.asset_ev_charger ch on ch.id = es.charger_id
  left join ems.asset_vehicle v on v.id = es.vehicle_id
  where es.site_id = p_site_id
    and es.session_end is null
    and ch.code = p_charger_code
  limit 1;
$fn$;

comment on function ems.fn_ev_session_planning_json is
  'EV session objekt pro LP (fn_planning_site_context). Session se NEvyřazuje při needed_wh=0 (auto nad targetem) — zůstává v plánu kvůli oportunistickému headroomu i jako známá zátěž. Null jen bez použitelných dat (kapacita / soc_at_connect). target_deadline smí být NULL (bez tvrdého cíle).';
