{
  "name": "Storm Response Auto-Switch — Template",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 1
            }
          ]
        }
      },
      "id": "trigger-hourly",
      "name": "Every Hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [240, 300]
    },
    {
      "parameters": {
        "jsCode": "// ╔══════════════════════════════════════════════════════════════╗\n// ║  🔧 SETUP — fill in the four sections below, then save.       ║\n// ║                                                                ║\n// ║  This workflow swaps two sets of ads on a schedule driven by   ║\n// ║  local weather. When a storm hits your city, your Storm ads    ║\n// ║  go live and your Regular ads pause. After a stretch of clear  ║\n// ║  days, they swap back automatically.                           ║\n// ║                                                                ║\n// ║  Works for any weather-sensitive business — roofers, towing,   ║\n// ║  HVAC, snow removal, umbrella shops, you name it.              ║\n// ╚══════════════════════════════════════════════════════════════╝\n\n// ─── 1. AdCritter API key ───────────────────────────────────────\n// Find this in your AdCritter account settings.\nconst apiKey = 'PASTE_YOUR_AC_LIVE_API_KEY_HERE';\n\n// ─── 2. Account IDs ─────────────────────────────────────────────\n// Get these from the AdCritter dashboard URL or the API.\nconst advertiserId = 'YOUR_ADVERTISER_ID';\nconst campaignId   = 'YOUR_CAMPAIGN_ID';   // for reference only\n\n// ─── 3. Your two ad sets ────────────────────────────────────────\n// \"stormAdIds\" run during a storm. \"regularAdIds\" run the rest of the time.\n// Both sets must already exist on the campaign above.\nconst stormAdIds = [\n  'STORM_AD_ID_1',\n  'STORM_AD_ID_2'\n];\nconst regularAdIds = [\n  'REGULAR_AD_ID_1',\n  'REGULAR_AD_ID_2'\n];\n\n// ─── 4. Location & storm rules ──────────────────────────────────\n// Look up your city's lat/lon at https://www.latlong.net/ (takes 5 seconds).\nconst city = {\n  name: 'Nashville',\n  lat:  36.1627,\n  lon: -86.7816,\n  timezone: 'America/Chicago'   // IANA timezone\n};\n\n// What counts as a storm? Hail is always included. Adjust wind to taste.\nconst windGustThresholdMph = 50;   // 50 mph ≈ severe-thunderstorm territory\nconst clearDaysToRevert    = 7;    // calm days before flipping back\n\nreturn [{\n  json: {\n    apiKey,\n    advertiserId,\n    campaignId,\n    stormAdIds,\n    regularAdIds,\n    city,\n    windGustThresholdMph,\n    clearDaysToRevert\n  }\n}];"
      },
      "id": "setup",
      "name": "🔧 Setup — Edit Me",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "url": "=https://api.open-meteo.com/v1/forecast?latitude={{ $json.city.lat }}&longitude={{ $json.city.lon }}&current=weather_code,wind_gusts_10m,wind_speed_10m&wind_speed_unit=mph&timezone={{ $json.city.timezone }}",
        "options": {}
      },
      "id": "fetch-weather",
      "name": "Fetch Local Weather",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "jsCode": "// Reads weather, remembers state across runs, decides what to do next.\n// Outputs a single item with one of three decisions:\n//   'swap-to-storm' | 'swap-to-regular' | 'none'\n\nconst setup   = $('🔧 Setup — Edit Me').first().json;\nconst weather = $input.first().json;\nconst state   = $getWorkflowStaticData('global');\n\n// First run: assume Regular ads are currently running\nif (!state.mode) {\n  state.mode = 'regular';\n  state.lastStormAt = null;\n}\n\n// Open-Meteo WMO codes 96 & 99 mean thunderstorm with hail\nconst code  = weather.current.weather_code;\nconst gust  = weather.current.wind_gusts_10m;\nconst isHail       = code === 96 || code === 99;\nconst isSevereWind = gust >= setup.windGustThresholdMph;\nconst stormNow     = isHail || isSevereWind;\n\nconst now = new Date();\nif (stormNow) state.lastStormAt = now.toISOString();\n\n// Decide what mode we *should* be in\nlet targetMode = state.mode;\nif (stormNow) {\n  targetMode = 'storm';\n} else if (state.mode === 'storm' && state.lastStormAt) {\n  const daysSince = (now - new Date(state.lastStormAt)) / 86400000;\n  if (daysSince >= setup.clearDaysToRevert) targetMode = 'regular';\n}\n\n// Decide which path to take\nlet decision = 'none';\nif (targetMode !== state.mode) {\n  decision = targetMode === 'storm' ? 'swap-to-storm' : 'swap-to-regular';\n  state.mode = targetMode;\n  state.lastSwitchAt = now.toISOString();\n}\n\nreturn [{\n  json: {\n    decision,\n    weatherCode: code,\n    windGustMph: gust,\n    decidedAt: now.toISOString()\n  }\n}];"
      },
      "id": "decide",
      "name": "Decide What to Do",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [900, 300]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "conditions": [
                  {
                    "id": "rule-storm",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "swap-to-storm",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Storm"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "conditions": [
                  {
                    "id": "rule-regular",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "swap-to-regular",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Regular"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "No Swap Needed"
        }
      },
      "id": "route",
      "name": "Decision Router",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [1120, 300]
    },
    {
      "parameters": {
        "jsCode": "// Build the operations: activate Storm ads, deactivate Regular ads.\nconst setup = $('🔧 Setup — Edit Me').first().json;\n\nconst ops = [];\nsetup.stormAdIds.forEach(adId   => ops.push({ adId, action: 'activate',   set: 'Storm'   }));\nsetup.regularAdIds.forEach(adId => ops.push({ adId, action: 'deactivate', set: 'Regular' }));\n\nreturn ops.map(op => ({\n  json: {\n    ...op,\n    advertiserId: setup.advertiserId,\n    apiKey: setup.apiKey\n  }\n}));"
      },
      "id": "swap-storm",
      "name": "Swap to Storm Ads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1340, 180]
    },
    {
      "parameters": {
        "jsCode": "// Build the operations: activate Regular ads, deactivate Storm ads.\nconst setup = $('🔧 Setup — Edit Me').first().json;\n\nconst ops = [];\nsetup.regularAdIds.forEach(adId => ops.push({ adId, action: 'activate',   set: 'Regular' }));\nsetup.stormAdIds.forEach(adId   => ops.push({ adId, action: 'deactivate', set: 'Storm'   }));\n\nreturn ops.map(op => ({\n  json: {\n    ...op,\n    advertiserId: setup.advertiserId,\n    apiKey: setup.apiKey\n  }\n}));"
      },
      "id": "swap-regular",
      "name": "Swap to Regular Ads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1340, 420]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.adcritter.com/v1/advertisers/{{ $json.advertiserId }}/ads/{{ $json.adId }}/{{ $json.action }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Api-Key",
              "value": "={{ $json.apiKey }}"
            }
          ]
        },
        "options": {}
      },
      "id": "switch-ad",
      "name": "Activate / Deactivate Ad",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1560, 300]
    }
  ],
  "connections": {
    "Every Hour": {
      "main": [[{ "node": "🔧 Setup — Edit Me", "type": "main", "index": 0 }]]
    },
    "🔧 Setup — Edit Me": {
      "main": [[{ "node": "Fetch Local Weather", "type": "main", "index": 0 }]]
    },
    "Fetch Local Weather": {
      "main": [[{ "node": "Decide What to Do", "type": "main", "index": 0 }]]
    },
    "Decide What to Do": {
      "main": [[{ "node": "Decision Router", "type": "main", "index": 0 }]]
    },
    "Decision Router": {
      "main": [
        [{ "node": "Swap to Storm Ads", "type": "main", "index": 0 }],
        [{ "node": "Swap to Regular Ads", "type": "main", "index": 0 }],
        []
      ]
    },
    "Swap to Storm Ads": {
      "main": [[{ "node": "Activate / Deactivate Ad", "type": "main", "index": 0 }]]
    },
    "Swap to Regular Ads": {
      "main": [[{ "node": "Activate / Deactivate Ad", "type": "main", "index": 0 }]]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "pinData": {}
}
