AutomationFlowsAI & RAG › Generate HVAC Events via Webhook to Postgres

Generate HVAC Events via Webhook to Postgres

Original n8n title: Hvac Event Generator V1.0 - Insert Operations

HVAC Event Generator v1.0 - INSERT OPERATIONS. Uses postgres. Webhook trigger; 16 nodes.

Webhook trigger★★★★☆ complexity16 nodesPostgres
AI & RAG Trigger: Webhook Nodes: 16 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "HVAC Event Generator v1.0 - INSERT OPERATIONS",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "hvac/generate-events",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "f21843c7-2e08-472a-a293-906cf7387453",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        -1408,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Policy Loader - START');\n\nconst sd = $getWorkflowStaticData('global');\n// 77\nsd.scenarios = sd.scenarios || {\n  \"winter_emergency_surge\": {\n    \"description\": \"Cold snap causes HVAC failures\",\n    \"event_count\": 50,\n    \"time_spread_minutes\": 120,\n    \"event_distribution\": {\n      \"emergency\": 0.60,\n      \"inquiry\": 0.25,\n      \"appointment\": 0.10,\n      \"complaint\": 0.05\n    },\n    \"urgency_bias\": \"high\",\n    \"time_pattern\": \"evening_heavy\",\n    \"customer_pool\": [1, 2, 3, 4],\n    \"revenue_range\": [250, 600]\n  },\n  \"routine_maintenance_day\": {\n    \"description\": \"Normal business day\",\n    \"event_count\": 30,\n    \"time_spread_minutes\": 480,\n    \"event_distribution\": {\n      \"appointment\": 0.50,\n      \"inquiry\": 0.25,\n      \"follow_up\": 0.15,\n      \"maintenance\": 0.10\n    },\n    \"urgency_bias\": \"low\",\n    \"time_pattern\": \"business_hours\",\n    \"customer_pool\": [1, 2, 3, 4, 5],\n    \"revenue_range\": [150, 350]\n  },\n  \"summer_heatwave\": {\n    \"description\": \"AC failures during heat emergency\",\n    \"event_count\": 75,\n    \"time_spread_minutes\": 180,\n    \"event_distribution\": {\n      \"emergency\": 0.70,\n      \"inquiry\": 0.20,\n      \"complaint\": 0.10\n    },\n    \"urgency_bias\": \"extreme\",\n    \"time_pattern\": \"afternoon_spike\",\n    \"customer_pool\": [1, 2, 5],\n    \"revenue_range\": [300, 800]\n  }\n};\n\nconst scenarioName = $input.first().json.scenario_name || 'routine_maintenance_day';\nconst scenario = sd.scenarios[scenarioName];\n\nif (!scenario) {\n  throw new Error(`Unknown scenario: ${scenarioName}. Available: ${Object.keys(sd.scenarios).join(', ')}`);\n}\n\nconst eventCount = $input.first().json.event_count || scenario.event_count;\nconst timeSpread = $input.first().json.time_spread_minutes || scenario.time_spread_minutes;\n\nconst output = {\n  scenario: {\n    ...scenario,\n    event_count: eventCount,\n    time_spread_minutes: timeSpread\n  },\n  scenario_name: scenarioName,\n  policy: {\n    idempotency_enabled: true,\n    idempotency_ttl_minutes: 5,\n    enable_jitter: true,\n    jitter_minutes: 5\n  }\n};\n\nconsole.log('[' + new Date().toISOString() + '] Policy Loader - Loaded scenario:', scenarioName);\nreturn output;"
      },
      "id": "446713eb-b222-459e-809f-987518d3358a",
      "name": "Policy Loader",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1200,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] State Initializer - START');\n\nfunction generateRunId() {\n  const prefix = 'GEN';\n  const random = Math.random().toString(36).substring(2, 9).toUpperCase();\n  const timestamp = Date.now().toString(36).toUpperCase();\n  return `${prefix}-${random}-${timestamp}`;\n}\n\nfunction generateUUID() {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n    const r = Math.random() * 16 | 0;\n    const v = c === 'x' ? r : (r & 0x3 | 0x8);\n    return v.toString(16);\n  });\n}\n\nconst scenario = $input.first().json.scenario;\nconst scenarioName = $input.first().json.scenario_name;\nconst policy = $input.first().json.policy;\n\nconst sd = $getWorkflowStaticData('global');\nsd._event_cache = sd._event_cache || {};\n\nconst now = Date.now();\nfor (const key in sd._event_cache) {\n  if (sd._event_cache[key].expires_at < now) {\n    delete sd._event_cache[key];\n  }\n}\n\nconst state = {\n  run_id: generateRunId(),\n  workflow_sequence_id: generateUUID(),\n  scenario_name: scenarioName,\n  goal: `Generate ${scenario.event_count} events for ${scenarioName}`,\n  constraints: {\n    max_events: scenario.event_count,\n    time_spread_minutes: scenario.time_spread_minutes,\n    respect_patterns: true\n  },\n  counters: {\n    events_created: 0,\n    events_deduped: 0,\n    errors: 0,\n    current_step: 0,\n    start_time: Date.now()\n  },\n  scratchpad: {\n    plan: `Create ${scenario.event_count} events following ${scenario.time_pattern} pattern`,\n    progress: \"Initializing...\",\n    observations: []\n  },\n  scenario: scenario,\n  policy: policy\n};\n\nconsole.log('[' + new Date().toISOString() + '] State Initializer - Created state with run_id:', state.run_id);\nreturn state;"
      },
      "id": "5ca90638-b795-4f78-b436-a7166fda9b85",
      "name": "State Initializer",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1008,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Generator Logic - START Step:', $input.first().json.counters.current_step);  \n\nconst state = $input.first().json;\nconst scenario = state.scenario;\nconst counters = state.counters;\n\nfunction actionId() {\n  return Date.now().toString(36).toUpperCase() + \n         Math.random().toString(36).substring(2, 10).toUpperCase();\n}\n\nfunction selectEventType(distribution) {\n  const rand = Math.random();\n  let cumulative = 0;\n  \n  for (const [eventType, probability] of Object.entries(distribution)) {\n    cumulative += probability;\n    if (rand <= cumulative) return eventType;\n  }\n  \n  return \"inquiry\";\n}\n\nfunction calculateUrgency(eventType, urgencyBias) {\n  const baseUrgency = {\n    \"emergency\": [8, 9, 10],\n    \"complaint\": [5, 6, 7],\n    \"inquiry\": [3, 4, 5],\n    \"appointment\": [2, 3, 4],\n    \"follow_up\": [1, 2, 3],\n    \"maintenance\": [2, 3, 4]\n  };\n  \n  let range = baseUrgency[eventType] || [3, 4, 5];\n  \n  if (urgencyBias === \"high\") {\n    range = range.slice(-2);\n  } else if (urgencyBias === \"extreme\") {\n    range = [Math.max(...range)];\n  }\n  \n  return range[Math.floor(Math.random() * range.length)];\n}\n\nfunction calculateTimestamp(scenario, eventIndex, totalEvents) {\n  const startTime = counters.start_time;\n  const spreadMs = scenario.time_spread_minutes * 60 * 1000;\n  \n  let baseOffset = (spreadMs / totalEvents) * eventIndex;\n  \n  if (scenario.time_pattern === \"evening_heavy\") {\n    const threshold = totalEvents * 0.7;\n    if (eventIndex > threshold) {\n      baseOffset = spreadMs * 0.7 + \n                   (eventIndex - threshold) * (spreadMs * 0.3) / (totalEvents * 0.3);\n    }\n  } else if (scenario.time_pattern === \"afternoon_spike\") {\n    const midStart = totalEvents * 0.3;\n    const midEnd = totalEvents * 0.7;\n    if (eventIndex > midStart && eventIndex < midEnd) {\n      baseOffset = spreadMs * 0.3 + \n                   (eventIndex - midStart) * (spreadMs * 0.4) / (midEnd - midStart);\n    }\n  }\n  \n  const jitter = state.policy.enable_jitter ? \n    (Math.random() - 0.5) * (state.policy.jitter_minutes * 60 * 1000) : 0;\n  \n  return new Date(startTime + baseOffset + jitter).toISOString();\n}\n\nfunction calculateRevenue(eventType, revenueRange) {\n  const [min, max] = revenueRange;\n  const base = min + Math.random() * (max - min);\n  \n  const multipliers = {\n    \"emergency\": 1.5,\n    \"complaint\": 1.0,\n    \"inquiry\": 0.8,\n    \"appointment\": 1.2,\n    \"follow_up\": 0.5,\n    \"maintenance\": 1.0\n  };\n  \n  return Math.round(base * (multipliers[eventType] || 1.0));\n}\n\nfunction generateDescription(eventType) {\n  const templates = {\n    \"emergency\": [\"No heat - urgent!\", \"AC failure - 90\u00b0 inside\", \"System down - immediate help\"],\n    \"complaint\": [\"Service was delayed\", \"Technician was rude\", \"Not satisfied with repair\"],\n    \"inquiry\": [\"Question about maintenance\", \"Need quote for install\", \"When is next service?\"],\n    \"appointment\": [\"Schedule spring tune-up\", \"Book filter replacement\", \"Annual inspection due\"],\n    \"follow_up\": [\"Following up on estimate\", \"Checking repair status\", \"Question about invoice\"],\n    \"maintenance\": [\"Routine 6-month check\", \"Filter change needed\", \"System inspection\"]\n  };\n  \n  const options = templates[eventType] || [\"Customer contact\"];\n  return options[Math.floor(Math.random() * options.length)];\n}\n\nconst eventType = selectEventType(scenario.event_distribution);\nconst customerId = scenario.customer_pool[Math.floor(Math.random() * scenario.customer_pool.length)];\nconst urgency = calculateUrgency(eventType, scenario.urgency_bias);\nconst timestamp = calculateTimestamp(scenario, counters.events_created, scenario.event_count);\nconst revenue = calculateRevenue(eventType, scenario.revenue_range);\nconst description = generateDescription(eventType);\n\nconst output = {\n  state: state,\n  next_action: {\n    type: \"create_event\",\n    action_id: actionId(),\n    event_data: {\n      event_type: eventType,\n      event_timestamp: timestamp,\n      customer_id: customerId,\n      urgency: urgency,\n      description: description,\n      estimated_revenue: revenue,\n      event_payload: {\n        source: \"event_generator\",\n        scenario: state.scenario_name,\n        run_id: state.run_id\n      },\n      is_manual_trigger: false,\n      scenario_name: state.scenario_name\n    }\n  },\n  state_update: {\n    confidence: Math.random() * 0.3 + 0.7,\n    reasoning: `Generated ${eventType} event based on ${scenario.urgency_bias} urgency scenario`\n  }\n};\n\nconsole.log('[' + new Date().toISOString() + '] Generator Logic - Generated:', eventType, 'urgency:', urgency);\nreturn output;"
      },
      "id": "cbc5ea01-603d-4a51-b520-10205d91dfad",
      "name": "Generator Logic",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -800,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Validator - START');\n\nconst item = $input.first().json;\nconst eventData = item.next_action.event_data;\n\nconst required = ['event_type', 'event_timestamp', 'customer_id', 'urgency', 'description'];\nfor (const field of required) {\n  if (!eventData[field]) {\n    throw new Error(`Missing required field: ${field}`);\n  }\n}\n\nif (eventData.urgency < 1 || eventData.urgency > 10) {\n  throw new Error(`Invalid urgency: ${eventData.urgency}. Must be 1-10`);\n}\n\nif (eventData.estimated_revenue < 0) {\n  throw new Error(`Invalid revenue: ${eventData.estimated_revenue}`);\n}\n\nconsole.log('[' + new Date().toISOString() + '] Validator - PASSED');\nreturn item;"
      },
      "id": "9d35b31e-0b5c-4ef9-99f3-78139b5b7407",
      "name": "Validator",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -608,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Idempotency Gate - START');\n\n// \ud83d\udd25 CLEAR CACHE FOR TESTING (remove this line once working)\nconst sd = $getWorkflowStaticData('global');\nsd._event_cache = {};\n\nconst output = $input.first().json;\nconst eventData = output.next_action.event_data;\nconst policy = output.state.policy;\n\nif (!policy.idempotency_enabled) {\n  console.log('[' + new Date().toISOString() + '] Idempotency Gate - DISABLED, passing through');\n  return { ...output, deduped: false };\n}\n\nconst keyParts = [\n  output.state.scenario_name,\n  eventData.event_type,\n  eventData.customer_id,\n  eventData.event_timestamp.substring(0, 16)\n];\nconst idempotencyKey = keyParts.join('::');\n\nconst now = Date.now();\nconst ttlMs = policy.idempotency_ttl_minutes * 60 * 1000;\n\nconst cached = sd._event_cache[idempotencyKey];\nif (cached && cached.expires_at > now) {\n  console.log('[' + new Date().toISOString() + '] Idempotency Gate - CACHE HIT:', idempotencyKey);\n  return {\n    ...output,\n    deduped: true,\n    cached_event_id: cached.event_id,\n    cache_hit_at: new Date().toISOString()\n  };\n}\n\nsd._event_cache[idempotencyKey] = {\n  created_at: now,\n  expires_at: now + ttlMs,\n  event_id: null\n};\n\nconsole.log('[' + new Date().toISOString() + '] Idempotency Gate - CACHE MISS, creating new event');\nreturn {\n  ...output,\n  deduped: false,\n  idempotency_key: idempotencyKey,\n  cache_miss_at: new Date().toISOString()\n};"
      },
      "id": "e88692e6-0edf-448f-bdc3-126248cfb0a1",
      "name": "Idempotency Gate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -400,
        112
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "c3871e55-71f8-45e1-b357-aad8e44df34f",
              "leftValue": "{{ $json.deduped }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "244895b9-d31c-4e13-960b-c2fad1053072",
      "name": "Dedup Checker",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -208,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Cache Hit Logger - Event was deduplicated');\n\nconst item = $input.first().json;\n\nreturn {\n  run_id: item.state.run_id,\n  workflow_sequence_id: item.state.workflow_sequence_id,\n  action: \"cache_hit\",\n  idempotency_key: item.idempotency_key,\n  cached_event_id: item.cached_event_id,\n  scenario_name: item.state.scenario_name,\n  event_type: item.next_action.event_data.event_type,\n  timestamp: new Date().toISOString(),\n  message: \"Event creation skipped (idempotency cache hit)\"\n};"
      },
      "id": "d3cc3350-e2ce-4f08-a97e-b7ed15627ebd",
      "name": "Cache Hit Logger",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "value": "hvac_events",
          "mode": "list"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "event_type": "={{ $json.next_action.event_data.event_type }}",
            "event_timestamp": "={{ $json.next_action.event_data.event_timestamp }}",
            "customer_id": "={{ $json.next_action.event_data.customer_id }}",
            "urgency": "={{ $json.next_action.event_data.urgency }}",
            "description": "={{ $json.next_action.event_data.description }}",
            "estimated_revenue": "={{ $json.next_action.event_data.estimated_revenue }}",
            "event_payload": "={{ JSON.stringify($json.next_action.event_data.event_payload) }}",
            "is_manual_trigger": "={{ $json.next_action.event_data.is_manual_trigger }}",
            "scenario_name": "={{ $json.next_action.event_data.scenario_name }}",
            "processed": "=false"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "event_id",
              "displayName": "event_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "event_timestamp",
              "displayName": "event_timestamp",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "event_type",
              "displayName": "event_type",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_id",
              "displayName": "customer_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "urgency",
              "displayName": "urgency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "estimated_revenue",
              "displayName": "estimated_revenue",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "event_payload",
              "displayName": "event_payload",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "object",
              "canBeUsedToMatch": true
            },
            {
              "id": "is_manual_trigger",
              "displayName": "is_manual_trigger",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "boolean",
              "canBeUsedToMatch": true
            },
            {
              "id": "scenario_name",
              "displayName": "scenario_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "processed",
              "displayName": "processed",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "boolean",
              "canBeUsedToMatch": true
            },
            {
              "id": "processed_at",
              "displayName": "processed_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "ca288bc5-1641-492a-bf5d-4dfc3ea05294",
      "name": "PostgreSQL Insert Event",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        0,
        208
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "value": "workflow_executions",
          "mode": "list"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "execution_id": "={{ $json.execution_id }}",
            "workflow_name": "event_generator",
            "event_id": "={{ parseInt($json.event_id) }}",
            "started_at": "={{ new Date().toISOString() }}",
            "completed_at": "={{ new Date().toISOString() }}",
            "status": "success",
            "duration_ms": 0
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "execution_id",
              "displayName": "execution_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "workflow_name",
              "displayName": "workflow_name",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "event_id",
              "displayName": "event_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "started_at",
              "displayName": "started_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "completed_at",
              "displayName": "completed_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "duration_ms",
              "displayName": "duration_ms",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "displayName": "error_message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "error_code",
              "displayName": "error_code",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "ddf00fea-9062-4ffe-bf9c-3ca6f5aade5d",
      "name": "Workflow Logger",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        400,
        208
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "value": "agent_decisions",
          "mode": "list"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "event_id": "={{ $('PostgreSQL Insert Event').first().json.event_id }}",
            "workflow_execution_id": "={{ $('Workflow Logger').first().json.execution_id }}",
            "workflow_sequence_id": "={{ $('Idempotency Gate').first().json.state.workflow_sequence_id }}",
            "agent_name": "=event_generator",
            "decision_type": "=create_event",
            "decision_timestamp": "={{ new Date().toISOString() }}",
            "step_number": "={{ $('Idempotency Gate').first().json.state.counters.current_step + 1 }}",
            "routing_reason": "={{ 'Scenario: ' + $('Idempotency Gate').first().json.state.scenario_name + ' [Event ' + ($('Idempotency Gate').first().json.state.counters.events_created + 1) + '/' + $('Idempotency Gate').first().json.state.scenario.event_count + ']' }}",
            "decision_confidence": "={{ $('Idempotency Gate').first().json.state_update.confidence }}",
            "classification_tags": "={{ JSON.stringify(['generated', $('Idempotency Gate').first().json.next_action.event_data.event_type]) }}",
            "action_taken": "=created_event",
            "outcome_status": "=success",
            "decision_payload": "={{ JSON.stringify($('Idempotency Gate').first().json.next_action.event_data.event_payload) }}",
            "processing_time_ms": 0,
            "token_count_input": 0,
            "token_count_output": 0,
            "cost_usd": 0,
            "parent_decision_id": 0
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "decision_id",
              "displayName": "decision_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "event_id",
              "displayName": "event_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "workflow_execution_id",
              "displayName": "workflow_execution_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "agent_name",
              "displayName": "agent_name",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "decision_type",
              "displayName": "decision_type",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "decision_timestamp",
              "displayName": "decision_timestamp",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "processing_time_ms",
              "displayName": "processing_time_ms",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "llm_model",
              "displayName": "llm_model",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "llm_provider",
              "displayName": "llm_provider",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "token_count_input",
              "displayName": "token_count_input",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "token_count_output",
              "displayName": "token_count_output",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "cost_usd",
              "displayName": "cost_usd",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "workflow_sequence_id",
              "displayName": "workflow_sequence_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "parent_decision_id",
              "displayName": "parent_decision_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "step_number",
              "displayName": "step_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "routing_reason",
              "displayName": "routing_reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "decision_confidence",
              "displayName": "decision_confidence",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "classification_tags",
              "displayName": "classification_tags",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "array",
              "canBeUsedToMatch": true
            },
            {
              "id": "action_taken",
              "displayName": "action_taken",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "outcome_status",
              "displayName": "outcome_status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "decision_payload",
              "displayName": "decision_payload",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "object",
              "canBeUsedToMatch": true
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "68991a63-1217-4f41-bbe1-0d05e2013b89",
      "name": "Decision Logger",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        608,
        208
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Counter Updater - START');\n\nconst output = $('Idempotency Gate').first().json;\nconst deduped = output.deduped || false;\n\n// Handle both paths: cache hit OR new event\nlet eventId;\nif (deduped) {\n  // Cache hit path - PostgreSQL Insert Event didn't run\n  eventId = output.cached_event_id;\n} else {\n  // New event path - PostgreSQL Insert Event ran successfully\n  eventId = $('PostgreSQL Insert Event').first().json.event_id;\n}\n\nconst state = JSON.parse(JSON.stringify(output.state));\n\nstate.counters.current_step += 1;\n\nif (deduped) {\n  state.counters.events_deduped += 1;\n} else {\n  state.counters.events_created += 1;\n  \n  // Update cache with the new event_id\n  const sd = $getWorkflowStaticData('global');\n  if (sd._event_cache && sd._event_cache[output.idempotency_key]) {\n    sd._event_cache[output.idempotency_key].event_id = eventId;\n  }\n}\n\nconst created = state.counters.events_created;\nconst total = state.scenario.event_count;\nconst pct = Math.round((created / total) * 100);\n\nstate.scratchpad.progress = `Created ${created}/${total} events (${pct}%)`;\nstate.scratchpad.observations.push(\n  deduped ? \n    `Step ${state.counters.current_step}: Deduped ${output.next_action.event_data.event_type}` :\n    `Step ${state.counters.current_step}: Created ${output.next_action.event_data.event_type} (ID: ${eventId})`\n);\n\nif (state.scratchpad.observations.length > 5) {\n  state.scratchpad.observations = state.scratchpad.observations.slice(-5);\n}\n\nconsole.log('[' + new Date().toISOString() + '] Counter Updater - Progress:', pct + '%');\nreturn state;"
      },
      "id": "3da08f44-c8af-4bda-8c0c-c4ec0d9dce3f",
      "name": "Counter Updater",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        800,
        208
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.counters.events_created >= $json.scenario.event_count }}",
              "value2": true
            }
          ]
        },
        "options": {}
      },
      "id": "a8ed975f-9dbb-4c3e-a6b8-4b71ba3af112",
      "name": "Budget Checker",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1008,
        208
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Success Response - Generating final output');\n\nconst state = $input.first().json;\n\nconst durationMs = Date.now() - state.counters.start_time;\nconst durationSec = (durationMs / 1000).toFixed(2);\n\nconst output = {\n  status: \"success\",\n  run_id: state.run_id,\n  workflow_sequence_id: state.workflow_sequence_id,\n  scenario_name: state.scenario_name,\n  summary: {\n    events_created: state.counters.events_created,\n    events_deduped: state.counters.events_deduped,\n    total_steps: state.counters.current_step,\n    errors: state.counters.errors,\n    duration_seconds: parseFloat(durationSec),\n    events_per_second: (state.counters.events_created / parseFloat(durationSec)).toFixed(2)\n  },\n  scenario: {\n    description: state.scenario.description,\n    event_count: state.scenario.event_count,\n    time_spread_minutes: state.scenario.time_spread_minutes,\n    pattern: state.scenario.time_pattern\n  },\n  observations: state.scratchpad.observations,\n  message: `Successfully generated ${state.counters.events_created} events in ${durationSec}s`,\n  timestamp: new Date().toISOString()\n};\n\nconsole.log('[' + new Date().toISOString() + '] Success Response - COMPLETE:', output.summary);\nreturn output;"
      },
      "id": "0a5b13d3-95f8-47f2-8369-670c3740ff78",
      "name": "Success Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "console.log('[' + new Date().toISOString() + '] Error Handler - Handling error');\n\nconst error = $input.first().json.error || $input.first().error || {};\nconst state = $input.first().json.state || {};\n\nreturn {\n  status: \"error\",\n  run_id: state.run_id || \"unknown\",\n  scenario_name: state.scenario_name || \"unknown\",\n  error: {\n    message: error.message || \"Unknown error\",\n    type: error.name || \"Error\",\n    timestamp: new Date().toISOString()\n  },\n  summary: {\n    events_created: state.counters?.events_created || 0,\n    events_deduped: state.counters?.events_deduped || 0,\n    total_steps: state.counters?.current_step || 0\n  },\n  message: \"Event generation failed - see error details\"\n};"
      },
      "id": "bd3b8019-6c3e-45c8-8c86-a25b6e8e65a2",
      "name": "Error Handler",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        304
      ]
    },
    {
      "parameters": {
        "action": "generate",
        "dataPropertyName": "execution_id"
      },
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        208,
        240
      ],
      "id": "462dd8bb-b268-4cf1-813a-69c6c62a0c7b",
      "name": "Crypto"
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Policy Loader",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Policy Loader": {
      "main": [
        [
          {
            "node": "State Initializer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "State Initializer": {
      "main": [
        [
          {
            "node": "Generator Logic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generator Logic": {
      "main": [
        [
          {
            "node": "Validator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validator": {
      "main": [
        [
          {
            "node": "Idempotency Gate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Idempotency Gate": {
      "main": [
        [
          {
            "node": "Dedup Checker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedup Checker": {
      "main": [
        [
          {
            "node": "Cache Hit Logger",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "PostgreSQL Insert Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cache Hit Logger": {
      "main": [
        [
          {
            "node": "Counter Updater",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL Insert Event": {
      "main": [
        [
          {
            "node": "Crypto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Logger": {
      "main": [
        [
          {
            "node": "Decision Logger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decision Logger": {
      "main": [
        [
          {
            "node": "Counter Updater",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Counter Updater": {
      "main": [
        [
          {
            "node": "Budget Checker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Budget Checker": {
      "main": [
        [
          {
            "node": "Success Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generator Logic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crypto": {
      "main": [
        [
          {
            "node": "Workflow Logger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d39cb5e4-af2a-4479-8fd0-c8cc90621fbe",
  "id": "WK0r3BZn6qxpuIwF",
  "tags": []
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

This workflow automates the generation and insertion of HVAC system events into a PostgreSQL database, ensuring reliable data logging for maintenance and monitoring without manual intervention. It's ideal for facility managers or IoT developers handling building automation who need to simulate or process real-time HVAC alerts efficiently. The key step involves the Generator Logic node, which processes incoming webhook data to create structured event records, followed by validation and idempotency checks to prevent duplicates before insertion.

Use this workflow when integrating HVAC sensors with a PostgreSQL backend for event-driven analytics, such as tracking temperature fluctuations in commercial buildings. Avoid it for high-volume real-time streaming where more scalable tools like Apache Kafka are better suited, or if your setup lacks webhook endpoints. Common variations include adding email notifications via integrations like SendGrid for critical alerts, or extending to multiple database tables for segmented HVAC zones.

About this workflow

HVAC Event Generator v1.0 - INSERT OPERATIONS. Uses postgres. Webhook trigger; 16 nodes.

Source: https://github.com/dshorter/ai-agent-platform/blob/bea780c82f33608c3ae775cd643a592d212a5251/n8n-workflows/xxevent-generator.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This template is for developers, teams, and automation enthusiasts who want a private, PIN-protected Telegram chatbot that answers questions from their own documents — without relying on external AI A

Postgres, Telegram, HTTP Request +2
AI & RAG

Question Answering Workflow. Uses httpRequest, postgres. Webhook trigger; 8 nodes.

HTTP Request, Postgres
AI & RAG

Command Processing Workflow. Uses postgres. Webhook trigger; 4 nodes.

Postgres
AI & RAG

Eu Clara – Funil Kiwify Completo. Uses postgres, openAi, httpRequest, gmail. Webhook trigger; 70 nodes.

Postgres, OpenAI, HTTP Request +1
AI & RAG

Lua Nova - Sistema Completo. Uses postgres, httpRequest, openAi. Webhook trigger; 55 nodes.

Postgres, HTTP Request, OpenAI