-- Function to handle atomic node heartbeat and metrics update -- Updated to include Swapper metrics CREATE OR REPLACE FUNCTION fn_node_heartbeat( p_node_id text, p_metrics jsonb ) RETURNS void AS $$ BEGIN INSERT INTO node_cache ( node_id, node_name, hostname, roles, environment, status, gpu, last_sync, cpu_model, cpu_cores, cpu_usage, gpu_model, gpu_vram_total, gpu_vram_used, ram_total, ram_used, disk_total, disk_used, agent_count_router, agent_count_system, last_heartbeat, dagi_router_url, self_healing_status, swapper_healthy, swapper_models_loaded, swapper_models_total ) VALUES ( p_node_id, p_metrics->>'node_name', p_metrics->>'hostname', (SELECT array_agg(elem) FROM jsonb_array_elements_text(p_metrics->'roles') AS elem), p_metrics->>'environment', p_metrics->>'status', p_metrics->>'gpu', NOW(), p_metrics->>'cpu_model', (p_metrics->>'cpu_cores')::integer, (p_metrics->>'cpu_usage')::numeric, p_metrics->>'gpu_model', (p_metrics->>'gpu_vram_total')::integer, (p_metrics->>'gpu_vram_used')::integer, (p_metrics->>'ram_total')::integer, (p_metrics->>'ram_used')::integer, (p_metrics->>'disk_total')::integer, (p_metrics->>'disk_used')::integer, (p_metrics->>'agent_count_router')::integer, (p_metrics->>'agent_count_system')::integer, NOW(), p_metrics->>'dagi_router_url', p_metrics->>'self_healing_status', (p_metrics->>'swapper_healthy')::boolean, (p_metrics->>'swapper_models_loaded')::integer, (p_metrics->>'swapper_models_total')::integer ) ON CONFLICT (node_id) DO UPDATE SET node_name = COALESCE(EXCLUDED.node_name, node_cache.node_name), hostname = COALESCE(EXCLUDED.hostname, node_cache.hostname), roles = COALESCE(EXCLUDED.roles, node_cache.roles), environment = COALESCE(EXCLUDED.environment, node_cache.environment), status = COALESCE(EXCLUDED.status, node_cache.status), gpu = COALESCE(EXCLUDED.gpu, node_cache.gpu), last_sync = NOW(), cpu_model = COALESCE(EXCLUDED.cpu_model, node_cache.cpu_model), cpu_cores = COALESCE(EXCLUDED.cpu_cores, node_cache.cpu_cores), cpu_usage = COALESCE(EXCLUDED.cpu_usage, node_cache.cpu_usage), gpu_model = COALESCE(EXCLUDED.gpu_model, node_cache.gpu_model), gpu_vram_total = COALESCE(EXCLUDED.gpu_vram_total, node_cache.gpu_vram_total), gpu_vram_used = COALESCE(EXCLUDED.gpu_vram_used, node_cache.gpu_vram_used), ram_total = COALESCE(EXCLUDED.ram_total, node_cache.ram_total), ram_used = COALESCE(EXCLUDED.ram_used, node_cache.ram_used), disk_total = COALESCE(EXCLUDED.disk_total, node_cache.disk_total), disk_used = COALESCE(EXCLUDED.disk_used, node_cache.disk_used), agent_count_router = COALESCE(EXCLUDED.agent_count_router, node_cache.agent_count_router), agent_count_system = COALESCE(EXCLUDED.agent_count_system, node_cache.agent_count_system), last_heartbeat = NOW(), dagi_router_url = COALESCE(EXCLUDED.dagi_router_url, node_cache.dagi_router_url), self_healing_status = COALESCE(EXCLUDED.self_healing_status, node_cache.self_healing_status), swapper_healthy = COALESCE(EXCLUDED.swapper_healthy, node_cache.swapper_healthy), swapper_models_loaded = COALESCE(EXCLUDED.swapper_models_loaded, node_cache.swapper_models_loaded), swapper_models_total = COALESCE(EXCLUDED.swapper_models_total, node_cache.swapper_models_total), updated_at = NOW(); END; $$ LANGUAGE plpgsql;