BE-LightningReport/Lightning_Report_Automatic.json

1771 lines
59 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"name": "Lightning_Report_Automatic",
"nodes": [
{
"parameters": {
"content": "Slack Interactivity Request URL → Webhook \"Slack Interaction Webhook\" production URL (same Slack app as Tarla Slack Account bot token)."
},
"type": "n8n-nodes-base.stickyNote",
"position": [
111936,
45568
],
"typeVersion": 1,
"id": "7f76c972-e794-4cb8-a044-8f7989243b64",
"name": "Sticky Note"
},
{
"parameters": {
"content": "export REPORT_SERVICE_TOKEN=\"your-long-random-secret\"\nTo run uvicorn report_service.main:app --host 0.0.0.0 --port 8000\n\nOn another terminal: ngrok http 8000\n",
"height": 128,
"width": 512
},
"type": "n8n-nodes-base.stickyNote",
"position": [
114864,
45536
],
"typeVersion": 1,
"id": "a3f1f98e-df9d-4c11-b7c8-3b02edcd29bf",
"name": "Sticky Note1"
},
{
"parameters": {
"method": "POST",
"url": "https://api-test.iklim.co/v1/auth/login",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "username",
"value": "info@iklim.co"
},
{
"name": "password",
"value": "gn6aV01Z759HTw9I"
}
]
},
"options": {}
},
"id": "0d7575f2-b40c-465f-aa83-f41a1c134a0d",
"name": "Login to iklim.co",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
110912,
45264
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "5c1668fb-f97b-4ec8-80f9-61bfb0b8274e",
"name": "username",
"value": "={{ $json.username }}",
"type": "string"
},
{
"id": "97c76bac-aa08-437f-ac48-a0cad9b93526",
"name": "type",
"value": "={{ $json.type }}",
"type": "string"
},
{
"id": "fc59d51f-b225-4abb-9f17-93173e650652",
"name": "accessToken",
"value": "={{ $json.accessToken }}",
"type": "string"
},
{
"id": "95e5ec41-2369-492b-9950-fc230a47f7fa",
"name": "accessTokenExpiration",
"value": "={{ $now.plus({ hours: 1 }).toMillis() }}",
"type": "string"
},
{
"id": "3d79d06a-ed55-48af-b4bc-b7fd67d80be1",
"name": "refreshToken",
"value": "={{ $json.refreshToken }}",
"type": "string"
},
{
"id": "aedf0ef4-9971-42e8-8b32-7d039342dd88",
"name": "refreshTokenExpiration",
"value": "={{ $now.plus({ days: 1 }).toMillis() }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
111152,
45248
],
"id": "01d82e72-f57e-4967-9557-a834fa3adbe6",
"name": "CalculateExpirations"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "5c1668fb-f97b-4ec8-80f9-61bfb0b8274e",
"name": "username",
"value": "={{ $json.username }}",
"type": "string"
},
{
"id": "97c76bac-aa08-437f-ac48-a0cad9b93526",
"name": "type",
"value": "={{ $json.type }}",
"type": "string"
},
{
"id": "fc59d51f-b225-4abb-9f17-93173e650652",
"name": "accessToken",
"value": "={{ $json.accessToken }}",
"type": "string"
},
{
"id": "95e5ec41-2369-492b-9950-fc230a47f7fa",
"name": "accessTokenExpiration",
"value": "={{ $now.plus({ hours: 1 }).toMillis() }}",
"type": "string"
},
{
"id": "3d79d06a-ed55-48af-b4bc-b7fd67d80be1",
"name": "refreshToken",
"value": "={{ $json.refreshToken }}",
"type": "string"
},
{
"id": "aedf0ef4-9971-42e8-8b32-7d039342dd88",
"name": "refreshTokenExpiration",
"value": "={{ $now.plus({ days: 1 }).toMillis() }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
111216,
45504
],
"id": "07091b81-3720-4f97-95c8-a008747759a8",
"name": "CalculateExpirations1"
},
{
"parameters": {
"method": "POST",
"url": "https://api-test.iklim.co/v1/auth/refresh",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "refreshToken",
"value": "={{ $input.auth..refreshToken }}"
}
]
},
"options": {}
},
"id": "53a6862d-052d-41c4-8e98-95b5b3731be2",
"name": "Refresh Token",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
110992,
45520
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ DateTime.fromMillis($json.refreshTokenExpiration - 300000) }}",
"rightValue": "={{ $('Daily Trigger').item.json.timestamp }}",
"operator": {
"type": "dateTime",
"operation": "before"
},
"id": "2e2eea90-cc23-412f-a0f3-c8b9747d0fae"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Relogin"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "2c01f003-c0d1-4ed7-b41b-5e281b7353bb",
"leftValue": "={{ DateTime.fromMillis($json.accessTokenExpiration - 600000)}}",
"rightValue": "={{ $('Daily Trigger').item.json.timestamp }}",
"operator": {
"type": "dateTime",
"operation": "before"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Refresh"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "590bec27-6e6f-4f0b-a701-3a359032c4ed",
"leftValue": "={{ $('Daily Trigger').item.json.timestamp }}",
"rightValue": "",
"operator": {
"type": "dateTime",
"operation": "exists",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Valid"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
110688,
45488
],
"id": "eef379cf-d802-40be-a123-8d95a6329015",
"name": "Switch"
},
{
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\n\n// initialize accessToken on staticData if it desn't exist yet\nif (!staticData.hasOwnProperty('user')) {\n staticData.user = null;\n}\n\nif (!staticData.hasOwnProperty('authType')) {\n staticData.authType = 'Bearer';\n}\n\nif (!staticData.hasOwnProperty('accessToken')) {\n staticData.accessToken = null;\n}\n\nif (!staticData.hasOwnProperty('accessTokenExpiration')) {\n staticData.accessTokenExpiration = 0;\n}\n\nif (!staticData.hasOwnProperty('refreshToken')) {\n staticData.refreshToken = null;\n}\n\nif (!staticData.hasOwnProperty('refreshTokenExpiration')) {\n staticData.refreshTokenExpiration = 0;\n}\n\nconst currentTime = $input.first().json.timestamp;\n\n\nreturn [\n {\n user: staticData.user,\n bearer: staticData.bearer,\n accessToken: staticData.accessToken,\n accessTokenExpiration: staticData.accessTokenExpiration,\n refreshToken: staticData.refreshToken,\n refreshTokenExpiration: staticData.refreshTokenExpiration,\n timestamp: currentTime\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
110496,
45504
],
"id": "4711e596-71a8-4fbf-ba74-263a08e8257a",
"name": "Restore Credentials",
"retryOnFail": false
},
{
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\n// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n staticData.user = item.json.username;\n staticData.authType = item.json.type;\n staticData.accessToken = item.json.accessToken;\n staticData.accessTokenExpiration = item.json.accessTokenExpiration;\n staticData.refreshToken = item.json.refreshToken;\n staticData.refreshTokenExpiration = item.json.refreshTokenExpiration;\n}\n\n//return [{ json: $getWorkflowStaticData('global') }];\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
111440,
45504
],
"id": "b1ab85ff-78ec-402c-913e-0eee4d103b08",
"name": "Store Refresh Credentials"
},
{
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\n// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n staticData.user = item.json.username;\n staticData.authType = item.json.type;\n staticData.accessToken = item.json.accessToken;\n staticData.accessTokenExpiration = item.json.accessTokenExpiration;\n staticData.refreshToken = item.json.refreshToken;\n staticData.refreshTokenExpiration = item.json.refreshTokenExpiration;\n}\n\n//return [{ json: $getWorkflowStaticData('global') }];\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
111360,
45248
],
"id": "38cf504f-6164-4dce-9a23-99027739135b",
"name": "Store Login Credentials"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "C09K30RDEE9",
"mode": "list",
"cachedResultName": "n8n-events"
},
"text": "=⚠️ {{ $workflow.name }} isimli iş akışı çalıştırılırken bir yetkilendirme hatası oluştu \n{{ $json.error?.message || '' }}",
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"type": "n8n-nodes-base.slack",
"typeVersion": 2.4,
"position": [
111152,
45072
],
"id": "43113413-4cd9-41c9-896e-2bbb288eee62",
"name": "Send Error Notification",
"webhookId": "0b3582cf-042e-43d6-bc02-4c9debab0566",
"credentials": {
"slackApi": {
"id": "OKgM8VkM05pJl9kU",
"name": "Tarla Slack Account"
}
}
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 12
}
]
}
},
"id": "a1daad99-6e47-488f-981a-9e1d14c50497",
"name": "Daily Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
109952,
45488
]
},
{
"parameters": {
"method": "POST",
"url": "https://api-test.iklim.co/v1/lightnings/within",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $json.headers.Authorization }}"
},
{
"name": "X-Signature",
"value": "={{ $json.headers['X-Signature'] }}"
},
{
"name": "X-Timestamp",
"value": "={{ $json.headers['X-Timestamp'] }}"
},
{
"name": "X-Nonce",
"value": "={{ $json.headers['X-Nonce'] }}"
},
{
"name": "X-Idempotency-Key",
"value": "={{ $json.headers['X-Idempotency-Key'] }}"
},
{
"name": "Content-Type",
"value": "={{ $json.headers['Content-Type'] }}"
},
{
"name": "Accept",
"value": "={{ $json.headers['Content-Type'] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.requestBody }}",
"options": {}
},
"id": "e64a9282-c1d5-47b3-905f-56c61c67fc4a",
"name": "Lightning Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
112016,
45776
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "bMJhm1lTDbCj9eY1",
"mode": "list",
"cachedResultName": "customers",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/bMJhm1lTDbCj9eY1"
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
110496,
45760
],
"id": "b1d352cc-5608-46b5-9662-39d08a55835d",
"name": "Iterate Customers"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "e2gQ68IGiYmCZkES",
"mode": "list",
"cachedResultName": "wind_turbine_farm",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/e2gQ68IGiYmCZkES"
},
"filters": {
"conditions": [
{
"keyName": "customer_id",
"keyValue": "={{ $json.id }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
111232,
45776
],
"id": "76d808f7-6ece-4de1-b46f-f49df940adf4",
"name": "Get Customer Wind Turbines"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "C0A9K1AC7SN",
"mode": "list",
"cachedResultName": "n8n-events"
},
"text": "=⚠️ {{ $workflow.name }} isimli iş akışı çalıştırılırken bir hata oluştu. \n{{ $('Iterate Customers').item.json.customer_name }} müşterisine ait {{ $('Get Customer Wind Turbines').item.json.name }} isimli türbin için yıldırımlar sorgulanırken şu hata oluştu: {{ $json.error.message }}",
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"type": "n8n-nodes-base.slack",
"typeVersion": 2.4,
"position": [
112160,
46064
],
"id": "bf94a207-c74e-436e-b102-1cd2c472af25",
"name": "Request Error Notification",
"webhookId": "38b3ebc5-a136-4b78-9c7a-98f40e92a9d5",
"notesInFlow": false,
"executeOnce": true,
"credentials": {
"slackApi": {
"id": "OKgM8VkM05pJl9kU",
"name": "Tarla Slack Account"
}
}
},
{
"parameters": {
"jsCode": "const turbines = $input.all();\n\n// 1. Safeguard: Skip if no turbines are found\nif (turbines.length === 0) {\n return { skip_customer: true };\n}\n\nconst lats = turbines.map(t => Number(t.json.latitude));\nconst lons = turbines.map(t => Number(t.json.longitude));\n\n// 2. Calculate Centroid\nconst n = lats.length;\nconst centroid_lat = lats.reduce((a, b) => a + b, 0) / n;\nconst centroid_lon = lons.reduce((a, b) => a + b, 0) / n;\n\n// 3. Haversine Distance Helper\nconst getDist = (lat1, lon1, lat2, lon2) => {\n const R = 6371e3; // Earth radius in meters\n const dLat = (lat2 - lat1) * Math.PI / 180;\n const dLon = (lon2 - lon1) * Math.PI / 180;\n const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLon/2)**2;\n return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n};\n\n// 4. Find Farthest Turbine\nlet maxDist = 0;\nfor (let i = 0; i < n; i++) {\n const d = getDist(centroid_lat, centroid_lon, lats[i], lons[i]);\n if (d > maxDist) maxDist = d;\n}\n\n// 5. Calculate Rings\n// Updated: Ensures a minimum 1000m radius even for a single turbine point\nconst r1 = Math.max(1000, Math.ceil(maxDist / 1000) * 1000);\nconst r4 = r1 + 6000;\nconst boundary = Math.min(r4 + 2000, 49999); \n\nreturn {\n skip_customer: false,\n farm_id: turbines[0].json.customer_id,\n customer_name: $node[\"Loop Over Items\"].json.customer_name,\n centroid_latitude: centroid_lat,\n centroid_longitude: centroid_lon,\n monitoring_boundary_m: boundary,\n rings: { r1, r2: r1+2000, r3: r1+4000, r4 }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
111440,
45776
],
"id": "53e74d39-3891-43c8-aadc-3f91e5a9d651",
"name": "Centroid & Distance Ring calculation"
},
{
"parameters": {
"jsCode": "// Merge the centroid/rings data with the new loop variables\nreturn {\n ...$input.item.json, \n pageNumber: 0,\n stopLoop: false,\n allStrikes: [],\n tStart: null,\n tLast: null\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
111632,
45776
],
"id": "7aafc3a0-1717-4e5d-9eee-d9bf0bf887e6",
"name": "Loop init"
},
{
"parameters": {
"jsCode": "const SILENCE_MS = 12 * 60 * 60 * 1000;\nconst response = $input.first().json; \nconst batchStrikes = response.lightnings || []; \nconst currentPage = response.pageNumber || 0; \n\nconst farm = $('Loop init').all().pop().json;\nlet allStrikes = [];\nlet tLast = null;\nlet tStart = null;\nlet stopLoop = false;\n\n// FIX: Clone the array to avoid 'Read Only' errors\nif (currentPage > 0) {\n try {\n const prevState = $('Logic Gate').all().pop().json;\n allStrikes = prevState.allStrikes ? [...prevState.allStrikes] : [];\n tLast = prevState.tLast;\n tStart = prevState.tStart;\n } catch (e) { }\n}\n\nif (batchStrikes.length === 0 && currentPage === 0) {\n return { stopLoop: true, allStrikes: [], pageNumber: 0, status: \"EMPTY\", customer_name: farm.customer_name };\n}\n\nif (currentPage === 0 && batchStrikes.length > 0) {\n tLast = new Date(batchStrikes[0].captured).getTime();\n}\n\nfor (let i = 0; i < batchStrikes.length; i++) {\n const currentTs = new Date(batchStrikes[i].captured).getTime();\n batchStrikes[i].timestamp = currentTs; \n allStrikes.push(batchStrikes[i]);\n\n if (batchStrikes[i + 1]) {\n const nextTs = new Date(batchStrikes[i + 1].captured).getTime();\n if (currentTs - nextTs > SILENCE_MS) {\n tStart = currentTs;\n stopLoop = true;\n break;\n }\n }\n}\n\nif (batchStrikes.length < 100) {\n stopLoop = true;\n if (!tStart && allStrikes.length > 0) {\n tStart = allStrikes[allStrikes.length - 1].timestamp;\n }\n}\n\n// Ensure End is later than Start\nconst timestamps = allStrikes.map(s => s.timestamp);\nconst final_tLast = timestamps.length > 0 ? Math.max(...timestamps) : tLast;\nconst final_tStart = tStart || (timestamps.length > 0 ? Math.min(...timestamps) : tLast);\n\nconst roundToMinute = (ts) => Math.floor(Number(ts || 0) / 60000);\nconst stormKey = [\n String(farm.customer_name || ''),\n roundToMinute(final_tStart),\n roundToMinute(final_tLast),\n].join('_');\n\nreturn { \n stopLoop, \n pageNumber: currentPage + 1,\n allStrikes, \n tStart: final_tStart, \n tLast: final_tLast,\n storm_start_min: roundToMinute(final_tStart),\n storm_end_min: roundToMinute(final_tLast),\n storm_key: stormKey,\n customer_name: farm.customer_name,\n status: stopLoop ? \"FINISHED\" : \"PAGING\"\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
112256,
45776
],
"id": "45fce946-69cc-4fd0-bce2-42d584f20c37",
"name": "Logic Gate"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "68ba538e-8070-4470-b0fa-30106a1f65b5",
"leftValue": "={{ $node[\"Logic Gate\"].json.stopLoop }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
112464,
45776
],
"id": "31610ff2-9211-40fe-8257-bdd7c6e74b38",
"name": "Stop Loop"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8b6c4fdd-0f94-4bc8-82ac-0996af68c534",
"leftValue": "={{ $node[\"Logic Gate\"].json.allStrikes.length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
112640,
45760
],
"id": "98ed23a6-bca9-4638-b47e-47da4b46be49",
"name": "Lightning found?"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "storm_key",
"keyValue": "={{ $json.storm_key }}"
}
]
},
"limit": 1
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
112768,
45552
],
"id": "559baa58-401d-4f8c-bc56-3ced36e88ba2",
"name": "Get row(s)",
"alwaysOutputData": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "02a4a5c6-2645-4d72-9952-9a62c69fa19f",
"leftValue": "={{ $json.skip_insert }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
113152,
45552
],
"id": "5131ea42-6675-4999-867f-7db6c89c76f8",
"name": "If"
},
{
"parameters": {
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"storm_start": "={{ $json.tStart }}",
"storm_end": "={{ $json.tLast }}",
"total_strikes": "={{ $json.allStrikes.length }}",
"customer_name": "={{ $json.customer_name }}",
"status": "waiting for confirmation",
"storm_key": "={{ $json.storm_key }}",
"storm_start_min": "={{ $json.storm_start_min }}",
"storm_end_min": "={{ $json.storm_end_min }}"
},
"matchingColumns": [],
"schema": [
{
"id": "customer_name",
"displayName": "customer_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "total_strikes",
"displayName": "total_strikes",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "storm_start",
"displayName": "storm_start",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "storm_end",
"displayName": "storm_end",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "storm_key",
"displayName": "storm_key",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "storm_start_min",
"displayName": "storm_start_min",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "storm_end_min",
"displayName": "storm_end_min",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": true,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
113392,
45552
],
"id": "b25febb7-b023-4b47-acc5-d527fd8dbd5f",
"name": "Insert row"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
110992,
45760
],
"id": "f7d058be-24ba-4296-9245-5d74687a7950",
"name": "Loop Over Items"
},
{
"parameters": {
"jsCode": "const crypto = require('crypto');\nconst HMAC_SECRET = 'c88f845bd6d520ded507ef6b02efc223019ccf68f41d9070705712d480ba5166';\nconst URI = '/v1/thunderstorms/within';\n\nconst storm = $('Logic Gate').all().pop().json;\nconst farm = $('Loop init').all().pop().json;\nconst auth = $('Restore Credentials').all().pop().json;\n\nif (!storm.tLast || !storm.tStart) {\n throw new Error(\"Missing storm timestamps. Skipping Thunderstorm query.\");\n}\n\nconst durationSeconds = Math.floor((storm.tLast - storm.tStart) / 1000);\nconst timestamp = Date.now().toString();\n\nconst bodyPayload = {\n latitude: Number(Number(farm.centroid_latitude).toFixed(6)),\n longitude: Number(Number(farm.centroid_longitude).toFixed(6)),\n radius: Math.max(50000, farm.monitoring_boundary_m), // Ensure coverage[cite: 2]\n backwardInterval: Math.max(3600, durationSeconds + 600), // Dynamic padding[cite: 2]\n endTimeEpoch: Number(storm.tLast), // Use actual storm end[cite: 2]\n intersectsWith: \"THREAT_POLYGON\",\n pageNumber: 0,\n pageSize: 10\n};\n\nconst bodyString = JSON.stringify(bodyPayload);\nconst dataToSign = `POST|${URI}|${timestamp}|${bodyString}`;\nconst signature = crypto.createHmac('sha256', HMAC_SECRET).update(dataToSign).digest('hex').toLowerCase();\n\nreturn {\n requestBody: bodyPayload,\n headers: {\n \"X-Signature\": signature,\n \"X-Timestamp\": timestamp,\n \"X-Nonce\": crypto.randomUUID(),\n \"X-Idempotency-Key\": crypto.randomUUID(),\n \"Authorization\": \"Bearer \" + auth.accessToken,\n \"Content-Type\": \"application/json\"\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
113440,
45312
],
"id": "870bef51-d739-4f6b-a56d-57045b932c2d",
"name": "Calculate Thunderstorm Headers"
},
{
"parameters": {
"method": "POST",
"url": "https://api-test.iklim.co/v1/thunderstorms/within",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $json.headers.Authorization }}"
},
{
"name": "X-Signature",
"value": "={{ $json.headers['X-Signature'] }}"
},
{
"name": "X-Timestamp",
"value": "={{ $json.headers['X-Timestamp'] }}"
},
{
"name": "X-Nonce",
"value": "={{ $json.headers['X-Nonce'] }}"
},
{
"name": "X-Idempotency-Key",
"value": "={{ $json.headers['X-Idempotency-Key'] }}"
},
{
"name": "Content-Type",
"value": "={{ $json.headers['Content-Type'] }}"
},
{
"name": "Accept",
"value": "={{ $json.headers['Content-Type'] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.requestBody }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
113648,
45312
],
"id": "51d39941-fe41-45ea-9422-13cc9bb91247",
"name": "HTTP Request"
},
{
"parameters": {
"jsCode": "const crypto = require('crypto');\nconst HMAC_SECRET = 'c88f845bd6d520ded507ef6b02efc223019ccf68f41d9070705712d480ba5166';\nconst METHOD = 'POST'; \nconst URI = '/v1/lightnings/within';\n\n// Get the override timestamp\nconst testConfig = $('Test Configuration').first().json;\nconst endTime = Number(testConfig.testTimestamp);\n\nconst allItems = $input.all();\nconst processedItems = [];\n\nfor (const item of allItems) {\n const farmData = $node[\"Loop init\"].json;\n const auth = $node[\"Restore Credentials\"].json;\n const pageNumber = item.json.pageNumber;\n const timestamp = Date.now().toString();\n \n const bodyPayload = {\n latitude: farmData.centroid_latitude,\n longitude: farmData.centroid_longitude,\n radius: farmData.monitoring_boundary_m, // Use dynamic radius[cite: 2]\n backwardInterval: 43200, // Fixed 12-hour window[cite: 2]\n endTimeEpoch: endTime, // Dynamic override[cite: 2]\n pageNumber: pageNumber,\n pageSize: 100\n };\n\n const bodyString = JSON.stringify(bodyPayload);\n const dataToSign = `${METHOD.toUpperCase()}|${URI}|${timestamp}|${bodyString}`;\n const signature = crypto.createHmac('sha256', HMAC_SECRET).update(dataToSign).digest('hex').toLowerCase();\n\n processedItems.push({\n json: {\n requestBody: bodyPayload,\n headers: {\n \"X-Signature\": signature,\n \"X-Timestamp\": timestamp,\n \"X-Nonce\": crypto.randomUUID(),\n \"X-Idempotency-Key\": crypto.randomUUID(),\n \"Authorization\": \"Bearer \" + auth.accessToken,\n \"Content-Type\": \"application/json\"\n }\n }\n });\n}\n\nreturn processedItems;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
111824,
45776
],
"id": "b01e06dd-7776-48b7-af3a-10555656e749",
"name": "Calculate Lightning Headers"
},
{
"parameters": {
"jsCode": "const turbines = $('Get Customer Wind Turbines').all().map((t) => t.json);\nconst storm = $('Logic Gate').all().pop().json;\nconst farm = $('Loop init').all().pop().json;\nconst thunder = $input.all().map((t) => t.json);\n\nconst getNodeRowsSafely = (nodeName) => {\n try {\n return $(nodeName)\n .all()\n .map((i) => i.json)\n .filter((r) => r && r.id !== undefined && r.id !== null);\n } catch (error) {\n return [];\n }\n};\n\nconst existingRows = getNodeRowsSafely('Get row(s)');\nconst insertedRows = getNodeRowsSafely('Insert row');\nconst stormLogId = insertedRows[0]?.id ?? existingRows[0]?.id ?? null;\n\nreturn {\n customer_name: storm.customer_name,\n storm_log_id: stormLogId,\n centroid_lat: farm.centroid_latitude,\n centroid_lon: farm.centroid_longitude,\n boundary_m: farm.monitoring_boundary_m,\n rings: farm.rings,\n timezone: 'Europe/Istanbul',\n t_start: storm.tStart,\n t_end: storm.tLast,\n n_strikes: (storm.allStrikes || []).length,\n turbines: turbines,\n strikes: storm.allStrikes || [],\n storm_records: thunder,\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
113856,
45312
],
"id": "ff7641de-d15a-4c44-a37d-a808236578e9",
"name": "Build Report Payload"
},
{
"parameters": {
"method": "POST",
"url": "https://patrol-plural-gooey.ngrok-free.dev/generate",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Report-Token",
"value": "test-secret-123"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "ngrok-skip-browser-warning",
"value": "1"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
114064,
45312
],
"id": "3d94c0d5-6972-47ac-a36d-b2688f6462f0",
"name": "Generate Report",
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "const customerName = String($node['Build Report Payload'].json.customer_name || 'customer').trim().replace(/\\s+/g, '_');\nconst now = new Date();\nconst year = String(now.getFullYear()).slice(-2);\nconst month = String(now.getMonth() + 1).padStart(2, '0');\nconst day = String(now.getDate()).padStart(2, '0');\nconst hour = String(now.getHours()).padStart(2, '0');\nconst minute = String(now.getMinutes()).padStart(2, '0');\nconst generatedAt = `${year}-${month}-${day}_${hour}-${minute}`;\nconst fileName = `${generatedAt}_${customerName}_iklimco_lightning_report.docx`;\n\nfor (const item of $input.all()) {\n if (!item.binary || !item.binary.data) {\n continue;\n }\n\n item.binary.data.fileName = fileName;\n item.binary.data.fileExtension = 'docx';\n item.binary.data.mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\n}\n\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
114336,
45312
],
"id": "c8e28536-a780-4d44-a6b2-fcfa7a2a2681",
"name": "Prepare DOCX Metadata"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"matchType": "allConditions",
"filters": {
"conditions": []
},
"limit": 1000
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
114544,
45312
],
"id": "2029bece-7a0c-45b5-8b29-38f284c38014",
"name": "Get Storm Logs For Next Id",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "const payload = $node['Build Report Payload'].json || {};\nconst reportFileItem = $('Prepare DOCX Metadata').all()[0] || {};\nconst rows = $input.all().map((item) => item.json || {});\n\nconst normalizeId = (row) => {\n const raw = row?.id ?? row?.ID ?? row?._id ?? null;\n\n if (raw === null || raw === undefined) {\n return null;\n }\n\n if (typeof raw === 'string' && raw.trim() === '') {\n return null;\n }\n\n const num = Number(raw);\n if (Number.isFinite(num) && num > 0) {\n return num;\n }\n\n return raw;\n};\n\nconst hasValue = (v) => !(v === null || v === undefined || v === '');\n\nlet resolvedStormLogId = hasValue(payload.storm_log_id) ? payload.storm_log_id : null;\n\nif (!hasValue(resolvedStormLogId)) {\n const byKey = rows.find((row) => String(row.storm_key || '') === String(payload.storm_key || ''));\n if (byKey) {\n resolvedStormLogId = normalizeId(byKey);\n }\n}\n\nif (!hasValue(resolvedStormLogId)) {\n const ids = rows\n .map(normalizeId)\n .map((id) => Number(id))\n .filter((id) => Number.isFinite(id) && id > 0);\n\n if (ids.length > 0) {\n resolvedStormLogId = Math.max(...ids);\n }\n}\n\nreturn [{\n json: {\n ...(reportFileItem.json || {}),\n ...payload,\n storm_log_id: hasValue(resolvedStormLogId) ? resolvedStormLogId : 'unknown',\n },\n binary: reportFileItem.binary,\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
114736,
45312
],
"id": "a803d533-d467-432c-af2c-4ef05d3d9eaf",
"name": "Resolve Storm Log Id"
},
{
"parameters": {
"resource": "file",
"options": {
"channelId": "C0A9K1AC7SN",
"initialComment": "=✅ Lightning report uploaded for *{{ $json.customer_name }}* (log id: *{{ $json.storm_log_id }}*)."
}
},
"type": "n8n-nodes-base.slack",
"typeVersion": 2.4,
"position": [
114912,
45312
],
"id": "f29bdf4e-05b7-4a6f-bded-17cfd92c363b",
"name": "Upload Report to Slack",
"webhookId": "c4d5e6f7-a8b9-4012-cdef-345678901234",
"credentials": {
"slackApi": {
"id": "S5M0i1zlyebqU6oP",
"name": "n8n_lightning_report_bot"
}
}
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "C0A9K1AC7SN",
"mode": "list",
"cachedResultName": "n8n-events"
},
"messageType": "block",
"blocksUi": "={{ {\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": `Approve or reject this report for *${$('Resolve Storm Log Id').item.json.customer_name ?? ''}* (log id: \\`${$('Resolve Storm Log Id').item.json.storm_log_id}\\`).`\n }\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"text\": { \"type\": \"plain_text\", \"text\": \"Approve\", \"emoji\": true },\n \"style\": \"primary\",\n \"action_id\": \"approve_storm_log\",\n \"value\": String($('Resolve Storm Log Id').item.json.storm_log_id)\n },\n {\n \"type\": \"button\",\n \"text\": { \"type\": \"plain_text\", \"text\": \"Reject\", \"emoji\": true },\n \"style\": \"danger\",\n \"action_id\": \"reject_storm_log\",\n \"value\": String($('Resolve Storm Log Id').item.json.storm_log_id)\n }\n ]\n }\n ]\n} }}",
"text": "Lightning storm report approval",
"otherOptions": {}
},
"type": "n8n-nodes-base.slack",
"typeVersion": 2.4,
"position": [
115120,
45312
],
"id": "ef2d29d2-8512-4409-9008-89f363c988dc",
"name": "Send Approval Buttons",
"webhookId": "d4e5f6a7-b8c9-0123-def0-456789abcdef",
"credentials": {
"slackApi": {
"id": "S5M0i1zlyebqU6oP",
"name": "n8n_lightning_report_bot"
}
}
},
{
"parameters": {
"httpMethod": "POST",
"path": "slack-storm-log-approval",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
110160,
46224
],
"id": "be48a900-a5bc-499c-aefa-dceeb9f5f0be",
"name": "Slack Interaction Webhook",
"webhookId": "c9d8e7f6-a5b4-4321-fedc-ba9876543211"
},
{
"parameters": {
"jsCode": "const raw = $input.first().json;\nlet payloadRaw = raw.payload;\nif (payloadRaw === undefined && raw.body !== undefined) {\n const b = raw.body;\n if (typeof b === 'string' && b.includes('payload=')) {\n try {\n payloadRaw = new URLSearchParams(b).get('payload');\n } catch {\n payloadRaw = undefined;\n }\n } else if (b && typeof b === 'object' && b.payload !== undefined) {\n payloadRaw = b.payload;\n }\n}\nlet payload;\nif (payloadRaw !== null && payloadRaw !== undefined && typeof payloadRaw === 'object') {\n payload = payloadRaw;\n} else if (typeof payloadRaw === 'string') {\n try {\n payload = JSON.parse(payloadRaw);\n } catch {\n return [{ json: { valid: false } }];\n }\n} else {\n return [{ json: { valid: false } }];\n}\nif (payload.type !== 'block_actions') {\n return [{ json: { valid: false } }];\n}\nconst action = payload.actions?.[0];\nconst rawId = String(action?.value ?? '').trim();\nif (!rawId || !action?.action_id) {\n return [{ json: { valid: false } }];\n}\nlet status = null;\nif (action.action_id === 'approve_storm_log') {\n status = 'confirmed';\n} else if (action.action_id === 'reject_storm_log') {\n status = 'rejected';\n}\nif (!status) {\n return [{ json: { valid: false } }];\n}\n\nconst sectionText = payload?.message?.blocks?.find((block) => block?.type === 'section')?.text?.text || '';\nconst fallbackText = payload?.message?.text || '';\nconst sourceText = sectionText || fallbackText;\nconst customerMatch = sourceText.match(/for \\*(.*?)\\* \\(log id:/i) || sourceText.match(/for (.*?) \\(log id:/i);\nconst customer_name = customerMatch?.[1]?.trim() || 'unknown customer';\n\nconst numId = Number(rawId);\nconst id = Number.isFinite(numId) ? numId : rawId;\nconst slackResponseUrl = typeof payload.response_url === 'string' ? payload.response_url.trim() : '';\nreturn [{ json: { valid: true, id, status, customer_name, slack_response_url: slackResponseUrl } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
110400,
46224
],
"id": "c2d0291b-1cd6-4858-a59b-e4a9bc6a8d5d",
"name": "Parse Slack Button Payload"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "b8c9d0e1-f2a3-4455-b667-c78890123456",
"leftValue": "={{ $json.valid }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
110640,
46224
],
"id": "1df1032d-dcd0-4b76-a442-a2329a7ef26f",
"name": "Valid Storm Approval Button?"
},
{
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyValue": "={{ $json.id }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"status": "={{ $json.status }}"
},
"matchingColumns": [],
"schema": [
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
111392,
46224
],
"id": "8d664796-186c-4074-b4c0-2c3914823f9b",
"name": "Approve Pending Row"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Still Valid After Ack?').first().json.slack_response_url }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ response_type: 'in_channel', replace_original: false, text: $('Still Valid After Ack?').first().json.status === 'confirmed' ? ('Storm log #' + String($('Still Valid After Ack?').first().json.id) + ' for ' + String($('Still Valid After Ack?').first().json.customer_name || 'unknown customer') + ' approved and recorded in storm_logs.') : ('Storm log #' + String($('Still Valid After Ack?').first().json.id) + ' for ' + String($('Still Valid After Ack?').first().json.customer_name || 'unknown customer') + ' rejected and recorded in storm_logs.') }) }}",
"options": {}
},
"id": "86388d90-0625-4062-853e-9ee8e72be88a",
"name": "Notify Slack Recording Result",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
111632,
46224
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
110896,
46224
],
"id": "20d9d846-ec6f-4029-aeeb-11540bacbae4",
"name": "Respond Slack Interaction"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "f7e8d9c0-a1b2-4333-c445-d56677889901",
"leftValue": "={{ $json.valid }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
111088,
46224
],
"id": "dc4a936d-46a5-4ac3-9af9-ca38400a2bb6",
"name": "Still Valid After Ack?"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "8a6510f7-a33d-4c87-9472-44329c24e08e",
"name": "testTimestamp",
"value": "=1777824000000",
"type": "number"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
110224,
45488
],
"id": "43deee45-026b-4e8d-9be1-b7d90796f8ab",
"name": "Test Configuration"
},
{
"parameters": {
"jsCode": "\n\n// Grab the data from the Logic Gate node explicitly\nconst stormData = $('Logic Gate').item.json;\n\n// Check if \"Get row(s)\" found anything\nconst existing = $input.all().filter(i => i.json && i.json.id !== undefined && i.json.id !== null);\n\nif (existing.length > 0) {\n // If found, merge and skip\n return [{ json: { ...stormData, skip_insert: true } }];\n}\n\n// If not found, merge and allow insert\nreturn [{ json: { ...stormData, skip_insert: false } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
112944,
45552
],
"id": "71ff30c4-efec-4a11-81af-bc68b7f8a58c",
"name": "Prepare Insert Decision"
}
],
"pinData": {},
"connections": {
"Login to iklim.co": {
"main": [
[
{
"node": "CalculateExpirations",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Error Notification",
"type": "main",
"index": 0
}
]
]
},
"CalculateExpirations": {
"main": [
[
{
"node": "Store Login Credentials",
"type": "main",
"index": 0
}
]
]
},
"CalculateExpirations1": {
"main": [
[
{
"node": "Store Refresh Credentials",
"type": "main",
"index": 0
}
]
]
},
"Refresh Token": {
"main": [
[
{
"node": "CalculateExpirations1",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Error Notification",
"type": "main",
"index": 0
}
]
]
},
"Restore Credentials": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Login to iklim.co",
"type": "main",
"index": 0
}
],
[
{
"node": "Refresh Token",
"type": "main",
"index": 0
}
],
[
{
"node": "Iterate Customers",
"type": "main",
"index": 0
}
]
]
},
"Store Login Credentials": {
"main": [
[
{
"node": "Restore Credentials",
"type": "main",
"index": 0
}
]
]
},
"Store Refresh Credentials": {
"main": [
[
{
"node": "Restore Credentials",
"type": "main",
"index": 0
}
]
]
},
"Daily Trigger": {
"main": [
[
{
"node": "Test Configuration",
"type": "main",
"index": 0
}
]
]
},
"Lightning Request": {
"main": [
[
{
"node": "Logic Gate",
"type": "main",
"index": 0
}
]
]
},
"Iterate Customers": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get Customer Wind Turbines": {
"main": [
[
{
"node": "Centroid & Distance Ring calculation",
"type": "main",
"index": 0
}
]
]
},
"Centroid & Distance Ring calculation": {
"main": [
[
{
"node": "Loop init",
"type": "main",
"index": 0
}
]
]
},
"Loop init": {
"main": [
[
{
"node": "Calculate Lightning Headers",
"type": "main",
"index": 0
}
]
]
},
"Logic Gate": {
"main": [
[
{
"node": "Stop Loop",
"type": "main",
"index": 0
}
]
]
},
"Stop Loop": {
"main": [
[
{
"node": "Lightning found?",
"type": "main",
"index": 0
}
],
[
{
"node": "Calculate Lightning Headers",
"type": "main",
"index": 0
}
]
]
},
"Lightning found?": {
"main": [
[
{
"node": "Get row(s)",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get row(s)": {
"main": [
[
{
"node": "Prepare Insert Decision",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Insert row",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Insert row": {
"main": [
[
{
"node": "Calculate Thunderstorm Headers",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Get Customer Wind Turbines",
"type": "main",
"index": 0
}
]
]
},
"Calculate Thunderstorm Headers": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Calculate Lightning Headers": {
"main": [
[
{
"node": "Lightning Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Build Report Payload",
"type": "main",
"index": 0
}
]
]
},
"Build Report Payload": {
"main": [
[
{
"node": "Generate Report",
"type": "main",
"index": 0
}
]
]
},
"Generate Report": {
"main": [
[
{
"node": "Prepare DOCX Metadata",
"type": "main",
"index": 0
}
]
]
},
"Prepare DOCX Metadata": {
"main": [
[
{
"node": "Get Storm Logs For Next Id",
"type": "main",
"index": 0
}
]
]
},
"Get Storm Logs For Next Id": {
"main": [
[
{
"node": "Resolve Storm Log Id",
"type": "main",
"index": 0
}
]
]
},
"Resolve Storm Log Id": {
"main": [
[
{
"node": "Upload Report to Slack",
"type": "main",
"index": 0
}
]
]
},
"Upload Report to Slack": {
"main": [
[
{
"node": "Send Approval Buttons",
"type": "main",
"index": 0
}
]
]
},
"Send Approval Buttons": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Slack Interaction Webhook": {
"main": [
[
{
"node": "Parse Slack Button Payload",
"type": "main",
"index": 0
}
]
]
},
"Parse Slack Button Payload": {
"main": [
[
{
"node": "Valid Storm Approval Button?",
"type": "main",
"index": 0
}
]
]
},
"Valid Storm Approval Button?": {
"main": [
[
{
"node": "Respond Slack Interaction",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Slack Interaction",
"type": "main",
"index": 0
}
]
]
},
"Respond Slack Interaction": {
"main": [
[
{
"node": "Still Valid After Ack?",
"type": "main",
"index": 0
}
]
]
},
"Still Valid After Ack?": {
"main": [
[
{
"node": "Approve Pending Row",
"type": "main",
"index": 0
}
]
]
},
"Approve Pending Row": {
"main": [
[
{
"node": "Notify Slack Recording Result",
"type": "main",
"index": 0
}
]
]
},
"Test Configuration": {
"main": [
[
{
"node": "Restore Credentials",
"type": "main",
"index": 0
}
]
]
},
"Prepare Insert Decision": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "4a1ab303-2e6c-46cf-84be-93330ae75b74",
"meta": {
"instanceId": "15c4ff3a74619031c77894fe5fb8c0fd585362ef637b1873abd56a139f543e12"
},
"id": "0GaXSvJtxW6JR6hv",
"tags": []
}