create or replace function ems.fn_site_configuration(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 null::jsonb else jsonb_build_object( 'site', ( select to_jsonb(x) from ( select s.id, s.code, s.name, s.timezone, s.latitude::float8 as latitude, s.longitude::float8 as longitude, s.active, s.notes, s.created_at from ems.site s where s.id = p_site_id ) x ), 'grid_connection', ( select to_jsonb(g.*) from ems.site_grid_connection g where g.site_id = p_site_id limit 1 ), 'market_config', ( select to_jsonb(m.*) from ems.site_market_config m where m.site_id = p_site_id and m.valid_from <= now() and (m.valid_to is null or m.valid_to > now()) order by m.valid_from desc limit 1 ), 'market_config_note', 'Zelený bonus za výrobu je u FVE polí (asset_pv_array), ne v obchodní konfiguraci.', 'endpoints', coalesce( ( select jsonb_agg( to_jsonb(e.*) || jsonb_build_object( 'auth_reference', case when e.auth_reference is null or btrim(e.auth_reference::text) = '' then null::text when length(btrim(e.auth_reference::text)) <= 4 then 'nastaveno'::text else concat('…', right(btrim(e.auth_reference::text), 2)) end ) order by e.id ) from ems.site_endpoint e where e.site_id = p_site_id ), '[]'::jsonb ), 'inverters', coalesce( ( select jsonb_agg( ( ( to_jsonb(ai.*) - 'deye_last_system_time_sync_at'::text - 'deye_last_system_time_sync_minute'::text - 'deye_last_tou_inactive_write_prague_date'::text - 'deye_tou_inactive_signature'::text ) || jsonb_build_object( 'endpoint_connection', ( select ep.host || case when ep.port is not null then ':' || ep.port::text else '' end from ems.site_endpoint ep where ep.id = ai.endpoint_id ), 'deye_meta', case when jsonb_strip_nulls( jsonb_build_object( 'deye_last_system_time_sync_at', to_jsonb(ai.deye_last_system_time_sync_at), 'deye_last_system_time_sync_minute', to_jsonb(ai.deye_last_system_time_sync_minute), 'deye_last_tou_inactive_write_prague_date', to_jsonb(ai.deye_last_tou_inactive_write_prague_date), 'deye_tou_inactive_signature', to_jsonb(ai.deye_tou_inactive_signature) ) ) = '{}'::jsonb then null::jsonb else jsonb_strip_nulls( jsonb_build_object( 'deye_last_system_time_sync_at', to_jsonb(ai.deye_last_system_time_sync_at), 'deye_last_system_time_sync_minute', to_jsonb(ai.deye_last_system_time_sync_minute), 'deye_last_tou_inactive_write_prague_date', to_jsonb(ai.deye_last_tou_inactive_write_prague_date), 'deye_tou_inactive_signature', to_jsonb(ai.deye_tou_inactive_signature) ) ) end ) ) order by ai.id ) from ems.asset_inverter ai where ai.site_id = p_site_id ), '[]'::jsonb ), 'batteries', coalesce( ( select jsonb_agg(to_jsonb(b.*) order by b.id) from ems.asset_battery b where b.site_id = p_site_id ), '[]'::jsonb ), 'pv_arrays', coalesce( ( select jsonb_agg(to_jsonb(p.*) order by p.id) from ems.asset_pv_array p where p.site_id = p_site_id ), '[]'::jsonb ), 'ev_chargers', coalesce( ( select jsonb_agg( to_jsonb(ec.*) || jsonb_build_object( 'endpoint_connection', se.host || case when se.port is not null then ':' || se.port::text else '' end ) order by ec.id ) from ems.asset_ev_charger ec left join ems.site_endpoint se on se.id = ec.endpoint_id where ec.site_id = p_site_id ), '[]'::jsonb ), 'vehicles', coalesce( ( select jsonb_agg( to_jsonb(v.*) || jsonb_build_object( 'api_reference', case when v.api_reference is null or btrim(v.api_reference::text) = '' then null::text when length(btrim(v.api_reference::text)) <= 4 then 'nastaveno'::text else concat('…', right(btrim(v.api_reference::text), 2)) end ) order by v.code ) from ems.asset_vehicle v where v.site_id = p_site_id ), '[]'::jsonb ), 'heat_pumps', coalesce( ( select jsonb_agg( to_jsonb(hp.*) || jsonb_build_object( 'endpoint_connection', se.host || case when se.port is not null then ':' || se.port::text else '' end ) order by hp.id ) from ems.asset_heat_pump hp left join ems.site_endpoint se on se.id = hp.endpoint_id where hp.site_id = p_site_id ), '[]'::jsonb ), 'operating_mode', ( select to_jsonb(om) from ( select m.mode_code, m.activated_at, m.activated_by, m.valid_until, m.previous_mode, m.notes, d.name as mode_name, d.description as mode_description, d.loxone_mode_value, d.ev_enabled, d.heat_pump_enabled, d.battery_mode, d.grid_mode, d.is_autonomous 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 ) om ), 'active_overrides', coalesce( ( select jsonb_agg(to_jsonb(o.*) order by o.valid_from desc) from ( select * from ems.site_override so where so.site_id = p_site_id and so.valid_from <= now() and (so.valid_to is null or so.valid_to > now()) order by so.valid_from desc limit 50 ) o ), '[]'::jsonb ), 'pv_forecast_calibration', ( select to_jsonb(c.*) from ems.site_pv_forecast_calibration c where c.site_id = p_site_id ), 'operational', jsonb_build_object( 'heartbeat_last_seen', (select hb.last_seen from ems.site_heartbeat hb where hb.site_id = p_site_id limit 1), 'heartbeat_status', (select hb.status from ems.site_heartbeat hb where hb.site_id = p_site_id limit 1), 'has_active_plan', exists ( select 1 from ems.planning_run pr where pr.site_id = p_site_id and pr.status = 'active' ), 'active_plan_created_at', ( select 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 ) ) ) end; $fn$; comment on function ems.fn_site_configuration(int) is 'GET /configuration jako jeden JSON bundle (maskované reference, deye_meta).';