create or replace function ems.fn_site_full_status(p_site_id int) returns jsonb language sql stable as $fn$ select case when not exists (select 1 from ems.site s0 where s0.id = p_site_id) then jsonb_build_object('error', 'not_found') else jsonb_build_object( 'site', ( select jsonb_build_object( 'id', s.id, 'code', s.code, 'name', s.name, 'timezone', s.timezone ) from ems.site s where s.id = p_site_id ), 'operating_mode', ( select jsonb_build_object( 'mode_code', m.mode_code, 'mode_name', d.name, 'activated_at', m.activated_at, 'activated_by', m.activated_by ) from ems.site_operating_mode m join ems.operating_mode_def d on d.code = m.mode_code where m.site_id = p_site_id ), 'heartbeat', ( select jsonb_build_object( 'last_seen', hb.last_seen, 'status', hb.status ) from ems.site_heartbeat hb where hb.site_id = p_site_id ), 'inverter_latest', ( select to_jsonb(li.*) from ems.vw_latest_inverter li where li.site_id = p_site_id order by li.measured_at desc nulls last limit 1 ), 'ev_chargers', coalesce( ( select jsonb_agg( jsonb_build_object( 'code', v.code, 'status', v.status, 'power_w', v.power_w, 'measured_at', v.measured_at ) order by v.measured_at desc nulls last ) from ( select distinct on (evc.charger_id) evc.charger_code as code, evc.status, evc.power_w, evc.measured_at from ems.vw_latest_ev_charger evc where evc.site_id = p_site_id order by evc.charger_id, evc.measured_at desc nulls last ) v ), '[]'::jsonb ), 'heat_pump_latest', ( select jsonb_build_object( 'power_w', hp.power_w, 'tuv_tank_temp_c', hp.tuv_tank_temp_c, 'measured_at', hp.measured_at ) from ems.vw_latest_heat_pump hp where hp.site_id = p_site_id order by hp.measured_at desc nulls last limit 1 ), 'battery_limits', ( select jsonb_build_object( 'reserve_soc', min(ab.reserve_soc_percent)::float, 'min_soc', min(ab.min_soc_percent)::float ) from ems.asset_battery ab where ab.site_id = p_site_id ), 'active_plan', ( select jsonb_build_object( 'id', pr.id, 'created_at', pr.created_at ) from ems.planning_run pr where pr.site_id = p_site_id and pr.status = 'active' order by pr.created_at desc limit 1 ), 'planning_intervals', coalesce( ( select jsonb_agg( jsonb_build_object( 'interval_start', pi.interval_start, 'battery_setpoint_w', pi.battery_setpoint_w, 'load_baseline_w', pi.load_baseline_w, 'pv_a_forecast_raw_w', pi.pv_a_forecast_raw_w, 'pv_b_forecast_raw_w', pi.pv_b_forecast_raw_w, 'pv_a_forecast_solver_w', pi.pv_a_forecast_solver_w, 'pv_b_forecast_solver_w', pi.pv_b_forecast_solver_w ) order by pi.interval_start ) from ems.planning_interval pi where pi.run_id = ( select pr2.id from ems.planning_run pr2 where pr2.site_id = p_site_id and pr2.status = 'active' order by pr2.created_at desc limit 1 ) ), '[]'::jsonb ), 'tomorrow_price_slot_count', ( select count(*)::int from ems.vw_site_effective_price vep where vep.site_id = p_site_id and (vep.interval_start at time zone coalesce( nullif(trim((select s2.timezone from ems.site s2 where s2.id = p_site_id)), ''), 'Europe/Prague' ))::date = ( ( current_timestamp at time zone coalesce( nullif(trim((select s3.timezone from ems.site s3 where s3.id = p_site_id)), ''), 'Europe/Prague' ) )::date + 1 ) ) ) end; $fn$; comment on function ems.fn_site_full_status(int) is 'Raw data pro GET /status/full (věk telemetrie a alerty dopočítá Python).';