BE-LightningReport/Lightning_Report_Automatic.json

2497 lines
82 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": "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": [
144864,
61392
],
"typeVersion": 1,
"id": "7421dafd-30d6-4b5a-8515-5b8dc7de8a7a",
"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": "20d63bf7-de85-4f71-94e3-5c479dd2991f",
"name": "Login to iklim.co",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
143664,
60480
],
"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": [
143904,
60464
],
"id": "40fc21b3-1a4c-44a0-9831-fa34b6730e33",
"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": [
143968,
60720
],
"id": "6e9dd3f4-ffb7-46dc-b6b8-8439698012e6",
"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": "4fbac84b-6069-4be5-b6d0-cca265f2364b",
"name": "Refresh Token",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
143744,
60736
],
"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": [
143440,
60704
],
"id": "f62f304e-72dd-4c55-834f-1d0569f0f7e3",
"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": [
143248,
60720
],
"id": "023b107a-6e4b-464c-8c96-62e8fcb929af",
"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": [
144192,
60720
],
"id": "f2e3ca7a-eb6b-48d7-bc69-83755dea9ee6",
"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": [
144112,
60464
],
"id": "5ed52fa8-32be-4612-b1a7-1adfb61acabf",
"name": "Store Login Credentials"
},
{
"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 yetkilendirme hatası oluştu \n{{ $json.error?.message || '' }}",
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"type": "n8n-nodes-base.slack",
"typeVersion": 2.4,
"position": [
143904,
60288
],
"id": "3fcb6f64-0273-4fce-a4d3-0ed465a65d7c",
"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": "9294eedf-baee-4020-8489-b7c10589a37b",
"name": "Daily Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
142704,
60704
]
},
{
"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": "a9d7863d-9929-4b22-a013-22cefb1939cc",
"name": "Lightning Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
144768,
60992
],
"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": [
143248,
60976
],
"id": "4322bbc8-14a9-4107-9b2e-330ba07a3b31",
"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": [
143984,
60992
],
"id": "22fdfc9c-9aae-4192-84ce-53b0cb05e452",
"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": [
144896,
60816
],
"id": "87edff46-b231-4e47-ab26-38d3d19bd3d0",
"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": [
144192,
60992
],
"id": "db2cd01a-fedb-4cae-ad69-9f6ba6ea659a",
"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": [
144384,
60992
],
"id": "282c62de-b9f8-4aed-9908-a8b8e153363e",
"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": [
145008,
60992
],
"id": "9dd01358-362f-4676-bf35-af32739c4cc1",
"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": [
145200,
60960
],
"id": "2fa3ce7d-fec7-49fe-bd69-099d3fd11c61",
"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": [
145392,
60976
],
"id": "0e8dfc78-4fe0-4eb1-9649-f173c50ee884",
"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": [
145632,
61008
],
"id": "2d8a4c7d-7feb-4d0f-9ef5-0db2e92325ed",
"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": [
146016,
61008
],
"id": "ae67e14a-6d20-4300-9095-a31dafc43b44",
"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": [
146256,
61008
],
"id": "8f2ab12b-8a8e-4616-a4ad-2238bf45a168",
"name": "Insert row"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
143744,
60976
],
"id": "fe493f2d-72e3-40c0-9a02-d4b1e1499676",
"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": [
146480,
61008
],
"id": "63828ae4-ff78-4c6e-b81c-88b4a9575eb0",
"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": [
146688,
61008
],
"id": "4107110b-79b2-4e53-ae98-cb4a39ce8631",
"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": [
144576,
60992
],
"id": "c7b6f9e1-561e-4a39-9cbe-d5978d3ddbdc",
"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": [
146896,
61008
],
"id": "122509a3-5b70-4dbf-ac9e-f02fff0a1daa",
"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": [
147104,
61008
],
"id": "75501180-13a3-4022-8033-03c803ef808a",
"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": [
147376,
61008
],
"id": "da5ce86c-b0a2-4cb1-a552-71fdba0e82d5",
"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": [
147584,
61008
],
"id": "71310c32-b4bf-466f-9bf0-c8b2be392b67",
"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": [
147776,
61008
],
"id": "55fd799a-5c5e-4127-86be-23ed202c9569",
"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": [
148432,
61024
],
"id": "7f552eec-e913-4259-981c-36c533980918",
"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": [
149072,
61184
],
"id": "44af280f-9c40-4380-bccf-40a7102a9e71",
"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": [
142912,
61440
],
"id": "d251f73d-92e7-4969-9363-0a49ec586a51",
"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": [
143152,
61440
],
"id": "4fac27df-10cd-4088-b263-43cf89965f14",
"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": [
143392,
61440
],
"id": "fe06e747-6740-4ef3-b546-a41d3c20e227",
"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": [
144144,
61440
],
"id": "ef6d5666-fe14-43cf-96d5-d4b374bf2aa5",
"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": "={{ (() => {\n const ack = $('Still Valid After Ack?').first().json;\n const isConfirmed = ack.status === 'confirmed';\n const id = String(ack.id ?? '');\n const customer = String(ack.customer_name || 'unknown customer');\n const verb = isConfirmed ? 'approved' : 'rejected';\n return {\n response_type: 'in_channel',\n replace_original: false,\n text: `Storm log #${id} for ${customer} ${verb} and recorded in storm_logs.`,\n };\n})() }}",
"options": {}
},
"id": "90d9a1c5-10b9-4d9a-baa9-3a2462631e84",
"name": "Notify Slack Recording Result",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
146784,
61824
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
143648,
61440
],
"id": "e1e695ba-c94e-4e5b-bb3d-f9d7614035a6",
"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": [
143840,
61440
],
"id": "f9ceff24-2b15-4778-b278-7786ead00f53",
"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": [
142976,
60704
],
"id": "542e9778-11be-440d-83f3-ee94d902d6fc",
"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": [
145808,
61008
],
"id": "8744897d-3ea2-4cd8-b4eb-a0495f2e7640",
"name": "Prepare Insert Decision"
},
{
"parameters": {
"jsCode": "const reportItem = $input.first();\nconst customer = $('Loop Over Items').item.json || {};\nconst contactEmail = String(customer.contact_email || '').trim();\nconst fileName = reportItem.binary?.data?.fileName || 'lightning_report.docx';\nconst customerName = reportItem.json.customer_name || customer.customer_name || 'Customer';\n\nreturn [{\n json: {\n ...reportItem.json,\n contact_email: contactEmail,\n customer_id: customer.id,\n email_file_name: fileName,\n customer_name: customerName,\n },\n binary: reportItem.binary,\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
147984,
61024
],
"id": "b50ace64-6a0d-44a6-9803-b4d6e58e6375",
"name": "Prepare Customer Email Context"
},
{
"parameters": {
"content": "Add these columns to the storm_logs datatable:\n- pending_contact_email (string)\n- pending_email_file_name (string)\n- pending_report_base64 (long text)\n- pending_report_mime_type (string)\n\nReports persist here until approval email is sent or rejected.",
"width": 420
},
"type": "n8n-nodes-base.stickyNote",
"position": [
147936,
60672
],
"typeVersion": 1,
"id": "cde69b18-d0f9-4a07-b901-17df8a2651a3",
"name": "Sticky Note Storm Logs Email Columns"
},
{
"parameters": {
"jsCode": "const item = $input.first();\nconst stormLogId = String(item.json.storm_log_id ?? '').trim();\nconst binaryMeta = item.binary?.data || {};\nlet reportBase64 = '';\n\ntry {\n const buffer = await this.helpers.getBinaryDataBuffer(0, 'data');\n reportBase64 = buffer.toString('base64');\n} catch {\n const raw = binaryMeta.data;\n if (typeof raw === 'string') {\n reportBase64 = raw.replace(/^data:[^;]+;base64,/, '').replace(/\\s+/g, '');\n } else if (raw) {\n reportBase64 = Buffer.from(raw).toString('base64');\n }\n}\n\nconst persistSkipped = !stormLogId || stormLogId === 'unknown' || !reportBase64;\n\nreturn [{\n json: {\n ...item.json,\n storm_log_id: stormLogId,\n pending_contact_email: item.json.contact_email || '',\n pending_email_file_name: item.json.email_file_name || 'lightning_report.docx',\n pending_report_base64: reportBase64,\n pending_report_mime_type: binaryMeta.mimeType || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n persist_skipped: persistSkipped,\n },\n binary: item.binary,\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
148192,
61024
],
"id": "c6baaa7b-b348-49b6-a854-fb3adfb4c561",
"name": "Build Pending Email Record"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "a9b8c7d6-e5f4-3210-abcd-ef0987654321",
"leftValue": "={{ $('Build Pending Email Record').first().json.persist_skipped }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
148624,
61024
],
"id": "a6aad493-3757-47c8-bd41-46d38c6fb1cc",
"name": "Can Persist Report?"
},
{
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyValue": "={{ $('Build Pending Email Record').first().json.storm_log_id }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"pending_contact_email": "={{ $('Build Pending Email Record').first().json.pending_contact_email }}",
"pending_email_file_name": "={{ $('Build Pending Email Record').first().json.pending_email_file_name }}",
"pending_report_base64": "={{ $('Build Pending Email Record').first().json.pending_report_base64 }}",
"pending_report_mime_type": "={{ $('Build Pending Email Record').first().json.pending_report_mime_type }}"
},
"matchingColumns": [],
"schema": [
{
"id": "pending_contact_email",
"displayName": "pending_contact_email",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "pending_email_file_name",
"displayName": "pending_email_file_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "pending_report_base64",
"displayName": "pending_report_base64",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "pending_report_mime_type",
"displayName": "pending_report_mime_type",
"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": [
148864,
61024
],
"id": "2031d43b-471e-4e91-b639-b826b0de78b7",
"name": "Persist Report To Storm Logs"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"leftValue": "={{ $('Still Valid After Ack?').first().json.status }}",
"rightValue": "confirmed",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
144336,
61712
],
"id": "54d38d5a-6cb3-4502-97c0-f9fa38983769",
"name": "Report Approved?"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyValue": "={{ $('Still Valid After Ack?').first().json.id }}"
}
]
},
"limit": 1
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
144608,
61632
],
"id": "61138681-af3b-4c69-9daf-cc4e8778de24",
"name": "Get Pending Report From Storm Logs",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "const ack = $('Still Valid After Ack?').first().json;\nconst stormLogId = String(ack.id ?? '').trim();\nconst rows = $('Get Pending Report From Storm Logs').all()\n .map((item) => item.json)\n .filter((row) => row && row.id !== undefined && row.id !== null);\nconst row = rows[0] || {};\nlet reportBase64 = String(row.pending_report_base64 || '').trim();\nreportBase64 = reportBase64.replace(/^data:[^;]+;base64,/, '').replace(/\\s+/g, '');\nconst hasQueued = reportBase64.length > 0;\n\nreturn [{\n json: {\n ...ack,\n has_queued_report: hasQueued,\n contact_email: String(row.pending_contact_email || '').trim(),\n customer_name: row.customer_name || ack.customer_name,\n email_file_name: row.pending_email_file_name || 'lightning_report.docx',\n pending_report_base64: reportBase64,\n pending_report_mime_type: row.pending_report_mime_type || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n storm_log_id: stormLogId,\n },\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
144832,
61632
],
"id": "51237c29-607f-4965-b955-f9eca7c1cfab",
"name": "Load Queued Customer Report"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "bMJhm1lTDbCj9eY1",
"mode": "list",
"cachedResultName": "customers",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/bMJhm1lTDbCj9eY1"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "customer_name",
"keyValue": "={{ $('Load Queued Customer Report').item.json.customer_name }}"
}
]
},
"limit": 1
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
145072,
61632
],
"id": "054cb939-b765-4dd6-8bda-89ddbe71ba07",
"name": "Lookup Customer Contact Email",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "const ack = $('Load Queued Customer Report').first().json;\nconst lookupRows = $input.all().map((i) => i.json).filter((r) => r && (r.contact_email || r.id));\nconst lookupEmail = lookupRows.length > 0 ? String(lookupRows[0].contact_email || '').trim() : '';\nconst contactEmail = String(ack.contact_email || '').trim() || lookupEmail;\n\nreturn [{\n json: {\n ...ack,\n contact_email: contactEmail,\n },\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
145280,
61632
],
"id": "909bdb7d-cdad-4ea9-bc47-e52d7de9dbe1",
"name": "Merge Customer Contact Email"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "e5f6a7b8-c9d0-1234-ef01-345678901234",
"leftValue": "={{ $json.contact_email }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty"
}
},
{
"id": "f6a7b8c9-d0e1-2345-f012-456789012346",
"leftValue": "={{ $json.has_queued_report }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
145488,
61632
],
"id": "0bfb34a1-725e-4390-9537-a458ad9e8b19",
"name": "Ready To Email Customer?"
},
{
"parameters": {
"jsCode": "const item = $input.first();\nconst toEmail = String(item.json.contact_email || '').trim();\nconst fromEmail = 'info@iklim.co';\nconst customerName = String(item.json.customer_name || 'Customer');\nconst fileName = String(item.json.email_file_name || 'lightning_report.docx');\nconst mimeType = item.json.pending_report_mime_type || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\n\nlet content = String(item.json.pending_report_base64 || '').trim();\ncontent = content.replace(/^data:[^;]+;base64,/, '').replace(/\\s+/g, '');\n\nif (!toEmail) {\n throw new Error('Missing contact_email for SendGrid');\n}\nif (!content) {\n throw new Error('Missing pending_report_base64 for SendGrid attachment');\n}\n\ntry {\n Buffer.from(content, 'base64');\n} catch {\n throw new Error('pending_report_base64 is not valid base64');\n}\n\nreturn [{\n json: {\n ...item.json,\n sendgrid_payload: {\n personalizations: [{ to: [{ email: toEmail }] }],\n from: { email: fromEmail, name: 'iklim.co' },\n subject: `Yıldırım Aktivite Raporu ${customerName}`,\n content: [{\n type: 'text/html',\n value: `<p>Sayın yetkili,</p><p><strong>${customerName}</strong> için yıldırım aktivite raporunuz ektedir.</p><p>İyi çalışmalar,<br>İklim.co</p>`,\n }],\n attachments: [{\n content,\n filename: fileName,\n type: mimeType,\n disposition: 'attachment',\n }],\n },\n },\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
145600,
61616
],
"id": "f3a4b5c6-d7e8-9012-3456-7890abcdef12",
"name": "Prepare SendGrid Email Payload"
},
{
"parameters": {
"method": "POST",
"url": "https://api.sendgrid.com/v3/mail/send",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "sendGridApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.sendgrid_payload }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
145712,
61616
],
"id": "df693a02-eda1-4a05-b801-9db5c35ab936",
"name": "Send Customer Report Email",
"credentials": {
"sendGridApi": {
"id": "fhinNHPH2Wnm5fUh",
"name": "SendGrid account"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"value": "PCYhmruG5hu52Alf",
"mode": "list",
"cachedResultName": "storm_logs",
"cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyValue": "={{ $('Still Valid After Ack?').first().json.id }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"pending_contact_email": "",
"pending_email_file_name": "",
"pending_report_base64": "",
"pending_report_mime_type": ""
},
"matchingColumns": [],
"schema": [
{
"id": "pending_contact_email",
"displayName": "pending_contact_email",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "pending_email_file_name",
"displayName": "pending_email_file_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "pending_report_base64",
"displayName": "pending_report_base64",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "pending_report_mime_type",
"displayName": "pending_report_mime_type",
"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": [
146176,
61824
],
"id": "518b94f2-5d48-4776-9fa9-7236dbfd8eb4",
"name": "Clear Pending Report In Storm Logs"
},
{
"parameters": {
"jsCode": "const ack = $('Still Valid After Ack?').first().json;\nconst stormLogId = String($json.storm_log_id ?? $json.id ?? ack.id ?? '').trim();\nreturn [{ json: { ...ack, ...$json, storm_log_id: stormLogId || ack.id } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
146368,
61824
],
"id": "66146634-8596-4392-ad9a-86cfb489e291",
"name": "Restore Approval Context"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d0e1f2a3-b4c5-6789-abcd-ef0123456780",
"leftValue": "={{ $('Still Valid After Ack?').first().json.slack_response_url }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
146560,
61824
],
"id": "a98e4c0d-2651-4841-86bf-e17a4fb7b73f",
"name": "Has Slack Response URL?"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "C0A9K1AC7SN",
"mode": "list",
"cachedResultName": "n8n-events"
},
"text": "=📧 Lightning report emailed to {{ $('Merge Customer Contact Email').item.json.contact_email }} for *{{ $('Merge Customer Contact Email').item.json.customer_name }}*.",
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"type": "n8n-nodes-base.slack",
"typeVersion": 2.4,
"position": [
146000,
61600
],
"id": "ed9d9a60-4b83-4ed6-bbf5-c2d1bb872f9a",
"name": "Email Sent Notification",
"webhookId": "e1f2a3b4-c5d6-7890-abcd-ef0123456789",
"credentials": {
"slackApi": {
"id": "OKgM8VkM05pJl9kU",
"name": "Tarla Slack Account"
}
}
}
],
"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
}
],
[
{
"node": "Request Error Notification",
"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": "Prepare Customer Email Context",
"type": "main",
"index": 0
}
]
]
},
"Prepare Customer Email Context": {
"main": [
[
{
"node": "Build Pending Email Record",
"type": "main",
"index": 0
}
]
]
},
"Build Pending Email Record": {
"main": [
[
{
"node": "Upload Report to Slack",
"type": "main",
"index": 0
}
]
]
},
"Upload Report to Slack": {
"main": [
[
{
"node": "Can Persist Report?",
"type": "main",
"index": 0
}
]
]
},
"Can Persist Report?": {
"main": [
[
{
"node": "Persist Report To Storm Logs",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Approval Buttons",
"type": "main",
"index": 0
}
]
]
},
"Persist Report To Storm Logs": {
"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": "Report Approved?",
"type": "main",
"index": 0
}
]
]
},
"Report Approved?": {
"main": [
[
{
"node": "Get Pending Report From Storm Logs",
"type": "main",
"index": 0
}
],
[
{
"node": "Clear Pending Report In Storm Logs",
"type": "main",
"index": 0
}
]
]
},
"Get Pending Report From Storm Logs": {
"main": [
[
{
"node": "Load Queued Customer Report",
"type": "main",
"index": 0
}
]
]
},
"Load Queued Customer Report": {
"main": [
[
{
"node": "Lookup Customer Contact Email",
"type": "main",
"index": 0
}
]
]
},
"Lookup Customer Contact Email": {
"main": [
[
{
"node": "Merge Customer Contact Email",
"type": "main",
"index": 0
}
]
]
},
"Merge Customer Contact Email": {
"main": [
[
{
"node": "Ready To Email Customer?",
"type": "main",
"index": 0
}
]
]
},
"Ready To Email Customer?": {
"main": [
[
{
"node": "Prepare SendGrid Email Payload",
"type": "main",
"index": 0
}
],
[
{
"node": "Clear Pending Report In Storm Logs",
"type": "main",
"index": 0
}
]
]
},
"Prepare SendGrid Email Payload": {
"main": [
[
{
"node": "Send Customer Report Email",
"type": "main",
"index": 0
}
]
]
},
"Send Customer Report Email": {
"main": [
[
{
"node": "Email Sent Notification",
"type": "main",
"index": 0
}
],
[
{
"node": "Clear Pending Report In Storm Logs",
"type": "main",
"index": 0
}
]
]
},
"Email Sent Notification": {
"main": [
[
{
"node": "Clear Pending Report In Storm Logs",
"type": "main",
"index": 0
}
]
]
},
"Clear Pending Report In Storm Logs": {
"main": [
[
{
"node": "Restore Approval Context",
"type": "main",
"index": 0
}
]
]
},
"Restore Approval Context": {
"main": [
[
{
"node": "Has Slack Response URL?",
"type": "main",
"index": 0
}
]
]
},
"Has Slack Response URL?": {
"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": "c793524f-e29c-4297-887b-2c791353f895",
"meta": {
"instanceId": "15c4ff3a74619031c77894fe5fb8c0fd585362ef637b1873abd56a139f543e12"
},
"id": "0GaXSvJtxW6JR6hv",
"tags": []
}