{
  "updatedAt": "2026-03-15T20:42:04.929Z",
  "createdAt": "2026-03-09T21:09:54.399Z",
  "id": "Wl4HBw8sGpfvTzta",
  "name": "VenueDesk \u2014 Lead Discovery (Daily)",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            }
          ]
        }
      },
      "id": "sched1",
      "name": "Daily at 6am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "path": "run-lead-discovery",
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "wh_disc",
      "name": "Manual Discovery Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        200,
        480
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "DO $$ BEGIN\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='county') THEN\n    ALTER TABLE leads ADD COLUMN county text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='phone') THEN\n    ALTER TABLE leads ADD COLUMN phone text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='source') THEN\n    ALTER TABLE leads ADD COLUMN source text DEFAULT 'manual';\n  END IF;\nEND $$; SELECT 1;",
        "options": {}
      },
      "id": "ensure1",
      "name": "Ensure DB Columns",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        420,
        380
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const counties = [\n  'Greater London','Kent','Surrey','Hertfordshire','Essex',\n  'Suffolk','Norfolk','Leicestershire','Derbyshire','West Midlands',\n  'Worcestershire','West Yorkshire','North Yorkshire','Lancashire',\n  'Greater Manchester','Cheshire','County Durham','Northumberland','Dorset','Hampshire',\n  'Oxfordshire','Cambridgeshire','Buckinghamshire','Berkshire','Devon',\n  'Cornwall','Somerset','Gloucestershire','Wiltshire','Shropshire'\n];\nconst venueTypes = [\n  {q:'village hall hire',             t:'Village Hall'},\n  {q:'community centre room hire',    t:'Community Centre'},\n  {q:'sports club function room',     t:'Sports Club'},\n  {q:'event venue hire',              t:'Event Venue'},\n  {q:'church hall hire',              t:'Community Centre'},\n  {q:'social club venue hire',        t:'Sports Club'},\n  {q:'hotel function room hire',      t:'Hotel'},\n  {q:'working mens club hire',        t:'Sports Club'}\n];\n\n// Rotate county by day-of-month so each day targets a different area\nconst county = counties[new Date().getDate() % counties.length];\n\nreturn venueTypes.map(v => ({\n  json: {\n    query:            v.q + ' ' + county,\n    county:           county,\n    venue_type_label: v.t\n  }\n}));"
      },
      "id": "gen1",
      "name": "Generate Searches",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        380
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://places.googleapis.com/v1/places:searchText",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Goog-Api-Key",
              "value": "={{$env.GOOGLE_PLACES_API_KEY}}"
            },
            {
              "name": "X-Goog-FieldMask",
              "value": "places.displayName,places.formattedAddress,places.websiteUri,places.nationalPhoneNumber,places.id"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "bodyParameters": {
          "parameters": [
            {
              "name": "textQuery",
              "value": "={{ $json.query }}"
            },
            {
              "name": "regionCode",
              "value": "GB"
            },
            {
              "name": "languageCode",
              "value": "en-GB"
            },
            {
              "name": "maxResultCount",
              "value": "=20"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 20000
        }
      },
      "id": "ddg_fetch",
      "name": "Search Google Maps",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        860,
        380
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\n// Get county and venue_type from upstream Generate Searches node\nlet genItem = {};\ntry { genItem = $('Generate Searches').item.json; } catch(e) {}\nconst county = String(genItem.county || item.county || 'England');\nconst vtype  = String(genItem.venue_type_label || item.venue_type_label || 'Community Venue');\n\n// Google Places API returns: { places: [ { displayName, formattedAddress, websiteUri, nationalPhoneNumber, id } ] }\nconst places = Array.isArray(item.places) ? item.places : [];\n\nif (places.length === 0) {\n  console.log('No places returned for query:', genItem.query || 'unknown');\n  return [];\n}\n\n// Chains/irrelevant venues to skip\nconst SKIP = /tesco|asda|sainsbury|morrisons|costa|starbucks|mcdonalds|pizza|kfc|nando|greggs|boots|lidl|aldi|amazon/i;\n\nconst results = [];\nfor (const p of places) {\n  const name    = (p.displayName && p.displayName.text) ? p.displayName.text.trim() : '';\n  const address = p.formattedAddress || '';\n  const website = p.websiteUri || '';\n  const phone   = p.nationalPhoneNumber || '';\n  const placeId = p.id || '';\n\n  if (!name || name.length < 3) continue;\n  if (SKIP.test(name)) continue;\n\n  results.push({\n    json: {\n      venue_name:  name.substring(0, 120),\n      website_url: website,\n      phone:       phone,\n      venue_type:  vtype,\n      county:      county,\n      notes:       ('Google Maps \u00b7 ' + county + (address ? ' \u00b7 ' + address : '')).substring(0, 255),\n      place_id:    placeId,\n      source:      'google_maps'\n    }\n  });\n}\n\nconsole.log('Parsed', results.length, 'venues from Google Maps for', vtype, 'in', county);\nreturn results;"
      },
      "id": "parse_ddg",
      "name": "Parse Google Maps Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        380
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO leads (venue_name, email, website_url, phone, venue_type, status, county, source, notes)\nSELECT\n  $${{ $json.venue_name }}$$::text,\n  $$$$::text,\n  $${{ $json.website_url }}$$::text,\n  $${{ $json.phone }}$$::text,\n  $${{ $json.venue_type }}$$::text,\n  $$new$$::text,\n  $${{ $json.county }}$$::text,\n  $$google_maps$$::text,\n  $${{ $json.notes }}$$::text\nWHERE NOT EXISTS (\n  SELECT 1 FROM leads WHERE website_url = $${{ $json.website_url }}$$ AND $${{ $json.website_url }}$$ <> $$$$\n)\nAND NOT EXISTS (\n  SELECT 1 FROM leads WHERE lower(venue_name) = lower($${{ $json.venue_name.substring(0,50) }}$$)\n);",
        "options": {}
      },
      "id": "insert1",
      "name": "Insert Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1300,
        380
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Daily at 6am": {
      "main": [
        [
          {
            "node": "Ensure DB Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Discovery Trigger": {
      "main": [
        [
          {
            "node": "Ensure DB Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ensure DB Columns": {
      "main": [
        [
          {
            "node": "Generate Searches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Searches": {
      "main": [
        [
          {
            "node": "Search Google Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Google Maps Results": {
      "main": [
        [
          {
            "node": "Insert Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google Maps": {
      "main": [
        [
          {
            "node": "Parse Google Maps Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": true
  },
  "staticData": {
    "node:Daily at 6am": {
      "recurrenceRules": []
    }
  },
  "meta": null,
  "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
  "activeVersionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
  "versionCounter": 70,
  "triggerCount": 2,
  "shared": [
    {
      "updatedAt": "2026-03-09T21:09:54.399Z",
      "createdAt": "2026-03-09T21:09:54.399Z",
      "role": "workflow:owner",
      "workflowId": "Wl4HBw8sGpfvTzta",
      "projectId": "xsdyFVsct988qLUy",
      "project": {
        "updatedAt": "2025-12-04T20:24:17.537Z",
        "createdAt": "2025-12-04T19:47:48.685Z",
        "id": "xsdyFVsct988qLUy",
        "name": "andrew johnson <andrew.ralston.johnson@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "e2485274-7097-4eb5-8502-e39b2308096c"
      }
    }
  ],
  "tags": [],
  "activeVersion": {
    "updatedAt": "2026-03-10T17:44:36.839Z",
    "createdAt": "2026-03-10T17:44:36.839Z",
    "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
    "workflowId": "Wl4HBw8sGpfvTzta",
    "nodes": [
      {
        "parameters": {
          "rule": {
            "interval": [
              {
                "field": "cronExpression",
                "expression": "0 6 * * *"
              }
            ]
          }
        },
        "id": "sched1",
        "name": "Daily at 6am",
        "type": "n8n-nodes-base.scheduleTrigger",
        "typeVersion": 1.2,
        "position": [
          200,
          300
        ]
      },
      {
        "parameters": {
          "path": "run-lead-discovery",
          "httpMethod": "POST",
          "responseMode": "onReceived",
          "options": {}
        },
        "id": "wh_disc",
        "name": "Manual Discovery Trigger",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          200,
          480
        ],
        "webhookId": "run-lead-discovery"
      },
      {
        "parameters": {
          "operation": "executeQuery",
          "query": "DO $$ BEGIN\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='county') THEN\n    ALTER TABLE leads ADD COLUMN county text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='phone') THEN\n    ALTER TABLE leads ADD COLUMN phone text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='source') THEN\n    ALTER TABLE leads ADD COLUMN source text DEFAULT 'manual';\n  END IF;\nEND $$; SELECT 1;",
          "options": {}
        },
        "id": "ensure1",
        "name": "Ensure DB Columns",
        "type": "n8n-nodes-base.postgres",
        "typeVersion": 2.5,
        "position": [
          420,
          380
        ],
        "credentials": {
          "postgres": {
            "id": "iEsRYyB7vjr5G7i8",
            "name": "Postgres account 3"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "const counties = [\n  'Greater London','Kent','Surrey','Hertfordshire','Essex',\n  'Suffolk','Norfolk','Leicestershire','Derbyshire','West Midlands',\n  'Worcestershire','West Yorkshire','North Yorkshire','Lancashire',\n  'Greater Manchester','Cheshire','County Durham','Northumberland','Dorset','Hampshire',\n  'Oxfordshire','Cambridgeshire','Buckinghamshire','Berkshire','Devon',\n  'Cornwall','Somerset','Gloucestershire','Wiltshire','Shropshire'\n];\nconst venueTypes = [\n  {q:'village hall hire',             t:'Village Hall'},\n  {q:'community centre room hire',    t:'Community Centre'},\n  {q:'sports club function room',     t:'Sports Club'},\n  {q:'event venue hire',              t:'Event Venue'},\n  {q:'church hall hire',              t:'Community Centre'},\n  {q:'social club venue hire',        t:'Sports Club'},\n  {q:'hotel function room hire',      t:'Hotel'},\n  {q:'working mens club hire',        t:'Sports Club'}\n];\n\n// Rotate county by day-of-month so each day targets a different area\nconst county = counties[new Date().getDate() % counties.length];\n\nreturn venueTypes.map(v => ({\n  json: {\n    query:            v.q + ' ' + county,\n    county:           county,\n    venue_type_label: v.t\n  }\n}));"
        },
        "id": "gen1",
        "name": "Generate Searches",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          640,
          380
        ]
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://places.googleapis.com/v1/places:searchText",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "X-Goog-Api-Key",
                "value": "={{$env.GOOGLE_PLACES_API_KEY}}"
              },
              {
                "name": "X-Goog-FieldMask",
                "value": "places.displayName,places.formattedAddress,places.websiteUri,places.nationalPhoneNumber,places.id"
              }
            ]
          },
          "sendBody": true,
          "contentType": "json",
          "bodyParameters": {
            "parameters": [
              {
                "name": "textQuery",
                "value": "={{ $json.query }}"
              },
              {
                "name": "regionCode",
                "value": "GB"
              },
              {
                "name": "languageCode",
                "value": "en-GB"
              },
              {
                "name": "maxResultCount",
                "value": "=20"
              }
            ]
          },
          "options": {
            "response": {
              "response": {
                "neverError": true
              }
            },
            "timeout": 20000
          }
        },
        "id": "ddg_fetch",
        "name": "Search Google Maps",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          860,
          380
        ]
      },
      {
        "parameters": {
          "jsCode": "const item = $input.item.json;\n\n// Get county and venue_type from upstream Generate Searches node\nlet genItem = {};\ntry { genItem = $('Generate Searches').item.json; } catch(e) {}\nconst county = String(genItem.county || item.county || 'England');\nconst vtype  = String(genItem.venue_type_label || item.venue_type_label || 'Community Venue');\n\n// Google Places API returns: { places: [ { displayName, formattedAddress, websiteUri, nationalPhoneNumber, id } ] }\nconst places = Array.isArray(item.places) ? item.places : [];\n\nif (places.length === 0) {\n  console.log('No places returned for query:', genItem.query || 'unknown');\n  return [];\n}\n\n// Chains/irrelevant venues to skip\nconst SKIP = /tesco|asda|sainsbury|morrisons|costa|starbucks|mcdonalds|pizza|kfc|nando|greggs|boots|lidl|aldi|amazon/i;\n\nconst results = [];\nfor (const p of places) {\n  const name    = (p.displayName && p.displayName.text) ? p.displayName.text.trim() : '';\n  const address = p.formattedAddress || '';\n  const website = p.websiteUri || '';\n  const phone   = p.nationalPhoneNumber || '';\n  const placeId = p.id || '';\n\n  if (!name || name.length < 3) continue;\n  if (SKIP.test(name)) continue;\n\n  results.push({\n    json: {\n      venue_name:  name.substring(0, 120),\n      website_url: website,\n      phone:       phone,\n      venue_type:  vtype,\n      county:      county,\n      notes:       ('Google Maps \u00b7 ' + county + (address ? ' \u00b7 ' + address : '')).substring(0, 255),\n      place_id:    placeId,\n      source:      'google_maps'\n    }\n  });\n}\n\nconsole.log('Parsed', results.length, 'venues from Google Maps for', vtype, 'in', county);\nreturn results;"
        },
        "id": "parse_ddg",
        "name": "Parse Google Maps Results",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1080,
          380
        ]
      },
      {
        "parameters": {
          "operation": "executeQuery",
          "query": "INSERT INTO leads (venue_name, email, website_url, phone, venue_type, status, county, source, notes)\nSELECT\n  $${{ $json.venue_name }}$$::text,\n  $$$$::text,\n  $${{ $json.website_url }}$$::text,\n  $${{ $json.phone }}$$::text,\n  $${{ $json.venue_type }}$$::text,\n  $$new$$::text,\n  $${{ $json.county }}$$::text,\n  $$google_maps$$::text,\n  $${{ $json.notes }}$$::text\nWHERE NOT EXISTS (\n  SELECT 1 FROM leads WHERE website_url = $${{ $json.website_url }}$$ AND $${{ $json.website_url }}$$ <> $$$$\n)\nAND NOT EXISTS (\n  SELECT 1 FROM leads WHERE lower(venue_name) = lower($${{ $json.venue_name.substring(0,50) }}$$)\n);",
          "options": {}
        },
        "id": "insert1",
        "name": "Insert Lead",
        "type": "n8n-nodes-base.postgres",
        "typeVersion": 2.5,
        "position": [
          1300,
          380
        ],
        "credentials": {
          "postgres": {
            "id": "iEsRYyB7vjr5G7i8",
            "name": "Postgres account 3"
          }
        }
      }
    ],
    "connections": {
      "Daily at 6am": {
        "main": [
          [
            {
              "node": "Ensure DB Columns",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Manual Discovery Trigger": {
        "main": [
          [
            {
              "node": "Ensure DB Columns",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Ensure DB Columns": {
        "main": [
          [
            {
              "node": "Generate Searches",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Generate Searches": {
        "main": [
          [
            {
              "node": "Search Google Maps",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse Google Maps Results": {
        "main": [
          [
            {
              "node": "Insert Lead",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Search Google Maps": {
        "main": [
          [
            {
              "node": "Parse Google Maps Results",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "andrew johnson",
    "name": null,
    "description": null,
    "autosaved": false,
    "workflowPublishHistory": [
      {
        "createdAt": "2026-03-15T20:42:04.955Z",
        "id": 1396,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "deactivated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      },
      {
        "createdAt": "2026-03-15T20:42:04.971Z",
        "id": 1397,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "activated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      },
      {
        "createdAt": "2026-03-10T17:44:36.868Z",
        "id": 919,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "deactivated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      },
      {
        "createdAt": "2026-03-10T17:44:36.882Z",
        "id": 920,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "activated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      }
    ]
  }
}