569 lines
23 KiB
JSON
569 lines
23 KiB
JSON
|
|
{
|
||
|
|
"workflowId": "005iI4jX5mq9DsTR",
|
||
|
|
"activeVersionId": "753f5423-4417-4c0a-bc0c-52e3b0318b32",
|
||
|
|
"nodes": [
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"promptType": "define",
|
||
|
|
"text": "={{ \"Extract the purchase order data from the attached image.\" }}",
|
||
|
|
"hasOutputParser": true,
|
||
|
|
"options": {
|
||
|
|
"systemMessage": "You are a purchase order parser. Given the attached PDF or scanned image, extract the purchase order header and line items.\n\nReturn ONLY a JSON object (no markdown, no commentary) matching this exact schema:\n\n{\n \"po_number\": string,\n \"supplier\": string,\n \"project_code\": string,\n \"required_by_date\": \"YYYY-MM-DD\" | null,\n \"ordered_by\": string | null,\n \"items\": [\n {\n \"description\": string,\n \"qty\": number,\n \"rate\": number,\n \"unit\": string | null,\n \"material_code\": string | null,\n \"cost_code\": string | null,\n \"disc_percent\": number | null,\n \"vat_percent\": number | null\n }\n ]\n}\n\nRules:\n- Use null (not \"\") for missing optional fields.\n- disc_percent and vat_percent must be 0-100 if provided.\n- Drop any line item missing description, qty, or rate.\n- po_number, supplier, project_code must be non-empty strings. If you cannot determine them, return empty strings (the caller will treat that as a failure).",
|
||
|
|
"passthroughBinaryImages": "={{ true }}",
|
||
|
|
"passthroughBinaryPdfs": "={{ false }}"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
||
|
|
"typeVersion": 3.1,
|
||
|
|
"position": [
|
||
|
|
-400,
|
||
|
|
-208
|
||
|
|
],
|
||
|
|
"id": "b6345673-410a-47a7-9be9-d42684666de1",
|
||
|
|
"name": "AI Agent1"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"schemaType": "manual",
|
||
|
|
"inputSchema": "{\n \"type\": \"object\",\n \"required\": [\"po_number\", \"supplier\", \"project_code\", \"items\"],\n \"properties\": {\n \"po_number\": {\"type\": \"string\", \"description\": \"Unique PO number printed on the document\"},\n \"supplier\": {\"type\": \"string\", \"description\": \"Name of the supplier or vendor issuing the PO\"},\n \"project_code\": {\"type\": \"string\", \"description\": \"Internal project code the PO is charged to (e.g. MEGA-001)\"},\n \"required_by_date\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Required-by date in ISO YYYY-MM-DD format, or null if not stated\"},\n \"ordered_by\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Person who placed the order, or null if not shown\"},\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Line items in the PO\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"description\", \"qty\", \"rate\"],\n \"properties\": {\n \"description\": {\"type\": \"string\", \"description\": \"Item description / part name\"},\n \"qty\": {\"type\": \"number\", \"description\": \"Quantity ordered (must be greater than 0)\"},\n \"rate\": {\"type\": \"number\", \"minimum\": 0, \"description\": \"Unit rate excluding VAT (must be 0 or greater)\"},\n \"unit\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Unit of measure (e.g. ea, m, kg), or null if not specified\"},\n \"material_code\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Internal material code, or null if not present\"},\n \"cost_code\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Cost code or GL account, or null if not present\"},\n \"disc_percent\": {\"type\": \"number\", \"minimum\": 0, \"maximum\": 100, \"nullable\": true, \"description\": \"Discount percentage 0-100, or null if not applied\"},\n \"vat_percent\": {\"type\": \"number\", \"minimum\": 0, \"maximum\": 100, \"nullable\": true, \"description\": \"VAT percentage 0-100, or null if not applied\"}\n }\n }\n }\n }\n}",
|
||
|
|
"autoFix": "={{ true }}"
|
||
|
|
},
|
||
|
|
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
|
||
|
|
"typeVersion": 1.3,
|
||
|
|
"position": [
|
||
|
|
-304,
|
||
|
|
32
|
||
|
|
],
|
||
|
|
"id": "eed3c658-971b-428f-a1e6-08b1562730a1",
|
||
|
|
"name": "Structured Output Parser1"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"url": "https://pb.mega.infra.apphub.co.za/api/collections/purchase_orders/records",
|
||
|
|
"sendQuery": true,
|
||
|
|
"queryParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "filter",
|
||
|
|
"value": "=processing_status=\"pending\" && (next_retry_at=\"\" || next_retry_at<\"{{ $now.toISO() }}\")"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "sort",
|
||
|
|
"value": "date_uploaded"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "perPage",
|
||
|
|
"value": "1"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"sendHeaders": true,
|
||
|
|
"headerParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "Authorization",
|
||
|
|
"value": "={{ $('Auth PB Admin').item.json.token }}"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"options": {}
|
||
|
|
},
|
||
|
|
"id": "14a3f094-fa28-4e91-983f-7c61aafd0f68",
|
||
|
|
"name": "Fetch Pending Batches",
|
||
|
|
"type": "n8n-nodes-base.httpRequest",
|
||
|
|
"typeVersion": 4.5,
|
||
|
|
"position": [
|
||
|
|
-2672,
|
||
|
|
240
|
||
|
|
],
|
||
|
|
"executeOnce": true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"method": "PATCH",
|
||
|
|
"url": "=https://pb.mega.infra.apphub.co.za/api/collections/purchase_orders/records/{{ $json.id }}",
|
||
|
|
"sendQuery": true,
|
||
|
|
"queryParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "filter",
|
||
|
|
"value": "=processing_status=\"pending\""
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"sendHeaders": true,
|
||
|
|
"headerParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "Authorization",
|
||
|
|
"value": "={{ $('Auth PB Admin').item.json.token }}"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "Content-Type",
|
||
|
|
"value": "application/json"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"sendBody": true,
|
||
|
|
"specifyBody": "json",
|
||
|
|
"jsonBody": "={ \"processing_status\": \"processing\", \"attempts\": {{ $json.attempts + 1 }}, \"last_attempt_at\": \"{{ $now.toISO() }}\" }",
|
||
|
|
"options": {
|
||
|
|
"response": {
|
||
|
|
"response": {
|
||
|
|
"responseFormat": "json"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"id": "733d1b8c-e2a8-476e-b446-15f143a612c9",
|
||
|
|
"name": "Atomic Claim",
|
||
|
|
"type": "n8n-nodes-base.httpRequest",
|
||
|
|
"typeVersion": 4.5,
|
||
|
|
"position": [
|
||
|
|
-1552,
|
||
|
|
-368
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"url": "=https://pb.mega.infra.apphub.co.za/api/files/purchase_orders/{{ $('Claim Successful?').item.json.id }}/{{ $('Claim Successful?').item.json.purchase_order_file }}",
|
||
|
|
"sendHeaders": true,
|
||
|
|
"headerParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "Authorization",
|
||
|
|
"value": "={{ $('Auth PB Admin').item.json.token }}"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"options": {
|
||
|
|
"response": {
|
||
|
|
"response": {
|
||
|
|
"responseFormat": "file"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"sendQuery": true,
|
||
|
|
"queryParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "token",
|
||
|
|
"value": "={{ $json.token }}"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"id": "d2574659-0419-4def-9a70-a9bcd535ccc6",
|
||
|
|
"name": "Download File",
|
||
|
|
"type": "n8n-nodes-base.httpRequest",
|
||
|
|
"typeVersion": 4.5,
|
||
|
|
"position": [
|
||
|
|
-1104,
|
||
|
|
-368
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"mode": "runOnceForEachItem",
|
||
|
|
"jsCode": "const sourceItem = $input.item;\nif (!sourceItem) {\n throw new Error('Build Callback Payload: no input item');\n}\nconst parsed = (sourceItem.json && typeof sourceItem.json.output === 'object' && sourceItem.json.output !== null)\n ? sourceItem.json.output\n : sourceItem.json;\n\n// This item's id is the purchase_orders row Atomic Claim just claimed --\n// the placeholder created at upload time (src/lib/po-placeholder.ts).\n// There is no separate batch row anymore.\nconst purchase_order_id = $('Atomic Claim').item.json.id;\nconst po_number_extracted = String(parsed.po_number || '').trim();\nconst supplier = String(parsed.supplier || '').trim();\nconst project_code = String(parsed.project_code || '').trim();\nif (!po_number_extracted || !supplier || !project_code) {\n throw new Error('AI Agent output missing required field(s): po_number=\"' + po_number_extracted + '\" supplier=\"' + supplier + '\" project_code=\"' + project_code + '\"');\n}\n\nconst po_number_normalized = po_number_extracted\n .replace(/^p\\.?o\\.?([-#.]|\\s+)?/i, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .toUpperCase();\nif (!po_number_normalized) {\n throw new Error('AI Agent po_number normalises to empty string: \"' + po_number_extracted + '\"');\n}\n\nconst required_by_date = parsed.required_by_date || undefined;\nconst ordered_by = parsed.ordered_by || undefined;\nconst rawItems = Array.isArray(parsed.items) ? parsed.items : [];\nconst items = rawItems.map(it => {\n if (!it) return null;\n return {\n ...it,\n qty: Number(it.qty),\n rate: Number(it.rate),\n material_code: (it.material_code && String(it.material_code).trim() !== '' && String(it.material_code).toLowerCase() !== 'null') ? String(it.material_code).trim() : '',\n cost_code: (it.cost_code && String(it.cost_code).trim() !== '' && String(it.cost_code).toLowerCase() !== 'null') ? String(it.cost_code).trim() : '',\n disc_percent: (it.disc_percent !== null && it.disc_percent !== undefined && !isNaN(it.disc_percent)) ? Number(it.disc_percent) : 0,\n vat_percent: (it.vat_percent !== null && it.vat_percent !== undefined && !isNaN(it.vat_percent)) ? Number(it.vat_percent) : 15\n };\n}).filter(it =>\n it &&\n typeof it.description === 'string' && it.description.trim().length > 0 &&\n !isNaN(it.qty) && it.qty >= 0 &&\n !isNaN(it.rate) && it.rate >= 0\n);\nif (items.length === 0) {\n throw new Error('AI Agent returned no valid line items (all missing description/qty/rate)');\n}\n\n// Duplicate detection now lives server-side in\n// /api/purchase-orders/process-batch (merge onto the existing PO, delete\n// this placeholder) -- it already owns the purchase_orders schema and is\n// covered by tests. n8n no longer queries or writes purchase_orders\n// directly to pre-empt it.\n//\n// purchase_order_number is the exact field name the route validates --\n// the old contract used po_number here, which would fail every upload.\nreturn {\n json: {\n purchase_order_id,\n extracted: {\n purchase_order_number: po_number_normalized,\n po_number_extracted,\n supplier,\n project_code,\n required_by_date,\n ordered_by,\n items,\n raw_ocr: { source: 'ai-agent', model: 'MiniMax-M3' },\n },\n },\n};"
|
||
|
|
},
|
||
|
|
"id": "5c8bbd95-216e-45d3-a239-67a4e7ae9ff6",
|
||
|
|
"name": "Build Callback Payload",
|
||
|
|
"type": "n8n-nodes-base.code",
|
||
|
|
"typeVersion": 2,
|
||
|
|
"position": [
|
||
|
|
64,
|
||
|
|
-368
|
||
|
|
],
|
||
|
|
"onError": "continueErrorOutput"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"mode": "runOnceForEachItem",
|
||
|
|
"jsCode": "const claimItem = $('Atomic Claim').item.json;\nconst purchaseOrderId = claimItem.id;\nconst attempts = Number(claimItem.attempts || 0);\nconst isDead = attempts >= 3;\nconst lastError = String(($input.item.json && $input.item.json.error) || 'callback_failed').slice(0, 2000);\nconst nextRetry = !isDead\n ? new Date(Date.now() + (attempts === 1 ? 60000 : 300000)).toISOString()\n : null;\n\nconst pbToken = $('Auth PB Admin').item.json.token;\n\ntry {\n const currentResp = await this.helpers.httpRequest({\n method: 'GET',\n url: 'https://pb.mega.infra.apphub.co.za/api/collections/purchase_orders/records/' + purchaseOrderId,\n headers: { Authorization: pbToken },\n returnResponse: true,\n });\n const currentBody = currentResp && (currentResp.body || currentResp);\n if (currentBody && currentBody.processing_status === 'processed') {\n return { json: { escalated: false, purchaseOrderId, skipped: true, reason: 'already_processed' } };\n }\n} catch (guardErr) {\n}\n\nawait this.helpers.httpRequest({\n method: 'PATCH',\n url: 'https://pb.mega.infra.apphub.co.za/api/collections/purchase_orders/records/' + purchaseOrderId,\n headers: {\n Authorization: pbToken,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n processing_status: isDead ? 'dead' : 'failed',\n last_error: lastError,\n last_attempt_at: new Date().toISOString(),\n next_retry_at: nextRetry,\n }),\n returnResponse: true,\n});\n\nreturn { json: { escalated: true, purchaseOrderId, status: isDead ? 'dead' : 'failed' } };\n"
|
||
|
|
},
|
||
|
|
"id": "6b33d888-4755-4321-869a-ba6aaca16a78",
|
||
|
|
"name": "Escalate Failure",
|
||
|
|
"type": "n8n-nodes-base.code",
|
||
|
|
"typeVersion": 2,
|
||
|
|
"position": [
|
||
|
|
736,
|
||
|
|
-32
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"resource": "document",
|
||
|
|
"modelId": {
|
||
|
|
"__rl": true,
|
||
|
|
"value": "MiniMax-M3",
|
||
|
|
"mode": "list",
|
||
|
|
"cachedResultName": "MiniMax-M3"
|
||
|
|
},
|
||
|
|
"text": "CRITICAL TABLE RULE: When generating the markdown table for items ordered, you MUST include both the Material Code and the Cost Code columns from the original document. Do not omit the cost code column.",
|
||
|
|
"inputType": "binary",
|
||
|
|
"options": {
|
||
|
|
"maxTokens": 524288
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"type": "@n8n/n8n-nodes-langchain.anthropic",
|
||
|
|
"typeVersion": 1,
|
||
|
|
"position": [
|
||
|
|
-656,
|
||
|
|
-704
|
||
|
|
],
|
||
|
|
"id": "37720a60-01c3-46b5-a182-f21efc048d6e",
|
||
|
|
"name": "Analyze document"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"parameters": {
|
||
|
|
"text": "={{ $json.text || ($json.content && $json.content[0] && $json.content[0].text) || $json.output || \"\" }}",
|
||
|
|
"schemaType": "manual",
|
||
|
|
"inputSchema": "{\n \"type\": \"object\",\n \"required\": [\"po_number\", \"supplier\", \"project_code\", \"items\"],\n \"properties\": {\n \"po_number\": {\"type\": \"string\", \"description\": \"Unique PO number printed on the document\"},\n \"supplier\": {\"type\": \"string\", \"description\": \"Name of the supplier or vendor issuing the PO\"},\n \"project_code\": {\"type\": \"string\", \"description\": \"Internal project code the PO is charged to (e.g. MEGA-001)\"},\n \"required_by_date\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Required-by date in ISO YYYY-MM-DD format, or null if not stated\"},\n \"ordered_by\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Person who placed the order, or null if not shown\"},\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Line items in the PO\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"description\", \"qty\", \"rate\"],\n \"properties\": {\n \"description\": {\"type\": \"string\", \"description\": \"Item description / part name\"},\n \"qty\": {\"type\": \"number\", \"description\": \"Quantity ordered (can be 0 or greater)\"},\n \"rate\": {\"type\": \"number\", \"minimum\": 0, \"description\": \"Unit rate excluding VAT (must be 0 or greater)\"},\n \"unit\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Unit of measure, or null if not specified\"},\n \"material_code\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Internal material code, or null if not present\"},\n \"cost_code\": {\"type\": \"string\", \"nullable\": true, \"description\": \"Cost code or GL account, or null if not present\"},\n \"disc_percent\": {\"type\": \"number\", \"minimum\": 0, \"maximum\": 100, \"nullable\": true, \"description\": \"Discount percentage 0-100, or null if not applied\"},\n \"vat_percent\": {\"type\": \"number\", \"minimum\": 0, \"maximum\": 100, \"nullable\": true, \"description\": \"VAT percentage 0-100, or null if not applied\"}\n }\n }\n }\n }\n}",
|
||
|
|
"options": {
|
||
|
|
"systemPromptTemplate": "CRITICAL DATA EXTRACTION RULE: Look closely at the line items table from the document text. Each row contains a Material Code (such as 513095) and a Cost Code (such as C26/09, 0001) located right next to the item number and description. You MUST extract these into the 'material_code' and 'cost_code' fields for every single line item. Do not miss the cost code."
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
|
||
|
|
"typeVersion": 1.2,
|
||
|
|
"position": [
|
||
|
|
-352,
|
||
|
|
-816
|
||
|
|
],
|
||
|
|
"id": "c21cbdd7-0488-468c-979d-dd61edba0eec",
|
||
|
|
"name": "Information Extractor"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cdce197b-e104-45b3-b12b-0f307b2103b1",
|
||
|
|
"name": "Get File Token",
|
||
|
|
"type": "n8n-nodes-base.httpRequest",
|
||
|
|
"typeVersion": 4.5,
|
||
|
|
"position": [
|
||
|
|
-1328,
|
||
|
|
-368
|
||
|
|
],
|
||
|
|
"parameters": {
|
||
|
|
"method": "POST",
|
||
|
|
"url": "https://pb.mega.infra.apphub.co.za/api/files/token",
|
||
|
|
"sendHeaders": true,
|
||
|
|
"headerParameters": {
|
||
|
|
"parameters": [
|
||
|
|
{
|
||
|
|
"name": "Authorization",
|
||
|
|
"value": "={{ $('Auth PB Admin').item.json.token }}"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"options": {
|
||
|
|
"response": {
|
||
|
|
"response": {
|
||
|
|
"responseFormat": "json"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"callbackJsonBody": "={{ $json }}",
|
||
|
|
"connections": {
|
||
|
|
"Route by Content Type": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Analyze document",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "AI Agent1",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Analyze document": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Information Extractor",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"MiniMax Chat Model": {
|
||
|
|
"ai_languageModel": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Information Extractor",
|
||
|
|
"type": "ai_languageModel",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Information Extractor": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Build Callback Payload",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Structured Output Parser1": {
|
||
|
|
"ai_outputParser": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "AI Agent1",
|
||
|
|
"type": "ai_outputParser",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"MiniMax Chat Model3": {
|
||
|
|
"ai_languageModel": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "AI Agent1",
|
||
|
|
"type": "ai_languageModel",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Parser Model Image": {
|
||
|
|
"ai_languageModel": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Structured Output Parser1",
|
||
|
|
"type": "ai_languageModel",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Every 5 minutes": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Auth PB Admin",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Auth PB Admin": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Fetch Pending Batches",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"node": "Watchdog: List Stuck",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Watchdog: List Stuck": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Limit",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Fetch Pending Batches": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Limit1",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Atomic Claim": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Claim Successful?",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Claim Successful?": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Get File Token",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Loop Over Items",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Download File": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Route by Content Type",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"AI Agent1": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Build Callback Payload",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Send Callback": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Loop Over Items",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Escalate Failure",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Escalate Failure": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Loop Over Items",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Skip Empty Items": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Has ID?",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Has ID?": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Atomic Claim",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Loop Over Items": {
|
||
|
|
"main": [
|
||
|
|
[],
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Skip Empty Items",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Limit": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Watchdog: Reset Stuck Rows",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Limit1": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Loop Over Items",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Build Callback Payload": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Send Callback",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Escalate Failure",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"Get File Token": {
|
||
|
|
"main": [
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"node": "Download File",
|
||
|
|
"type": "main",
|
||
|
|
"index": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|