{"data":[{"updatedAt":"2026-03-09T07:32:17.331Z","createdAt":"2026-03-09T07:32:17.331Z","id":"042y2miRKkmICaOm","name":"ERP_ARCHITECT","active":true,"isArchived":false,"nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-architect","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd1","name":"Architect","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const req = $input.first().json;\n const userRequest = req.request || req.body || \"Describe the ERP module needed\";\n\n // Read Prisma schema\n c�nst schemaRes = await hostCmd(\"cat /opt/erp/metallkart-erp/prisma/schema.prisma 2>/dev/null || echo NO_SCGEMA\");\n const prismaSchema = (schemaRes.output || \"\").trim();\n\n // Read src structure\n const srcRes = await hostCmd(\"find /opt/erp/metallkart-erp/src -type f -name '*.ts' 2>/dev/null | head -30\");\n const srcFiles = (srcRes.output || \"\").trim();\n\n // Call Anthropic API (Opus)\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const systemPrompt = `You are ERP_ARCHITECT for MetallKart ERP (Fastify + TypeScript + Prisma + PostgreSQL).\nAnalyze the user's business need and produce a precise JSON specification.\nCurrent Prisma schema:\\n${prismaSchema.substring(0,3000)}\nExisting files:\\n${srcFiles}\nReturn ONLY valid JSON (no markdown) with this structure:\n{\n \"module_name\": \"string\",\n \"description\": \"lowlmedium|high\"\n}`;\n\n const body = JSON.stringify({\n model: \"claude-opus-4-5\",\n max_tokens: 4000,\n system: systemPrompt,\n messages: [{role:\"user\", content: userRequest}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}})}\n );\n r.on(\"error\",reject);\n r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error: \"API_ERROR: \"+JSON.stringify(apiRes.error).substring(0,200)}}];\n\n const rawSpec = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n let spec;\n try {\n const cleaned = rawSpec.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n spec = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{error:\"PARSE_ERROR\", raw: rawSpec.substring(0,500)}}];\n }\n\n return [{json:{\n spec,\n raw_spec: rawSpec.substring(0,2000),\n request: userRequest,\n status: \"awaiting_validation\"\n }}];\n}\nreturn run();\n"}},{"id":"rsp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Architect","type":"main","index":0}]]},"Architect":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"0d82bda6-ca1a-493b-870b-7131ef106245","activeVersionId":"0d82bda6-ca1a-493b-870b-7131ef106245","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T07:32:17.333Z","createdAt":"2026-03-09T07:32:17.333Z","role":"workflow:owner","workflowId":"042y2miRKkmICaOm","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:32:17.338Z","createdAt":"2026-03-09T07:32:17.338Z","versionId":"0d82bda6-ca1a-493b-870b-7131ef106245","workflowId":"042y2miRKkmICaOm","nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-architect","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd1","name":"Architect","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const req = $input.first().json;\n const userRequest = req.request || req.body || \"Describe the ERP module needed\";\n\n // Read Prisma schema\n c�nst schemaRes = await hostCmd(\"cat /opt/erp/metallkart-erp/prisma/schema.prisma 2>/dev/null || echo NO_SCGEMA\");\n const prismaSchema = (schemaRes.output || \"\").trim();\n\n // Read src structure\n const srcRes = await hostCmd(\"find /opt/erp/metallkart-erp/src -type f -name '*.ts' 2>/dev/null | head -30\");\n const srcFiles = (srcRes.output || \"\").trim();\n\n // Call Anthropic API (Opus)\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const systemPrompt = `You are ERP_ARCHITECT for MetallKart ERP (Fastify + TypeScript + Prisma + PostgreSQL).\nAnalyze the user's business need and produce a precise JSON specification.\nCurrent Prisma schema:\\n${prismaSchema.substring(0,3000)}\nExisting files:\\n${srcFiles}\nReturn ONLY valid JSON (no markdown) with this structure:\n{\n \"module_name\": \"string\",\n \"description\": \"lowlmedium|high\"\n}`;\n\n const body = JSON.stringify({\n model: \"claude-opus-4-5\",\n max_tokens: 4000,\n system: systemPrompt,\n messages: [{role:\"user\", content: userRequest}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}})}\n );\n r.on(\"error\",reject);\n r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error: \"API_ERROR: \"+JSON.stringify(apiRes.error).substring(0,200)}}];\n\n const rawSpec = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n let spec;\n try {\n const cleaned = rawSpec.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n spec = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{error:\"PARSE_ERROR\", raw: rawSpec.substring(0,500)}}];\n }\n\n return [{json:{\n spec,\n raw_spec: rawSpec.substring(0,2000),\n request: userRequest,\n status: \"awaiting_validation\"\n }}];\n}\nreturn run();\n"}},{"id":"rsp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Architect","type":"main","index":0}]]},"Architect":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-02-28T08:13:39.976Z","createdAt":"2026-02-28T08:13:39.976Z","id":"1VzwIAoEdB6QrgFX","name":"ORCHESTRATOR_ROUTER","active":false,"isArchived":false,"nodes":[{"id":"orch-webhook","name":"Webhook Router","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,400],"parameters":{"path":"orchestrator","httpMethod":"POST","responseMode":"responseNode","options":{}}},{"id":"orch-router","name":"Router","type":"n8n-nodes-base.code","typeVersion":2,"position":[420,400],"parameters":{"jsCode":"\nconst body = $input.item.json.body || $input.item.json;\nconst {\n intent = '',\n supplier_id = null,\n supplier_name = null,\n thread_id = null,\n message_id = null,\n doc_id = null,\n constraints = {},\n last_state_summary = ''\n} = body;\n\nconst trace_id = 'trc-' + Date.now() + '-' + Math.random().toString(36).slice(2,7);\n\n// Mapping intent → workflow\nconst ROUTES = {\n 'acte_sverki_send': 'wf_act_sverki_quarterly',\n 'acte_sverki_prepare': 'wf_supplier_sync',\n 'acte_sverki_relance': 'wf_act_sverki_quarterly',\n 'classify_supplier_email': 'wf_email_scan',\n 'update_supplier_db': 'wf_supplier_sync',\n 'send_email': 'wf_email_writer',\n 'log': 'wf_log_only',\n 'ping': 'wf_log_only',\n};\n\n// Vérification champs requis par intent\nconst REQUIRED = {\n 'acte_sverki_send': [],\n 'acte_sverki_prepare': [],\n 'acte_sverki_relance': ['supplier_id'],\n 'classify_supplier_email': ['thread_id'],\n 'update_supplier_db': ['supplier_id'],\n 'send_email': ['supplier_id', 'doc_id'],\n 'log': [],\n 'ping': [],\n};\n\nconst required = REQUIRED[intent] || [];\nconst missing = required.filter(k => !body[k]);\n\nif (!intent) {\n return [{ json: {\n route: 'ASK_MIN_CONTEXT',\n workflow: null,\n payload: {},\n ask: ['intent'],\n trace_id,\n state_update: { summary: 'Intent manquant.', important_ids: {} }\n }}];\n}\n\nif (missing.length > 0) {\n return [{ json: {\n route: 'ASK_MIN_CONTEXT',\n workflow: null,\n payload: {},\n ask: missing,\n trace_id,\n state_update: { summary: `Champs manquants pour ${intent}.`, important_ids: {} }\n }}];\n}\n\nconst workflow = ROUTES[intent] || 'wf_log_only';\n\n// Payload minimal\nconst payload = {\n trace_id,\n intent,\n language: constraints.language || 'ru',\n output_mode: constraints.output_mode || 'short',\n};\nif (supplier_id) payload.supplier_id = supplier_id;\nif (supplier_name) payload.supplier_name = supplier_name;\nif (thread_id) payload.thread_id = thread_id;\nif (message_id) payload.message_id = message_id;\nif (doc_id) payload.doc_id = doc_id;\nif (constraints.quarter) payload.quarter = constraints.quarter;\n\nconst route = workflow === 'wf_log_only' && intent === 'ping' ? 'DONE' : 'RUN_N8N';\n\nreturn [{ json: {\n route,\n workflow,\n payload,\n ask: [],\n trace_id,\n intent,\n state_update: {\n summary: `Route: ${route} → ${workflow} | intent: ${intent} | trace: ${trace_id}`,\n important_ids: {\n supplier_id: supplier_id || null,\n thread_id: thread_id || null,\n doc_id: doc_id || null\n }\n }\n}}];\n"}},{"id":"orch-switch","name":"Switch Workflow","type":"n8n-nodes-base.switch","typeVersion":3,"position":[640,400],"parameters":{"mode":"rules","rules":{"values":[{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_act_sverki_quarterly","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_supplier_sync","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_email_scan","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_email_writer","operator":{"type":"string","operation":"equals"}}]}}]},"options":{"fallbackOutput":"extra"}}},{"id":"orch-call-sverki","name":"Call ACT_SVERKI","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,200],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-send-requests","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.payload) }}","options":{"timeout":60000}}},{"id":"orch-call-sync","name":"Call SUPPLIER_SYNC","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,340],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.payload) }}","options":{"timeout":120000}}},{"id":"orch-call-scan","name":"Call EMAIL_SCAN","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,480],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/audit-append","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"email_scan_requested\",\"payload\":{{ JSON.stringify($json.payload) }}}","options":{"timeout":10000}}},{"id":"orch-call-writer","name":"Call EMAIL_WRITER","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,620],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/audit-append","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"email_writer_requested\",\"payload\":{{ JSON.stringify($json.payload) }}}","options":{"timeout":10000}}},{"id":"orch-fallback","name":"Log Only","type":"n8n-nodes-base.code","typeVersion":2,"position":[860,760],"parameters":{"jsCode":"return [{ json: { ...$input.item.json, wf_result: { status: 'logged', message: 'Route DONE ou wf_log_only' } } }];"}},{"id":"orch-format","name":"Format Response","type":"n8n-nodes-base.code","typeVersion":2,"position":[1100,400],"parameters":{"jsCode":"\nconst d = $input.item.json;\nconst router = d.route ? d : {};\nconst wf_result = d.wf_result || d;\nreturn [{ json: {\n route: router.route || 'RUN_N8N',\n workflow: router.workflow || null,\n payload: router.payload || {},\n ask: router.ask || [],\n trace_id: router.trace_id || d.trace_id || null,\n wf_result,\n state_update: router.state_update || {}\n}}];\n"}},{"id":"orch-respond","name":"Respond JSON","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1300,400],"parameters":{"respondWith":"json","responseBody":"={{ $json }}","options":{"responseCode":200}}}],"connections":{"Webhook Router":{"main":[[{"node":"Router","type":"main","index":0}]]},"Router":{"main":[[{"node":"Switch Workflow","type":"main","index":0}]]},"Switch Workflow":{"main":[[{"node":"Call ACT_SVERKI","type":"main","index":0}],[{"node":"Call SUPPLIER_SYNC","type":"main","index":0}],[{"node":"Call EMAIL_SCAN","type":"main","index":0}],[{"node":"Call EMAIL_WRITER","type":"main","index":0}],[{"node":"Log Only","type":"main","index":0}]]},"Call ACT_SVERKI":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call SUPPLIER_SYNC":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call EMAIL_SCAN":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call EMAIL_WRITER":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Log Only":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Format Response":{"main":[[{"node":"Respond JSON","type":"main","index":0}]]}},"settings":{"callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"9e55fc56-9671-499e-b190-fc27dc7fd996","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-02-28T08:13:39.979Z","createdAt":"2026-02-28T08:13:39.979Z","role":"workflow:owner","workflowId":"1VzwIAoEdB6QrgFX","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-01T20:00:46.497Z","createdAt":"2026-03-01T20:00:46.497Z","id":"2i5r0yUmjCY5VV2w","name":"ACT_SVERKI_QUARTERLY_LAUNCHER","active":true,"isArchived":false,"nodes":[{"id":"cron1","name":"Cron J+6 fin trimestre","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1,"position":[240,300],"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 10 6 1,4,7,10 *"}]}}},{"id":"code1","name":"Calcule Trimestre","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"const now = new Date();\nconst month = now.getMonth() + 1;\nconst year = now.getFullYear();\nlet quarter, qYear;\nif (month === 4) { quarter = \"Q1\"; qYear = year; }\nelse if (month === 7) { quarter = \"Q2\"; qYear = year; }\nelse if (month === 10) { quarter = \"Q3\"; qYear = year; }\nelse if (month === 1) { quarter = \"Q4\"; qYear = year - 1; }\nelse {\n // Fallback : calculer le trimestre precedent\n const prevMonth = month <= 3 ? month + 9 : month - 3;\n qYear = month <= 3 ? year - 1 : year;\n const q = Math.ceil(prevMonth / 3);\n quarter = `Q${q}`;\n}\nconst quarterLabel = `${qYear}-${quarter}`;\nreturn [{ json: { quarter: quarterLabel, year: qYear, launched_at: now.toISOString() } }];"}},{"id":"http1","name":"Phase 1 — Prepare List","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[680,300],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"bodyParameters":{"parameters":[{"name":"quarter","value":"={{ $json.quarter }}"},{"name":"test_mode","value":false}]},"options":{"response":{"response":{"responseFormat":"json"}}}}},{"id":"wait1","name":"Attendre Phase 1","type":"n8n-nodes-base.wait","typeVersion":1,"position":[900,300],"parameters":{"unit":"seconds","amount":60}},{"id":"http2","name":"Phase 2 — Merge Tracking","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[1120,300],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p2-merge-tracking","sendBody":true,"bodyParameters":{"parameters":[{"name":"quarter","value":"={{ $json.quarter }}"}]},"options":{}}},{"id":"wait2","name":"Attendre Phase 2","type":"n8n-nodes-base.wait","typeVersion":1,"position":[1340,300],"parameters":{"unit":"seconds","amount":30}},{"id":"http3","name":"SEND REQUESTS","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[1560,300],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-send-requests","sendBody":true,"bodyParameters":{"parameters":[{"name":"quarter","value":"={{ $json.quarter }}"},{"name":"test_mode","value":false}]},"options":{}}},{"id":"http4","name":"Activer Reminder Engine","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[1780,300],"parameters":{"method":"POST","url":"http://n8n:5678/api/v1/workflows/t2H8fGlGjg2lA3ct/activate","sendHeaders":true,"headerParameters":{"parameters":[{"name":"X-N8N-API-KEY","value":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDk2OTVlYi1jOTE5LTQwOTUtYTJkMy00YzQ5NTFkODEzOTUiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwianRpIjoiNjRmN2E4MmItMTkyNC00MTQxLTkzZGItMDQ3MjMzNDdhNGIxIiwiaWF0IjoxNzcyMjMwNjg5LCJleHAiOjE3NzQ4MTgwMDB9.-1mvxi1EAUcH49G474F3D1BJZ749V8bDCFIYvpCbW-c"}]},"sendBody":true,"bodyParameters":{"parameters":[]},"options":{}}}],"connections":{"Cron J+6 fin trimestre":{"main":[[{"node":"Calcule Trimestre","type":"main","index":0}]]},"Calcule Trimestre":{"main":[[{"node":"Phase 1 — Prepare List","type":"main","index":0}]]},"Phase 1 — Prepare List":{"main":[[{"node":"Attendre Phase 1","type":"main","index":0}]]},"Attendre Phase 1":{"main":[[{"node":"Phase 2 — Merge Tracking","type":"main","index":0}]]},"Phase 2 — Merge Tracking":{"main":[[{"node":"Attendre Phase 2","type":"main","index":0}]]},"Attendre Phase 2":{"main":[[{"node":"SEND REQUESTS","type":"main","index":0}]]},"SEND REQUESTS":{"main":[[{"node":"Activer Reminder Engine","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":{"node:Cron J+6 fin trimestre":{"recurrenceRules":[]}},"meta":null,"pinData":null,"versionId":"ddbbcba5-9425-4731-b387-c070a51bc681","activeVersionId":"ddbbcba5-9425-4731-b387-c070a51bc681","triggerCount":1,"shared":[{"updatedAt":"2026-03-01T20:00:46.499Z","createdAt":"2026-03-01T20:00:46.499Z","role":"workflow:owner","workflowId":"2i5r0yUmjCY5VV2w","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T20:00:46.502Z","createdAt":"2026-03-01T20:00:46.502Z","versionId":"ddbbcba5-9425-4731-b387-c070a51bc681","workflowId":"2i5r0yUmjCY5VV2w","nodes":[{"id":"cron1","name":"Cron J+6 fin trimestre","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1,"position":[240,300],"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 10 6 1,4,7,10 *"}]}}},{"id":"code1","name":"Calcule Trimestre","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"const now = new Date();\nconst month = now.getMonth() + 1;\nconst year = now.getFullYear();\nlet quarter, qYear;\nif (month === 4) { quarter = \"Q1\"; qYear = year; }\nelse if (month === 7) { quarter = \"Q2\"; qYear = year; }\nelse if (month === 10) { quarter = \"Q3\"; qYear = year; }\nelse if (month === 1) { quarter = \"Q4\"; qYear = year - 1; }\nelse {\n // Fallback : calculer le trimestre precedent\n const prevMonth = month <= 3 ? month + 9 : month - 3;\n qYear = month <= 3 ? year - 1 : year;\n const q = Math.ceil(prevMonth / 3);\n quarter = `Q${q}`;\n}\nconst quarterLabel = `${qYear}-${quarter}`;\nreturn [{ json: { quarter: quarterLabel, year: qYear, launched_at: now.toISOString() } }];"}},{"id":"http1","name":"Phase 1 — Prepare List","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[680,300],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"bodyParameters":{"parameters":[{"name":"quarter","value":"={{ $json.quarter }}"},{"name":"test_mode","value":false}]},"options":{"response":{"response":{"responseFormat":"json"}}}}},{"id":"wait1","name":"Attendre Phase 1","type":"n8n-nodes-base.wait","typeVersion":1,"position":[900,300],"parameters":{"unit":"seconds","amount":60}},{"id":"http2","name":"Phase 2 — Merge Tracking","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[1120,300],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p2-merge-tracking","sendBody":true,"bodyParameters":{"parameters":[{"name":"quarter","value":"={{ $json.quarter }}"}]},"options":{}}},{"id":"wait2","name":"Attendre Phase 2","type":"n8n-nodes-base.wait","typeVersion":1,"position":[1340,300],"parameters":{"unit":"seconds","amount":30}},{"id":"http3","name":"SEND REQUESTS","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[1560,300],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-send-requests","sendBody":true,"bodyParameters":{"parameters":[{"name":"quarter","value":"={{ $json.quarter }}"},{"name":"test_mode","value":false}]},"options":{}}},{"id":"http4","name":"Activer Reminder Engine","type":"n8n-nodes-base.httpRequest","typeVersion":4,"position":[1780,300],"parameters":{"method":"POST","url":"http://n8n:5678/api/v1/workflows/t2H8fGlGjg2lA3ct/activate","sendHeaders":true,"headerParameters":{"parameters":[{"name":"X-N8N-API-KEY","value":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDk2OTVlYi1jOTE5LTQwOTUtYTJkMy00YzQ5NTFkODEzOTUiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwianRpIjoiNjRmN2E4MmItMTkyNC00MTQxLTkzZGItMDQ3MjMzNDdhNGIxIiwiaWF0IjoxNzcyMjMwNjg5LCJleHAiOjE3NzQ4MTgwMDB9.-1mvxi1EAUcH49G474F3D1BJZ749V8bDCFIYvpCbW-c"}]},"sendBody":true,"bodyParameters":{"parameters":[]},"options":{}}}],"connections":{"Cron J+6 fin trimestre":{"main":[[{"node":"Calcule Trimestre","type":"main","index":0}]]},"Calcule Trimestre":{"main":[[{"node":"Phase 1 — Prepare List","type":"main","index":0}]]},"Phase 1 — Prepare List":{"main":[[{"node":"Attendre Phase 1","type":"main","index":0}]]},"Attendre Phase 1":{"main":[[{"node":"Phase 2 — Merge Tracking","type":"main","index":0}]]},"Phase 2 — Merge Tracking":{"main":[[{"node":"Attendre Phase 2","type":"main","index":0}]]},"Attendre Phase 2":{"main":[[{"node":"SEND REQUESTS","type":"main","index":0}]]},"SEND REQUESTS":{"main":[[{"node":"Activer Reminder Engine","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-03T07:35:27.891Z","createdAt":"2026-02-26T20:45:15.995Z","id":"2n0cLZ2EaarMsDiX","name":"ACT_SVERKI_INBOUND_TRIAGE","active":true,"isArchived":false,"nodes":[{"parameters":{"jsCode":"const OWN_EMAILS = new Set([\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n]);\nconst item = $input.first().json;\nconst fromRaw = item.from || item.fromAddress || '';\nconst fromEmail = (fromRaw.match(/<(.+)>/) || [,''])[1].toLowerCase() || fromRaw.toLowerCase().trim();\nconst subject = (item.subject || '').toLowerCase();\n// Rejeter nos propres emails\nif (OWN_EMAILS.has(fromEmail)) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'own_email', from_email: fromEmail } }];\n}\n// Rejeter auto-réponses\nconst autoReply = ['auto-reply','out of office','автоответ','нет на месте','delivery','mailer-daemon'];\nif (autoReply.some(s => subject.includes(s))) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'auto_reply', from_email: fromEmail } }];\n}\nreturn [{ json: { ...item, _relevant: true, is_relevant: true, from_email: fromEmail, subject: item.subject || '' } }];"},"name":"Check Relevance","type":"n8n-nodes-base.code","typeVersion":2,"position":[464,304],"id":"3594f7ba-2a8e-465c-a635-e708cd692024"},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"c1","leftValue":"={{$json.is_relevant}}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}],"combinator":"and"},"options":{}},"name":"IF Relevant","type":"n8n-nodes-base.if","typeVersion":2,"position":[704,304],"id":"fce4f47d-ff39-49db-9e12-9c689dd91623"},{"parameters":{"method":"POST","url":"https://api.openai.com/v1/chat/completions","authentication":"predefinedCredentialType","nodeCredentialType":"openAiApi","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={\"model\":\"gpt-4o-mini\",\"temperature\":0.1,\"max_tokens\":500,\"messages\":[{\"role\":\"system\",\"content\":\"Signal flags: strong_accounting_signal={{ $json.strong_accounting_signal }}, weak_signal={{ $json.weak_signal }}. If strong_accounting_signal is true, prioritize ACT/INVOICE categories. If weak_signal is true, be more conservative.\"},{\"role\":\"system\",\"content\":\"You are Pomoshchnik Bukhgalter - Triage vkhodayshchikh pisem for OOO Metal Design. You receive an inbound email that may be a response to a reconciliation act request. Classify it.\\n\\nCategories:\\n- ACT_RECEIVED: supplier sent signed reconciliation act\\n- NOT_OUR_SUPPLIER: they say not our supplier\\n- WRONG_EMAIL: wrong email address\\n- WILL_SEND_LATER: they will send later\\n- REFUSAL: explicit refusal\\n- OTHER: anything else\\n\\nReturn ONLY JSON:\\n{\\\"type\\\":\\\"inbound_triage\\\",\\\"category\\\":\\\"ACT_RECEIVED|NOT_OUR_SUPPLIER|WRONG_EMAIL|WILL_SEND_LATER|REFUSAL|OTHER\\\",\\\"confidence\\\":0.0,\\\"supplier_email\\\":\\\"\\\",\\\"supplier_name\\\":\\\"\\\",\\\"proposed_action\\\":\\\"MARK_RECEIVED|EXCLUDE|UPDATE_EMAIL|POSTPONE|NEED_HUMAN\\\",\\\"new_email\\\":\\\"\\\",\\\"postpone_days\\\":0,\\\"reply_template_id\\\":\\\"T1|T2|T3|NONE\\\",\\\"exclude_reason\\\":\\\"\\\",\\\"notes\\\":\\\"\\\"}\"},{\"role\":\"user\",\"content\":\"From: {{ $json.from_raw }}\\nSubject: {{ $json.subject }}\\nDate: {{ $json.date }}\\nAttachments: {{ $json.attachment_names }}\\n\\nBody:\\n{{ ($json.textPlain || '').slice(0,600)}}\"}]}","options":{"timeout":30000}},"name":"AI Classify","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[960,208],"id":"453a0531-6081-480c-89ae-0725233f045d","credentials":{"openAiApi":{"id":"44G2SWG1pfhc4c1L","name":"OpenAi account"}}},{"parameters":{"jsCode":"const items = $input.all();\nconst r = [];\nfor (const item of items) {\n let c;\n try {\n c = JSON.parse(item.json.choices[0].message.content.trim());\n } catch(e) {\n c = { category:'OTHER', confidence:0.3, supplier_email:'', notes:'PARSE_ERROR:'+e.message };\n }\n const orig = item.json.original_data || {};\n r.push({ json: {\n ...orig,\n class: c.category || 'OTHER',\n confidence: parseFloat(c.confidence) || 0,\n supplier_email: c.supplier_email || orig.from_email || '',\n supplier_name: c.supplier_name || '',\n proposed_action: c.proposed_action || 'NONE',\n exclude_reason: c.exclude_reason || '',\n ai_notes: c.notes || '',\n raw_category: c.category || 'OTHER',\n }});\n}\nreturn r;"},"name":"Parse Classification","type":"n8n-nodes-base.code","typeVersion":2,"position":[1200,208],"id":"11f1f257-6580-4305-b53a-212dfeff5959"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"options":{}},"name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1200,400],"id":"97c9dbfa-ef11-43b5-b6da-61ef4cc4c0cc","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const inbound = $(\"Parse Classification\").first().json;\n\n// Fallback : extraire l'email de l'expediteur IMAP si Claude ne l'a pas fourni\nconst rawFrom = $(\"Email Trigger (IMAP)\").first().json.from || \"\";\nconst emailMatch = rawFrom.match(/<([^>]+)>/) || rawFrom.match(/([\\w.+-]+@[\\w-]+\\.[\\w.]+)/);\nconst imapFromEmail = emailMatch ? emailMatch[1].toLowerCase().trim() : rawFrom.toLowerCase().trim();\n\nconst fromEmail = (inbound.supplier_email || inbound.from_email || imapFromEmail).toLowerCase().trim();\nconst QUARTER = \"2026-Q1\";\n\nconst tracking = $input.all();\nconst matched = tracking.filter(i => {\n const te = (i.json.email || \"\").toLowerCase().trim();\n return te === fromEmail && (i.json.quarter || \"\") === QUARTER;\n});\n\nif (matched.length === 0) {\n return [{ json: { _no_match: true, from_email: fromEmail } }];\n}\nreturn matched.map(i => ({ json: {\n ...i.json,\n supplier_email: fromEmail,\n class: inbound.class,\n confidence: inbound.confidence,\n proposed_action: inbound.proposed_action,\n ai_notes: inbound.ai_notes || \"\",\n}}));"},"name":"Match Supplier","type":"n8n-nodes-base.code","typeVersion":2,"position":[1504,304],"id":"610ede48-a77e-475b-8902-cfcb6118aab6"},{"parameters":{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"f1","leftValue":"={{ $json._no_match }}","rightValue":true,"operator":{"type":"boolean","operation":"notEquals"}}],"combinator":"and"},"options":{}},"name":"IF Found","type":"n8n-nodes-base.if","typeVersion":2,"position":[1760,304],"id":"26dc8fbc-4aff-4d7c-a478-d66b72054294"},{"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $json.supplier_email || $json.from_email }}","status":"={{ $json.class === 'ACT_RECEIVED' ? 'act_received' : 'responded' }}","last_inbound_class":"={{ $json.class }}","notes":"={{ $json.ai_notes || '' }}"},"matchingColumns":["email"],"schema":[]},"options":{}},"name":"Update Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2000,208],"id":"ede2f3ce-288f-49b6-8d02-9706cc33eda4","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"return[{json:{action:'ignored',reason:'Not relevant to act sverki'}}]"},"name":"Ignore","type":"n8n-nodes-base.code","typeVersion":2,"position":[960,512],"id":"139ec916-74ed-4c4d-8e58-3a550050fcd9"},{"parameters":{"operation":"append","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"value":"act_sverki_triage","__rl":true,"mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"values":[{"column":"received_at","fieldValue":"={{ new Date().toISOString() }}"},{"column":"from_email","fieldValue":"={{ $json.from_email }}"},{"column":"subject","fieldValue":"={{ $json.subject }}"},{"column":"class","fieldValue":"={{ $json.class }}"},{"column":"confidence","fieldValue":"={{ $json.confidence }}"},{"column":"proposed_action","fieldValue":"={{ $json.proposed_action }}"},{"column":"supplier_name","fieldValue":"={{ $json.supplier_name }}"},{"column":"ai_notes","fieldValue":"={{ $json.ai_notes }}"}]},"matchingColumns":[],"schema":[]},"options":{}},"name":"Append Triage Review","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1408,608],"id":"e9a2a430-d74a-4fa8-b3ee-cf8273e87290","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"conf1","leftValue":"={{ $json.confidence }}","rightValue":0.75,"operator":{"type":"number","operation":"gte"}}],"combinator":"and"},"options":{}},"name":"IF High Confidence","type":"n8n-nodes-base.if","typeVersion":2,"position":[1008,304],"id":"4028f276-7177-4198-acbf-387d5da0a844"},{"parameters":{"options":{}},"type":"n8n-nodes-base.emailReadImap","typeVersion":2.1,"position":[80,0],"id":"af2d1df6-4be1-447b-bbd1-684787413f9c","name":"Email Trigger (IMAP)","credentials":{"imap":{"id":"2Nf4EiUI3xesWxuJ","name":"IMAP comptable@"}}},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst fromDomain = (item.from_email || '').toLowerCase().split('@')[1] || '';\nconst subject = (item.subject || '').toLowerCase();\nconst body = (item.body_snippet || '').substring(0, 600).toLowerCase();\n\nconst MARKETING_DOMAINS = [\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','sendgrid.com',\n 'amazonses.com','uni-sender.com','unisender.com','sendsay.ru','estismail.ru',\n 'mindbox.ru','list-manage.com','campaign-archive.com','mandrillapp.com',\n 'sparkpostmail.com','postmarkapp.com','mailgun.org'\n];\nconst MARKETING_KW = [\n 'акция','скидка','скидки','распродажа','вебинар','подпишитесь','промокод',\n 'unsubscribe','newsletter','discount','sale','отписаться','рассылка',\n 'спецпредложение','купон','подписка на'\n];\n\nconst isMarketingDomain = MARKETING_DOMAINS.some(d => fromDomain.includes(d));\nconst hasMarketingKw = MARKETING_KW.some(kw => subject.includes(kw) || body.includes(kw));\nconst isMarketing = isMarketingDomain || hasMarketingKw;\n\nreturn [{ json: {\n ...item,\n skip_ai: isMarketing,\n pre_category: isMarketing ? 'MARKETING' : null,\n pre_confidence:isMarketing ? 0.95 : null,\n pre_reason: isMarketing\n ? (isMarketingDomain ? 'marketing_domain:' + fromDomain : 'marketing_keyword')\n : null,\n}}];\n"},"id":"triage-marketing-bl","name":"Marketing Blacklist","type":"n8n-nodes-base.code","typeVersion":2,"position":[608,304]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"id":"mkt1","leftValue":"={{ $json.skip_ai }}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}]},"options":{}},"id":"triage-if-marketing","name":"IF Marketing","type":"n8n-nodes-base.if","typeVersion":2,"position":[800,304]},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst text = ((item.subject || '') + ' ' + (item.body_snippet || '').substring(0, 1500)).toLowerCase();\n\nconst STRONG = [\n 'счет ','счёт ','счет-фактур','счёт-фактур','упд','универсальный передаточный',\n 'накладная','акт сверки','акт-сверки','сверка','взаиморасчет','взаиморасчёт',\n 'коммерческое предложение','договор','спецификация','оплата','платеж','платёж',\n 'invoice','contract','поставк','отгрузк','тtn','торг-12','торг12'\n];\n\nconst hasStrong = STRONG.some(kw => text.includes(kw));\n\nreturn [{ json: {\n ...item,\n strong_accounting_signal: hasStrong,\n weak_signal: !hasStrong,\n}}];\n"},"id":"triage-signal-check","name":"Signal Check","type":"n8n-nodes-base.code","typeVersion":2,"position":[1008,400]}],"connections":{"Check Relevance":{"main":[[{"node":"IF Relevant","type":"main","index":0}]]},"IF Relevant":{"main":[[{"node":"Marketing Blacklist","type":"main","index":0}],[{"node":"Ignore","type":"main","index":0}]]},"AI Classify":{"main":[[{"node":"Parse Classification","type":"main","index":0}]]},"Parse Classification":{"main":[[{"node":"IF High Confidence","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Match Supplier","type":"main","index":0}]]},"Match Supplier":{"main":[[{"node":"IF Found","type":"main","index":0}]]},"IF Found":{"main":[[{"node":"Update Tracking","type":"main","index":0}],[]]},"IF High Confidence":{"main":[[{"node":"Read Tracking","type":"main","index":0}],[{"node":"Append Triage Review","type":"main","index":0}]]},"Append Triage Review":{"main":[[{"node":"Ignore","type":"main","index":0}]]},"Email Trigger (IMAP)":{"main":[[{"node":"Check Relevance","type":"main","index":0}]]},"Marketing Blacklist":{"main":[[{"node":"IF Marketing","type":"main","index":0}]]},"IF Marketing":{"main":[[{"node":"Ignore","type":"main","index":0}],[{"node":"Signal Check","type":"main","index":0}]]},"Signal Check":{"main":[[{"node":"AI Classify","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":{"node:Email Trigger (IMAP)":{"lastMessageUid":77}},"meta":{"templateCredsSetupCompleted":true},"pinData":{},"versionId":"d4e5cb7e-6d34-4311-8c41-0a8ac81afa39","activeVersionId":"d4e5cb7e-6d34-4311-8c41-0a8ac81afa39","triggerCount":1,"shared":[{"updatedAt":"2026-02-26T20:45:16.004Z","createdAt":"2026-02-26T20:45:16.004Z","role":"workflow:owner","workflowId":"2n0cLZ2EaarMsDiX","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-03T07:43:43.000Z","createdAt":"2026-03-03T07:35:27.892Z","versionId":"d4e5cb7e-6d34-4311-8c41-0a8ac81afa39","workflowId":"2n0cLZ2EaarMsDiX","nodes":[{"parameters":{"jsCode":"const OWN_EMAILS = new Set([\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n]);\nconst item = $input.first().json;\nconst fromRaw = item.from || item.fromAddress || '';\nconst fromEmail = (fromRaw.match(/<(.+)>/) || [,''])[1].toLowerCase() || fromRaw.toLowerCase().trim();\nconst subject = (item.subject || '').toLowerCase();\n// Rejeter nos propres emails\nif (OWN_EMAILS.has(fromEmail)) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'own_email', from_email: fromEmail } }];\n}\n// Rejeter auto-réponses\nconst autoReply = ['auto-reply','out of office','автоответ','нет на месте','delivery','mailer-daemon'];\nif (autoReply.some(s => subject.includes(s))) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'auto_reply', from_email: fromEmail } }];\n}\nreturn [{ json: { ...item, _relevant: true, is_relevant: true, from_email: fromEmail, subject: item.subject || '' } }];"},"name":"Check Relevance","type":"n8n-nodes-base.code","typeVersion":2,"position":[464,304],"id":"3594f7ba-2a8e-465c-a635-e708cd692024"},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"c1","leftValue":"={{$json.is_relevant}}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}],"combinator":"and"},"options":{}},"name":"IF Relevant","type":"n8n-nodes-base.if","typeVersion":2,"position":[704,304],"id":"fce4f47d-ff39-49db-9e12-9c689dd91623"},{"parameters":{"method":"POST","url":"https://api.openai.com/v1/chat/completions","authentication":"predefinedCredentialType","nodeCredentialType":"openAiApi","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={\"model\":\"gpt-4o-mini\",\"temperature\":0.1,\"max_tokens\":500,\"messages\":[{\"role\":\"system\",\"content\":\"Signal flags: strong_accounting_signal={{ $json.strong_accounting_signal }}, weak_signal={{ $json.weak_signal }}. If strong_accounting_signal is true, prioritize ACT/INVOICE categories. If weak_signal is true, be more conservative.\"},{\"role\":\"system\",\"content\":\"You are Pomoshchnik Bukhgalter - Triage vkhodayshchikh pisem for OOO Metal Design. You receive an inbound email that may be a response to a reconciliation act request. Classify it.\\n\\nCategories:\\n- ACT_RECEIVED: supplier sent signed reconciliation act\\n- NOT_OUR_SUPPLIER: they say not our supplier\\n- WRONG_EMAIL: wrong email address\\n- WILL_SEND_LATER: they will send later\\n- REFUSAL: explicit refusal\\n- OTHER: anything else\\n\\nReturn ONLY JSON:\\n{\\\"type\\\":\\\"inbound_triage\\\",\\\"category\\\":\\\"ACT_RECEIVED|NOT_OUR_SUPPLIER|WRONG_EMAIL|WILL_SEND_LATER|REFUSAL|OTHER\\\",\\\"confidence\\\":0.0,\\\"supplier_email\\\":\\\"\\\",\\\"supplier_name\\\":\\\"\\\",\\\"proposed_action\\\":\\\"MARK_RECEIVED|EXCLUDE|UPDATE_EMAIL|POSTPONE|NEED_HUMAN\\\",\\\"new_email\\\":\\\"\\\",\\\"postpone_days\\\":0,\\\"reply_template_id\\\":\\\"T1|T2|T3|NONE\\\",\\\"exclude_reason\\\":\\\"\\\",\\\"notes\\\":\\\"\\\"}\"},{\"role\":\"user\",\"content\":\"From: {{ $json.from_raw }}\\nSubject: {{ $json.subject }}\\nDate: {{ $json.date }}\\nAttachments: {{ $json.attachment_names }}\\n\\nBody:\\n{{ ($json.textPlain || '').slice(0,600)}}\"}]}","options":{"timeout":30000}},"name":"AI Classify","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[960,208],"id":"453a0531-6081-480c-89ae-0725233f045d","credentials":{"openAiApi":{"id":"44G2SWG1pfhc4c1L","name":"OpenAi account"}}},{"parameters":{"jsCode":"const items = $input.all();\nconst r = [];\nfor (const item of items) {\n let c;\n try {\n c = JSON.parse(item.json.choices[0].message.content.trim());\n } catch(e) {\n c = { category:'OTHER', confidence:0.3, supplier_email:'', notes:'PARSE_ERROR:'+e.message };\n }\n const orig = item.json.original_data || {};\n r.push({ json: {\n ...orig,\n class: c.category || 'OTHER',\n confidence: parseFloat(c.confidence) || 0,\n supplier_email: c.supplier_email || orig.from_email || '',\n supplier_name: c.supplier_name || '',\n proposed_action: c.proposed_action || 'NONE',\n exclude_reason: c.exclude_reason || '',\n ai_notes: c.notes || '',\n raw_category: c.category || 'OTHER',\n }});\n}\nreturn r;"},"name":"Parse Classification","type":"n8n-nodes-base.code","typeVersion":2,"position":[1200,208],"id":"11f1f257-6580-4305-b53a-212dfeff5959"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"options":{}},"name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1200,400],"id":"97c9dbfa-ef11-43b5-b6da-61ef4cc4c0cc","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const inbound = $(\"Parse Classification\").first().json;\n\n// Fallback : extraire l'email de l'expediteur IMAP si Claude ne l'a pas fourni\nconst rawFrom = $(\"Email Trigger (IMAP)\").first().json.from || \"\";\nconst emailMatch = rawFrom.match(/<([^>]+)>/) || rawFrom.match(/([\\w.+-]+@[\\w-]+\\.[\\w.]+)/);\nconst imapFromEmail = emailMatch ? emailMatch[1].toLowerCase().trim() : rawFrom.toLowerCase().trim();\n\nconst fromEmail = (inbound.supplier_email || inbound.from_email || imapFromEmail).toLowerCase().trim();\nconst QUARTER = \"2026-Q1\";\n\nconst tracking = $input.all();\nconst matched = tracking.filter(i => {\n const te = (i.json.email || \"\").toLowerCase().trim();\n return te === fromEmail && (i.json.quarter || \"\") === QUARTER;\n});\n\nif (matched.length === 0) {\n return [{ json: { _no_match: true, from_email: fromEmail } }];\n}\nreturn matched.map(i => ({ json: {\n ...i.json,\n supplier_email: fromEmail,\n class: inbound.class,\n confidence: inbound.confidence,\n proposed_action: inbound.proposed_action,\n ai_notes: inbound.ai_notes || \"\",\n}}));"},"name":"Match Supplier","type":"n8n-nodes-base.code","typeVersion":2,"position":[1504,304],"id":"610ede48-a77e-475b-8902-cfcb6118aab6"},{"parameters":{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"f1","leftValue":"={{ $json._no_match }}","rightValue":true,"operator":{"type":"boolean","operation":"notEquals"}}],"combinator":"and"},"options":{}},"name":"IF Found","type":"n8n-nodes-base.if","typeVersion":2,"position":[1760,304],"id":"26dc8fbc-4aff-4d7c-a478-d66b72054294"},{"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $json.supplier_email || $json.from_email }}","status":"={{ $json.class === 'ACT_RECEIVED' ? 'act_received' : 'responded' }}","last_inbound_class":"={{ $json.class }}","notes":"={{ $json.ai_notes || '' }}"},"matchingColumns":["email"],"schema":[]},"options":{}},"name":"Update Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2000,208],"id":"ede2f3ce-288f-49b6-8d02-9706cc33eda4","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"return[{json:{action:'ignored',reason:'Not relevant to act sverki'}}]"},"name":"Ignore","type":"n8n-nodes-base.code","typeVersion":2,"position":[960,512],"id":"139ec916-74ed-4c4d-8e58-3a550050fcd9"},{"parameters":{"operation":"append","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"value":"act_sverki_triage","__rl":true,"mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"values":[{"column":"received_at","fieldValue":"={{ new Date().toISOString() }}"},{"column":"from_email","fieldValue":"={{ $json.from_email }}"},{"column":"subject","fieldValue":"={{ $json.subject }}"},{"column":"class","fieldValue":"={{ $json.class }}"},{"column":"confidence","fieldValue":"={{ $json.confidence }}"},{"column":"proposed_action","fieldValue":"={{ $json.proposed_action }}"},{"column":"supplier_name","fieldValue":"={{ $json.supplier_name }}"},{"column":"ai_notes","fieldValue":"={{ $json.ai_notes }}"}]},"matchingColumns":[],"schema":[]},"options":{}},"name":"Append Triage Review","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1408,608],"id":"e9a2a430-d74a-4fa8-b3ee-cf8273e87290","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"conf1","leftValue":"={{ $json.confidence }}","rightValue":0.75,"operator":{"type":"number","operation":"gte"}}],"combinator":"and"},"options":{}},"name":"IF High Confidence","type":"n8n-nodes-base.if","typeVersion":2,"position":[1008,304],"id":"4028f276-7177-4198-acbf-387d5da0a844"},{"parameters":{"options":{}},"type":"n8n-nodes-base.emailReadImap","typeVersion":2.1,"position":[80,0],"id":"af2d1df6-4be1-447b-bbd1-684787413f9c","name":"Email Trigger (IMAP)","credentials":{"imap":{"id":"2Nf4EiUI3xesWxuJ","name":"IMAP comptable@"}}},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst fromDomain = (item.from_email || '').toLowerCase().split('@')[1] || '';\nconst subject = (item.subject || '').toLowerCase();\nconst body = (item.body_snippet || '').substring(0, 600).toLowerCase();\n\nconst MARKETING_DOMAINS = [\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','sendgrid.com',\n 'amazonses.com','uni-sender.com','unisender.com','sendsay.ru','estismail.ru',\n 'mindbox.ru','list-manage.com','campaign-archive.com','mandrillapp.com',\n 'sparkpostmail.com','postmarkapp.com','mailgun.org'\n];\nconst MARKETING_KW = [\n 'акция','скидка','скидки','распродажа','вебинар','подпишитесь','промокод',\n 'unsubscribe','newsletter','discount','sale','отписаться','рассылка',\n 'спецпредложение','купон','подписка на'\n];\n\nconst isMarketingDomain = MARKETING_DOMAINS.some(d => fromDomain.includes(d));\nconst hasMarketingKw = MARKETING_KW.some(kw => subject.includes(kw) || body.includes(kw));\nconst isMarketing = isMarketingDomain || hasMarketingKw;\n\nreturn [{ json: {\n ...item,\n skip_ai: isMarketing,\n pre_category: isMarketing ? 'MARKETING' : null,\n pre_confidence:isMarketing ? 0.95 : null,\n pre_reason: isMarketing\n ? (isMarketingDomain ? 'marketing_domain:' + fromDomain : 'marketing_keyword')\n : null,\n}}];\n"},"id":"triage-marketing-bl","name":"Marketing Blacklist","type":"n8n-nodes-base.code","typeVersion":2,"position":[608,304]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"id":"mkt1","leftValue":"={{ $json.skip_ai }}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}]},"options":{}},"id":"triage-if-marketing","name":"IF Marketing","type":"n8n-nodes-base.if","typeVersion":2,"position":[800,304]},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst text = ((item.subject || '') + ' ' + (item.body_snippet || '').substring(0, 1500)).toLowerCase();\n\nconst STRONG = [\n 'счет ','счёт ','счет-фактур','счёт-фактур','упд','универсальный передаточный',\n 'накладная','акт сверки','акт-сверки','сверка','взаиморасчет','взаиморасчёт',\n 'коммерческое предложение','договор','спецификация','оплата','платеж','платёж',\n 'invoice','contract','поставк','отгрузк','тtn','торг-12','торг12'\n];\n\nconst hasStrong = STRONG.some(kw => text.includes(kw));\n\nreturn [{ json: {\n ...item,\n strong_accounting_signal: hasStrong,\n weak_signal: !hasStrong,\n}}];\n"},"id":"triage-signal-check","name":"Signal Check","type":"n8n-nodes-base.code","typeVersion":2,"position":[1008,400]}],"connections":{"Check Relevance":{"main":[[{"node":"IF Relevant","type":"main","index":0}]]},"IF Relevant":{"main":[[{"node":"Marketing Blacklist","type":"main","index":0}],[{"node":"Ignore","type":"main","index":0}]]},"AI Classify":{"main":[[{"node":"Parse Classification","type":"main","index":0}]]},"Parse Classification":{"main":[[{"node":"IF High Confidence","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Match Supplier","type":"main","index":0}]]},"Match Supplier":{"main":[[{"node":"IF Found","type":"main","index":0}]]},"IF Found":{"main":[[{"node":"Update Tracking","type":"main","index":0}],[]]},"IF High Confidence":{"main":[[{"node":"Read Tracking","type":"main","index":0}],[{"node":"Append Triage Review","type":"main","index":0}]]},"Append Triage Review":{"main":[[{"node":"Ignore","type":"main","index":0}]]},"Email Trigger (IMAP)":{"main":[[{"node":"Check Relevance","type":"main","index":0}]]},"Marketing Blacklist":{"main":[[{"node":"IF Marketing","type":"main","index":0}]]},"IF Marketing":{"main":[[{"node":"Ignore","type":"main","index":0}],[{"node":"Signal Check","type":"main","index":0}]]},"Signal Check":{"main":[[{"node":"AI Classify","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":"Version d4e5cb7e","description":"Fix AI Classify: remplace body_snippet par textPlain.slice(0,600) pour eviter les erreurs JSON invalide avec les emails HTML","autosaved":true},"tags":[]},{"updatedAt":"2026-03-02T09:55:56.526Z","createdAt":"2026-03-02T09:55:56.526Z","id":"5S0AuWzvUjRd44k9","name":"_TEMP_SEND_VERIFICATION_EMAIL","active":false,"isArchived":false,"nodes":[{"id":"trigger1","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[240,300],"parameters":{}},{"id":"code1","name":"Prepare Attachment","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"mode":"runOnceForAllItems","jsCode":"const b64 = 'UEsDBBQAAAAAAAAAAADxYTwnQgMAAEIDAAAaAAAAeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHM8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8UmVsYXRpb25zaGlwcyB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL3BhY2thZ2UvMjAwNi9yZWxhdGlvbnNoaXBzIj48UmVsYXRpb25zaGlwIElkPSJySWQxIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3dvcmtzaGVldCIgVGFyZ2V0PSJ3b3Jrc2hlZXRzL3NoZWV0MS54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQyIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3dvcmtzaGVldCIgVGFyZ2V0PSJ3b3Jrc2hlZXRzL3NoZWV0Mi54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQzIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3RoZW1lIiBUYXJnZXQ9InRoZW1lL3RoZW1lMS54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQ0IiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3N0eWxlcyIgVGFyZ2V0PSJzdHlsZXMueG1sIi8+PFJlbGF0aW9uc2hpcCBJZD0icklkNSIgVHlwZT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcy9zaGVldE1ldGFkYXRhIiBUYXJnZXQ9Im1ldGFkYXRhLnhtbCIvPjwvUmVsYXRpb25zaGlwcz5QSwMEFAAAAAAAAAAAADAPiGveHQAA3h0AABMAAAB4bC90aGVtZS90aGVtZTEueG1sPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPGE6dGhlbWUgeG1sbnM6YT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL2RyYXdpbmdtbC8yMDA2L21haW4iIG5hbWU9Ik9mZmljZSBUaGVtZSI+PGE6dGhlbWVFbGVtZW50cz48YTpjbHJTY2hlbWUgbmFtZT0iT2ZmaWNlIj48YTpkazE+PGE6c3lzQ2xyIHZhbD0id2luZG93VGV4dCIgbGFzdENscj0iMDAwMDAwIi8+PC9hOmRrMT48YTpsdDE+PGE6c3lzQ2xyIHZhbD0id2luZG93IiBsYXN0Q2xyPSJGRkZGRkYiLz48L2E6bHQxPjxhOmRrMj48YTpzcmdiQ2xyIHZhbD0iMUY0OTdEIi8+PC9hOmRrMj48YTpsdDI+PGE6c3JnYkNsciB2YWw9IkVFRUNFMSIvPjwvYTpsdDI+PGE6YWNjZW50MT48YTpzcmdiQ2xyIHZhbD0iNEY4MUJEIi8+PC9hOmFjY2VudDE+PGE6YWNjZW50Mj48YTpzcmdiQ2xyIHZhbD0iQzA1MDREIi8+PC9hOmFjY2VudDI+PGE6YWNjZW50Mz48YTpzcmdiQ2xyIHZhbD0iOUJCQjU5Ii8+PC9hOmFjY2VudDM+PGE6YWNjZW50ND48YTpzcmdiQ2xyIHZhbD0iODA2NEEyIi8+PC9hOmFjY2VudDQ+PGE6YWNjZW50NT48YTpzcmdiQ2xyIHZhbD0iNEJBQ0M2Ii8+PC9hOmFjY2VudDU+PGE6YWNjZW50Nj48YTpzcmdiQ2xyIHZhbD0iRjc5NjQ2Ii8+PC9hOmFjY2VudDY+PGE6aGxpbms+PGE6c3JnYkNsciB2YWw9IjAwMDBGRiIvPjwvYTpobGluaz48YTpmb2xIbGluaz48YTpzcmdiQ2xyIHZhbD0iODAwMDgwIi8+PC9hOmZvbEhsaW5rPjwvYTpjbHJTY2hlbWU+PGE6Zm9udFNjaGVtZSBuYW1lPSJPZmZpY2UiPjxhOm1ham9yRm9udD48YTpsYXRpbiB0eXBlZmFjZT0iQ2FtYnJpYSIvPjxhOmVhIHR5cGVmYWNlPSIiLz48YTpjcyB0eXBlZmFjZT0iIi8+PGE6Zm9udCBzY3JpcHQ9IkpwYW4iIHR5cGVmYWNlPSLvvK3vvLMg77yw44K044K344OD44KvIi8+PGE6Zm9udCBzY3JpcHQ9IkhhbmciIHR5cGVmYWNlPSLrp5HsnYAg6rOg65SVIi8+PGE6Zm9udCBzY3JpcHQ9IkhhbnMiIHR5cGVmYWNlPSLlrovkvZMiLz48YTpmb250IHNjcmlwdD0iSGFudCIgdHlwZWZhY2U9IuaWsOe0sOaYjumrlCIvPjxhOmZvbnQgc2NyaXB0PSJBcmFiIiB0eXBlZmFjZT0iVGltZXMgTmV3IFJvbWFuIi8+PGE6Zm9udCBzY3JpcHQ9IkhlYnIiIHR5cGVmYWNlPSJUaW1lcyBOZXcgUm9tYW4iLz48YTpmb250IHNjcmlwdD0iVGhhaSIgdHlwZWZhY2U9IlRhaG9tYSIvPjxhOmZvbnQgc2NyaXB0PSJFdGhpIiB0eXBlZmFjZT0iTnlhbGEiLz48YTpmb250IHNjcmlwdD0iQmVuZyIgdHlwZWZhY2U9IlZyaW5kYSIvPjxhOmZvbnQgc2NyaXB0PSJHdWpyIiB0eXBlZmFjZT0iU2hydXRpIi8+PGE6Zm9udCBzY3JpcHQ9IktobXIiIHR5cGVmYWNlPSJNb29sQm9yYW4iLz48YTpmb250IHNjcmlwdD0iS25kYSIgdHlwZWZhY2U9IlR1bmdhIi8+PGE6Zm9udCBzY3JpcHQ9Ikd1cnUiIHR5cGVmYWNlPSJSYWF2aSIvPjxhOmZvbnQgc2NyaXB0PSJDYW5zIiB0eXBlZmFjZT0iRXVwaGVtaWEiLz48YTpmb250IHNjcmlwdD0iQ2hlciIgdHlwZWZhY2U9IlBsYW50YWdlbmV0IENoZXJva2VlIi8+PGE6Zm9udCBzY3JpcHQ9IllpaWkiIHR5cGVmYWNlPSJNaWNyb3NvZnQgWWkgQmFpdGkiLz48YTpmb250IHNjcmlwdD0iVGlidCIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBIaW1hbGF5YSIvPjxhOmZvbnQgc2NyaXB0PSJUaGFhIiB0eXBlZmFjZT0iTVYgQm9saSIvPjxhOmZvbnQgc2NyaXB0PSJEZXZhIiB0eXBlZmFjZT0iTWFuZ2FsIi8+PGE6Zm9udCBzY3JpcHQ9IlRlbHUiIHR5cGVmYWNlPSJHYXV0YW1pIi8+PGE6Zm9udCBzY3JpcHQ9IlRhbWwiIHR5cGVmYWNlPSJMYXRoYSIvPjxhOmZvbnQgc2NyaXB0PSJTeXJjIiB0eXBlZmFjZT0iRXN0cmFuZ2VsbyBFZGVzc2EiLz48YTpmb250IHNjcmlwdD0iT3J5YSIgdHlwZWZhY2U9IkthbGluZ2EiLz48YTpmb250IHNjcmlwdD0iTWx5bSIgdHlwZWZhY2U9IkthcnRpa2EiLz48YTpmb250IHNjcmlwdD0iTGFvbyIgdHlwZWZhY2U9IkRva0NoYW1wYSIvPjxhOmZvbnQgc2NyaXB0PSJTaW5oIiB0eXBlZmFjZT0iSXNrb29sYSBQb3RhIi8+PGE6Zm9udCBzY3JpcHQ9Ik1vbmciIHR5cGVmYWNlPSJNb25nb2xpYW4gQmFpdGkiLz48YTpmb250IHNjcmlwdD0iVmlldCIgdHlwZWZhY2U9IlRpbWVzIE5ldyBSb21hbiIvPjxhOmZvbnQgc2NyaXB0PSJVaWdoIiB0eXBlZmFjZT0iTWljcm9zb2Z0IFVpZ2h1ciIvPjxhOmZvbnQgc2NyaXB0PSJHZW9yIiB0eXBlZmFjZT0iU3lsZmFlbiIvPjwvYTptYWpvckZvbnQ+PGE6bWlub3JGb250PjxhOmxhdGluIHR5cGVmYWNlPSJDYWxpYnJpIi8+PGE6ZWEgdHlwZWZhY2U9IiIvPjxhOmNzIHR5cGVmYWNlPSIiLz48YTpmb250IHNjcmlwdD0iSnBhbiIgdHlwZWZhY2U9Iu+8re+8syDvvLDjgrTjgrfjg4Pjgq8iLz48YTpmb250IHNjcmlwdD0iSGFuZyIgdHlwZWZhY2U9IuunkeydgCDqs6DrlJUiLz48YTpmb250IHNjcmlwdD0iSGFucyIgdHlwZWZhY2U9IuWui+S9kyIvPjxhOmZvbnQgc2NyaXB0PSJIYW50IiB0eXBlZmFjZT0i5paw57Sw5piO6auUIi8+PGE6Zm9udCBzY3JpcHQ9IkFyYWIiIHR5cGVmYWNlPSJBcmlhbCIvPjxhOmZvbnQgc2NyaXB0PSJIZWJyIiB0eXBlZmFjZT0iQXJpYWwiLz48YTpmb250IHNjcmlwdD0iVGhhaSIgdHlwZWZhY2U9IlRhaG9tYSIvPjxhOmZvbnQgc2NyaXB0PSJFdGhpIiB0eXBlZmFjZT0iTnlhbGEiLz48YTpmb250IHNjcmlwdD0iQmVuZyIgdHlwZWZhY2U9IlZyaW5kYSIvPjxhOmZvbnQgc2NyaXB0PSJHdWpyIiB0eXBlZmFjZT0iU2hydXRpIi8+PGE6Zm9udCBzY3JpcHQ9IktobXIiIHR5cGVmYWNlPSJEYXVuUGVuaCIvPjxhOmZvbnQgc2NyaXB0PSJLbmRhIiB0eXBlZmFjZT0iVHVuZ2EiLz48YTpmb250IHNjcmlwdD0iR3VydSIgdHlwZWZhY2U9IlJhYXZpIi8+PGE6Zm9udCBzY3JpcHQ9IkNhbnMiIHR5cGVmYWNlPSJFdXBoZW1pYSIvPjxhOmZvbnQgc2NyaXB0PSJDaGVyIiB0eXBlZmFjZT0iUGxhbnRhZ2VuZXQgQ2hlcm9rZWUiLz48YTpmb250IHNjcmlwdD0iWWlpaSIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBZaSBCYWl0aSIvPjxhOmZvbnQgc2NyaXB0PSJUaWJ0IiB0eXBlZmFjZT0iTWljcm9zb2Z0IEhpbWFsYXlhIi8+PGE6Zm9udCBzY3JpcHQ9IlRoYWEiIHR5cGVmYWNlPSJNViBCb2xpIi8+PGE6Zm9udCBzY3JpcHQ9IkRldmEiIHR5cGVmYWNlPSJNYW5nYWwiLz48YTpmb250IHNjcmlwdD0iVGVsdSIgdHlwZWZhY2U9IkdhdXRhbWkiLz48YTpmb250IHNjcmlwdD0iVGFtbCIgdHlwZWZhY2U9IkxhdGhhIi8+PGE6Zm9udCBzY3JpcHQ9IlN5cmMiIHR5cGVmYWNlPSJFc3RyYW5nZWxvIEVkZXNzYSIvPjxhOmZvbnQgc2NyaXB0PSJPcnlhIiB0eXBlZmFjZT0iS2FsaW5nYSIvPjxhOmZvbnQgc2NyaXB0PSJNbHltIiB0eXBlZmFjZT0iS2FydGlrYSIvPjxhOmZvbnQgc2NyaXB0PSJMYW9vIiB0eXBlZmFjZT0iRG9rQ2hhbXBhIi8+PGE6Zm9udCBzY3JpcHQ9IlNpbmgiIHR5cGVmYWNlPSJJc2tvb2xhIFBvdGEiLz48YTpmb250IHNjcmlwdD0iTW9uZyIgdHlwZWZhY2U9Ik1vbmdvbGlhbiBCYWl0aSIvPjxhOmZvbnQgc2NyaXB0PSJWaWV0IiB0eXBlZmFjZT0iQXJpYWwiLz48YTpmb250IHNjcmlwdD0iVWlnaCIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBVaWdodXIiLz48YTpmb250IHNjcmlwdD0iR2VvciIgdHlwZWZhY2U9IlN5bGZhZW4iLz48L2E6bWlub3JGb250PjwvYTpmb250U2NoZW1lPjxhOmZtdFNjaGVtZSBuYW1lPSJPZmZpY2UiPjxhOmZpbGxTdHlsZUxzdD48YTpzb2xpZEZpbGw+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiLz48L2E6c29saWRGaWxsPjxhOmdyYWRGaWxsIHJvdFdpdGhTaGFwZT0iMSI+PGE6Z3NMc3Q+PGE6Z3MgcG9zPSIwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6dGludCB2YWw9IjUwMDAwIi8+PGE6c2F0TW9kIHZhbD0iMzAwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSIzNTAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSIzNzAwMCIvPjxhOnNhdE1vZCB2YWw9IjMwMDAwMCIvPjwvYTpzY2hlbWVDbHI+PC9hOmdzPjxhOmdzIHBvcz0iMTAwMDAwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6dGludCB2YWw9IjE1MDAwIi8+PGE6c2F0TW9kIHZhbD0iMzUwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOmxpbiBhbmc9IjE2MjAwMDAwIiBzY2FsZWQ9IjEiLz48L2E6Z3JhZEZpbGw+PGE6Z3JhZEZpbGwgcm90V2l0aFNoYXBlPSIxIj48YTpnc0xzdD48YTpncyBwb3M9IjAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTp0aW50IHZhbD0iMTAwMDAwIi8+PGE6c2hhZGUgdmFsPSIxMDAwMDAiLz48YTpzYXRNb2QgdmFsPSIxMzAwMDAiLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjEwMDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI1MDAwMCIvPjxhOnNoYWRlIHZhbD0iMTAwMDAwIi8+PGE6c2F0TW9kIHZhbD0iMzUwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOmxpbiBhbmc9IjE2MjAwMDAwIiBzY2FsZWQ9IjAiLz48L2E6Z3JhZEZpbGw+PC9hOmZpbGxTdHlsZUxzdD48YTpsblN0eWxlTHN0PjxhOmxuIHc9Ijk1MjUiIGNhcD0iZmxhdCIgY21wZD0ic25nIiBhbGduPSJjdHIiPjxhOnNvbGlkRmlsbD48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6c2hhZGUgdmFsPSI5NTAwMCIvPjxhOnNhdE1vZCB2YWw9IjEwNTAwMCIvPjwvYTpzY2hlbWVDbHI+PC9hOnNvbGlkRmlsbD48YTpwcnN0RGFzaCB2YWw9InNvbGlkIi8+PC9hOmxuPjxhOmxuIHc9IjI1NDAwIiBjYXA9ImZsYXQiIGNtcGQ9InNuZyIgYWxnbj0iY3RyIj48YTpzb2xpZEZpbGw+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiLz48L2E6c29saWRGaWxsPjxhOnByc3REYXNoIHZhbD0ic29saWQiLz48L2E6bG4+PGE6bG4gdz0iMzgxMDAiIGNhcD0iZmxhdCIgY21wZD0ic25nIiBhbGduPSJjdHIiPjxhOnNvbGlkRmlsbD48YTpzY2hlbWVDbHIgdmFsPSJwaENsciIvPjwvYTpzb2xpZEZpbGw+PGE6cHJzdERhc2ggdmFsPSJzb2xpZCIvPjwvYTpsbj48L2E6bG5TdHlsZUxzdD48YTplZmZlY3RTdHlsZUxzdD48YTplZmZlY3RTdHlsZT48YTplZmZlY3RMc3Q+PGE6b3V0ZXJTaGR3IGJsdXJSYWQ9IjQwMDAwIiBkaXN0PSIyMDAwMCIgZGlyPSI1NDAwMDAwIiByb3RXaXRoU2hhcGU9IjAiPjxhOnNyZ2JDbHIgdmFsPSIwMDAwMDAiPjxhOmFscGhhIHZhbD0iMzgwMDAiLz48L2E6c3JnYkNscj48L2E6b3V0ZXJTaGR3PjwvYTplZmZlY3RMc3Q+PC9hOmVmZmVjdFN0eWxlPjxhOmVmZmVjdFN0eWxlPjxhOmVmZmVjdExzdD48YTpvdXRlclNoZHcgYmx1clJhZD0iNDAwMDAiIGRpc3Q9IjIzMDAwIiBkaXI9IjU0MDAwMDAiIHJvdFdpdGhTaGFwZT0iMCI+PGE6c3JnYkNsciB2YWw9IjAwMDAwMCI+PGE6YWxwaGEgdmFsPSIzNTAwMCIvPjwvYTpzcmdiQ2xyPjwvYTpvdXRlclNoZHc+PC9hOmVmZmVjdExzdD48L2E6ZWZmZWN0U3R5bGU+PGE6ZWZmZWN0U3R5bGU+PGE6ZWZmZWN0THN0PjxhOm91dGVyU2hkdyBibHVyUmFkPSI0MDAwMCIgZGlzdD0iMjMwMDAiIGRpcj0iNTQwMDAwMCIgcm90V2l0aFNoYXBlPSIwIj48YTpzcmdiQ2xyIHZhbD0iMDAwMDAwIj48YTphbHBoYSB2YWw9IjM1MDAwIi8+PC9hOnNyZ2JDbHI+PC9hOm91dGVyU2hkdz48L2E6ZWZmZWN0THN0PjxhOnNjZW5lM2Q+PGE6Y2FtZXJhIHByc3Q9Im9ydGhvZ3JhcGhpY0Zyb250Ij48YTpyb3QgbGF0PSIwIiBsb249IjAiIHJldj0iMCIvPjwvYTpjYW1lcmE+PGE6bGlnaHRSaWcgcmlnPSJ0aHJlZVB0IiBkaXI9InQiPjxhOnJvdCBsYXQ9IjAiIGxvbj0iMCIgcmV2PSIxMjAwMDAwIi8+PC9hOmxpZ2h0UmlnPjwvYTpzY2VuZTNkPjxhOnNwM2Q+PGE6YmV2ZWxUIHc9IjYzNTAwIiBoPSIyNTQwMCIvPjwvYTpzcDNkPjwvYTplZmZlY3RTdHlsZT48L2E6ZWZmZWN0U3R5bGVMc3Q+PGE6YmdGaWxsU3R5bGVMc3Q+PGE6c29saWRGaWxsPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIi8+PC9hOnNvbGlkRmlsbD48YTpncmFkRmlsbCByb3RXaXRoU2hhcGU9IjEiPjxhOmdzTHN0PjxhOmdzIHBvcz0iMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI0MDAwMCIvPjxhOnNhdE1vZCB2YWw9IjM1MDAwMCIvPjwvYTpzY2hlbWVDbHI+PC9hOmdzPjxhOmdzIHBvcz0iNDAwMDAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTp0aW50IHZhbD0iNDUwMDAiLz48YTpzaGFkZSB2YWw9Ijk5MDAwIi8+PGE6c2F0TW9kIHZhbD0iMzUwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSIxMDAwMDAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTpzaGFkZSB2YWw9IjIwMDAwIi8+PGE6c2F0TW9kIHZhbD0iMjU1MDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOnBhdGggcGF0aD0iY2lyY2xlIj48YTpmaWxsVG9SZWN0IGw9IjUwMDAwIiB0PSItODAwMDAiIHI9IjUwMDAwIiBiPSIxODAwMDAiLz48L2E6cGF0aD48L2E6Z3JhZEZpbGw+PGE6Z3JhZEZpbGwgcm90V2l0aFNoYXBlPSIxIj48YTpnc0xzdD48YTpncyBwb3M9IjAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTp0aW50IHZhbD0iODAwMDAiLz48YTpzYXRNb2QgdmFsPSIzMDAwMDAiLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjEwMDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnNoYWRlIHZhbD0iMzAwMDAiLz48YTpzYXRNb2QgdmFsPSIyMDAwMDAiLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48L2E6Z3NMc3Q+PGE6cGF0aCBwYXRoPSJjaXJjbGUiPjxhOmZpbGxUb1JlY3QgbD0iNTAwMDAiIHQ9IjUwMDAwIiByPSI1MDAwMCIgYj0iNTAwMDAiLz48L2E6cGF0aD48L2E6Z3JhZEZpbGw+PC9hOmJnRmlsbFN0eWxlTHN0PjwvYTpmbXRTY2hlbWU+PC9hOnRoZW1lRWxlbWVudHM+PGE6b2JqZWN0RGVmYXVsdHM+PGE6c3BEZWY+PGE6c3BQci8+PGE6Ym9keVByLz48YTpsc3RTdHlsZS8+PGE6c3R5bGU+PGE6bG5SZWYgaWR4PSIxIj48YTpzY2hlbWVDbHIgdmFsPSJhY2NlbnQxIi8+PC9hOmxuUmVmPjxhOmZpbGxSZWYgaWR4PSIzIj48YTpzY2hlbWVDbHIgdmFsPSJhY2NlbnQxIi8+PC9hOmZpbGxSZWY+PGE6ZWZmZWN0UmVmIGlkeD0iMiI+PGE6c2NoZW1lQ2xyIHZhbD0iYWNjZW50MSIvPjwvYTplZmZlY3RSZWY+PGE6Zm9udFJlZiBpZHg9Im1pbm9yIj48YTpzY2hlbWVDbHIgdmFsPSJsdDEiLz48L2E6Zm9udFJlZj48L2E6c3R5bGU+PC9hOnNwRGVmPjxhOmxuRGVmPjxhOnNwUHIvPjxhOmJvZHlQci8+PGE6bHN0U3R5bGUvPjxhOnN0eWxlPjxhOmxuUmVmIGlkeD0iMiI+PGE6c2NoZW1lQ2xyIHZhbD0iYWNjZW50MSIvPjwvYTpsblJlZj48YTpmaWxsUmVmIGlkeD0iMCI+PGE6c2NoZW1lQ2xyIHZhbD0iYWNjZW50MSIvPjwvYTpmaWxsUmVmPjxhOmVmZmVjdFJlZiBpZHg9IjEiPjxhOnNjaGVtZUNsciB2YWw9ImFjY2VudDEiLz48L2E6ZWZmZWN0UmVmPjxhOmZvbnRSZWYgaWR4PSJtaW5vciI+PGE6c2NoZW1lQ2xyIHZhbD0idHgxIi8+PC9hOmZvbnRSZWY+PC9hOnN0eWxlPjwvYTpsbkRlZj48L2E6b2JqZWN0RGVmYXVsdHM+PGE6ZXh0cmFDbHJTY2hlbWVMc3QvPjwvYTp0aGVtZT5QSwMEFAAAAAAAAAAAAFX0BJRaBAAAWgQAAA0AAAB4bC9zdHlsZXMueG1sPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPHN0eWxlU2hlZXQgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9zcHJlYWRzaGVldG1sLzIwMDYvbWFpbiIgeG1sbnM6dnQ9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L2RvY1Byb3BzVlR5cGVzIj48bnVtRm10cyBjb3VudD0iMSI+PG51bUZtdCBudW1GbXRJZD0iNTYiIGZvcm1hdENvZGU9IiZxdW90O+S4iuWNiC/kuIvljYggJnF1b3Q7aGgmcXVvdDvmmYImcXVvdDttbSZxdW90O+WIhiZxdW90O3NzJnF1b3Q756eSICZxdW90OyIvPjwvbnVtRm10cz48Zm9udHMgY291bnQ9IjEiPjxmb250PjxzeiB2YWw9IjEyIi8+PGNvbG9yIHRoZW1lPSIxIi8+PG5hbWUgdmFsPSJDYWxpYnJpIi8+PGZhbWlseSB2YWw9IjIiLz48c2NoZW1lIHZhbD0ibWlub3IiLz48L2ZvbnQ+PC9mb250cz48ZmlsbHMgY291bnQ9IjIiPjxmaWxsPjxwYXR0ZXJuRmlsbCBwYXR0ZXJuVHlwZT0ibm9uZSIvPjwvZmlsbD48ZmlsbD48cGF0dGVybkZpbGwgcGF0dGVyblR5cGU9ImdyYXkxMjUiLz48L2ZpbGw+PC9maWxscz48Ym9yZGVycyBjb3VudD0iMSI+PGJvcmRlcj48bGVmdC8+PHJpZ2h0Lz48dG9wLz48Ym90dG9tLz48ZGlhZ29uYWwvPjwvYm9yZGVyPjwvYm9yZGVycz48Y2VsbFN0eWxlWGZzIGNvdW50PSIxIj48eGYgbnVtRm10SWQ9IjAiIGZvbnRJZD0iMCIgZmlsbElkPSIwIiBib3JkZXJJZD0iMCIvPjwvY2VsbFN0eWxlWGZzPjxjZWxsWGZzIGNvdW50PSIxIj48eGYgbnVtRm10SWQ9IjAiIGZvbnRJZD0iMCIgZmlsbElkPSIwIiBib3JkZXJJZD0iMCIgeGZJZD0iMCIgYXBwbHlOdW1iZXJGb3JtYXQ9IjEiLz48L2NlbGxYZnM+PGNlbGxTdHlsZXMgY291bnQ9IjEiPjxjZWxsU3R5bGUgbmFtZT0iTm9ybWFsIiB4ZklkPSIwIiBidWlsdGluSWQ9IjAiLz48L2NlbGxTdHlsZXM+PGR4ZnMgY291bnQ9IjAiLz48dGFibGVTdHlsZXMgY291bnQ9IjAiIGRlZmF1bHRUYWJsZVN0eWxlPSJUYWJsZVN0eWxlTWVkaXVtOSIgZGVmYXVsdFBpdm90U3R5bGU9IlBpdm90U3R5bGVNZWRpdW00Ii8+PC9zdHlsZVNoZWV0PlBLAwQUAAAAAAAAAAAA+oDcDIEQAACBEAAAGAAAAHhsL3dvcmtzaGVldHMvc2hlZXQxLnhtbDw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04IiBzdGFuZGFsb25lPSJ5ZXMiPz4NCjx3b3Jrc2hlZXQgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9zcHJlYWRzaGVldG1sLzIwMDYvbWFpbiIgeG1sbnM6cj0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcyI+PGRpbWVuc2lvbiByZWY9IkExOkQxOCIvPjxzaGVldFZpZXdzPjxzaGVldFZpZXcgd29ya2Jvb2tWaWV3SWQ9IjAiLz48L3NoZWV0Vmlld3M+PGNvbHM+PGNvbCBtaW49IjEiIG1heD0iMSIgd2lkdGg9IjMyLjgzMjAzMTI1IiBjdXN0b21XaWR0aD0iMSIvPjxjb2wgbWluPSIyIiBtYXg9IjIiIHdpZHRoPSIyOC44MzIwMzEyNSIgY3VzdG9tV2lkdGg9IjEiLz48Y29sIG1pbj0iMyIgbWF4PSIzIiB3aWR0aD0iMzguODMyMDMxMjUiIGN1c3RvbVdpZHRoPSIxIi8+PGNvbCBtaW49IjQiIG1heD0iNCIgd2lkdGg9IjEyLjgzMjAzMTI1IiBjdXN0b21XaWR0aD0iMSIvPjwvY29scz48c2hlZXREYXRhPjxyb3cgcj0iMSI+PGMgcj0iQTEiIHQ9InN0ciI+PHY+RW1haWw8L3Y+PC9jPjxjIHI9IkIxIiB0PSJzdHIiPjx2Pk5vbSBhY3R1ZWwgKHRyYWNraW5nKTwvdj48L2M+PGMgcj0iQzEiIHQ9InN0ciI+PHY+VnJhaSBub20gKEV4Y2VsINCh0L3QsNCx0LbQtdC90LjQtSk8L3Y+PC9jPjxjIHI9IkQxIiB0PSJzdHIiPjx2PlN0YXR1dDwvdj48L2M+PC9yb3c+PHJvdyByPSIyIj48YyByPSJBMiIgdD0ic3RyIj48dj5lX3N1aG92YUBraXJlbGlzLnJ1PC92PjwvYz48YyByPSJCMiIgdD0ic3RyIj48dj5raXJlbGlzLnJ1PC92PjwvYz48YyByPSJDMiIgdD0ic3RyIj48dj7QmtCY0KDQldCb0JjQoSDQntCe0J48L3Y+PC9jPjxjIHI9IkQyIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMyI+PGMgcj0iQTMiIHQ9InN0ciI+PHY+NzEwMTE1dHZlckBtYWlsLnJ1PC92PjwvYz48YyByPSJCMyIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDMyIgdD0ic3RyIj48dj7QpdC+0LzRg9GC0L7QsiDQkNC90LTRgNC10Lkg0JDQvdC00YDQtdC10LLQuNGHINCY0J88L3Y+PC9jPjxjIHI9IkQzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iNCI+PGMgcj0iQTQiIHQ9InN0ciI+PHY+emFrYXpAbW9iaXhpbS5ydTwvdj48L2M+PGMgcj0iQjQiIHQ9InN0ciI+PHY+bW9iaXhpbS5ydTwvdj48L2M+PGMgcj0iQzQiIHQ9InN0ciI+PHY+0JzQntCR0JjQpdCY0Jwg0J7QntCePC92PjwvYz48YyByPSJENCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjUiPjxjIHI9IkE1IiB0PSJzdHIiPjx2PmNoZWNoZXZpY2luX3Rwc0BtYWlsLnJ1PC92PjwvYz48YyByPSJCNSIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDNSIgdD0ic3RyIj48dj7QotCf0KEg0J7QntCePC92PjwvYz48YyByPSJENSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjYiPjxjIHI9IkE2IiB0PSJzdHIiPjx2PnN2YXJrYXR2ZXJAeWFuZGV4LnJ1PC92PjwvYz48YyByPSJCNiIgdD0ic3RyIj48dj55YW5kZXgucnU8L3Y+PC9jPjxjIHI9IkM2IiB0PSJzdHIiPjx2PtCQ0JLQldCh0KLQkCDQntCe0J48L3Y+PC9jPjxjIHI9IkQ2IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iNyI+PGMgcj0iQTciIHQ9InN0ciI+PHY+b29vdHJla0BpbmJveC5ydTwvdj48L2M+PGMgcj0iQjciIHQ9InN0ciI+PHY+aW5ib3gucnU8L3Y+PC9jPjxjIHI9IkM3IiB0PSJzdHIiPjx2PtCi0KDQrdCaINCe0J7Qnjwvdj48L2M+PGMgcj0iRDciIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSI4Ij48YyByPSJBOCIgdD0ic3RyIj48dj51cmFsMUBjb2xvcnBvd2RlcmNvYXRpbmcuY29tPC92PjwvYz48YyByPSJCOCIgdD0ic3RyIj48dj5jb2xvcnBvd2RlcmNvYXRpbmcuY29tPC92PjwvYz48YyByPSJDOCIgdD0ic3RyIj48dj7QpNCt0JnQpdCe0J0g0J7QntCePC92PjwvYz48YyByPSJEOCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjkiPjxjIHI9IkE5IiB0PSJzdHIiPjx2PjE0NEBhdXRvcGFjay5ydTwvdj48L2M+PGMgcj0iQjkiIHQ9InN0ciI+PHY+YXV0b3BhY2sucnU8L3Y+PC9jPjxjIHI9IkM5IiB0PSJzdHIiPjx2PtCi0JQg0JDQktCi0J7Qn9CQ0Jog0J7QntCePC92PjwvYz48YyByPSJEOSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjEwIj48YyByPSJBMTAiIHQ9InN0ciI+PHY+bWFpbEBzaHlta2EucnU8L3Y+PC9jPjxjIHI9IkIxMCIgdD0ic3RyIj48dj5zaHlta2EucnU8L3Y+PC9jPjxjIHI9IkMxMCIgdD0ic3RyIj48dj7QqNCj0JzQmtCQINCe0J7QniDQmNCd0KLQldCg0J3QldCiLdCc0JDQk9CQ0JfQmNCdPC92PjwvYz48YyByPSJEMTAiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSIxMSI+PGMgcj0iQTExIiB0PSJzdHIiPjx2PmV2a29uZHJhdEBtYWlsLnJ1PC92PjwvYz48YyByPSJCMTEiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzExIiB0PSJzdHIiPjx2PtCQ0JLQotCe0JPQm9Ce0JHQo9ChINCe0J7Qnjwvdj48L2M+PGMgcj0iRDExIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTIiPjxjIHI9IkExMiIgdD0ic3RyIj48dj5kb2xnQGFrdmEtdml0YS5uZXQ8L3Y+PC9jPjxjIHI9IkIxMiIgdD0ic3RyIj48dj5ha3ZhLXZpdGEubmV0PC92PjwvYz48YyByPSJDMTIiIHQ9InN0ciI+PHY+0JAt0JIt0JrQntCc0J/QkNCd0Jgg0J7QntCePC92PjwvYz48YyByPSJEMTIiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSIxMyI+PGMgcj0iQTEzIiB0PSJzdHIiPjx2PnR2ZXI0NzU2NzhAbWFpbC5ydTwvdj48L2M+PGMgcj0iQjEzIiB0PSJzdHIiPjx2Pm1haWwucnU8L3Y+PC9jPjxjIHI9IkMxMyIgdD0ic3RyIj48dj7Qn9Cg0J7QnC3Qn9Ce0KHQotCQ0JLQmtCQINCe0J7Qnjwvdj48L2M+PGMgcj0iRDEzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTQiPjxjIHI9IkExNCIgdD0ic3RyIj48dj5vdi52aWtoYXJldmFAc2V2ZXJzdGFsLmNvbTwvdj48L2M+PGMgcj0iQjE0IiB0PSJzdHIiPjx2PnNldmVyc3RhbC5jb208L3Y+PC9jPjxjIHI9IkMxNCIgdD0ic3RyIj48dj7QrtCd0JjQpNCV0J3QoSDQntCe0J48L3Y+PC9jPjxjIHI9IkQxNCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjE1Ij48YyByPSJBMTUiIHQ9InN0ciI+PHY+aS5zaGFrdXJvdkBtZXRpemkucnU8L3Y+PC9jPjxjIHI9IkIxNSIgdD0ic3RyIj48dj5tZXRpemkucnU8L3Y+PC9jPjxjIHI9IkMxNSIgdD0ic3RyIj48dj7QkNCb0KzQpNCQINCg0JDQnNCV0J3QmtCQINCe0J7Qnjwvdj48L2M+PGMgcj0iRDE1IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTYiPjxjIHI9IkExNiIgdD0ic3RyIj48dj5zb3Jva2luX2JhdUBtYWlsLnJ1PC92PjwvYz48YyByPSJCMTYiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzE2IiB0PSJzdHIiPjx2PtCi0JLQldCg0KHQmtCY0JUg0J/QmNCb0J7QoNCQ0JzQqyDQntCe0J48L3Y+PC9jPjxjIHI9IkQxNiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjE3Ij48YyByPSJBMTciIHQ9InN0ciI+PHY+c2VyZ2V5X29vb19oYXJkQG1haWwucnU8L3Y+PC9jPjxjIHI9IkIxNyIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDMTciIHQ9InN0ciI+PHY+0KXQkNCg0JTQktCj0JQg0J7QntCePC92PjwvYz48YyByPSJEMTciIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSIxOCI+PGMgcj0iQTE4IiB0PSJzdHIiPjx2PnRzQGF2YWxvbi5zdTwvdj48L2M+PGMgcj0iQjE4IiB0PSJzdHIiPjx2PmF2YWxvbi5zdTwvdj48L2M+PGMgcj0iQzE4IiB0PSJzdHIiPjx2PtCQ0JLQkNCb0J7QnS3QoSDQntCe0J48L3Y+PC9jPjxjIHI9IkQxOCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48L3NoZWV0RGF0YT48aWdub3JlZEVycm9ycz48aWdub3JlZEVycm9yIG51bWJlclN0b3JlZEFzVGV4dD0iMSIgc3FyZWY9IkExOkQxOCIvPjwvaWdub3JlZEVycm9ycz48L3dvcmtzaGVldD5QSwMEFAAAAAAAAAAAALeVZcljHQAAYx0AABgAAAB4bC93b3Jrc2hlZXRzL3NoZWV0Mi54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8d29ya3NoZWV0IHhtbG5zPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvc3ByZWFkc2hlZXRtbC8yMDA2L21haW4iIHhtbG5zOnI9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMiPjxkaW1lbnNpb24gcmVmPSJBMTpEMzUiLz48c2hlZXRWaWV3cz48c2hlZXRWaWV3IHdvcmtib29rVmlld0lkPSIwIi8+PC9zaGVldFZpZXdzPjxjb2xzPjxjb2wgbWluPSIxIiBtYXg9IjEiIHdpZHRoPSIzMi44MzIwMzEyNSIgY3VzdG9tV2lkdGg9IjEiLz48Y29sIG1pbj0iMiIgbWF4PSIyIiB3aWR0aD0iMjguODMyMDMxMjUiIGN1c3RvbVdpZHRoPSIxIi8+PGNvbCBtaW49IjMiIG1heD0iMyIgd2lkdGg9IjEyLjgzMjAzMTI1IiBjdXN0b21XaWR0aD0iMSIvPjxjb2wgbWluPSI0IiBtYXg9IjQiIHdpZHRoPSIzMC44MzIwMzEyNSIgY3VzdG9tV2lkdGg9IjEiLz48L2NvbHM+PHNoZWV0RGF0YT48cm93IHI9IjEiPjxjIHI9IkExIiB0PSJzdHIiPjx2PkVtYWlsPC92PjwvYz48YyByPSJCMSIgdD0ic3RyIj48dj5Ob20gYWN0dWVsICh0cmFja2luZyk8L3Y+PC9jPjxjIHI9IkMxIiB0PSJzdHIiPjx2PlN0YXR1dDwvdj48L2M+PGMgcj0iRDEiIHQ9InN0ciI+PHY+Tm90ZXMgZW52b2k8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMiI+PGMgcj0iQTIiIHQ9InN0ciI+PHY+Y2xpZW50LnNlcnZpY2VAZGVsbGluLnJ1PC92PjwvYz48YyByPSJCMiIgdD0ic3RyIj48dj5kZWxsaW4ucnU8L3Y+PC9jPjxjIHI9IkMyIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjMiPjxjIHI9IkEzIiB0PSJzdHIiPjx2PmdpbWVpbkBzYmVyYmFuay1tYWlsLmNvbTwvdj48L2M+PGMgcj0iQjMiIHQ9InN0ciI+PHY+c2JlcmJhbmstbWFpbC5jb208L3Y+PC9jPjxjIHI9IkMzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjQiPjxjIHI9IkE0IiB0PSJzdHIiPjx2PmFyY2hpYmFsZDgzQG1haWwucnU8L3Y+PC9jPjxjIHI9IkI0IiB0PSJzdHIiPjx2Pm1haWwucnU8L3Y+PC9jPjxjIHI9IkM0IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQ0IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjUiPjxjIHI9IkE1IiB0PSJzdHIiPjx2PmEuYmFidXJpZGlzQG1ldGl6aS5ydTwvdj48L2M+PGMgcj0iQjUiIHQ9InN0ciI+PHY+bWV0aXppLnJ1PC92PjwvYz48YyByPSJDNSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJENSIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSI2Ij48YyByPSJBNiIgdD0ic3RyIj48dj5wcm8xQGVjbzY5LnJ1PC92PjwvYz48YyByPSJCNiIgdD0ic3RyIj48dj5lY282OS5ydTwvdj48L2M+PGMgcj0iQzYiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDYiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iNyI+PGMgcj0iQTciIHQ9InN0ciI+PHY+bS51c3Ryb2toYW5vdkBna2F0bW9zLnJ1PC92PjwvYz48YyByPSJCNyIgdD0ic3RyIj48dj5na2F0bW9zLnJ1PC92PjwvYz48YyByPSJDNyIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJENyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSI4Ij48YyByPSJBOCIgdD0ic3RyIj48dj43MDdAbWV0YWxsLTc3LnJ1PC92PjwvYz48YyByPSJCOCIgdD0ic3RyIj48dj5tZXRhbGwtNzcucnU8L3Y+PC9jPjxjIHI9IkM4IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQ4IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjkiPjxjIHI9IkE5IiB0PSJzdHIiPjx2PnNlcnZpY2VAcml2ZXRndW4ucnU8L3Y+PC9jPjxjIHI9IkI5IiB0PSJzdHIiPjx2PnJpdmV0Z3VuLnJ1PC92PjwvYz48YyByPSJDOSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEOSIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxMCI+PGMgcj0iQTEwIiB0PSJzdHIiPjx2PmVrYkBrb2xlc2Fyb2xpa2kuY29tPC92PjwvYz48YyByPSJCMTAiIHQ9InN0ciI+PHY+a29sZXNhcm9saWtpLmNvbTwvdj48L2M+PGMgcj0iQzEwIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxMCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxMSI+PGMgcj0iQTExIiB0PSJzdHIiPjx2PmF1dG9waXRlckBhdXRvcGl0ZXIucnU8L3Y+PC9jPjxjIHI9IkIxMSIgdD0ic3RyIj48dj5hdXRvcGl0ZXIucnU8L3Y+PC9jPjxjIHI9IkMxMSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMTEiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTIiPjxjIHI9IkExMiIgdD0ic3RyIj48dj50dEB0dG9rYXJrYS5ydTwvdj48L2M+PGMgcj0iQjEyIiB0PSJzdHIiPjx2PnR0b2thcmthLnJ1PC92PjwvYz48YyByPSJDMTIiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDEyIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjEzIj48YyByPSJBMTMiIHQ9InN0ciI+PHY+cGFsbGV0MjAyNUBtYWlsLnJ1PC92PjwvYz48YyByPSJCMTMiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzEzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxMyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxNCI+PGMgcj0iQTE0IiB0PSJzdHIiPjx2PmVfYXN0YWZ1cm92YUBraXJlbGlzLnJ1PC92PjwvYz48YyByPSJCMTQiIHQ9InN0ciI+PHY+a2lyZWxpcy5ydTwvdj48L2M+PGMgcj0iQzE0IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxNCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxNSI+PGMgcj0iQTE1IiB0PSJzdHIiPjx2PmEuYnV0YWV2QGdrYXRtb3MucnU8L3Y+PC9jPjxjIHI9IkIxNSIgdD0ic3RyIj48dj5na2F0bW9zLnJ1PC92PjwvYz48YyByPSJDMTUiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDE1IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjE2Ij48YyByPSJBMTYiIHQ9InN0ciI+PHY+YnVoNkBvLXBpcnMucnU8L3Y+PC9jPjxjIHI9IkIxNiIgdD0ic3RyIj48dj5vLXBpcnMucnU8L3Y+PC9jPjxjIHI9IkMxNiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMTYiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTciPjxjIHI9IkExNyIgdD0ic3RyIj48dj5rZG5Aeml0YXIucnU8L3Y+PC9jPjxjIHI9IkIxNyIgdD0ic3RyIj48dj56aXRhci5ydTwvdj48L2M+PGMgcj0iQzE3IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxNyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxOCI+PGMgcj0iQTE4IiB0PSJzdHIiPjx2PmJveWFyZC10dmVyQG1haWwucnU8L3Y+PC9jPjxjIHI9IkIxOCIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDMTgiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDE4IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjE5Ij48YyByPSJBMTkiIHQ9InN0ciI+PHY+c2FsZTEyQGF1dG9wYWNrLnJ1PC92PjwvYz48YyByPSJCMTkiIHQ9InN0ciI+PHY+YXV0b3BhY2sucnU8L3Y+PC9jPjxjIHI9IkMxOSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMTkiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjAiPjxjIHI9IkEyMCIgdD0ic3RyIj48dj5hLnZvbGtvbnNreUBzdDc3LnJ1PC92PjwvYz48YyByPSJCMjAiIHQ9InN0ciI+PHY+c3Q3Ny5ydTwvdj48L2M+PGMgcj0iQzIwIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyMCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIyMSI+PGMgcj0iQTIxIiB0PSJzdHIiPjx2Pm9wZXJhdG9yMkBzcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkIyMSIgdD0ic3RyIj48dj5zcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkMyMSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjEiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjIiPjxjIHI9IkEyMiIgdD0ic3RyIj48dj5uZXpidXhAbWFpbC5ydTwvdj48L2M+PGMgcj0iQjIyIiB0PSJzdHIiPjx2Pm1haWwucnU8L3Y+PC9jPjxjIHI9IkMyMiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjIiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjMiPjxjIHI9IkEyMyIgdD0ic3RyIj48dj5idWhzdmVya2FAdGVjaG5peC1ydXMucnU8L3Y+PC9jPjxjIHI9IkIyMyIgdD0ic3RyIj48dj50ZWNobml4LXJ1cy5ydTwvdj48L2M+PGMgcj0iQzIzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyMyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIyNCI+PGMgcj0iQTI0IiB0PSJzdHIiPjx2Pm1hbmFnZXJAdGVjaG5peC1ydXMucnU8L3Y+PC9jPjxjIHI9IkIyNCIgdD0ic3RyIj48dj50ZWNobml4LXJ1cy5ydTwvdj48L2M+PGMgcj0iQzI0IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyNCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIyNSI+PGMgcj0iQTI1IiB0PSJzdHIiPjx2Pm1zazhAdHJ1Ym9wcm9kdWN0LnJ1PC92PjwvYz48YyByPSJCMjUiIHQ9InN0ciI+PHY+dHJ1Ym9wcm9kdWN0LnJ1PC92PjwvYz48YyByPSJDMjUiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDI1IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjI2Ij48YyByPSJBMjYiIHQ9InN0ciI+PHY+c252QHBsYXN0ZWxvLnJ1PC92PjwvYz48YyByPSJCMjYiIHQ9InN0ciI+PHY+cGxhc3RlbG8ucnU8L3Y+PC9jPjxjIHI9IkMyNiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjYiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjciPjxjIHI9IkEyNyIgdD0ic3RyIj48dj50bUBrcmVwY28ucnU8L3Y+PC9jPjxjIHI9IkIyNyIgdD0ic3RyIj48dj5rcmVwY28ucnU8L3Y+PC9jPjxjIHI9IkMyNyIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjciIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjgiPjxjIHI9IkEyOCIgdD0ic3RyIj48dj5vcmRlcnNAbXZpZGVvLnJ1PC92PjwvYz48YyByPSJCMjgiIHQ9InN0ciI+PHY+bXZpZGVvLnJ1PC92PjwvYz48YyByPSJDMjgiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDI4IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjI5Ij48YyByPSJBMjkiIHQ9InN0ciI+PHY+dHVuaW5ndHZlckBtYWlsLnJ1PC92PjwvYz48YyByPSJCMjkiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzI5IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyOSIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIzMCI+PGMgcj0iQTMwIiB0PSJzdHIiPjx2PnNhbGVzMkBnZW8tcGxhc3QuY29tPC92PjwvYz48YyByPSJCMzAiIHQ9InN0ciI+PHY+Z2VvLXBsYXN0LmNvbTwvdj48L2M+PGMgcj0iQzMwIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzMCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIzMSI+PGMgcj0iQTMxIiB0PSJzdHIiPjx2PnMua29yem5pa292YUB6aXRhci5ydTwvdj48L2M+PGMgcj0iQjMxIiB0PSJzdHIiPjx2PnppdGFyLnJ1PC92PjwvYz48YyByPSJDMzEiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDMxIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjMyIj48YyByPSJBMzIiIHQ9InN0ciI+PHY+bWl0c2theWFAbWV0YWxsb3RvcmcucnU8L3Y+PC9jPjxjIHI9IkIzMiIgdD0ic3RyIj48dj5tZXRhbGxvdG9yZy5ydTwvdj48L2M+PGMgcj0iQzMyIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzMiIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIzMyI+PGMgcj0iQTMzIiB0PSJzdHIiPjx2PnNhZHJldGRpbm92YUBzcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkIzMyIgdD0ic3RyIj48dj5zcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkMzMyIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMzMiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMzQiPjxjIHI9IkEzNCIgdD0ic3RyIj48dj50ZWhuby1yZWFsQGJrLnJ1PC92PjwvYz48YyByPSJCMzQiIHQ9InN0ciI+PHY+YmsucnU8L3Y+PC9jPjxjIHI9IkMzNCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMzQiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMzUiPjxjIHI9IkEzNSIgdD0ic3RyIj48dj5tMkBzdGl2cHJvZmlsLnJ1PC92PjwvYz48YyByPSJCMzUiIHQ9InN0ciI+PHY+c3RpdnByb2ZpbC5ydTwvdj48L2M+PGMgcj0iQzM1IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzNSIgdD0ic3RyIj48dj5BdXRvLWFkZGVkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjwvc2hlZXREYXRhPjxpZ25vcmVkRXJyb3JzPjxpZ25vcmVkRXJyb3IgbnVtYmVyU3RvcmVkQXNUZXh0PSIxIiBzcXJlZj0iQTE6RDM1Ii8+PC9pZ25vcmVkRXJyb3JzPjwvd29ya3NoZWV0PlBLAwQUAAAAAAAAAAAAYIAAgYgDAACIAwAADwAAAHhsL21ldGFkYXRhLnhtbDw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04IiBzdGFuZGFsb25lPSJ5ZXMiPz4NCjxtZXRhZGF0YSB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL3NwcmVhZHNoZWV0bWwvMjAwNi9tYWluIiB4bWxuczp4bHJkPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL29mZmljZS9zcHJlYWRzaGVldG1sLzIwMTcvcmljaGRhdGEiIHhtbG5zOnhkYT0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2Uvc3ByZWFkc2hlZXRtbC8yMDE3L2R5bmFtaWNhcnJheSI+CiAgPG1ldGFkYXRhVHlwZXMgY291bnQ9IjEiPgogICAgPG1ldGFkYXRhVHlwZSBuYW1lPSJYTERBUFIiIG1pblN1cHBvcnRlZFZlcnNpb249IjEyMDAwMCIgY29weT0iMSIgcGFzdGVBbGw9IjEiIHBhc3RlVmFsdWVzPSIxIiBtZXJnZT0iMSIgc3BsaXRGaXJzdD0iMSIgcm93Q29sU2hpZnQ9IjEiIGNsZWFyRm9ybWF0cz0iMSIgY2xlYXJDb21tZW50cz0iMSIgYXNzaWduPSIxIiBjb2VyY2U9IjEiIGNlbGxNZXRhPSIxIi8+CiAgPC9tZXRhZGF0YVR5cGVzPgogIDxmdXR1cmVNZXRhZGF0YSBuYW1lPSJYTERBUFIiIGNvdW50PSIxIj4KICAgIDxiaz4KICAgICAgPGV4dExzdD4KICAgICAgICA8ZXh0IHVyaT0ie2JkYmI4Y2RjLWZhMWUtNDk2ZS1hODU3LTNjM2YzMGMwMjljM30iPgogICAgICAgICAgPHhkYTpkeW5hbWljQXJyYXlQcm9wZXJ0aWVzIGZEeW5hbWljPSIxIiBmQ29sbGFwc2VkPSIwIi8+CiAgICAgICAgPC9leHQ+CiAgICAgIDwvZXh0THN0PgogICAgPC9iaz4KICA8L2Z1dHVyZU1ldGFkYXRhPgogIDxjZWxsTWV0YWRhdGEgY291bnQ9IjEiPgogICAgPGJrPgogICAgICA8cmMgdD0iMSIgdj0iMCIvPgogICAgPC9iaz4KICA8L2NlbGxNZXRhZGF0YT4KPC9tZXRhZGF0YT5QSwMEFAAAAAAAAAAAABu9lH6DAQAAgwEAAA8AAAB4bC93b3JrYm9vay54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8d29ya2Jvb2sgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9zcHJlYWRzaGVldG1sLzIwMDYvbWFpbiIgeG1sbnM6cj0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcyI+PHdvcmtib29rUHIgY29kZU5hbWU9IlRoaXNXb3JrYm9vayIvPjxzaGVldHM+PHNoZWV0IG5hbWU9IjE3X8OgX3bDqXJpZmllciIgc2hlZXRJZD0iMSIgcjppZD0icklkMSIvPjxzaGVldCBuYW1lPSIzNF90cmFja2luZ19vbmx5IiBzaGVldElkPSIyIiByOmlkPSJySWQyIi8+PC9zaGVldHM+PC93b3JrYm9vaz5QSwMEFAAAAAAAAAAAAEpqEflMAgAATAIAAAsAAABfcmVscy8ucmVsczw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04IiBzdGFuZGFsb25lPSJ5ZXMiPz4NCjxSZWxhdGlvbnNoaXBzIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvcGFja2FnZS8yMDA2L3JlbGF0aW9uc2hpcHMiPjxSZWxhdGlvbnNoaXAgSWQ9InJJZDIiIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9wYWNrYWdlLzIwMDYvcmVsYXRpb25zaGlwcy9tZXRhZGF0YS9jb3JlLXByb3BlcnRpZXMiIFRhcmdldD0iZG9jUHJvcHMvY29yZS54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQzIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL2V4dGVuZGVkLXByb3BlcnRpZXMiIFRhcmdldD0iZG9jUHJvcHMvYXBwLnhtbCIvPjxSZWxhdGlvbnNoaXAgSWQ9InJJZDEiIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMvb2ZmaWNlRG9jdW1lbnQiIFRhcmdldD0ieGwvd29ya2Jvb2sueG1sIi8+PC9SZWxhdGlvbnNoaXBzPlBLAwQUAAAAAAAAAAAAurdfBGACAABgAgAAEAAAAGRvY1Byb3BzL2FwcC54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8UHJvcGVydGllcyB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvZXh0ZW5kZWQtcHJvcGVydGllcyIgeG1sbnM6dnQ9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L2RvY1Byb3BzVlR5cGVzIj48QXBwbGljYXRpb24+U2hlZXRKUzwvQXBwbGljYXRpb24+PEhlYWRpbmdQYWlycz48dnQ6dmVjdG9yIHNpemU9IjIiIGJhc2VUeXBlPSJ2YXJpYW50Ij48dnQ6dmFyaWFudD48dnQ6bHBzdHI+V29ya3NoZWV0czwvdnQ6bHBzdHI+PC92dDp2YXJpYW50Pjx2dDp2YXJpYW50Pjx2dDppND4yPC92dDppND48L3Z0OnZhcmlhbnQ+PC92dDp2ZWN0b3I+PC9IZWFkaW5nUGFpcnM+PFRpdGxlc09mUGFydHM+PHZ0OnZlY3RvciBzaXplPSIyIiBiYXNlVHlwZT0ibHBzdHIiPjx2dDpscHN0cj4xN1/DoF92w6lyaWZpZXI8L3Z0Omxwc3RyPjx2dDpscHN0cj4zNF90cmFja2luZ19vbmx5PC92dDpscHN0cj48L3Z0OnZlY3Rvcj48L1RpdGxlc09mUGFydHM+PC9Qcm9wZXJ0aWVzPlBLAwQUAAAAAAAAAAAA1pJ8EVoBAABaAQAAEQAAAGRvY1Byb3BzL2NvcmUueG1sPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPGNwOmNvcmVQcm9wZXJ0aWVzIHhtbG5zOmNwPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvcGFja2FnZS8yMDA2L21ldGFkYXRhL2NvcmUtcHJvcGVydGllcyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpkY3Rlcm1zPSJodHRwOi8vcHVybC5vcmcvZGMvdGVybXMvIiB4bWxuczpkY21pdHlwZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlLyIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIvPlBLAwQUAAAAAAAAAAAAx3XJjZ0IAACdCAAAEwAAAFtDb250ZW50X1R5cGVzXS54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8VHlwZXMgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9wYWNrYWdlLzIwMDYvY29udGVudC10eXBlcyIgeG1sbnM6eHNkPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSI+PERlZmF1bHQgRXh0ZW5zaW9uPSJ4bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi94bWwiLz48RGVmYXVsdCBFeHRlbnNpb249ImJpbiIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5tcy1leGNlbC5zaGVldC5iaW5hcnkubWFjcm9FbmFibGVkLm1haW4iLz48RGVmYXVsdCBFeHRlbnNpb249InZtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC52bWxEcmF3aW5nIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJkYXRhIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50Lm1vZGVsK2RhdGEiLz48RGVmYXVsdCBFeHRlbnNpb249ImJtcCIgQ29udGVudFR5cGU9ImltYWdlL2JtcCIvPjxEZWZhdWx0IEV4dGVuc2lvbj0icG5nIiBDb250ZW50VHlwZT0iaW1hZ2UvcG5nIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJnaWYiIENvbnRlbnRUeXBlPSJpbWFnZS9naWYiLz48RGVmYXVsdCBFeHRlbnNpb249ImVtZiIgQ29udGVudFR5cGU9ImltYWdlL3gtZW1mIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJ3bWYiIENvbnRlbnRUeXBlPSJpbWFnZS94LXdtZiIvPjxEZWZhdWx0IEV4dGVuc2lvbj0ianBnIiBDb250ZW50VHlwZT0iaW1hZ2UvanBlZyIvPjxEZWZhdWx0IEV4dGVuc2lvbj0ianBlZyIgQ29udGVudFR5cGU9ImltYWdlL2pwZWciLz48RGVmYXVsdCBFeHRlbnNpb249InRpZiIgQ29udGVudFR5cGU9ImltYWdlL3RpZmYiLz48RGVmYXVsdCBFeHRlbnNpb249InRpZmYiIENvbnRlbnRUeXBlPSJpbWFnZS90aWZmIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJwZGYiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi9wZGYiLz48RGVmYXVsdCBFeHRlbnNpb249InJlbHMiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtcGFja2FnZS5yZWxhdGlvbnNoaXBzK3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL3dvcmtib29rLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLnNoZWV0Lm1haW4reG1sIi8+PE92ZXJyaWRlIFBhcnROYW1lPSIveGwvd29ya3NoZWV0cy9zaGVldDEueG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnNwcmVhZHNoZWV0bWwud29ya3NoZWV0K3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL3dvcmtzaGVldHMvc2hlZXQyLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLndvcmtzaGVldCt4bWwiLz48T3ZlcnJpZGUgUGFydE5hbWU9Ii94bC90aGVtZS90aGVtZTEueG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnRoZW1lK3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL3N0eWxlcy54bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtb2ZmaWNlZG9jdW1lbnQuc3ByZWFkc2hlZXRtbC5zdHlsZXMreG1sIi8+PE92ZXJyaWRlIFBhcnROYW1lPSIvZG9jUHJvcHMvY29yZS54bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtcGFja2FnZS5jb3JlLXByb3BlcnRpZXMreG1sIi8+PE92ZXJyaWRlIFBhcnROYW1lPSIvZG9jUHJvcHMvYXBwLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5leHRlbmRlZC1wcm9wZXJ0aWVzK3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL21ldGFkYXRhLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLnNoZWV0TWV0YWRhdGEreG1sIi8+PC9UeXBlcz5QSwECAAAUAAAAAAAAAAAA8WE8J0IDAABCAwAAGgAAAAAAAAAAAAAAAAAAAAAAeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHNQSwECAAAUAAAAAAAAAAAAMA+Ia94dAADeHQAAEwAAAAAAAAAAAAAAAAB6AwAAeGwvdGhlbWUvdGhlbWUxLnhtbFBLAQIAABQAAAAAAAAAAABV9ASUWgQAAFoEAAANAAAAAAAAAAAAAAAAAIkhAAB4bC9zdHlsZXMueG1sUEsBAgAAFAAAAAAAAAAAAPqA3AyBEAAAgRAAABgAAAAAAAAAAAAAAAAADiYAAHhsL3dvcmtzaGVldHMvc2hlZXQxLnhtbFBLAQIAABQAAAAAAAAAAAC3lWXJYx0AAGMdAAAYAAAAAAAAAAAAAAAAAMU2AAB4bC93b3Jrc2hlZXRzL3NoZWV0Mi54bWxQSwECAAAUAAAAAAAAAAAAYIAAgYgDAACIAwAADwAAAAAAAAAAAAAAAABeVAAAeGwvbWV0YWRhdGEueG1sUEsBAgAAFAAAAAAAAAAAABu9lH6DAQAAgwEAAA8AAAAAAAAAAAAAAAAAE1gAAHhsL3dvcmtib29rLnhtbFBLAQIAABQAAAAAAAAAAABKahH5TAIAAEwCAAALAAAAAAAAAAAAAAAAAMNZAABfcmVscy8ucmVsc1BLAQIAABQAAAAAAAAAAAC6t18EYAIAAGACAAAQAAAAAAAAAAAAAAAAADhcAABkb2NQcm9wcy9hcHAueG1sUEsBAgAAFAAAAAAAAAAAANaSfBFaAQAAWgEAABEAAAAAAAAAAAAAAAAAxl4AAGRvY1Byb3BzL2NvcmUueG1sUEsBAgAAFAAAAAAAAAAAAMd1yY2dCAAAnQgAABMAAAAAAAAAAAAAAAAAT2AAAFtDb250ZW50X1R5cGVzXS54bWxQSwUGAAAAAAsACwDBAgAAHWkAAAAA';\nconst buffer = Buffer.from(b64, 'base64');\nreturn [{\n json: { filename: 'verification_fournisseurs.xlsx' },\n binary: {\n attachment: {\n data: b64,\n mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n fileName: 'verification_fournisseurs.xlsx',\n fileSize: buffer.length,\n fileExtension: 'xlsx'\n }\n }\n}];"}},{"id":"email1","name":"Send Email","type":"n8n-nodes-base.emailSend","typeVersion":2.1,"position":[680,300],"parameters":{"fromEmail":"purchase@metdesigntver.ru","toEmail":"gruart@cifem-ru.ru","subject":"Vérification fournisseurs — Акт сверки Q1-2026","message":"
Bonjour Louis,
Ci-joint le fichier de vérification pour la liste fournisseurs Акт сверки Q1-2026.
Feuille 1 — 17 à vérifier : fournisseurs déjà dans le tracking. Nom actuel (domaine email) → vrai nom issu de ton fichier Снабжение. Merci de confirmer ou corriger.
Feuille 2 — 34 tracking only : présents dans le tracking (historique email Phase 1) mais absents du fichier officiel. À valider avec ton collègue : garder ou exclure ?
Note : les 55 nouveaux fournisseurs ont déjà été ajoutés au tracking (statut pending, Q1-2026).
Joe ⚡ — MetallKart
","options":{"attachmentsUi":{"attachmentsBinary":[{"property":"attachment"}]}}},"credentials":{"smtp":{"id":"JTfjUcwtoiOr3XBB","name":"SMTP account"}}}],"connections":{"Manual Trigger":{"main":[[{"node":"Prepare Attachment","type":"main","index":0}]]},"Prepare Attachment":{"main":[[{"node":"Send Email","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"6aa2697e-b5ba-46ef-beda-8075c9f87ec3","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-03-02T09:55:56.528Z","createdAt":"2026-03-02T09:55:56.528Z","role":"workflow:owner","workflowId":"5S0AuWzvUjRd44k9","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-01T15:12:18.346Z","createdAt":"2026-02-27T19:52:13.416Z","id":"6OlMHMXo4KdYO01Y","name":"PREPARE_LIST — Phase 1: Write to Review","active":true,"isArchived":false,"nodes":[{"parameters":{"path":"act-sverki-p1-write-review","httpMethod":"POST","responseMode":"lastNode","options":{}},"id":"trigger-phase1","name":"Webhook P1","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[220,300],"webhookId":"p1-write-review"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"Feuille 1","mode":"name"},"options":{}},"id":"read-source","name":"Read Feuille 1","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[440,300],"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"\nconst items = $input.all();\nconst now = new Date().toISOString();\nconst ts = Date.now();\nconst ago = new Date();\nago.setMonth(ago.getMonth() - 12); // 12 mois (élargi)\n\n// ── Blacklists ──────────────────────────────────────────\nconst OWN_EMAILS = new Set([\n '891906@mail.ru',\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n 'director@metdesigntver.ru',\n]);\n\nconst BLACKLIST_DOMAINS = [\n // Services email marketing\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','amazonses.com',\n 'uni-sender.com','unisender.com','sendsay.ru','estismail.ru','mindbox.ru',\n // SaaS / outils internes\n 'moysklad.ru','moy-sklad.ru','1c.ru','kontur.ru','sbis.ru','taxcom.ru',\n 'diadoc.ru','skbkontur.ru','edo.ru','1cbo.ru',\n // E-commerce / newsletters connues\n 'e.petrovich.ru','petrovich.ru','ozon.ru','wildberries.ru','avito.ru',\n 'leroymerlin.ru','vseinstrumenti.ru','chipdip.ru','dns-shop.ru',\n // Plateformes generiques\n 'noreply','no-reply','mailer','robot@','donotreply',\n];\n\nconst MARKETING_KW = [\n 'акция','скидка','скидки','распродажа','вебинар','подпишитесь','промокод',\n 'unsubscribe','newsletter','discount','sale','отписаться','рассылка','купон',\n];\n\n// ── Signaux comptables forts ────────────────────────────\nconst STRONG_SIGNALS = [\n 'счет','счёт','упд','накладная','акт сверки','акт-сверки','сверка',\n 'взаиморасчет','взаиморасчёт','коммерческое предложение','договор',\n 'спецификация','оплата','платеж','платёж','invoice','поставк','отгрузк',\n 'торг-12','торг12','ттн','тtn','закупк','заказ на поставку',\n];\n\nconst filtered = items.filter(item => {\n const s = item.json;\n const email = (s.Email || '').toLowerCase().trim();\n const sub = (s.LastSubject || '').toLowerCase();\n const domain= email.split('@')[1] || '';\n\n // 1. Email valide\n if (!email || !email.includes('@') || email.length < 5) return false;\n\n // 2. Pas nos propres emails\n if (OWN_EMAILS.has(email)) return false;\n\n // 3. Pas un domaine blacklisté\n if (BLACKLIST_DOMAINS.some(d => domain.includes(d) || email.includes(d))) return false;\n\n // 4. Pas un no-reply / robot\n const localPart = email.split('@')[0];\n if (['noreply','no-reply','donotreply','mailer','robot','info','support','admin','help','feedback','notification'].some(p => localPart.startsWith(p))) return false;\n\n // 5. Pas un email marketing (subject)\n if (MARKETING_KW.some(kw => sub.includes(kw))) return false;\n\n // 6. Signal comptable fort obligatoire (fournisseur actif = a envoyé facture/BL)\n const hasStrong = STRONG_SIGNALS.some(kw => sub.includes(kw));\n if (!hasStrong) {\n // Tolérance : email récent (<6 mois) avec pièce jointe potentielle\n let recent = false;\n if (s.LastSeenDate) {\n try { recent = new Date(s.LastSeenDate) > ago; } catch(e) { recent = false; }\n }\n const hasAttach = s.HasAttachment === true || s.HasAttachment === 'TRUE' || s.HasAttachment === '1';\n if (!(recent && hasAttach)) return false;\n }\n\n return true;\n});\n\nreturn filtered.map((item, i) => {\n const s = item.json;\n return {\n json: {\n email: s.Email,\n supplier_name_raw: s.ContactName || '',\n last_subject: s.LastSubject || '',\n last_date: s.LastSeenDate || '',\n row_id: 'R-' + ts + '-' + i,\n supplier_name: s.Company || s.ContactName || 'Unknown',\n contact_name: s.ContactName || '',\n phone: s.Phone || '',\n status: 'pending_review',\n review_ts: now,\n }\n };\n});\n"},"id":"transform-review","name":"Transform to Review Rows","type":"n8n-nodes-base.code","typeVersion":2,"position":[600,300]},{"id":"clear-review-001","name":"Clear Review Sheet","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1000,300],"parameters":{"method":"POST","url":"https://sheets.googleapis.com/v4/spreadsheets/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/values/act_sverki_review!A1:Z:clear","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api","options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}},"onError":"continueRegularOutput"},{"id":"wait-quota-001","name":"Wait Quota","type":"n8n-nodes-base.wait","typeVersion":1,"position":[1100,300],"parameters":{"resume":"timeInterval","unit":"seconds","amount":10},"webhookId":"wait-quota-webhook-001"},{"id":"collect-rows-001","name":"Collect All Rows","type":"n8n-nodes-base.code","typeVersion":2,"position":[800,300],"parameters":{"jsCode":"const items = $input.all();\nconst headers = Object.keys(items[0].json);\nconst rows = items.map(i => headers.map(h => i.json[h] !== null && i.json[h] !== undefined ? String(i.json[h]) : ''));\nreturn [{ json: { headers, count: rows.length, values: rows } }];"}},{"id":"batch-append-001","name":"Batch Append to Review","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1300,300],"parameters":{"method":"POST","url":"https://sheets.googleapis.com/v4/spreadsheets/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/values/act_sverki_review!A2:append?valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api","sendBody":true,"specifyBody":"json","options":{},"jsonBody":"={{ JSON.stringify({ values: $('Collect All Rows').item.json.values }) }}"},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"id":"p1-write-header","name":"Write Header Row","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1020,440],"parameters":{"method":"PUT","url":"https://sheets.googleapis.com/v4/spreadsheets/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/values/act_sverki_review!A1?valueInputOption=USER_ENTERED","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api","sendBody":true,"specifyBody":"json","jsonBody":"{\"values\":[[\"email\",\"supplier_name_raw\",\"last_subject\",\"last_date\",\"row_id\",\"supplier_name\",\"contact_name\",\"phone\",\"status\",\"review_ts\"]]}","options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"Webhook P1":{"main":[[{"node":"Read Feuille 1","type":"main","index":0}]]},"Read Feuille 1":{"main":[[{"node":"Transform to Review Rows","type":"main","index":0}]]},"Transform to Review Rows":{"main":[[{"node":"Collect All Rows","type":"main","index":0}]]},"Collect All Rows":{"main":[[{"node":"Clear Review Sheet","type":"main","index":0}]]},"Clear Review Sheet":{"main":[[{"node":"Write Header Row","type":"main","index":0}]]},"Wait Quota":{"main":[[{"node":"Batch Append to Review","type":"main","index":0}]]},"Write Header Row":{"main":[[{"node":"Wait Quota","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"8a317a09-8fde-4e13-a11d-d8980486fffd","activeVersionId":"8a317a09-8fde-4e13-a11d-d8980486fffd","triggerCount":1,"shared":[{"updatedAt":"2026-02-27T19:52:13.418Z","createdAt":"2026-02-27T19:52:13.418Z","role":"workflow:owner","workflowId":"6OlMHMXo4KdYO01Y","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T15:12:18.346Z","createdAt":"2026-03-01T15:12:18.346Z","versionId":"8a317a09-8fde-4e13-a11d-d8980486fffd","workflowId":"6OlMHMXo4KdYO01Y","nodes":[{"parameters":{"path":"act-sverki-p1-write-review","httpMethod":"POST","responseMode":"lastNode","options":{}},"id":"trigger-phase1","name":"Webhook P1","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[220,300],"webhookId":"p1-write-review"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"Feuille 1","mode":"name"},"options":{}},"id":"read-source","name":"Read Feuille 1","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[440,300],"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"\nconst items = $input.all();\nconst now = new Date().toISOString();\nconst ts = Date.now();\nconst ago = new Date();\nago.setMonth(ago.getMonth() - 12); // 12 mois (élargi)\n\n// ── Blacklists ──────────────────────────────────────────\nconst OWN_EMAILS = new Set([\n '891906@mail.ru',\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n 'director@metdesigntver.ru',\n]);\n\nconst BLACKLIST_DOMAINS = [\n // Services email marketing\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','amazonses.com',\n 'uni-sender.com','unisender.com','sendsay.ru','estismail.ru','mindbox.ru',\n // SaaS / outils internes\n 'moysklad.ru','moy-sklad.ru','1c.ru','kontur.ru','sbis.ru','taxcom.ru',\n 'diadoc.ru','skbkontur.ru','edo.ru','1cbo.ru',\n // E-commerce / newsletters connues\n 'e.petrovich.ru','petrovich.ru','ozon.ru','wildberries.ru','avito.ru',\n 'leroymerlin.ru','vseinstrumenti.ru','chipdip.ru','dns-shop.ru',\n // Plateformes generiques\n 'noreply','no-reply','mailer','robot@','donotreply',\n];\n\nconst MARKETING_KW = [\n 'акция','скидка','скидки','распродажа','вебинар','подпишитесь','промокод',\n 'unsubscribe','newsletter','discount','sale','отписаться','рассылка','купон',\n];\n\n// ── Signaux comptables forts ────────────────────────────\nconst STRONG_SIGNALS = [\n 'счет','счёт','упд','накладная','акт сверки','акт-сверки','сверка',\n 'взаиморасчет','взаиморасчёт','коммерческое предложение','договор',\n 'спецификация','оплата','платеж','платёж','invoice','поставк','отгрузк',\n 'торг-12','торг12','ттн','тtn','закупк','заказ на поставку',\n];\n\nconst filtered = items.filter(item => {\n const s = item.json;\n const email = (s.Email || '').toLowerCase().trim();\n const sub = (s.LastSubject || '').toLowerCase();\n const domain= email.split('@')[1] || '';\n\n // 1. Email valide\n if (!email || !email.includes('@') || email.length < 5) return false;\n\n // 2. Pas nos propres emails\n if (OWN_EMAILS.has(email)) return false;\n\n // 3. Pas un domaine blacklisté\n if (BLACKLIST_DOMAINS.some(d => domain.includes(d) || email.includes(d))) return false;\n\n // 4. Pas un no-reply / robot\n const localPart = email.split('@')[0];\n if (['noreply','no-reply','donotreply','mailer','robot','info','support','admin','help','feedback','notification'].some(p => localPart.startsWith(p))) return false;\n\n // 5. Pas un email marketing (subject)\n if (MARKETING_KW.some(kw => sub.includes(kw))) return false;\n\n // 6. Signal comptable fort obligatoire (fournisseur actif = a envoyé facture/BL)\n const hasStrong = STRONG_SIGNALS.some(kw => sub.includes(kw));\n if (!hasStrong) {\n // Tolérance : email récent (<6 mois) avec pièce jointe potentielle\n let recent = false;\n if (s.LastSeenDate) {\n try { recent = new Date(s.LastSeenDate) > ago; } catch(e) { recent = false; }\n }\n const hasAttach = s.HasAttachment === true || s.HasAttachment === 'TRUE' || s.HasAttachment === '1';\n if (!(recent && hasAttach)) return false;\n }\n\n return true;\n});\n\nreturn filtered.map((item, i) => {\n const s = item.json;\n return {\n json: {\n email: s.Email,\n supplier_name_raw: s.ContactName || '',\n last_subject: s.LastSubject || '',\n last_date: s.LastSeenDate || '',\n row_id: 'R-' + ts + '-' + i,\n supplier_name: s.Company || s.ContactName || 'Unknown',\n contact_name: s.ContactName || '',\n phone: s.Phone || '',\n status: 'pending_review',\n review_ts: now,\n }\n };\n});\n"},"id":"transform-review","name":"Transform to Review Rows","type":"n8n-nodes-base.code","typeVersion":2,"position":[600,300]},{"id":"clear-review-001","name":"Clear Review Sheet","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1000,300],"parameters":{"method":"POST","url":"https://sheets.googleapis.com/v4/spreadsheets/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/values/act_sverki_review!A1:Z:clear","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api","options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}},"onError":"continueRegularOutput"},{"id":"wait-quota-001","name":"Wait Quota","type":"n8n-nodes-base.wait","typeVersion":1,"position":[1100,300],"parameters":{"resume":"timeInterval","unit":"seconds","amount":10},"webhookId":"wait-quota-webhook-001"},{"id":"collect-rows-001","name":"Collect All Rows","type":"n8n-nodes-base.code","typeVersion":2,"position":[800,300],"parameters":{"jsCode":"const items = $input.all();\nconst headers = Object.keys(items[0].json);\nconst rows = items.map(i => headers.map(h => i.json[h] !== null && i.json[h] !== undefined ? String(i.json[h]) : ''));\nreturn [{ json: { headers, count: rows.length, values: rows } }];"}},{"id":"batch-append-001","name":"Batch Append to Review","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1300,300],"parameters":{"method":"POST","url":"https://sheets.googleapis.com/v4/spreadsheets/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/values/act_sverki_review!A2:append?valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api","sendBody":true,"specifyBody":"json","options":{},"jsonBody":"={{ JSON.stringify({ values: $('Collect All Rows').item.json.values }) }}"},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"id":"p1-write-header","name":"Write Header Row","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1020,440],"parameters":{"method":"PUT","url":"https://sheets.googleapis.com/v4/spreadsheets/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/values/act_sverki_review!A1?valueInputOption=USER_ENTERED","authentication":"predefinedCredentialType","nodeCredentialType":"googleSheetsOAuth2Api","sendBody":true,"specifyBody":"json","jsonBody":"{\"values\":[[\"email\",\"supplier_name_raw\",\"last_subject\",\"last_date\",\"row_id\",\"supplier_name\",\"contact_name\",\"phone\",\"status\",\"review_ts\"]]}","options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"Webhook P1":{"main":[[{"node":"Read Feuille 1","type":"main","index":0}]]},"Read Feuille 1":{"main":[[{"node":"Transform to Review Rows","type":"main","index":0}]]},"Transform to Review Rows":{"main":[[{"node":"Collect All Rows","type":"main","index":0}]]},"Collect All Rows":{"main":[[{"node":"Clear Review Sheet","type":"main","index":0}]]},"Clear Review Sheet":{"main":[[{"node":"Write Header Row","type":"main","index":0}]]},"Wait Quota":{"main":[[{"node":"Batch Append to Review","type":"main","index":0}]]},"Write Header Row":{"main":[[{"node":"Wait Quota","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-01T19:39:03.368Z","createdAt":"2026-03-01T19:37:55.451Z","id":"7Cl7LEPpD0IZXZoB","name":"_TEMP_FIX_STIVPROFIL","active":true,"isArchived":false,"nodes":[{"id":"wh1","name":"Trigger","type":"n8n-nodes-base.webhook","typeVersion":2,"webhookId":"fix-stivprofil-wh","position":[240,300],"parameters":{"httpMethod":"POST","path":"fix-stivprofil","responseMode":"onReceived"}},{"id":"code1","name":"Prepare Data","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"return [{ json: { email: \"m2@stivprofil.ru\", status: \"Отправлено\", request_sent_at: \"2026-03-01T19:33:51.454Z\" } }];"}},{"id":"gs1","name":"Update Status","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[680,300],"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $json.email }}","status":"={{ $json.status }}","request_sent_at":"={{ $json.request_sent_at }}"},"matchingColumns":["email"],"schema":[]},"options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"Trigger":{"main":[[{"node":"Prepare Data","type":"main","index":0}]]},"Prepare Data":{"main":[[{"node":"Update Status","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"29d4f858-d004-4204-9380-628ec27629be","activeVersionId":"29d4f858-d004-4204-9380-628ec27629be","triggerCount":1,"shared":[{"updatedAt":"2026-03-01T19:37:55.453Z","createdAt":"2026-03-01T19:37:55.453Z","role":"workflow:owner","workflowId":"7Cl7LEPpD0IZXZoB","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T19:39:03.369Z","createdAt":"2026-03-01T19:39:03.369Z","versionId":"29d4f858-d004-4204-9380-628ec27629be","workflowId":"7Cl7LEPpD0IZXZoB","nodes":[{"id":"wh1","name":"Trigger","type":"n8n-nodes-base.webhook","typeVersion":2,"webhookId":"fix-stivprofil-wh","position":[240,300],"parameters":{"httpMethod":"POST","path":"fix-stivprofil","responseMode":"onReceived"}},{"id":"code1","name":"Prepare Data","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"return [{ json: { email: \"m2@stivprofil.ru\", status: \"Отправлено\", request_sent_at: \"2026-03-01T19:33:51.454Z\" } }];"}},{"id":"gs1","name":"Update Status","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[680,300],"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $json.email }}","status":"={{ $json.status }}","request_sent_at":"={{ $json.request_sent_at }}"},"matchingColumns":["email"],"schema":[]},"options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"Trigger":{"main":[[{"node":"Prepare Data","type":"main","index":0}]]},"Prepare Data":{"main":[[{"node":"Update Status","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-09T07:30:03.123Z","createdAt":"2026-03-09T07:30:03.123Z","id":"7eLMAeoAUH1NnhsQ","name":"ERP_PING_TEST","active":true,"isArchived":false,"nodes":[{"id":"wh_ping","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-ping","httpMethod":"POST","authentication":"none","responseMode":"responseNode","options":{}}},{"id":"cd_ping","name":"Ping","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"return [{json:{pong:true, ts: Date.now()}}];\n"}},{"id":"rsp_ping","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Ping","type":"main","index":0}]]},"Ping":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"162a45fc-c99c-4bd5-b278-7115b0975bc0","activeVersionId":"162a45fc-c99c-4bd5-b278-7115b0975bc0","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T07:30:03.127Z","createdAt":"2026-03-09T07:30:03.127Z","role":"workflow:owner","workflowId":"7eLMAeoAUH1NnhsQ","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:30:03.131Z","createdAt":"2026-03-09T07:30:03.131Z","versionId":"162a45fc-c99c-4bd5-b278-7115b0975bc0","workflowId":"7eLMAeoAUH1NnhsQ","nodes":[{"id":"wh_ping","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-ping","httpMethod":"POST","authentication":"none","responseMode":"responseNode","options":{}}},{"id":"cd_ping","name":"Ping","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"return [{json:{pong:true, ts: Date.now()}}];\n"}},{"id":"rsp_ping","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Ping","type":"main","index":0}]]},"Ping":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-09T07:12:51.978Z","createdAt":"2026-03-09T07:12:51.978Z","id":"AArL9KxfN06OHuEA","name":"ERP_ARCHITECT","active":true,"isArchived":false,"nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-architect","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd1","name":"Architect","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\";});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const req = $input.first().json;\n const userRequest = req.request || req.body || \"Describe the ERP module needed\";\n\n const schemaRes = await hostCmd(\"cat /opt/erp/metallkart-erp/prisma/schema.prisma 2>/dev/null || echo NO_SCHEMA\");\n const prismaSchema = (schemaRes.output || \"\").trim();\n\n const srcRes = await hostCmd(\"find /opt/erp/metallkart-erp/src -type f -name '*.ts' 2>/dev/null | head -30\");\n const srcFiles = (srcRes.output || \"\").trim();\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const systemPrompt = `You are ERP_ARCHITECT for MetallKart ERP (Fastify + TypeScript + Prisma + PostgreSQL).\\nAnalyze the user's business need and produce a precise JSON specification.\\nCurrent Prisma schema:\\n${prismaSchema.substring(0,3000)}\\nExisting files:\\n${srcFiles}\\nReturn ONLY valid JSON (no markdown) with this structure:\\n{\\n \"module_name\": \"string\",\\n \"description\": \"string\",\\n \"prisma_models\": [{\"name\":\"ModelName\",\"fields\":[{\"name\":\"f\",\"type\":\"t\",\"required\":true}]}],\\n \"routes\": [{\"method\":\"GET\",\"path\":\"/api/...\",\"description\":\"...\",\"auth\":true}],\\n \"files_to_create\": [{\"path\":\"src/modules/xxx/xxx.ts\",\"purpose\":\"...\"}],\\n \"migration_needed\": true,\\n \"estimated_complexity\": \"low|medium|high\"\\n}`;\n\n const body = JSON.stringify({\n model: \"claude-opus-4-5\",\n max_tokens: 4000,\n system: systemPrompt,\n messages: [{role:\"user\", content: userRequest}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);\n r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error: \"API_ERROR: \"+JSON.stringify(apiRes.error).substring(0,200)}}];\n\n const rawSpec = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n let spec;\n try {\n const cleaned = rawSpec.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n spec = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{error:\"PARSE_ERROR\", raw: rawSpec.substring(0,500)}}];\n }\n\n return [{json:{\n spec,\n raw_spec: rawSpec.substring(0,2000),\n request: userRequest,\n status: \"awaiting_validation\"\n }}];\n}\nreturn run();\n"}},{"id":"rsp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Architect","type":"main","index":0}]]},"Architect":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"d8a5f419-0d5d-4f04-ae51-ea52352edf7c","activeVersionId":"d8a5f419-0d5d-4f04-ae51-ea52352edf7c","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T07:12:51.980Z","createdAt":"2026-03-09T07:12:51.980Z","role":"workflow:owner","workflowId":"AArL9KxfN06OHuEA","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:12:51.983Z","createdAt":"2026-03-09T07:12:51.983Z","versionId":"d8a5f419-0d5d-4f04-ae51-ea52352edf7c","workflowId":"AArL9KxfN06OHuEA","nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-architect","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd1","name":"Architect","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\";});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const req = $input.first().json;\n const userRequest = req.request || req.body || \"Describe the ERP module needed\";\n\n const schemaRes = await hostCmd(\"cat /opt/erp/metallkart-erp/prisma/schema.prisma 2>/dev/null || echo NO_SCHEMA\");\n const prismaSchema = (schemaRes.output || \"\").trim();\n\n const srcRes = await hostCmd(\"find /opt/erp/metallkart-erp/src -type f -name '*.ts' 2>/dev/null | head -30\");\n const srcFiles = (srcRes.output || \"\").trim();\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const systemPrompt = `You are ERP_ARCHITECT for MetallKart ERP (Fastify + TypeScript + Prisma + PostgreSQL).\\nAnalyze the user's business need and produce a precise JSON specification.\\nCurrent Prisma schema:\\n${prismaSchema.substring(0,3000)}\\nExisting files:\\n${srcFiles}\\nReturn ONLY valid JSON (no markdown) with this structure:\\n{\\n \"module_name\": \"string\",\\n \"description\": \"string\",\\n \"prisma_models\": [{\"name\":\"ModelName\",\"fields\":[{\"name\":\"f\",\"type\":\"t\",\"required\":true}]}],\\n \"routes\": [{\"method\":\"GET\",\"path\":\"/api/...\",\"description\":\"...\",\"auth\":true}],\\n \"files_to_create\": [{\"path\":\"src/modules/xxx/xxx.ts\",\"purpose\":\"...\"}],\\n \"migration_needed\": true,\\n \"estimated_complexity\": \"low|medium|high\"\\n}`;\n\n const body = JSON.stringify({\n model: \"claude-opus-4-5\",\n max_tokens: 4000,\n system: systemPrompt,\n messages: [{role:\"user\", content: userRequest}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);\n r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error: \"API_ERROR: \"+JSON.stringify(apiRes.error).substring(0,200)}}];\n\n const rawSpec = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n let spec;\n try {\n const cleaned = rawSpec.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n spec = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{error:\"PARSE_ERROR\", raw: rawSpec.substring(0,500)}}];\n }\n\n return [{json:{\n spec,\n raw_spec: rawSpec.substring(0,2000),\n request: userRequest,\n status: \"awaiting_validation\"\n }}];\n}\nreturn run();\n"}},{"id":"rsp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Architect","type":"main","index":0}]]},"Architect":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-09T07:13:30.906Z","createdAt":"2026-03-09T07:13:30.906Z","id":"AJGBCk5ULlfUT59y","name":"ERP_CODER","active":true,"isArchived":false,"nodes":[{"id":"wh2","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-coder","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd2","name":"Coder","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const inp = $input.first().json;\n const spec = inp.spec || inp;\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const schemaRes = await hostCmd(\"cat /opt/erp/metallkart-erp/prisma/schema.prisma 2>/dev/null\");\n const prismaSchema = (schemaRes.output || \"\").trim();\n\n const systemPrompt = `You are ERP_CODER for MetallKart ERP (Fastify + TypeScript + Prisma + PostgreSQL).\\nGenerate production-quality TypeScript code based on the spec.\\nProject structure: /opt/erp/metallkart-erp/\\n- src/modules/{module}/ for routes and logic\\n- src/plugins/ for shared plugins\\n- Use Fastify with TypeBox schemas\\n- Use Prisma client from @prisma/client\\n- Export routes as FastifyPlugin\\nCurrent Prisma schema: ${prismaSchema.substring(0,2000)}\\nReturn ONLY a JSON array of files (no markdown):\\n[{\"path\": \"src/modules/xxx/xxx.ts\", \"content\": \"...full file content...\"}]`;\n\n const body = JSON.stringify({\n model: \"claude-sonnet-4-5\",\n max_tokens: 8000,\n system: systemPrompt,\n messages: [{role:\"user\", content: \"Generate code for this spec:\\n\"+JSON.stringify(spec,null,2)}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error:\"API_ERROR: \"+JSON.stringify(apiRes.error).substring(0,200)}}];\n const rawCode = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n\n let files;\n try {\n const cleaned = rawCode.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n files = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{error:\"PARSE_ERROR\", raw: rawCode.substring(0,500)}}];\n }\n\n const written = [];\n const basePath = \"/opt/erp/metallkart-erp/\";\n for (const file of files) {\n const fullPath = basePath + file.path;\n const dir = fullPath.substring(0, fullPath.lastIndexOf(\"/\"));\n await hostCmd(`mkdir -p \"${dir}\"`);\n const b64content = Buffer.from(file.content).toString(\"base64\");\n const writeRes = await hostCmd(`echo '${b64content}' | base64 -d > \"${fullPath}\" && echo OK_WRITE`);\n written.push({path: file.path, status: writeRes.output.includes(\"OK_WRITE\") ? \"written\" : \"error\", detail: writeRes.error || \"\"});\n }\n\n return [{json:{spec, files_written: written, total: files.length, status: \"code_written\"}}];\n}\nreturn run();\n"}},{"id":"rsp2","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Coder","type":"main","index":0}]]},"Coder":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"a7614f74-8779-47e1-abb6-66278e4fd397","activeVersionId":"a7614f74-8779-47e1-abb6-66278e4fd397","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T07:13:30.907Z","createdAt":"2026-03-09T07:13:30.907Z","role":"workflow:owner","workflowId":"AJGBCk5ULlfUT59y","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:13:30.910Z","createdAt":"2026-03-09T07:13:30.910Z","versionId":"a7614f74-8779-47e1-abb6-66278e4fd397","workflowId":"AJGBCk5ULlfUT59y","nodes":[{"id":"wh2","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-coder","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd2","name":"Coder","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const inp = $input.first().json;\n const spec = inp.spec || inp;\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const schemaRes = await hostCmd(\"cat /opt/erp/metallkart-erp/prisma/schema.prisma 2>/dev/null\");\n const prismaSchema = (schemaRes.output || \"\").trim();\n\n const systemPrompt = `You are ERP_CODER for MetallKart ERP (Fastify + TypeScript + Prisma + PostgreSQL).\\nGenerate production-quality TypeScript code based on the spec.\\nProject structure: /opt/erp/metallkart-erp/\\n- src/modules/{module}/ for routes and logic\\n- src/plugins/ for shared plugins\\n- Use Fastify with TypeBox schemas\\n- Use Prisma client from @prisma/client\\n- Export routes as FastifyPlugin\\nCurrent Prisma schema: ${prismaSchema.substring(0,2000)}\\nReturn ONLY a JSON array of files (no markdown):\\n[{\"path\": \"src/modules/xxx/xxx.ts\", \"content\": \"...full file content...\"}]`;\n\n const body = JSON.stringify({\n model: \"claude-sonnet-4-5\",\n max_tokens: 8000,\n system: systemPrompt,\n messages: [{role:\"user\", content: \"Generate code for this spec:\\n\"+JSON.stringify(spec,null,2)}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error:\"API_ERROR: \"+JSON.stringify(apiRes.error).substring(0,200)}}];\n const rawCode = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n\n let files;\n try {\n const cleaned = rawCode.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n files = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{error:\"PARSE_ERROR\", raw: rawCode.substring(0,500)}}];\n }\n\n const written = [];\n const basePath = \"/opt/erp/metallkart-erp/\";\n for (const file of files) {\n const fullPath = basePath + file.path;\n const dir = fullPath.substring(0, fullPath.lastIndexOf(\"/\"));\n await hostCmd(`mkdir -p \"${dir}\"`);\n const b64content = Buffer.from(file.content).toString(\"base64\");\n const writeRes = await hostCmd(`echo '${b64content}' | base64 -d > \"${fullPath}\" && echo OK_WRITE`);\n written.push({path: file.path, status: writeRes.output.includes(\"OK_WRITE\") ? \"written\" : \"error\", detail: writeRes.error || \"\"});\n }\n\n return [{json:{spec, files_written: written, total: files.length, status: \"code_written\"}}];\n}\nreturn run();\n"}},{"id":"rsp2","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Coder","type":"main","index":0}]]},"Coder":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-02-26T08:41:40.410Z","createdAt":"2026-02-26T08:41:40.410Z","id":"AuditLogApp01","name":"AUDIT_LOG_APPEND","active":true,"isArchived":false,"nodes":[{"parameters":{"httpMethod":"POST","path":"audit-append","responseMode":"responseNode","options":{}},"name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[820,380],"webhookId":"AuditLogApp01-wh","id":"45ce0b73-d645-49e2-a0ba-4a8ed8c4b8a3"},{"parameters":{"jsCode":"// === AUTH CHECK ===\nconst INTERNAL_KEY = 'GjBiD5UCV3jIfu9ZYF5SrrqYNiSEy8_uh9FiSuxlXySS-kg6drTN_i8JUQg9zt18';\nconst headers = $input.first().json.headers || {};\nconst providedKey = headers['x-internal-key'] || '';\nif (providedKey !== INTERNAL_KEY) {\n return [{ json: { ok: false, error: 'AUTH_FAILED' } }];\n}\nconst body = $input.first().json.body || $input.first().json;\nconst logEntry = {\n ts: new Date().toISOString(),\n request_id: body.request_id || '',\n command: body.command || '',\n approved: body.approved || false,\n status: body.status || 'unknown',\n result: body.result || '',\n source: body.source || 'openclaw'\n};\nconst fs = require('fs');\nconst line = JSON.stringify(logEntry) + '\\n';\nfs.appendFileSync('/home/node/.n8n/audit.jsonl', line, 'utf8');\nreturn [{ json: { ok: true, logged: logEntry } }];"},"name":"Code in JavaScript","type":"n8n-nodes-base.code","typeVersion":2,"position":[1040,380],"id":"a0329214-5327-48d1-bea8-2a78e17328d9"},{"parameters":{"respondWith":"allIncomingItems","options":{}},"name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1260,380],"id":"2565bdeb-a532-45b7-857e-0a443713f5eb"}],"connections":{"Webhook":{"main":[[{"node":"Code in JavaScript","type":"main","index":0}]]},"Code in JavaScript":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"staticData":null,"meta":null,"pinData":null,"versionId":"4160bb98-3bb8-4205-b33f-d140fff53d84","activeVersionId":"4160bb98-3bb8-4205-b33f-d140fff53d84","triggerCount":1,"shared":[{"updatedAt":"2026-02-26T08:41:40.415Z","createdAt":"2026-02-26T08:41:40.415Z","role":"workflow:owner","workflowId":"AuditLogApp01","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-02-26T08:51:34.000Z","createdAt":"2026-02-26T08:41:40.416Z","versionId":"4160bb98-3bb8-4205-b33f-d140fff53d84","workflowId":"AuditLogApp01","nodes":[{"parameters":{"httpMethod":"POST","path":"audit-append","responseMode":"responseNode","options":{}},"name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[820,380],"webhookId":"AuditLogApp01-wh","id":"45ce0b73-d645-49e2-a0ba-4a8ed8c4b8a3"},{"parameters":{"jsCode":"// === AUTH CHECK ===\nconst INTERNAL_KEY = 'GjBiD5UCV3jIfu9ZYF5SrrqYNiSEy8_uh9FiSuxlXySS-kg6drTN_i8JUQg9zt18';\nconst headers = $input.first().json.headers || {};\nconst providedKey = headers['x-internal-key'] || '';\nif (providedKey !== INTERNAL_KEY) {\n return [{ json: { ok: false, error: 'AUTH_FAILED' } }];\n}\nconst body = $input.first().json.body || $input.first().json;\nconst logEntry = {\n ts: new Date().toISOString(),\n request_id: body.request_id || '',\n command: body.command || '',\n approved: body.approved || false,\n status: body.status || 'unknown',\n result: body.result || '',\n source: body.source || 'openclaw'\n};\nconst fs = require('fs');\nconst line = JSON.stringify(logEntry) + '\\n';\nfs.appendFileSync('/home/node/.n8n/audit.jsonl', line, 'utf8');\nreturn [{ json: { ok: true, logged: logEntry } }];"},"name":"Code in JavaScript","type":"n8n-nodes-base.code","typeVersion":2,"position":[1040,380],"id":"a0329214-5327-48d1-bea8-2a78e17328d9"},{"parameters":{"respondWith":"allIncomingItems","options":{}},"name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1260,380],"id":"2565bdeb-a532-45b7-857e-0a443713f5eb"}],"connections":{"Webhook":{"main":[[{"node":"Code in JavaScript","type":"main","index":0}]]},"Code in JavaScript":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]}},"authors":"import","name":"Version 4160bb98","description":"","autosaved":false},"tags":[]},{"updatedAt":"2026-03-03T15:13:51.363Z","createdAt":"2026-02-26T08:41:06.955Z","id":"BizExecGate01","name":"BUSINESS_EXECUTION_GATE","active":true,"isArchived":false,"nodes":[{"parameters":{"httpMethod":"POST","path":"business-exec","responseMode":"responseNode","options":{}},"id":"829e82d5-8124-4c21-92ad-fe787401b0a4","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[832,384],"webhookId":"BizExecGate01-wh"},{"parameters":{"jsCode":"const body = $input.item.json.body || $input.item.json;\nconst { command, approved, request_id, data } = body;\n\nif (!command) {\n return [{ json: { ok: false, error: \"NO_COMMAND\", skipAll: true } }];\n}\n\n// prepare_list does not require approval\nconst safeCommands = [\"prepare_list\", \"status\", \"check\"];\nif (!safeCommands.includes(command) && !approved) {\n return [{ json: { ok: false, error: \"APPROVAL_REQUIRED\", command, request_id, skipAll: true } }];\n}\n\nreturn [{ json: { ok: true, command, approved: approved || safeCommands.includes(command), request_id, data, skipAll: false } }];"},"id":"gate-router","name":"Route Command","type":"n8n-nodes-base.code","typeVersion":2,"position":[1008,384]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"id":"skip-check","leftValue":"={{ String($json.skipAll) }}","rightValue":"true","operator":{"type":"string","operation":"equals"}}]},"options":{}},"id":"gate-if-skip","name":"Should Skip?","type":"n8n-nodes-base.if","typeVersion":2.1,"position":[1200,384]},{"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"specifyBody":"json","jsonBody":"{}","options":{"timeout":30000}},"id":"gate-http-p1","name":"Call Phase 1","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1392,480]},{"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p2-merge-tracking","sendBody":true,"specifyBody":"json","jsonBody":"{}","options":{"timeout":30000}},"id":"gate-http-p2","name":"Call Phase 2","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1600,480]},{"parameters":{"jsCode":"const router = $('Route Command').item.json;\nconst p1 = $('Call Phase 1').item.json;\nconst p2 = $('Call Phase 2').item.json;\nreturn [{ json: {\n ok: true,\n command: router.command,\n request_id: router.request_id,\n phase1: p1,\n phase2: p2\n} }];"},"id":"gate-format","name":"Format Response","type":"n8n-nodes-base.code","typeVersion":2,"position":[1776,480]},{"parameters":{"respondWith":"allIncomingItems","options":{}},"id":"730abda4-3ca4-44a2-a884-972fd169ef6b","name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2000,384]}],"connections":{"Webhook":{"main":[[{"node":"Route Command","type":"main","index":0}]]},"Route Command":{"main":[[{"node":"Should Skip?","type":"main","index":0}]]},"Should Skip?":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}],[{"node":"Call Phase 1","type":"main","index":0}]]},"Call Phase 1":{"main":[[{"node":"Call Phase 2","type":"main","index":0}]]},"Call Phase 2":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Format Response":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":true},"staticData":null,"meta":null,"pinData":{},"versionId":"96108297-8add-4b6b-85d0-6edadb706521","activeVersionId":"753ef618-1cfc-4f70-89c0-d12bd6267769","triggerCount":1,"shared":[{"updatedAt":"2026-02-26T08:41:06.961Z","createdAt":"2026-02-26T08:41:06.961Z","role":"workflow:owner","workflowId":"BizExecGate01","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-02-28T07:17:04.712Z","createdAt":"2026-02-28T07:17:04.712Z","versionId":"753ef618-1cfc-4f70-89c0-d12bd6267769","workflowId":"BizExecGate01","nodes":[{"parameters":{"httpMethod":"POST","path":"business-exec","responseMode":"responseNode","options":{}},"id":"829e82d5-8124-4c21-92ad-fe787401b0a4","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[820,380],"webhookId":"BizExecGate01-wh"},{"parameters":{"jsCode":"const body = $input.item.json.body || $input.item.json;\nconst { command, approved, request_id, data } = body;\n\nif (!command) {\n return [{ json: { ok: false, error: \"NO_COMMAND\", skipAll: true } }];\n}\n\n// prepare_list does not require approval\nconst safeCommands = [\"prepare_list\", \"status\", \"check\"];\nif (!safeCommands.includes(command) && !approved) {\n return [{ json: { ok: false, error: \"APPROVAL_REQUIRED\", command, request_id, skipAll: true } }];\n}\n\nreturn [{ json: { ok: true, command, approved: approved || safeCommands.includes(command), request_id, data, skipAll: false } }];"},"id":"gate-router","name":"Route Command","type":"n8n-nodes-base.code","typeVersion":2,"position":[1000,380]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"id":"skip-check","leftValue":"={{ String($json.skipAll) }}","rightValue":"true","operator":{"type":"string","operation":"equals"}}]}},"id":"gate-if-skip","name":"Should Skip?","type":"n8n-nodes-base.if","typeVersion":2.1,"position":[1200,380]},{"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"specifyBody":"json","jsonBody":"{}","options":{"timeout":30000}},"id":"gate-http-p1","name":"Call Phase 1","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1400,300]},{"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p2-merge-tracking","sendBody":true,"specifyBody":"json","jsonBody":"{}","options":{"timeout":30000}},"id":"gate-http-p2","name":"Call Phase 2","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1600,300]},{"parameters":{"jsCode":"const router = $('Route Command').item.json;\nconst p1 = $('Call Phase 1').item.json;\nconst p2 = $('Call Phase 2').item.json;\nreturn [{ json: {\n ok: true,\n command: router.command,\n request_id: router.request_id,\n phase1: p1,\n phase2: p2\n} }];"},"id":"gate-format","name":"Format Response","type":"n8n-nodes-base.code","typeVersion":2,"position":[1800,300]},{"parameters":{"respondWith":"allIncomingItems","options":{}},"id":"730abda4-3ca4-44a2-a884-972fd169ef6b","name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2000,380]}],"connections":{"Webhook":{"main":[[{"node":"Route Command","type":"main","index":0}]]},"Route Command":{"main":[[{"node":"Should Skip?","type":"main","index":0}]]},"Should Skip?":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}],[{"node":"Call Phase 1","type":"main","index":0}]]},"Call Phase 1":{"main":[[{"node":"Call Phase 2","type":"main","index":0}]]},"Call Phase 2":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Format Response":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-02-27T23:03:51.497Z","createdAt":"2026-02-27T23:02:59.193Z","id":"CmAvLjSXatusUnBi","name":"MIGRATION_STATUTS_RU","active":false,"isArchived":false,"nodes":[{"id":"mig-trigger","name":"Trigger","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"migration-statuts-ru","httpMethod":"POST","responseMode":"onReceived"}},{"id":"mig-read","name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[400,300],"parameters":{"operation":"read","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"id":"mig-code","name":"Map Statuts","type":"n8n-nodes-base.code","typeVersion":2,"position":[600,300],"parameters":{"jsCode":"const map = { pending: 'Ожидает', sent: 'Отправлено', resolved: 'Закрыто', excluded: 'Исключён', responded: 'Ответ получен', disputed: 'Разногласия', pending_review: 'На проверке' };\nreturn $input.all()\n .filter(i => map[i.json.status])\n .map(i => ({ json: { row_number: i.json.row_number, status: map[i.json.status] } }));"}},{"id":"mig-update","name":"Update Status","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[800,300],"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"status":"={{ $json.status }}"},"matchingColumns":["row_number"]}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"Trigger":{"main":[[{"node":"Read Tracking","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Map Statuts","type":"main","index":0}]]},"Map Statuts":{"main":[[{"node":"Update Status","type":"main","index":0}]]}},"settings":{"callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"afa1bdd3-fd94-4985-a597-13cba4c5fbd8","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-02-27T23:02:59.196Z","createdAt":"2026-02-27T23:02:59.196Z","role":"workflow:owner","workflowId":"CmAvLjSXatusUnBi","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-09T07:14:09.144Z","createdAt":"2026-03-09T07:14:09.144Z","id":"FVVwMcYyuxYBoXaT","name":"ERP_REVIEWER","active":true,"isArchived":false,"nodes":[{"id":"wh3","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-reviewer","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd3","name":"Reviewer","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const inp = $input.first().json;\n const spec = inp.spec;\n const files_written = inp.files_written || [];\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const fileContents = [];\n for (const fw of files_written) {\n if (fw.status === \"written\") {\n const res = await hostCmd(`cat \"/opt/erp/metallkart-erp/${fw.path}\" 2>/dev/null | head -100`);\n fileContents.push({path: fw.path, content: res.output || \"\"});\n }\n }\n\n const systemPrompt = `You are ERP_REVIEWER. Validate that generated code matches the spec.\\nCheck: correct routes, Prisma models used, TypeScript types, Fastify patterns, no obvious bugs.\\nReturn ONLY valid JSON (no markdown):\\n{\\n \"approved\": true/false,\\n \"score\": 0-100,\\n \"issues\": [\"issue1\", \"issue2\"],\\n \"recommendation\": \"deploy\" or \"fix\",\\n \"summary\": \"brief explanation\"\\n}`;\n\n const body = JSON.stringify({\n model: \"claude-haiku-4-5-20251001\",\n max_tokens: 1000,\n system: systemPrompt,\n messages: [{role:\"user\", content:\n \"Spec:\\n\"+JSON.stringify(spec,null,2).substring(0,1500)+\n \"\\n\\nGenerated files:\\n\"+JSON.stringify(fileContents,null,2).substring(0,3000)\n }]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error:\"API_ERROR\"}}];\n const raw = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n\n let review;\n try {\n const cleaned = raw.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n review = JSON.parse(cleaned);\n } catch(e) {\n review = {approved: false, score: 0, issues: [\"PARSE_ERROR: \"+raw.substring(0,200)], recommendation: \"fix\"};\n }\n\n return [{json:{...inp, review, status: review.recommendation === \"deploy\" ? \"ready_to_deploy\" : \"needs_fix\"}}];\n}\nreturn run();\n"}},{"id":"rsp3","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Reviewer","type":"main","index":0}]]},"Reviewer":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"7d489844-22b9-4ed1-9d29-d3ad7e5b23f0","activeVersionId":"7d489844-22b9-4ed1-9d29-d3ad7e5b23f0","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T07:14:09.149Z","createdAt":"2026-03-09T07:14:09.149Z","role":"workflow:owner","workflowId":"FVVwMcYyuxYBoXaT","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:14:09.156Z","createdAt":"2026-03-09T07:14:09.156Z","versionId":"7d489844-22b9-4ed1-9d29-d3ad7e5b23f0","workflowId":"FVVwMcYyuxYBoXaT","nodes":[{"id":"wh3","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-reviewer","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd3","name":"Reviewer","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const inp = $input.first().json;\n const spec = inp.spec;\n const files_written = inp.files_written || [];\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const fileContents = [];\n for (const fw of files_written) {\n if (fw.status === \"written\") {\n const res = await hostCmd(`cat \"/opt/erp/metallkart-erp/${fw.path}\" 2>/dev/null | head -100`);\n fileContents.push({path: fw.path, content: res.output || \"\"});\n }\n }\n\n const systemPrompt = `You are ERP_REVIEWER. Validate that generated code matches the spec.\\nCheck: correct routes, Prisma models used, TypeScript types, Fastify patterns, no obvious bugs.\\nReturn ONLY valid JSON (no markdown):\\n{\\n \"approved\": true/false,\\n \"score\": 0-100,\\n \"issues\": [\"issue1\", \"issue2\"],\\n \"recommendation\": \"deploy\" or \"fix\",\\n \"summary\": \"brief explanation\"\\n}`;\n\n const body = JSON.stringify({\n model: \"claude-haiku-4-5-20251001\",\n max_tokens: 1000,\n system: systemPrompt,\n messages: [{role:\"user\", content:\n \"Spec:\\n\"+JSON.stringify(spec,null,2).substring(0,1500)+\n \"\\n\\nGenerated files:\\n\"+JSON.stringify(fileContents,null,2).substring(0,3000)\n }]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{error:\"API_ERROR\"}}];\n const raw = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n\n let review;\n try {\n const cleaned = raw.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n review = JSON.parse(cleaned);\n } catch(e) {\n review = {approved: false, score: 0, issues: [\"PARSE_ERROR: \"+raw.substring(0,200)], recommendation: \"fix\"};\n }\n\n return [{json:{...inp, review, status: review.recommendation === \"deploy\" ? \"ready_to_deploy\" : \"needs_fix\"}}];\n}\nreturn run();\n"}},{"id":"rsp3","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Reviewer","type":"main","index":0}]]},"Reviewer":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-03T15:13:40.033Z","createdAt":"2026-03-01T17:36:02.802Z","id":"FrWxicrvPUNxZsjA","name":"NIKA_QUERY_TRACKING","active":true,"isArchived":false,"nodes":[{"id":"webhook1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[240,300],"parameters":{"httpMethod":"GET","path":"nika-query-tracking","responseMode":"responseNode"},"webhookId":"nika-query-tracking-wh"},{"id":"sheets1","name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[460,300],"parameters":{"operation":"read","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"filtersUI":{},"options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"id":"agg1","name":"Aggregate","type":"n8n-nodes-base.code","typeVersion":2,"position":[680,300],"parameters":{"mode":"runOnceForAllItems","jsCode":"const items = $input.all();\nconst quarter = $('Webhook').first().json.query?.quarter || '2026-Q1';\nconst rows = items.map(i => i.json).filter(r => !quarter || r.quarter === quarter);\nconst byStatus = {};\nfor (const r of rows) {\n const s = r.status || 'unknown';\n if (!byStatus[s]) byStatus[s] = [];\n byStatus[s].push({ name: r.supplier_name, email: r.email });\n}\nreturn [{ json: { total: rows.length, quarter, byStatus, rows } }];"}},{"id":"resp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[900,300],"parameters":{"respondWith":"json","responseBody":"={{ $json }}"}}],"connections":{"Webhook":{"main":[[{"node":"Read Tracking","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Aggregate","type":"main","index":0}]]},"Aggregate":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":true},"staticData":null,"meta":null,"pinData":null,"versionId":"4ff983ba-f171-44b7-875a-6cab7e71de3f","activeVersionId":"4ff983ba-f171-44b7-875a-6cab7e71de3f","triggerCount":1,"shared":[{"updatedAt":"2026-03-01T17:36:02.806Z","createdAt":"2026-03-01T17:36:02.806Z","role":"workflow:owner","workflowId":"FrWxicrvPUNxZsjA","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T18:14:01.129Z","createdAt":"2026-03-01T18:14:01.129Z","versionId":"4ff983ba-f171-44b7-875a-6cab7e71de3f","workflowId":"FrWxicrvPUNxZsjA","nodes":[{"id":"webhook1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[240,300],"parameters":{"httpMethod":"GET","path":"nika-query-tracking","responseMode":"responseNode"},"webhookId":"nika-query-tracking-wh"},{"id":"sheets1","name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[460,300],"parameters":{"operation":"read","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"filtersUI":{},"options":{}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"id":"agg1","name":"Aggregate","type":"n8n-nodes-base.code","typeVersion":2,"position":[680,300],"parameters":{"mode":"runOnceForAllItems","jsCode":"const items = $input.all();\nconst quarter = $('Webhook').first().json.query?.quarter || '2026-Q1';\nconst rows = items.map(i => i.json).filter(r => !quarter || r.quarter === quarter);\nconst byStatus = {};\nfor (const r of rows) {\n const s = r.status || 'unknown';\n if (!byStatus[s]) byStatus[s] = [];\n byStatus[s].push({ name: r.supplier_name, email: r.email });\n}\nreturn [{ json: { total: rows.length, quarter, byStatus, rows } }];"}},{"id":"resp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[900,300],"parameters":{"respondWith":"json","responseBody":"={{ $json }}"}}],"connections":{"Webhook":{"main":[[{"node":"Read Tracking","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Aggregate","type":"main","index":0}]]},"Aggregate":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-02-15T09:45:06.018Z","createdAt":"2026-02-14T23:21:13.251Z","id":"KujXIXxIssKKJxC3","name":"Excel Suppliers","active":false,"isArchived":false,"nodes":[{"parameters":{},"type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[-432,0],"id":"c5e32c6b-08f6-4218-9e7f-5731d23cf97c","name":"When clicking ‘Execute workflow’"},{"parameters":{"fileSelector":"/home/node/.n8n-files/maildump_6m.ndjson","options":{}},"type":"n8n-nodes-base.readWriteFile","typeVersion":1.1,"position":[-208,0],"id":"01254ad0-fc42-4b89-a564-1e8f1728eb2d","name":"Read/Write Files from Disk","alwaysOutputData":true,"executeOnce":true,"retryOnFail":false,"notesInFlow":true},{"parameters":{"jsCode":"const buf = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst content = buf.toString('utf8');\nconst lines = content.split('\\n').filter(l => l.trim().length > 0);\nreturn lines.map(line => ({ json: JSON.parse(line) }));"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[16,0],"id":"9e296c85-2e6f-477a-9e99-c6535aa82550","name":"Code in JavaScript"},{"parameters":{"jsCode":"function extractPhone(text) {\n if (!text) return '';\n const m = text.match(/(\\+7|7|8)\\s*[\\(\\- ]?\\s*\\d{3}\\s*[\\)\\- ]?\\s*\\d{3}[\\- ]?\\d{2}[\\- ]?\\d{2}/);\n return m ? m[0].replace(/\\s+/g, ' ').trim() : '';\n}\nfunction clean(s) { return (s || '').toString().replace(/\\s+/g, ' ').trim(); }\n\nconst map = new Map();\n\nfor (const it of items) {\n const e = (it.json.fromEmail || '').toLowerCase().trim();\n if (!e || !e.includes('@')) continue;\n\n const fromName = clean(it.json.fromName);\n const subject = clean(it.json.subject);\n const date = clean(it.json.date);\n const phone = extractPhone(it.json.text || '');\n\n const company = (e.split('@')[1] || '');\n\n const prev = map.get(e);\n if (!prev) {\n map.set(e, {\n Company: company,\n Email: e,\n ContactName: fromName,\n Phone: phone,\n LastSeenDate: date,\n LastSubject: subject,\n });\n } else {\n // обновим “последнее письмо”\n if (date && (!prev.LastSeenDate || date > prev.LastSeenDate)) {\n prev.LastSeenDate = date;\n prev.LastSubject = subject || prev.LastSubject;\n if (fromName) prev.ContactName = fromName;\n if (phone) prev.Phone = phone;\n } else {\n if (!prev.ContactName && fromName) prev.ContactName = fromName;\n if (!prev.Phone && phone) prev.Phone = phone;\n }\n }\n}\n\nreturn Array.from(map.values()).map(r => ({ json: r }));"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[240,0],"id":"c6bb885d-d8b3-48de-b1b0-715b2133c6e6","name":"Code in JavaScript1"},{"parameters":{"operation":"append","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"list","cachedResultName":"Répertoire contreparties (6 mois)","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/edit?usp=drivesdk"},"sheetName":{"__rl":true,"value":"gid=0","mode":"list","cachedResultName":"Feuille 1","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/edit#gid=0"},"columns":{"mappingMode":"autoMapInputData","value":{},"matchingColumns":[],"schema":[{"id":"Company","displayName":"Company","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"Email","displayName":"Email","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"ContactName","displayName":"ContactName","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"Phone","displayName":"Phone","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"LastSeenDate","displayName":"LastSeenDate","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"LastSubject","displayName":"LastSubject","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true}],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{}},"type":"n8n-nodes-base.googleSheets","typeVersion":4.7,"position":[-432,224],"id":"a4b430f5-7baa-4883-9135-5e6a279f36ab","name":"Append row in sheet","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"fromEmail":"purchase@metdesigntver.ru","toEmail":"891906@mail.ru","subject":"обновление список контрагентов снабжение","html":"Добрый день Тамара !\nИнформирую Вас что обновил список актуальных контрагентов Металл Дизайна ! Ссылка ниже :\n\nhttps://docs.google.com/spreadsheets/d/1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4/edit?gid=0#gid=0\n\nХорошего дня ! ))\nВаш ИА\n","options":{}},"type":"n8n-nodes-base.emailSend","typeVersion":2.1,"position":[16,224],"id":"467a33f7-c06a-4938-aced-bf026a568b44","name":"Send an Email","webhookId":"334fd30f-0c80-4daa-9f1d-0b8db7c9fe15","credentials":{"smtp":{"id":"JTfjUcwtoiOr3XBB","name":"SMTP account"}}},{"parameters":{"jsCode":"return [{\n json: {\n rowsWritten: items.length,\n sheetUrl: 'https://docs.google.com/spreadsheets/d/10HBgHa9pBNb.../edit', // вставьте вашу ссылку\n runAt: new Date().toISOString(),\n }\n}];"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[-208,224],"id":"89f8dca8-983e-462c-b452-723e12574640","name":"Code in JavaScript2"},{"parameters":{"jsCode":"function safeForSheets(v) {\n if (v === null || v === undefined) return '';\n let s = String(v).replace(/\\u0000/g, '');\n // чтобы Google Sheets не пытался интерпретировать как формулу\n if (/^[=+\\-@]/.test(s)) s = \"'\" + s;\n return s;\n}\n\n// декодирование MIME encoded-word: =?UTF-8?B?...?= и =?UTF-8?Q?...?=\nfunction decodeMimeWord(str) {\n if (!str) return '';\n let s = String(str);\n\n // декодируем все куски вида =?charset?B/Q?encoded?=\n s = s.replace(/=\\?([^?]+)\\?([bBqQ])\\?([^?]*)\\?=/g, (m, charset, enc, text) => {\n charset = (charset || '').toLowerCase();\n enc = (enc || '').toUpperCase();\n\n try {\n if (enc === 'B') {\n // base64\n const buf = Buffer.from(text, 'base64');\n // чаще всего UTF-8; для других charset оставим как utf8 (обычно хватает)\n return buf.toString(charset.includes('utf-8') ? 'utf8' : 'utf8');\n } else {\n // Q-encoding: \"_\" = space, =HH hex bytes\n let t = text.replace(/_/g, ' ');\n const bytes = [];\n for (let i = 0; i < t.length; i++) {\n if (t[i] === '=' && i + 2 < t.length && /^[0-9A-Fa-f]{2}$/.test(t.slice(i + 1, i + 3))) {\n bytes.push(parseInt(t.slice(i + 1, i + 3), 16));\n i += 2;\n } else {\n // ASCII char\n bytes.push(t.charCodeAt(i));\n }\n }\n return Buffer.from(bytes).toString(charset.includes('utf-8') ? 'utf8' : 'utf8');\n }\n } catch (e) {\n return m; // если не смогли — оставим как есть\n }\n });\n\n // если после декода остались лишние пробелы\n return s.replace(/\\s+/g, ' ').trim();\n}\n\nreturn items.map(it => ({\n json: {\n Company: safeForSheets(it.json.Company),\n Email: safeForSheets(it.json.Email),\n ContactName: safeForSheets(decodeMimeWord(it.json.ContactName)),\n Phone: safeForSheets(it.json.Phone),\n LastSeenDate: safeForSheets(it.json.LastSeenDate),\n LastSubject: safeForSheets(decodeMimeWord(it.json.LastSubject)),\n }\n}));"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[464,0],"id":"05e6aa47-9eff-41df-802a-e645ecc85c3d","name":"Code in JavaScript3"},{"parameters":{"jsCode":"function safe(v) {\n if (v === null || v === undefined) return '';\n let s = v.toString();\n if (/^[=+\\-@]/.test(s)) s = \"'\" + s; // чтобы Google Sheets не считал формулой\n return s;\n}\n\nreturn items.map(it => ({\n json: {\n Company: safe(it.json.Company),\n Email: safe(it.json.Email),\n ContactName: safe(it.json.ContactName),\n Phone: safe(it.json.Phone),\n LastSeenDate: safe(it.json.LastSeenDate),\n LastSubject: safe(it.json.LastSubject),\n }\n}));"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[688,0],"id":"9c058edd-de72-433a-9ec5-0d429fb4b35e","name":"Code in JavaScript4"}],"connections":{"When clicking ‘Execute workflow’":{"main":[[{"node":"Read/Write Files from Disk","type":"main","index":0}]]},"Read/Write Files from Disk":{"main":[[{"node":"Code in JavaScript","type":"main","index":0}]]},"Code in JavaScript":{"main":[[{"node":"Code in JavaScript1","type":"main","index":0}]]},"Code in JavaScript1":{"main":[[{"node":"Code in JavaScript3","type":"main","index":0}]]},"Append row in sheet":{"main":[[{"node":"Code in JavaScript2","type":"main","index":0}]]},"Code in JavaScript2":{"main":[[{"node":"Send an Email","type":"main","index":0}]]},"Code in JavaScript3":{"main":[[{"node":"Code in JavaScript4","type":"main","index":0}]]},"Code in JavaScript4":{"main":[[{"node":"Append row in sheet","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","binaryMode":"separate","availableInMCP":false},"staticData":null,"meta":{"templateCredsSetupCompleted":true},"pinData":{"When clicking ‘Execute workflow’":[{"json":{},"pairedItem":{"item":0}}]},"versionId":"ef097e96-21cf-4a2b-a574-944cbcbbc179","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-02-15T15:04:40.881Z","createdAt":"2026-02-15T15:04:40.881Z","role":"workflow:owner","workflowId":"KujXIXxIssKKJxC3","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-08T23:06:38.658Z","createdAt":"2026-03-08T23:06:38.658Z","id":"MKlBgHWSLGYXuTGM","name":"TEST_ANTHROPIC_DIRECT","active":true,"isArchived":false,"nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"test-anthropic","httpMethod":"POST","responseMode":"responseNode"}},{"id":"api1","name":"CallClaude","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[460,300],"parameters":{"method":"POST","url":"https://api.anthropic.com/v1/messages","sendHeaders":true,"headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $env.ANTHROPIC_API_KEY }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"{\"model\":\"claude-haiku-4-5-20251001\",\"max_tokens\":20,\"messages\":[{\"role\":\"user\",\"content\":\"Reply PONG\"}]}"}},{"id":"resp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ $json }}"}}],"connections":{"Webhook":{"main":[[{"node":"CallClaude","type":"main","index":0}]]},"CallClaude":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"518c915b-51fa-4175-bc9c-62d900a3d25b","activeVersionId":"518c915b-51fa-4175-bc9c-62d900a3d25b","triggerCount":1,"shared":[{"updatedAt":"2026-03-08T23:06:38.700Z","createdAt":"2026-03-08T23:06:38.700Z","role":"workflow:owner","workflowId":"MKlBgHWSLGYXuTGM","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-08T23:06:38.724Z","createdAt":"2026-03-08T23:06:38.724Z","versionId":"518c915b-51fa-4175-bc9c-62d900a3d25b","workflowId":"MKlBgHWSLGYXuTGM","nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"test-anthropic","httpMethod":"POST","responseMode":"responseNode"}},{"id":"api1","name":"CallClaude","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[460,300],"parameters":{"method":"POST","url":"https://api.anthropic.com/v1/messages","sendHeaders":true,"headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $env.ANTHROPIC_API_KEY }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"{\"model\":\"claude-haiku-4-5-20251001\",\"max_tokens\":20,\"messages\":[{\"role\":\"user\",\"content\":\"Reply PONG\"}]}"}},{"id":"resp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ $json }}"}}],"connections":{"Webhook":{"main":[[{"node":"CallClaude","type":"main","index":0}]]},"CallClaude":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-01T13:49:01.160Z","createdAt":"2026-02-26T20:44:18.911Z","id":"OZXpb9t0nQvlRC12","name":"ACT_SVERKI_SEND_REQUESTS","active":true,"isArchived":false,"nodes":[{"parameters":{"httpMethod":"POST","path":"act-sverki-send-requests","responseMode":"responseNode","options":{}},"name":"Webhook Trigger","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[208,304],"id":"43784716-8e65-4e1e-b4f7-58f64278c155","webhookId":"send-requests-wh"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"options":{}},"name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[464,304],"id":"37e28e9c-b0e7-4119-9e2f-e957eed827de","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const rows = $input.all().map(i => i.json);\nconst QUARTER = '2026-Q1';\nconst OWN_EMAILS = new Set([\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n]);\nconst BLACKLIST_DOMAINS = [\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','amazonses.com',\n 'uni-sender.com','unisender.com','sendsay.ru','estismail.ru','mindbox.ru',\n 'moysklad.ru','1c.ru','kontur.ru','sbis.ru','taxcom.ru','diadoc.ru','skbkontur.ru',\n 'e.petrovich.ru','petrovich.ru','ozon.ru','wildberries.ru','avito.ru',\n 'vseinstrumenti.ru','dellin.ru','cdek.ru','leroymerlin.ru',\n];\nconst BLACKLIST_LOCAL = ['noreply','no-reply','donotreply','mailer','robot',\n 'survey','mkt','notification','feedback'];\nconst pending = rows.filter(r => {\n if (r.status !== 'Ожидает' && r.status !== 'pending') return false;\n if (r.excluded === true || r.excluded === 'true') return false;\n if ((r.quarter || '') !== QUARTER) return false;\n const email = (r.email || '').toLowerCase().trim();\n if (!email || !email.includes('@')) return false;\n if (OWN_EMAILS.has(email)) return false;\n const domain = email.split('@')[1] || '';\n const local = email.split('@')[0] || '';\n if (BLACKLIST_DOMAINS.some(d => domain.includes(d))) return false;\n if (BLACKLIST_LOCAL.some(p => local.startsWith(p))) return false;\n return true;\n}).slice(0, 50);\nif (pending.length === 0) return [{ json: { _empty: true, count: 0 } }];\nreturn pending.map(r => ({ json: r }));"},"name":"Filter Pending","type":"n8n-nodes-base.code","typeVersion":2,"position":[704,304],"id":"1abaa1bf-0b4d-450b-82c1-97b12288c148"},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"c1","leftValue":"={{$json._empty}}","rightValue":true,"operator":{"type":"boolean","operation":"notEquals"}}],"combinator":"and"},"options":{}},"name":"IF Has Pending","type":"n8n-nodes-base.if","typeVersion":2,"position":[960,304],"id":"c34afa76-c6c0-4dbf-96cd-e16dd226e5d1"},{"parameters":{"jsCode":"\nconst qMap = { 'Q1':'1-ый','Q2':'2-ой','Q3':'3-ий','Q4':'4-ый' };\n\nreturn $input.all().map(item => {\n const s = item.json;\n const quarter = s.quarter || '2026-Q1';\n const isTest = s._test_mode === true;\n const emailAddr = (s.email || s.Email || '').trim();\n const sendTo = isTest ? 'purchase@metdesigntver.ru' : emailAddr;\n const supplierName = s.supplier_name || s.supplier_name_raw || 'Коллеги';\n\n const parts = quarter.split('-');\n const qYear = parts[0] || '2026';\n const qNum = parts[1] || 'Q1';\n const quarterRu = (qMap[qNum] || qNum) + ' квартал ' + qYear + ' г.';\n\n const subject = isTest\n ? `[TEST] Акт сверки → ${emailAddr} (${supplierName})`\n : `Запрос Акта сверки за ${quarterRu}`;\n\n const emailBody = `Уважаемые коллеги,\\n\\nВ рамках сверки взаиморасчётов за ${quarterRu}, просим Вас направить акт сверки с ООО \"Металл Дизайн\" на адрес: comptable@metdesigntver.ru\\n\\nПросим отправлять Акт Сверки в формате Excel, а также скан акта сверки с подписью и штампом, желательно в формате PDF.\\n\\nЕсли у Вас есть вопросы — пожалуйста, свяжитесь с нами.\\n\\nС уважением,\\nБухгалтерия ООО \"Металл Дизайн\"` + (isTest ? `\\n\\n[TEST — destinataire réel: ${emailAddr}]` : '');\n\n return { json: {\n ...s,\n email: emailAddr,\n _email_to: sendTo,\n _email_subject: subject,\n _email_body: emailBody,\n }};\n});\n"},"name":"Build Email","type":"n8n-nodes-base.code","typeVersion":2,"position":[1100,300],"id":"9ea4c9bf-9d39-4266-8891-2ff65dcf7235"},{"parameters":{"jsCode":"return $input.all().map(i => ({ json: i.json }));"},"name":"Request Approval","type":"n8n-nodes-base.code","typeVersion":2,"position":[1300,300],"id":"d86d3559-4daf-4c09-8339-0bd6dc106554","onError":"continueRegularOutput"},{"parameters":{"fromEmail":"comptable@metdesigntver.ru","toEmail":"={{ $json._email_to }}","subject":"={{ $json._email_subject }}","text":"={{ $json._email_body }}","options":{}},"name":"Send Email","type":"n8n-nodes-base.emailSend","typeVersion":1,"position":[1500,300],"id":"296556a4-28a3-49ec-9ff8-f97669f29b48","webhookId":"1b2ca550-d885-41c5-902e-f3de766a88a4","credentials":{"smtp":{"id":"6Aqbk9ebjplWgljK","name":"SMTP comptable@"}}},{"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $('Build Email').item.json.email }}","status":"Отправлено","request_sent_at":"={{ new Date().toISOString() }}"},"matchingColumns":["email"],"schema":[{"id":"supplier_name","displayName":"supplier_name","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"email","displayName":"email","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"quarter","displayName":"quarter","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"status","displayName":"status","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"request_sent_at","displayName":"request_sent_at","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"last_reminder_at","displayName":"last_reminder_at","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"reminder_count","displayName":"reminder_count","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"next_reminder_at","displayName":"next_reminder_at","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"excluded","displayName":"excluded","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"exclude_reason","displayName":"exclude_reason","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"last_inbound_class","displayName":"last_inbound_class","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"notes","displayName":"notes","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"needs_1c_check","displayName":"needs_1c_check","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"ai_confidence","displayName":"ai_confidence","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"ai_doc_type","displayName":"ai_doc_type","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"row_id","displayName":"row_id","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"supplier_id","displayName":"supplier_id","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"document_number","displayName":"document_number","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"document_date","displayName":"document_date","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"debit","displayName":"debit","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"credit","displayName":"credit","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"balance","displayName":"balance","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"currency","displayName":"currency","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"comment","displayName":"comment","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"review_ts","displayName":"review_ts","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"merged_ts","displayName":"merged_ts","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"resolved","displayName":"resolved","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"resolution_comment","displayName":"resolution_comment","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"_empty","displayName":"_empty","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"count","displayName":"count","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"reviewCount","displayName":"reviewCount","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"trackingCount","displayName":"trackingCount","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"row_number","displayName":"row_number","required":false,"defaultMatch":false,"display":true,"type":"number","canBeUsedToMatch":true,"readOnly":true,"removed":false}]},"options":{}},"name":"Update Status Sent","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1700,300],"id":"02fd93e4-ff70-4a02-968f-7038d5e72956","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const sent = $('Update Status Sent').all().filter(i => !i.json._empty);\nconst pending = $('Filter Pending').all().filter(i => !i.json._empty);\nreturn [{ json: {\n timestamp: new Date().toISOString(),\n total_pending: pending.length,\n sent: sent.length,\n success: true,\n message: `${sent.length} email(s) envoyé(s) sur ${pending.length} en attente.`\n} }];"},"name":"Build Summary","type":"n8n-nodes-base.code","typeVersion":2,"position":[2208,304],"id":"215f4d2a-a6bd-4784-bc08-eb1bb2fcde68"},{"parameters":{"jsCode":"return[{json:{timestamp:new Date().toISOString(),total_pending:0,success:true,message:'No pending suppliers to send requests to.'}}]"},"name":"No Pending Summary","type":"n8n-nodes-base.code","typeVersion":2,"position":[1200,512],"id":"7004e266-7ff4-4a95-bf11-c755e514d9f5"},{"parameters":{"respondWith":"allIncomingItems","options":{}},"name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2464,304],"id":"db98228c-4106-434f-b80c-4259e026c17e"},{"id":"test-mode-filter-001","name":"Test Mode Filter","type":"n8n-nodes-base.code","typeVersion":2,"position":[900,300],"parameters":{"jsCode":"const isTest = $('Webhook Trigger').first().json.test_mode === true;\nconst items = $input.all();\nif (!items.length || items[0].json._empty) return [{ json: { _empty: true } }];\nif (isTest) {\n return [{ json: { ...items[0].json, _test_mode: true } }];\n}\nreturn items.map(i => ({ json: { ...i.json, _test_mode: false } }));"}}],"connections":{"Webhook Trigger":{"main":[[{"node":"Read Tracking","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Filter Pending","type":"main","index":0}]]},"Filter Pending":{"main":[[{"node":"IF Has Pending","type":"main","index":0}]]},"IF Has Pending":{"main":[[{"node":"Test Mode Filter","type":"main","index":0}],[{"node":"No Pending Summary","type":"main","index":0}]]},"Build Email":{"main":[[{"node":"Request Approval","type":"main","index":0}]]},"Send Email":{"main":[[{"node":"Update Status Sent","type":"main","index":0}]]},"Update Status Sent":{"main":[[{"node":"Build Summary","type":"main","index":0}]]},"Build Summary":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]},"No Pending Summary":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]},"Test Mode Filter":{"main":[[{"node":"Build Email","type":"main","index":0}]]},"Request Approval":{"main":[[{"node":"Send Email","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":true},"staticData":null,"meta":null,"pinData":{"Webhook Trigger":[{"json":{"headers":{"host":"n8n.srv1377973.hstgr.cloud","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36","content-length":"56","accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,fr;q=0.6","content-type":"application/json","cookie":"rl_page_init_referrer=RudderEncrypt%3AU2FsdGVkX19RzcrOP5f%2FIE1VQdhl7lcnEdvipVNd6M4zpxNfYv6lJ3QA5buWnl7c; rl_page_init_referring_domain=RudderEncrypt%3AU2FsdGVkX19qFWNYbLID7vl7KdlEtZNe40hvOAr795DmJ1CNlt%2BToFEAlBgnEVyl; rl_anonymous_id=RudderEncrypt%3AU2FsdGVkX19HmHpclhz%2BP%2BUnKbkC9U2qMEfqgmB%2FH%2BupF22dLgJypr1dLSK2I%2B%2FkOngO2rnfvKijIBM9FkjmWA%3D%3D; rl_user_id=RudderEncrypt%3AU2FsdGVkX1%2B4a4XiaYQj7WYfYMcXnfH4y4QQQ%2Fow8rfkdLd6B%2BdJ8Ph4is8kTNqAcshYWgLKBGR944PBLGI357%2F%2FJlKONteeEvKqgQWv6ACzxGMAymywA0wYGCGP2Imu3cRWFGMcA88PWW09bUF04xW75eAcXro05P9VvZORajY%3D; rl_trait=RudderEncrypt%3AU2FsdGVkX19WveEKwM7SMifjJOZjti9yzMkJGJT6Ir6a8zxxeG4ZhC%2BTPzsdoQnsUztymE1qoE2M3Es7ik905YUWM6wvlmZaNwBREKT8n4egjP%2B1DF%2FPCormVH8bKZCKwQa%2FpMThcIxVxH3ls5qGqi4bC2gfEdfJr40L9zXsk2M%3D; rl_session=RudderEncrypt%3AU2FsdGVkX19JoTYSy7q5gTMHW4F1v8jnGbfYCwqXfnrhIftDkOtQ5Qw7TWqeGfAgETtDBjr%2BYMJpZJldxMP0xQPrEJr07rO9zkOkjVkcflmBP%2FVAs7twS1GaAnLAWQ5m%2BrYtERbuxAXF89Aauc3Xaw%3D%3D; ph_phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo_posthog=%7B%22%24device_id%22%3A%22019c5d78-19e1-7fde-a178-9426263c7442%22%2C%22distinct_id%22%3A%22c9922039809bbab0c436a265341c79e4bbd2f49cd921be7919955df4d32d0ed9%23109695eb-c919-4095-a2d3-4c4951d81395%22%2C%22%24sesid%22%3A%5B1772229674020%2C%22019ca061-192d-72d8-85d4-73d7d116f4ac%22%2C1772217243905%5D%2C%22%24epp%22%3Atrue%2C%22%24initial_person_info%22%3A%7B%22r%22%3A%22%24direct%22%2C%22u%22%3A%22https%3A%2F%2Fn8n.srv1377973.hstgr.cloud%2Fsignout%22%7D%2C%22%24user_state%22%3A%22identified%22%7D","origin":"https://n8n.srv1377973.hstgr.cloud","priority":"u=1, i","sec-ch-ua":"\"Not:A-Brand\";v=\"99\", \"Google Chrome\";v=\"145\", \"Chromium\";v=\"145\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","via":"2.0 Caddy","x-forwarded-for":"64.253.89.2","x-forwarded-host":"n8n.srv1377973.hstgr.cloud","x-forwarded-proto":"https"},"params":{},"query":{},"body":{"source":"test","timestamp":"2026-02-27T22:06:25.269Z"},"webhookUrl":"https://n8n.srv1377973.hstgr.cloud/webhook/act-sverki-send-requests","executionMode":"production"},"pairedItem":{"item":0}}]},"versionId":"2c534bf2-11fc-480a-87ea-efb4b2c088b4","activeVersionId":"2c534bf2-11fc-480a-87ea-efb4b2c088b4","triggerCount":1,"shared":[{"updatedAt":"2026-02-26T20:44:18.913Z","createdAt":"2026-02-26T20:44:18.913Z","role":"workflow:owner","workflowId":"OZXpb9t0nQvlRC12","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T13:49:01.161Z","createdAt":"2026-03-01T13:49:01.161Z","versionId":"2c534bf2-11fc-480a-87ea-efb4b2c088b4","workflowId":"OZXpb9t0nQvlRC12","nodes":[{"parameters":{"httpMethod":"POST","path":"act-sverki-send-requests","responseMode":"responseNode","options":{}},"name":"Webhook Trigger","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[208,304],"id":"43784716-8e65-4e1e-b4f7-58f64278c155","webhookId":"send-requests-wh"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"options":{}},"name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[464,304],"id":"37e28e9c-b0e7-4119-9e2f-e957eed827de","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const rows = $input.all().map(i => i.json);\nconst QUARTER = '2026-Q1';\nconst OWN_EMAILS = new Set([\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n]);\nconst BLACKLIST_DOMAINS = [\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','amazonses.com',\n 'uni-sender.com','unisender.com','sendsay.ru','estismail.ru','mindbox.ru',\n 'moysklad.ru','1c.ru','kontur.ru','sbis.ru','taxcom.ru','diadoc.ru','skbkontur.ru',\n 'e.petrovich.ru','petrovich.ru','ozon.ru','wildberries.ru','avito.ru',\n 'vseinstrumenti.ru','dellin.ru','cdek.ru','leroymerlin.ru',\n];\nconst BLACKLIST_LOCAL = ['noreply','no-reply','donotreply','mailer','robot',\n 'survey','mkt','notification','feedback'];\nconst pending = rows.filter(r => {\n if (r.status !== 'Ожидает' && r.status !== 'pending') return false;\n if (r.excluded === true || r.excluded === 'true') return false;\n if ((r.quarter || '') !== QUARTER) return false;\n const email = (r.email || '').toLowerCase().trim();\n if (!email || !email.includes('@')) return false;\n if (OWN_EMAILS.has(email)) return false;\n const domain = email.split('@')[1] || '';\n const local = email.split('@')[0] || '';\n if (BLACKLIST_DOMAINS.some(d => domain.includes(d))) return false;\n if (BLACKLIST_LOCAL.some(p => local.startsWith(p))) return false;\n return true;\n}).slice(0, 50);\nif (pending.length === 0) return [{ json: { _empty: true, count: 0 } }];\nreturn pending.map(r => ({ json: r }));"},"name":"Filter Pending","type":"n8n-nodes-base.code","typeVersion":2,"position":[704,304],"id":"1abaa1bf-0b4d-450b-82c1-97b12288c148"},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"c1","leftValue":"={{$json._empty}}","rightValue":true,"operator":{"type":"boolean","operation":"notEquals"}}],"combinator":"and"},"options":{}},"name":"IF Has Pending","type":"n8n-nodes-base.if","typeVersion":2,"position":[960,304],"id":"c34afa76-c6c0-4dbf-96cd-e16dd226e5d1"},{"parameters":{"jsCode":"\nconst qMap = { 'Q1':'1-ый','Q2':'2-ой','Q3':'3-ий','Q4':'4-ый' };\n\nreturn $input.all().map(item => {\n const s = item.json;\n const quarter = s.quarter || '2026-Q1';\n const isTest = s._test_mode === true;\n const emailAddr = (s.email || s.Email || '').trim();\n const sendTo = isTest ? 'purchase@metdesigntver.ru' : emailAddr;\n const supplierName = s.supplier_name || s.supplier_name_raw || 'Коллеги';\n\n const parts = quarter.split('-');\n const qYear = parts[0] || '2026';\n const qNum = parts[1] || 'Q1';\n const quarterRu = (qMap[qNum] || qNum) + ' квартал ' + qYear + ' г.';\n\n const subject = isTest\n ? `[TEST] Акт сверки → ${emailAddr} (${supplierName})`\n : `Запрос Акта сверки за ${quarterRu}`;\n\n const emailBody = `Уважаемые коллеги,\\n\\nВ рамках сверки взаиморасчётов за ${quarterRu}, просим Вас направить акт сверки с ООО \"Металл Дизайн\" на адрес: comptable@metdesigntver.ru\\n\\nПросим отправлять Акт Сверки в формате Excel, а также скан акта сверки с подписью и штампом, желательно в формате PDF.\\n\\nЕсли у Вас есть вопросы — пожалуйста, свяжитесь с нами.\\n\\nС уважением,\\nБухгалтерия ООО \"Металл Дизайн\"` + (isTest ? `\\n\\n[TEST — destinataire réel: ${emailAddr}]` : '');\n\n return { json: {\n ...s,\n email: emailAddr,\n _email_to: sendTo,\n _email_subject: subject,\n _email_body: emailBody,\n }};\n});\n"},"name":"Build Email","type":"n8n-nodes-base.code","typeVersion":2,"position":[1100,300],"id":"9ea4c9bf-9d39-4266-8891-2ff65dcf7235"},{"parameters":{"jsCode":"return $input.all().map(i => ({ json: i.json }));"},"name":"Request Approval","type":"n8n-nodes-base.code","typeVersion":2,"position":[1300,300],"id":"d86d3559-4daf-4c09-8339-0bd6dc106554","onError":"continueRegularOutput"},{"parameters":{"fromEmail":"comptable@metdesigntver.ru","toEmail":"={{ $json._email_to }}","subject":"={{ $json._email_subject }}","text":"={{ $json._email_body }}","options":{}},"name":"Send Email","type":"n8n-nodes-base.emailSend","typeVersion":1,"position":[1500,300],"id":"296556a4-28a3-49ec-9ff8-f97669f29b48","webhookId":"1b2ca550-d885-41c5-902e-f3de766a88a4","credentials":{"smtp":{"id":"6Aqbk9ebjplWgljK","name":"SMTP comptable@"}}},{"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $('Build Email').item.json.email }}","status":"Отправлено","request_sent_at":"={{ new Date().toISOString() }}"},"matchingColumns":["email"],"schema":[{"id":"supplier_name","displayName":"supplier_name","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"email","displayName":"email","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"quarter","displayName":"quarter","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"status","displayName":"status","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"request_sent_at","displayName":"request_sent_at","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"last_reminder_at","displayName":"last_reminder_at","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"reminder_count","displayName":"reminder_count","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"next_reminder_at","displayName":"next_reminder_at","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"excluded","displayName":"excluded","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"exclude_reason","displayName":"exclude_reason","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"last_inbound_class","displayName":"last_inbound_class","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"notes","displayName":"notes","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"needs_1c_check","displayName":"needs_1c_check","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"ai_confidence","displayName":"ai_confidence","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"ai_doc_type","displayName":"ai_doc_type","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"row_id","displayName":"row_id","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"supplier_id","displayName":"supplier_id","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"document_number","displayName":"document_number","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"document_date","displayName":"document_date","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"debit","displayName":"debit","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"credit","displayName":"credit","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"balance","displayName":"balance","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"currency","displayName":"currency","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"comment","displayName":"comment","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"review_ts","displayName":"review_ts","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"merged_ts","displayName":"merged_ts","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"resolved","displayName":"resolved","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"resolution_comment","displayName":"resolution_comment","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"_empty","displayName":"_empty","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"count","displayName":"count","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"reviewCount","displayName":"reviewCount","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"trackingCount","displayName":"trackingCount","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true,"removed":false},{"id":"row_number","displayName":"row_number","required":false,"defaultMatch":false,"display":true,"type":"number","canBeUsedToMatch":true,"readOnly":true,"removed":false}]},"options":{}},"name":"Update Status Sent","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1700,300],"id":"02fd93e4-ff70-4a02-968f-7038d5e72956","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const sent = $('Update Status Sent').all().filter(i => !i.json._empty);\nconst pending = $('Filter Pending').all().filter(i => !i.json._empty);\nreturn [{ json: {\n timestamp: new Date().toISOString(),\n total_pending: pending.length,\n sent: sent.length,\n success: true,\n message: `${sent.length} email(s) envoyé(s) sur ${pending.length} en attente.`\n} }];"},"name":"Build Summary","type":"n8n-nodes-base.code","typeVersion":2,"position":[2208,304],"id":"215f4d2a-a6bd-4784-bc08-eb1bb2fcde68"},{"parameters":{"jsCode":"return[{json:{timestamp:new Date().toISOString(),total_pending:0,success:true,message:'No pending suppliers to send requests to.'}}]"},"name":"No Pending Summary","type":"n8n-nodes-base.code","typeVersion":2,"position":[1200,512],"id":"7004e266-7ff4-4a95-bf11-c755e514d9f5"},{"parameters":{"respondWith":"allIncomingItems","options":{}},"name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2464,304],"id":"db98228c-4106-434f-b80c-4259e026c17e"},{"id":"test-mode-filter-001","name":"Test Mode Filter","type":"n8n-nodes-base.code","typeVersion":2,"position":[900,300],"parameters":{"jsCode":"const isTest = $('Webhook Trigger').first().json.test_mode === true;\nconst items = $input.all();\nif (!items.length || items[0].json._empty) return [{ json: { _empty: true } }];\nif (isTest) {\n return [{ json: { ...items[0].json, _test_mode: true } }];\n}\nreturn items.map(i => ({ json: { ...i.json, _test_mode: false } }));"}}],"connections":{"Webhook Trigger":{"main":[[{"node":"Read Tracking","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Filter Pending","type":"main","index":0}]]},"Filter Pending":{"main":[[{"node":"IF Has Pending","type":"main","index":0}]]},"IF Has Pending":{"main":[[{"node":"Test Mode Filter","type":"main","index":0}],[{"node":"No Pending Summary","type":"main","index":0}]]},"Build Email":{"main":[[{"node":"Request Approval","type":"main","index":0}]]},"Send Email":{"main":[[{"node":"Update Status Sent","type":"main","index":0}]]},"Update Status Sent":{"main":[[{"node":"Build Summary","type":"main","index":0}]]},"Build Summary":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]},"No Pending Summary":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]},"Test Mode Filter":{"main":[[{"node":"Build Email","type":"main","index":0}]]},"Request Approval":{"main":[[{"node":"Send Email","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-09T11:17:57.728Z","createdAt":"2026-03-09T11:17:57.728Z","id":"R7Rt30w33BmkU59j","name":"ERP_TELEGRAM_BOT","active":true,"isArchived":false,"nodes":[{"id":"tg1","name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","typeVersion":1.1,"position":[250,300],"parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}},{"id":"code1","name":"Process","type":"n8n-nodes-base.code","typeVersion":2,"position":[500,300],"parameters":{"jsCode":"const msg = $input.first().json.message;\nconst chatId = msg.chat.id;\nconst text = msg.text || '';\nreturn [{json: {chatId, text, from: msg.from.first_name}}];"}},{"id":"tg2","name":"Reply","type":"n8n-nodes-base.telegram","typeVersion":1.2,"position":[750,300],"parameters":{"chatId":"={{ $json.chatId }}","text":"=ERP Bot actif. Message recu: {{ $json.text }}","additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}}],"connections":{"Telegram Trigger":{"main":[[{"node":"Process","type":"main","index":0}]]},"Process":{"main":[[{"node":"Reply","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"ac89339e-6208-44ce-96d5-ae90b3114aaf","activeVersionId":"ac89339e-6208-44ce-96d5-ae90b3114aaf","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T11:17:57.729Z","createdAt":"2026-03-09T11:17:57.729Z","role":"workflow:owner","workflowId":"R7Rt30w33BmkU59j","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T11:30:53.000Z","createdAt":"2026-03-09T11:17:57.733Z","versionId":"ac89339e-6208-44ce-96d5-ae90b3114aaf","workflowId":"R7Rt30w33BmkU59j","nodes":[{"id":"tg1","name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","typeVersion":1.1,"position":[250,300],"parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}},{"id":"code1","name":"Process","type":"n8n-nodes-base.code","typeVersion":2,"position":[500,300],"parameters":{"jsCode":"const msg = $input.first().json.message;\nconst chatId = msg.chat.id;\nconst text = msg.text || '';\nreturn [{json: {chatId, text, from: msg.from.first_name}}];"}},{"id":"tg2","name":"Reply","type":"n8n-nodes-base.telegram","typeVersion":1.2,"position":[750,300],"parameters":{"chatId":"={{ $json.chatId }}","text":"=ERP Bot actif. Message recu: {{ $json.text }}","additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}}],"connections":{"Telegram Trigger":{"main":[[{"node":"Process","type":"main","index":0}]]},"Process":{"main":[[{"node":"Reply","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":"Version ac89339e","description":"","autosaved":false},"tags":[]},{"updatedAt":"2026-03-03T10:31:19.491Z","createdAt":"2026-03-01T19:58:49.772Z","id":"XDmTEZdyvXmGjEWo","name":"ACT_SVERKI_INBOUND_TRIAGE_PURCHASE","active":true,"isArchived":false,"nodes":[{"parameters":{"jsCode":"const OWN_EMAILS = new Set([\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n]);\nconst item = $input.first().json;\nconst fromRaw = item.from || item.fromAddress || '';\nconst fromEmail = (fromRaw.match(/<(.+)>/) || [,''])[1].toLowerCase() || fromRaw.toLowerCase().trim();\nconst subject = (item.subject || '').toLowerCase();\n// Rejeter nos propres emails\nif (OWN_EMAILS.has(fromEmail)) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'own_email', from_email: fromEmail } }];\n}\n// Rejeter auto-réponses\nconst autoReply = ['auto-reply','out of office','автоответ','нет на месте','delivery','mailer-daemon'];\nif (autoReply.some(s => subject.includes(s))) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'auto_reply', from_email: fromEmail } }];\n}\nreturn [{ json: { ...item, _relevant: true, is_relevant: true, from_email: fromEmail, subject: item.subject || '' } }];"},"name":"Check Relevance","type":"n8n-nodes-base.code","typeVersion":2,"position":[464,304],"id":"3594f7ba-2a8e-465c-a635-e708cd692024"},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"c1","leftValue":"={{$json.is_relevant}}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}],"combinator":"and"},"options":{}},"name":"IF Relevant","type":"n8n-nodes-base.if","typeVersion":2,"position":[704,304],"id":"fce4f47d-ff39-49db-9e12-9c689dd91623"},{"parameters":{"method":"POST","url":"https://api.openai.com/v1/chat/completions","authentication":"predefinedCredentialType","nodeCredentialType":"openAiApi","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={\"model\":\"gpt-4o-mini\",\"temperature\":0.1,\"max_tokens\":500,\"messages\":[{\"role\":\"system\",\"content\":\"Signal flags: strong_accounting_signal={{ $json.strong_accounting_signal }}, weak_signal={{ $json.weak_signal }}. If strong_accounting_signal is true, prioritize ACT/INVOICE categories. If weak_signal is true, be more conservative.\"},{\"role\":\"system\",\"content\":\"You are Pomoshchnik Bukhgalter - Triage vkhodayshchikh pisem for OOO Metal Design. You receive an inbound email that may be a response to a reconciliation act request. Classify it.\\n\\nCategories:\\n- ACT_RECEIVED: supplier sent signed reconciliation act\\n- NOT_OUR_SUPPLIER: they say not our supplier\\n- WRONG_EMAIL: wrong email address\\n- WILL_SEND_LATER: they will send later\\n- REFUSAL: explicit refusal\\n- OTHER: anything else\\n\\nReturn ONLY JSON:\\n{\\\"type\\\":\\\"inbound_triage\\\",\\\"category\\\":\\\"ACT_RECEIVED|NOT_OUR_SUPPLIER|WRONG_EMAIL|WILL_SEND_LATER|REFUSAL|OTHER\\\",\\\"confidence\\\":0.0,\\\"supplier_email\\\":\\\"\\\",\\\"supplier_name\\\":\\\"\\\",\\\"proposed_action\\\":\\\"MARK_RECEIVED|EXCLUDE|UPDATE_EMAIL|POSTPONE|NEED_HUMAN\\\",\\\"new_email\\\":\\\"\\\",\\\"postpone_days\\\":0,\\\"reply_template_id\\\":\\\"T1|T2|T3|NONE\\\",\\\"exclude_reason\\\":\\\"\\\",\\\"notes\\\":\\\"\\\"}\"},{\"role\":\"user\",\"content\":\"From: {{ $json.from_raw }}\\nSubject: {{ $json.subject }}\\nDate: {{ $json.date }}\\nAttachments: {{ $json.attachment_names }}\\n\\nBody:\\n{{ ($json.textPlain || '').slice(0,600) }}\"}]}\"\"}]}","options":{"timeout":30000}},"name":"AI Classify","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[960,208],"id":"453a0531-6081-480c-89ae-0725233f045d","credentials":{"openAiApi":{"id":"44G2SWG1pfhc4c1L","name":"OpenAi account"}}},{"parameters":{"jsCode":"const items = $input.all();\nconst r = [];\nfor (const item of items) {\n let c;\n try {\n c = JSON.parse(item.json.choices[0].message.content.trim());\n } catch(e) {\n c = { category:'OTHER', confidence:0.3, supplier_email:'', notes:'PARSE_ERROR:'+e.message };\n }\n const orig = item.json.original_data || {};\n r.push({ json: {\n ...orig,\n class: c.category || 'OTHER',\n confidence: parseFloat(c.confidence) || 0,\n supplier_email: c.supplier_email || orig.from_email || '',\n supplier_name: c.supplier_name || '',\n proposed_action: c.proposed_action || 'NONE',\n exclude_reason: c.exclude_reason || '',\n ai_notes: c.notes || '',\n raw_category: c.category || 'OTHER',\n }});\n}\nreturn r;"},"name":"Parse Classification","type":"n8n-nodes-base.code","typeVersion":2,"position":[1200,208],"id":"11f1f257-6580-4305-b53a-212dfeff5959"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"options":{}},"name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1200,400],"id":"97c9dbfa-ef11-43b5-b6da-61ef4cc4c0cc","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const inbound = $(\"Parse Classification\").first().json;\nconst rawFrom = $(\"Email Trigger (IMAP)\").first().json.from || \"\";\nconst emailMatch = rawFrom.match(/<([^>]+)>/) || rawFrom.match(/([\\w.+-]+@[\\w-]+\\.[\\w.]+)/);\nconst imapFromEmail = emailMatch ? emailMatch[1].toLowerCase().trim() : rawFrom.toLowerCase().trim();\nconst fromEmail = (inbound.supplier_email || inbound.from_email || imapFromEmail).toLowerCase().trim();\nconst QUARTER = \"2026-Q1\";\nconst tracking = $input.all();\nconst matched = tracking.filter(i => {\n const te = (i.json.email || \"\").toLowerCase().trim();\n return te === fromEmail && (i.json.quarter || \"\") === QUARTER;\n});\nif (matched.length === 0) {\n return [{ json: { _no_match: true, from_email: fromEmail } }];\n}\nreturn matched.map(i => ({ json: { ...i.json, supplier_email: fromEmail, class: inbound.class, confidence: inbound.confidence, proposed_action: inbound.proposed_action, ai_notes: inbound.ai_notes || \"\" }}));"},"name":"Match Supplier","type":"n8n-nodes-base.code","typeVersion":2,"position":[1504,304],"id":"610ede48-a77e-475b-8902-cfcb6118aab6"},{"parameters":{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"f1","leftValue":"={{ $json._no_match }}","rightValue":true,"operator":{"type":"boolean","operation":"notEquals"}}],"combinator":"and"},"options":{}},"name":"IF Found","type":"n8n-nodes-base.if","typeVersion":2,"position":[1760,304],"id":"26dc8fbc-4aff-4d7c-a478-d66b72054294"},{"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $json.supplier_email || $json.from_email }}","status":"={{ $json.class === 'ACT_RECEIVED' ? 'act_received' : 'responded' }}","last_inbound_class":"={{ $json.class }}","notes":"={{ $json.ai_notes || '' }}"},"matchingColumns":["email"],"schema":[]},"options":{}},"name":"Update Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2000,208],"id":"ede2f3ce-288f-49b6-8d02-9706cc33eda4","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"return[{json:{action:'ignored',reason:'Not relevant to act sverki'}}]"},"name":"Ignore","type":"n8n-nodes-base.code","typeVersion":2,"position":[960,512],"id":"139ec916-74ed-4c4d-8e58-3a550050fcd9"},{"parameters":{"operation":"append","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"value":"act_sverki_triage","__rl":true,"mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"values":[{"column":"received_at","fieldValue":"={{ new Date().toISOString() }}"},{"column":"from_email","fieldValue":"={{ $json.from_email }}"},{"column":"subject","fieldValue":"={{ $json.subject }}"},{"column":"class","fieldValue":"={{ $json.class }}"},{"column":"confidence","fieldValue":"={{ $json.confidence }}"},{"column":"proposed_action","fieldValue":"={{ $json.proposed_action }}"},{"column":"supplier_name","fieldValue":"={{ $json.supplier_name }}"},{"column":"ai_notes","fieldValue":"={{ $json.ai_notes }}"}]},"matchingColumns":[],"schema":[]},"options":{}},"name":"Append Triage Review","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1408,608],"id":"e9a2a430-d74a-4fa8-b3ee-cf8273e87290","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict","version":1},"conditions":[{"id":"conf1","leftValue":"={{ $json.confidence }}","rightValue":0.75,"operator":{"type":"number","operation":"gte"}}],"combinator":"and"},"options":{}},"name":"IF High Confidence","type":"n8n-nodes-base.if","typeVersion":2,"position":[1008,304],"id":"4028f276-7177-4198-acbf-387d5da0a844"},{"parameters":{"options":{}},"type":"n8n-nodes-base.emailReadImap","typeVersion":2.1,"position":[80,0],"id":"af2d1df6-4be1-447b-bbd1-684787413f9c","name":"Email Trigger (IMAP)","credentials":{"imap":{"id":"IkVBLt5LyVFWnnAh","name":"IMAP account"}}},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst fromDomain = (item.from_email || '').toLowerCase().split('@')[1] || '';\nconst subject = (item.subject || '').toLowerCase();\nconst body = (item.body_snippet || '').substring(0, 600).toLowerCase();\n\nconst MARKETING_DOMAINS = [\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','sendgrid.com',\n 'amazonses.com','uni-sender.com','unisender.com','sendsay.ru','estismail.ru',\n 'mindbox.ru','list-manage.com','campaign-archive.com','mandrillapp.com',\n 'sparkpostmail.com','postmarkapp.com','mailgun.org'\n];\nconst MARKETING_KW = [\n 'акция','скидка','скидки','распродажа','вебинар','подпишитесь','промокод',\n 'unsubscribe','newsletter','discount','sale','отписаться','рассылка',\n 'спецпредложение','купон','подписка на'\n];\n\nconst isMarketingDomain = MARKETING_DOMAINS.some(d => fromDomain.includes(d));\nconst hasMarketingKw = MARKETING_KW.some(kw => subject.includes(kw) || body.includes(kw));\nconst isMarketing = isMarketingDomain || hasMarketingKw;\n\nreturn [{ json: {\n ...item,\n skip_ai: isMarketing,\n pre_category: isMarketing ? 'MARKETING' : null,\n pre_confidence:isMarketing ? 0.95 : null,\n pre_reason: isMarketing\n ? (isMarketingDomain ? 'marketing_domain:' + fromDomain : 'marketing_keyword')\n : null,\n}}];\n"},"id":"triage-marketing-bl","name":"Marketing Blacklist","type":"n8n-nodes-base.code","typeVersion":2,"position":[608,304]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"id":"mkt1","leftValue":"={{ $json.skip_ai }}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}]},"options":{}},"id":"triage-if-marketing","name":"IF Marketing","type":"n8n-nodes-base.if","typeVersion":2,"position":[800,304]},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst text = ((item.subject || '') + ' ' + (item.body_snippet || '').substring(0, 1500)).toLowerCase();\n\nconst STRONG = [\n 'счет ','счёт ','счет-фактур','счёт-фактур','упд','универсальный передаточный',\n 'накладная','акт сверки','акт-сверки','сверка','взаиморасчет','взаиморасчёт',\n 'коммерческое предложение','договор','спецификация','оплата','платеж','платёж',\n 'invoice','contract','поставк','отгрузк','тtn','торг-12','торг12'\n];\n\nconst hasStrong = STRONG.some(kw => text.includes(kw));\n\nreturn [{ json: {\n ...item,\n strong_accounting_signal: hasStrong,\n weak_signal: !hasStrong,\n}}];\n"},"id":"triage-signal-check","name":"Signal Check","type":"n8n-nodes-base.code","typeVersion":2,"position":[1008,400]}],"connections":{"Check Relevance":{"main":[[{"node":"IF Relevant","type":"main","index":0}]]},"IF Relevant":{"main":[[{"node":"Marketing Blacklist","type":"main","index":0}],[{"node":"Ignore","type":"main","index":0}]]},"AI Classify":{"main":[[{"node":"Parse Classification","type":"main","index":0}]]},"Parse Classification":{"main":[[{"node":"IF High Confidence","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Match Supplier","type":"main","index":0}]]},"Match Supplier":{"main":[[{"node":"IF Found","type":"main","index":0}]]},"IF Found":{"main":[[{"node":"Update Tracking","type":"main","index":0}],[]]},"IF High Confidence":{"main":[[{"node":"Read Tracking","type":"main","index":0}],[{"node":"Append Triage Review","type":"main","index":0}]]},"Append Triage Review":{"main":[[{"node":"Ignore","type":"main","index":0}]]},"Email Trigger (IMAP)":{"main":[[{"node":"Check Relevance","type":"main","index":0}]]},"Marketing Blacklist":{"main":[[{"node":"IF Marketing","type":"main","index":0}]]},"IF Marketing":{"main":[[{"node":"Ignore","type":"main","index":0}],[{"node":"Signal Check","type":"main","index":0}]]},"Signal Check":{"main":[[{"node":"AI Classify","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":{"node:Email Trigger (IMAP)":{"lastMessageUid":37061}},"meta":null,"pinData":{},"versionId":"425d5623-07fa-4ea8-bc3f-057690806163","activeVersionId":"425d5623-07fa-4ea8-bc3f-057690806163","triggerCount":1,"shared":[{"updatedAt":"2026-03-01T19:58:49.774Z","createdAt":"2026-03-01T19:58:49.774Z","role":"workflow:owner","workflowId":"XDmTEZdyvXmGjEWo","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-03T10:33:15.000Z","createdAt":"2026-03-03T10:31:19.494Z","versionId":"425d5623-07fa-4ea8-bc3f-057690806163","workflowId":"XDmTEZdyvXmGjEWo","nodes":[{"parameters":{"jsCode":"const OWN_EMAILS = new Set([\n 'comptable@metdesigntver.ru',\n 'purchase@metdesigntver.ru',\n 'info@metdesigntver.ru',\n]);\nconst item = $input.first().json;\nconst fromRaw = item.from || item.fromAddress || '';\nconst fromEmail = (fromRaw.match(/<(.+)>/) || [,''])[1].toLowerCase() || fromRaw.toLowerCase().trim();\nconst subject = (item.subject || '').toLowerCase();\n// Rejeter nos propres emails\nif (OWN_EMAILS.has(fromEmail)) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'own_email', from_email: fromEmail } }];\n}\n// Rejeter auto-réponses\nconst autoReply = ['auto-reply','out of office','автоответ','нет на месте','delivery','mailer-daemon'];\nif (autoReply.some(s => subject.includes(s))) {\n return [{ json: { ...item, _relevant: false, is_relevant: false, _skip_reason: 'auto_reply', from_email: fromEmail } }];\n}\nreturn [{ json: { ...item, _relevant: true, is_relevant: true, from_email: fromEmail, subject: item.subject || '' } }];"},"name":"Check Relevance","type":"n8n-nodes-base.code","typeVersion":2,"position":[464,304],"id":"3594f7ba-2a8e-465c-a635-e708cd692024"},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"c1","leftValue":"={{$json.is_relevant}}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}],"combinator":"and"},"options":{}},"name":"IF Relevant","type":"n8n-nodes-base.if","typeVersion":2,"position":[704,304],"id":"fce4f47d-ff39-49db-9e12-9c689dd91623"},{"parameters":{"method":"POST","url":"https://api.openai.com/v1/chat/completions","authentication":"predefinedCredentialType","nodeCredentialType":"openAiApi","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={\"model\":\"gpt-4o-mini\",\"temperature\":0.1,\"max_tokens\":500,\"messages\":[{\"role\":\"system\",\"content\":\"Signal flags: strong_accounting_signal={{ $json.strong_accounting_signal }}, weak_signal={{ $json.weak_signal }}. If strong_accounting_signal is true, prioritize ACT/INVOICE categories. If weak_signal is true, be more conservative.\"},{\"role\":\"system\",\"content\":\"You are Pomoshchnik Bukhgalter - Triage vkhodayshchikh pisem for OOO Metal Design. You receive an inbound email that may be a response to a reconciliation act request. Classify it.\\n\\nCategories:\\n- ACT_RECEIVED: supplier sent signed reconciliation act\\n- NOT_OUR_SUPPLIER: they say not our supplier\\n- WRONG_EMAIL: wrong email address\\n- WILL_SEND_LATER: they will send later\\n- REFUSAL: explicit refusal\\n- OTHER: anything else\\n\\nReturn ONLY JSON:\\n{\\\"type\\\":\\\"inbound_triage\\\",\\\"category\\\":\\\"ACT_RECEIVED|NOT_OUR_SUPPLIER|WRONG_EMAIL|WILL_SEND_LATER|REFUSAL|OTHER\\\",\\\"confidence\\\":0.0,\\\"supplier_email\\\":\\\"\\\",\\\"supplier_name\\\":\\\"\\\",\\\"proposed_action\\\":\\\"MARK_RECEIVED|EXCLUDE|UPDATE_EMAIL|POSTPONE|NEED_HUMAN\\\",\\\"new_email\\\":\\\"\\\",\\\"postpone_days\\\":0,\\\"reply_template_id\\\":\\\"T1|T2|T3|NONE\\\",\\\"exclude_reason\\\":\\\"\\\",\\\"notes\\\":\\\"\\\"}\"},{\"role\":\"user\",\"content\":\"From: {{ $json.from_raw }}\\nSubject: {{ $json.subject }}\\nDate: {{ $json.date }}\\nAttachments: {{ $json.attachment_names }}\\n\\nBody:\\n{{ ($json.textPlain || '').slice(0,600) }}\"}]}\"\"}]}","options":{"timeout":30000}},"name":"AI Classify","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[960,208],"id":"453a0531-6081-480c-89ae-0725233f045d","credentials":{"openAiApi":{"id":"44G2SWG1pfhc4c1L","name":"OpenAi account"}}},{"parameters":{"jsCode":"const items = $input.all();\nconst r = [];\nfor (const item of items) {\n let c;\n try {\n c = JSON.parse(item.json.choices[0].message.content.trim());\n } catch(e) {\n c = { category:'OTHER', confidence:0.3, supplier_email:'', notes:'PARSE_ERROR:'+e.message };\n }\n const orig = item.json.original_data || {};\n r.push({ json: {\n ...orig,\n class: c.category || 'OTHER',\n confidence: parseFloat(c.confidence) || 0,\n supplier_email: c.supplier_email || orig.from_email || '',\n supplier_name: c.supplier_name || '',\n proposed_action: c.proposed_action || 'NONE',\n exclude_reason: c.exclude_reason || '',\n ai_notes: c.notes || '',\n raw_category: c.category || 'OTHER',\n }});\n}\nreturn r;"},"name":"Parse Classification","type":"n8n-nodes-base.code","typeVersion":2,"position":[1200,208],"id":"11f1f257-6580-4305-b53a-212dfeff5959"},{"parameters":{"documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"options":{}},"name":"Read Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1200,400],"id":"97c9dbfa-ef11-43b5-b6da-61ef4cc4c0cc","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"const inbound = $(\"Parse Classification\").first().json;\nconst rawFrom = $(\"Email Trigger (IMAP)\").first().json.from || \"\";\nconst emailMatch = rawFrom.match(/<([^>]+)>/) || rawFrom.match(/([\\w.+-]+@[\\w-]+\\.[\\w.]+)/);\nconst imapFromEmail = emailMatch ? emailMatch[1].toLowerCase().trim() : rawFrom.toLowerCase().trim();\nconst fromEmail = (inbound.supplier_email || inbound.from_email || imapFromEmail).toLowerCase().trim();\nconst QUARTER = \"2026-Q1\";\nconst tracking = $input.all();\nconst matched = tracking.filter(i => {\n const te = (i.json.email || \"\").toLowerCase().trim();\n return te === fromEmail && (i.json.quarter || \"\") === QUARTER;\n});\nif (matched.length === 0) {\n return [{ json: { _no_match: true, from_email: fromEmail } }];\n}\nreturn matched.map(i => ({ json: { ...i.json, supplier_email: fromEmail, class: inbound.class, confidence: inbound.confidence, proposed_action: inbound.proposed_action, ai_notes: inbound.ai_notes || \"\" }}));"},"name":"Match Supplier","type":"n8n-nodes-base.code","typeVersion":2,"position":[1504,304],"id":"610ede48-a77e-475b-8902-cfcb6118aab6"},{"parameters":{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"conditions":[{"id":"f1","leftValue":"={{ $json._no_match }}","rightValue":true,"operator":{"type":"boolean","operation":"notEquals"}}],"combinator":"and"},"options":{}},"name":"IF Found","type":"n8n-nodes-base.if","typeVersion":2,"position":[1760,304],"id":"26dc8fbc-4aff-4d7c-a478-d66b72054294"},{"parameters":{"operation":"update","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"email":"={{ $json.supplier_email || $json.from_email }}","status":"={{ $json.class === 'ACT_RECEIVED' ? 'act_received' : 'responded' }}","last_inbound_class":"={{ $json.class }}","notes":"={{ $json.ai_notes || '' }}"},"matchingColumns":["email"],"schema":[]},"options":{}},"name":"Update Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2000,208],"id":"ede2f3ce-288f-49b6-8d02-9706cc33eda4","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"return[{json:{action:'ignored',reason:'Not relevant to act sverki'}}]"},"name":"Ignore","type":"n8n-nodes-base.code","typeVersion":2,"position":[960,512],"id":"139ec916-74ed-4c4d-8e58-3a550050fcd9"},{"parameters":{"operation":"append","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"value":"act_sverki_triage","__rl":true,"mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"values":[{"column":"received_at","fieldValue":"={{ new Date().toISOString() }}"},{"column":"from_email","fieldValue":"={{ $json.from_email }}"},{"column":"subject","fieldValue":"={{ $json.subject }}"},{"column":"class","fieldValue":"={{ $json.class }}"},{"column":"confidence","fieldValue":"={{ $json.confidence }}"},{"column":"proposed_action","fieldValue":"={{ $json.proposed_action }}"},{"column":"supplier_name","fieldValue":"={{ $json.supplier_name }}"},{"column":"ai_notes","fieldValue":"={{ $json.ai_notes }}"}]},"matchingColumns":[],"schema":[]},"options":{}},"name":"Append Triage Review","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1408,608],"id":"e9a2a430-d74a-4fa8-b3ee-cf8273e87290","credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict","version":1},"conditions":[{"id":"conf1","leftValue":"={{ $json.confidence }}","rightValue":0.75,"operator":{"type":"number","operation":"gte"}}],"combinator":"and"},"options":{}},"name":"IF High Confidence","type":"n8n-nodes-base.if","typeVersion":2,"position":[1008,304],"id":"4028f276-7177-4198-acbf-387d5da0a844"},{"parameters":{"options":{}},"type":"n8n-nodes-base.emailReadImap","typeVersion":2.1,"position":[80,0],"id":"af2d1df6-4be1-447b-bbd1-684787413f9c","name":"Email Trigger (IMAP)","credentials":{"imap":{"id":"IkVBLt5LyVFWnnAh","name":"IMAP account"}}},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst fromDomain = (item.from_email || '').toLowerCase().split('@')[1] || '';\nconst subject = (item.subject || '').toLowerCase();\nconst body = (item.body_snippet || '').substring(0, 600).toLowerCase();\n\nconst MARKETING_DOMAINS = [\n 'mailchimp.com','sendpulse.com','getresponse.com','sendgrid.net','sendgrid.com',\n 'amazonses.com','uni-sender.com','unisender.com','sendsay.ru','estismail.ru',\n 'mindbox.ru','list-manage.com','campaign-archive.com','mandrillapp.com',\n 'sparkpostmail.com','postmarkapp.com','mailgun.org'\n];\nconst MARKETING_KW = [\n 'акция','скидка','скидки','распродажа','вебинар','подпишитесь','промокод',\n 'unsubscribe','newsletter','discount','sale','отписаться','рассылка',\n 'спецпредложение','купон','подписка на'\n];\n\nconst isMarketingDomain = MARKETING_DOMAINS.some(d => fromDomain.includes(d));\nconst hasMarketingKw = MARKETING_KW.some(kw => subject.includes(kw) || body.includes(kw));\nconst isMarketing = isMarketingDomain || hasMarketingKw;\n\nreturn [{ json: {\n ...item,\n skip_ai: isMarketing,\n pre_category: isMarketing ? 'MARKETING' : null,\n pre_confidence:isMarketing ? 0.95 : null,\n pre_reason: isMarketing\n ? (isMarketingDomain ? 'marketing_domain:' + fromDomain : 'marketing_keyword')\n : null,\n}}];\n"},"id":"triage-marketing-bl","name":"Marketing Blacklist","type":"n8n-nodes-base.code","typeVersion":2,"position":[608,304]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"id":"mkt1","leftValue":"={{ $json.skip_ai }}","rightValue":true,"operator":{"type":"boolean","operation":"equals"}}]},"options":{}},"id":"triage-if-marketing","name":"IF Marketing","type":"n8n-nodes-base.if","typeVersion":2,"position":[800,304]},{"parameters":{"jsCode":"\nconst item = $input.item.json;\nconst text = ((item.subject || '') + ' ' + (item.body_snippet || '').substring(0, 1500)).toLowerCase();\n\nconst STRONG = [\n 'счет ','счёт ','счет-фактур','счёт-фактур','упд','универсальный передаточный',\n 'накладная','акт сверки','акт-сверки','сверка','взаиморасчет','взаиморасчёт',\n 'коммерческое предложение','договор','спецификация','оплата','платеж','платёж',\n 'invoice','contract','поставк','отгрузк','тtn','торг-12','торг12'\n];\n\nconst hasStrong = STRONG.some(kw => text.includes(kw));\n\nreturn [{ json: {\n ...item,\n strong_accounting_signal: hasStrong,\n weak_signal: !hasStrong,\n}}];\n"},"id":"triage-signal-check","name":"Signal Check","type":"n8n-nodes-base.code","typeVersion":2,"position":[1008,400]}],"connections":{"Check Relevance":{"main":[[{"node":"IF Relevant","type":"main","index":0}]]},"IF Relevant":{"main":[[{"node":"Marketing Blacklist","type":"main","index":0}],[{"node":"Ignore","type":"main","index":0}]]},"AI Classify":{"main":[[{"node":"Parse Classification","type":"main","index":0}]]},"Parse Classification":{"main":[[{"node":"IF High Confidence","type":"main","index":0}]]},"Read Tracking":{"main":[[{"node":"Match Supplier","type":"main","index":0}]]},"Match Supplier":{"main":[[{"node":"IF Found","type":"main","index":0}]]},"IF Found":{"main":[[{"node":"Update Tracking","type":"main","index":0}],[]]},"IF High Confidence":{"main":[[{"node":"Read Tracking","type":"main","index":0}],[{"node":"Append Triage Review","type":"main","index":0}]]},"Append Triage Review":{"main":[[{"node":"Ignore","type":"main","index":0}]]},"Email Trigger (IMAP)":{"main":[[{"node":"Check Relevance","type":"main","index":0}]]},"Marketing Blacklist":{"main":[[{"node":"IF Marketing","type":"main","index":0}]]},"IF Marketing":{"main":[[{"node":"Ignore","type":"main","index":0}],[{"node":"Signal Check","type":"main","index":0}]]},"Signal Check":{"main":[[{"node":"AI Classify","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":"Version 425d5623","description":"","autosaved":true},"tags":[]},{"updatedAt":"2026-03-03T15:13:25.511Z","createdAt":"2026-03-01T19:18:37.858Z","id":"XsYUfeqmTIBfG0NQ","name":"AI Metrics Dashboard","active":true,"isArchived":false,"nodes":[{"parameters":{"httpMethod":"GET","path":"ai-metrics","responseMode":"responseNode","options":{}},"id":"c99cb419-ab3a-4eeb-aa7e-c4ca2701b572","name":"Webhook Metrics","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[260,300]},{"parameters":{"jsCode":"const data=$input.first().json;\nif(data.action===\"metrics_response\"){return [{json:data}];}\nreturn [{json:{summary:{total_requests:0,message:\"No data\"},today:{},daily:{}}}];"},"id":"15664ee9-33b2-48ce-b92a-25e07435a175","name":"Compute Metrics","type":"n8n-nodes-base.code","typeVersion":2,"position":[700,300]},{"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify(, null, 2) }}","options":{"responseCode":200}},"id":"c3f72db2-1f3b-4f73-8e85-ef0d04b037d5","name":"Respond Metrics","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[920,300]},{"parameters":{"method":"POST","url":"http://localhost:5678/webhook/ai-router-v4","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"get_metrics\"}","options":{"timeout":10000}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[480,300],"id":"fetch-router-metrics","name":"Fetch Router Metrics"}],"connections":{"Webhook Metrics":{"main":[[{"node":"Fetch Router Metrics","type":"main","index":0}]]},"Fetch Router Metrics":{"main":[[{"node":"Compute Metrics","type":"main","index":0}]]},"Compute Metrics":{"main":[[{"node":"Respond Metrics","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","availableInMCP":true},"staticData":null,"meta":null,"pinData":null,"versionId":"154db265-5643-42ff-8015-2b1681c7f965","activeVersionId":"154db265-5643-42ff-8015-2b1681c7f965","triggerCount":1,"shared":[{"updatedAt":"2026-03-01T19:18:37.860Z","createdAt":"2026-03-01T19:18:37.860Z","role":"workflow:owner","workflowId":"XsYUfeqmTIBfG0NQ","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T22:46:58.000Z","createdAt":"2026-03-01T20:03:50.836Z","versionId":"154db265-5643-42ff-8015-2b1681c7f965","workflowId":"XsYUfeqmTIBfG0NQ","nodes":[{"parameters":{"httpMethod":"GET","path":"ai-metrics","responseMode":"responseNode","options":{}},"id":"c99cb419-ab3a-4eeb-aa7e-c4ca2701b572","name":"Webhook Metrics","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[220,300]},{"parameters":{"jsCode":"// Compute Metrics - reads from own staticData shared with AI Router\n// Since staticData is per-workflow, we use n8n internal API to read AI Router's data\n// But simpler: we aggregate what we can from our own knowledge\n\nconst staticData = $getWorkflowStaticData('global');\n\n// If metrics were forwarded here by AI Router, use them\n// Otherwise, return what we have\nconst metrics = staticData.metrics || {};\nconst today = new Date().toISOString().split('T')[0];\n\n// Aggregate all days\nlet totalRequests = 0;\nlet totalCacheHits = 0;\nlet totalOllama = 0;\nlet totalHaiku = 0;\nlet totalSonnet = 0;\nlet totalCostSaved = 0;\nconst dailyBreakdown = [];\n\nconst days = Object.keys(metrics).sort().reverse().slice(0, 30);\nfor (const day of days) {\n const m = metrics[day];\n totalRequests += m.total_requests || 0;\n totalCacheHits += m.cache_hits || 0;\n totalOllama += m.ollama_calls || 0;\n totalHaiku += m.haiku_calls || 0;\n totalSonnet += m.sonnet_calls || 0;\n totalCostSaved += m.estimated_cost_saved || 0;\n dailyBreakdown.push({ date: day, ...m });\n}\n\nconst total = totalRequests + totalCacheHits;\nconst localRate = total > 0 ? ((totalCacheHits + totalOllama) / total * 100).toFixed(1) : '0';\n\nreturn [{\n json: {\n summary: {\n total_requests: total,\n cache_hits: totalCacheHits,\n ollama_calls: totalOllama,\n haiku_calls: totalHaiku,\n sonnet_calls: totalSonnet,\n local_processing_rate: localRate + '%',\n estimated_cost_saved_usd: totalCostSaved.toFixed(4)\n },\n today: metrics[today] || { message: 'No data for today yet' },\n daily_breakdown: dailyBreakdown,\n note: 'Metrics are stored in AI Router workflow staticData. Call AI Router first to generate data.'\n }\n}];"},"id":"15664ee9-33b2-48ce-b92a-25e07435a175","name":"Compute Metrics","type":"n8n-nodes-base.code","typeVersion":2,"position":[440,300]},{"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify(, null, 2) }}","options":{"responseCode":200}},"id":"c3f72db2-1f3b-4f73-8e85-ef0d04b037d5","name":"Respond Metrics","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[660,300]}],"connections":{"Webhook Metrics":{"main":[[{"node":"Compute Metrics","type":"main","index":0}]]},"Compute Metrics":{"main":[[{"node":"Respond Metrics","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":"Version 154db265","description":"","autosaved":false},"tags":[]},{"updatedAt":"2026-03-09T07:06:26.890Z","createdAt":"2026-03-06T19:58:50.611Z","id":"Y5kkG8IIFTxSSlDs","name":"CLAUDE_TERMINAL","active":true,"isArchived":false,"nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[250,300],"parameters":{"httpMethod":"POST","path":"claude-terminal","responseMode":"responseNode","options":{}}},{"id":"code1","name":"Execute","type":"n8n-nodes-base.code","typeVersion":2,"position":[500,300],"parameters":{"jsCode":"const body = $input.first().json.body;\nif (body.secret !== \"mk_terminal_2026\") return [{json:{error:\"unauthorized\"}}];\n\nif (body.mode === \"deploy_workflow\") {\n const http = require(\"http\");\n const wfData = body.wf_json;\n const key = body.n8n_key;\n const createWf = (data) => new Promise((resolve) => {\n const payload = Buffer.from(JSON.stringify(data));\n const req = http.request({hostname:\"localhost\",port:5678,path:\"/api/v1/workflows\",method:\"POST\",\n headers:{\"X-N8N-API-KEY\":key,\"Content-Type\":\"application/json\",\"Content-Length\":payload.length}\n }, (res) => { let b=\"\"; res.on(\"data\",c=>b+=c); res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}); });\n req.on(\"error\",e=>resolve({error:e.message})); req.write(payload); req.end();\n });\n const activateWf = (id) => new Promise((resolve) => {\n const req = http.request({hostname:\"localhost\",port:5678,path:\"/api/v1/workflows/\"+id+\"/activate\",method:\"POST\",\n headers:{\"X-N8N-API-KEY\":key,\"Content-Type\":\"application/json\",\"Content-Length\":2}\n }, (res) => { resolve(res.statusCode); });\n req.on(\"error\",e=>resolve(0)); req.write(\"{}\"); req.end();\n });\n const result = await createWf(wfData);\n if (result.id) {\n const actStatus = await activateWf(result.id);\n return [{json:{id:result.id,name:result.name,activated:actStatus===200,success:true}}];\n }\n return [{json:{error:JSON.stringify(result).substring(0,200),success:false}}];\n}\n\nconst {execSync} = require(\"child_process\");\ntry {\n const out = execSync(body.command, {timeout:120000,encoding:\"utf8\",maxBuffer:5*1024*1024});\n return [{json:{output:out,success:true}}];\n} catch(e) {\n return [{json:{output:e.stdout||\"\",error:e.stderr||e.message,success:false}}];\n}"}},{"id":"resp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[750,300],"parameters":{"respondWith":"json","responseBody":"={{ $json }}","options":{}}}],"connections":{"Webhook":{"main":[[{"node":"Execute","type":"main","index":0}]]},"Execute":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":true},"staticData":null,"meta":null,"pinData":null,"versionId":"92fe9d19-bda7-432a-8a29-0191ecb6b02b","activeVersionId":"92fe9d19-bda7-432a-8a29-0191ecb6b02b","triggerCount":1,"shared":[{"updatedAt":"2026-03-06T19:58:50.616Z","createdAt":"2026-03-06T19:58:50.616Z","role":"workflow:owner","workflowId":"Y5kkG8IIFTxSSlDs","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:06:26.893Z","createdAt":"2026-03-09T07:06:26.893Z","versionId":"92fe9d19-bda7-432a-8a29-0191ecb6b02b","workflowId":"Y5kkG8IIFTxSSlDs","nodes":[{"id":"wh1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[250,300],"parameters":{"httpMethod":"POST","path":"claude-terminal","responseMode":"responseNode","options":{}}},{"id":"code1","name":"Execute","type":"n8n-nodes-base.code","typeVersion":2,"position":[500,300],"parameters":{"jsCode":"const body = $input.first().json.body;\nif (body.secret !== \"mk_terminal_2026\") return [{json:{error:\"unauthorized\"}}];\n\nif (body.mode === \"deploy_workflow\") {\n const http = require(\"http\");\n const wfData = body.wf_json;\n const key = body.n8n_key;\n const createWf = (data) => new Promise((resolve) => {\n const payload = Buffer.from(JSON.stringify(data));\n const req = http.request({hostname:\"localhost\",port:5678,path:\"/api/v1/workflows\",method:\"POST\",\n headers:{\"X-N8N-API-KEY\":key,\"Content-Type\":\"application/json\",\"Content-Length\":payload.length}\n }, (res) => { let b=\"\"; res.on(\"data\",c=>b+=c); res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}); });\n req.on(\"error\",e=>resolve({error:e.message})); req.write(payload); req.end();\n });\n const activateWf = (id) => new Promise((resolve) => {\n const req = http.request({hostname:\"localhost\",port:5678,path:\"/api/v1/workflows/\"+id+\"/activate\",method:\"POST\",\n headers:{\"X-N8N-API-KEY\":key,\"Content-Type\":\"application/json\",\"Content-Length\":2}\n }, (res) => { resolve(res.statusCode); });\n req.on(\"error\",e=>resolve(0)); req.write(\"{}\"); req.end();\n });\n const result = await createWf(wfData);\n if (result.id) {\n const actStatus = await activateWf(result.id);\n return [{json:{id:result.id,name:result.name,activated:actStatus===200,success:true}}];\n }\n return [{json:{error:JSON.stringify(result).substring(0,200),success:false}}];\n}\n\nconst {execSync} = require(\"child_process\");\ntry {\n const out = execSync(body.command, {timeout:120000,encoding:\"utf8\",maxBuffer:5*1024*1024});\n return [{json:{output:out,success:true}}];\n} catch(e) {\n return [{json:{output:e.stdout||\"\",error:e.stderr||e.message,success:false}}];\n}"}},{"id":"resp1","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[750,300],"parameters":{"respondWith":"json","responseBody":"={{ $json }}","options":{}}}],"connections":{"Webhook":{"main":[[{"node":"Execute","type":"main","index":0}]]},"Execute":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-02-27T19:48:20.462Z","createdAt":"2026-02-27T19:48:20.462Z","id":"a354as1QiYGTblhp","name":"PREPARE_LIST — Phase 1: Write to Review","active":false,"isArchived":false,"nodes":[{"parameters":{},"id":"trigger-phase1","name":"On Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[220,300]},{"parameters":{"jsCode":"// Phase 1 — Read \"Feuille 1\" with exponential backoff\n// This Code node embeds the sheets-client logic directly for n8n portability.\n\nconst SPREADSHEET_ID = $env.SPREADSHEET_ID;\nconst SOURCE_RANGE = $env.SOURCE_RANGE || \"'Feuille 1'!A1:Z\";\nconst MAX_RETRIES = 5;\nconst INITIAL_BACKOFF = 1000;\nconst RATE_LIMIT_MS = 1000;\n\nfunction sleep(ms) { return new Promise(r => setTimeout(r, ms)); }\n\nfunction isRetryable(status) {\n return [429, 500, 502, 503, 504].includes(status);\n}\n\nasync function fetchWithBackoff(url, options) {\n let backoff = INITIAL_BACKOFF;\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n const resp = await fetch(url, options);\n if (resp.ok) return resp.json();\n if (!isRetryable(resp.status) || attempt === MAX_RETRIES) {\n throw new Error(`Google Sheets API error ${resp.status}: ${await resp.text()}`);\n }\n const jitter = Math.floor(Math.random() * 500);\n const wait = backoff + jitter;\n console.warn(`Retry ${attempt}/${MAX_RETRIES}, status=${resp.status}, wait=${wait}ms`);\n await sleep(wait);\n backoff *= 2;\n }\n}\n\n// Rate-limit: wait before call\nawait sleep(RATE_LIMIT_MS);\n\n// Use n8n's built-in OAuth2 credential to get access token\nconst credentials = await this.getCredentials('googleSheetsOAuth2Api');\nconst accessToken = credentials.oauthTokenData?.access_token;\n\nif (!accessToken) throw new Error('No Google OAuth2 access token available');\n\nconst encodedRange = encodeURIComponent(SOURCE_RANGE);\nconst url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${encodedRange}?valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=FORMATTED_STRING`;\n\nconst data = await fetchWithBackoff(url, {\n headers: { Authorization: `Bearer ${accessToken}` }\n});\n\nconst values = data.values || [];\nif (values.length === 0) return [{ json: { rows: [], headers: [] } }];\n\nconst headers = values[0].map(String);\nconst rows = values.slice(1).map((row, idx) => {\n const obj = {};\n headers.forEach((h, i) => { obj[h] = row[i] !== undefined ? row[i] : null; });\n return obj;\n});\n\nreturn [{ json: { headers, rows, count: rows.length } }];"},"id":"read-source","name":"Read Source Sheet","type":"n8n-nodes-base.code","typeVersion":2,"position":[440,300],"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}},{"parameters":{"jsCode":"// Transform source rows into review rows\n\nconst input = $input.first().json;\nconst rows = input.rows || [];\n\nconst REVIEW_HEADERS = [\n 'row_id','supplier_id','supplier_name','document_number','document_date',\n 'debit','credit','balance','currency','comment','status','review_ts'\n];\n\nconst now = new Date().toISOString();\nconst ts = Date.now();\n\nconst reviewRows = rows.map((r, i) => ({\n row_id: `R-${ts}-${i}`,\n supplier_id: r.supplier_id ?? '',\n supplier_name: r.supplier_name ?? '',\n document_number: r.document_number ?? '',\n document_date: r.document_date ?? '',\n debit: r.debit ?? 0,\n credit: r.credit ?? 0,\n balance: r.balance ?? 0,\n currency: r.currency ?? '',\n comment: r.comment ?? '',\n status: 'pending_review',\n review_ts: now\n}));\n\nreturn [{ json: { reviewHeaders: REVIEW_HEADERS, reviewRows, count: reviewRows.length } }];"},"id":"transform-review","name":"Transform to Review Rows","type":"n8n-nodes-base.code","typeVersion":2,"position":[660,300]},{"parameters":{"jsCode":"// Write review rows to act_sverki_review via Sheets API with backoff\n\nconst SPREADSHEET_ID = $env.SPREADSHEET_ID;\nconst REVIEW_RANGE = $env.REVIEW_RANGE || \"'act_sverki_review'!A1\";\nconst MAX_RETRIES = 5;\nconst INITIAL_BACKOFF = 1000;\nconst RATE_LIMIT_MS = 1000;\n\nfunction sleep(ms) { return new Promise(r => setTimeout(r, ms)); }\nfunction isRetryable(s) { return [429,500,502,503,504].includes(s); }\n\nasync function fetchWithBackoff(url, options) {\n let backoff = INITIAL_BACKOFF;\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n const resp = await fetch(url, options);\n if (resp.ok) return resp.json();\n if (!isRetryable(resp.status) || attempt === MAX_RETRIES) {\n throw new Error(`Sheets API error ${resp.status}: ${await resp.text()}`);\n }\n await sleep(backoff + Math.floor(Math.random() * 500));\n backoff *= 2;\n }\n}\n\nconst input = $input.first().json;\nconst { reviewHeaders, reviewRows } = input;\n\nif (!reviewRows || reviewRows.length === 0) {\n return [{ json: { updatedRows: 0, message: 'No rows to write' } }];\n}\n\nconst values = reviewRows.map(row => reviewHeaders.map(h => row[h] ?? ''));\n\nawait sleep(RATE_LIMIT_MS);\n\nconst credentials = await this.getCredentials('googleSheetsOAuth2Api');\nconst accessToken = credentials.oauthTokenData?.access_token;\nif (!accessToken) throw new Error('No OAuth2 access token');\n\nconst encodedRange = encodeURIComponent(REVIEW_RANGE);\nconst url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${encodedRange}:append?valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS`;\n\nconst result = await fetchWithBackoff(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({ values })\n});\n\nreturn [{ json: {\n updatedRows: result.updates?.updatedRows ?? reviewRows.length,\n phase: 'writeToReview',\n success: true\n} }];"},"id":"write-review","name":"Write to Review Sheet","type":"n8n-nodes-base.code","typeVersion":2,"position":[880,300],"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"On Trigger":{"main":[[{"node":"Read Source Sheet","type":"main","index":0}]]},"Read Source Sheet":{"main":[[{"node":"Transform to Review Rows","type":"main","index":0}]]},"Transform to Review Rows":{"main":[[{"node":"Write to Review Sheet","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"f910145d-52b5-4fe4-94bb-4206dee6fed1","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-02-27T19:48:20.466Z","createdAt":"2026-02-27T19:48:20.466Z","role":"workflow:owner","workflowId":"a354as1QiYGTblhp","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-03T15:12:57.538Z","createdAt":"2026-02-28T08:13:47.130Z","id":"aY6grkgKPKWqalHg","name":"ORCHESTRATOR_ROUTER","active":true,"isArchived":false,"nodes":[{"id":"fbc1817d-28ab-41b1-a0f7-ef139a802991","name":"Webhook Router","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,400],"parameters":{"path":"orchestrator","httpMethod":"POST","responseMode":"responseNode","options":{}},"webhookId":"fbc1817d-28ab-41b1-a0f7-ef139a802991"},{"id":"orch-router","name":"Router","type":"n8n-nodes-base.code","typeVersion":2,"position":[420,400],"parameters":{"jsCode":"\nconst body = $input.item.json.body || $input.item.json;\nconst {\n intent = '',\n supplier_id = null,\n supplier_name = null,\n thread_id = null,\n message_id = null,\n doc_id = null,\n constraints = {},\n last_state_summary = ''\n} = body;\n\nconst trace_id = 'trc-' + Date.now() + '-' + Math.random().toString(36).slice(2,7);\n\n// Mapping intent → workflow\nconst ROUTES = {\n 'acte_sverki_send': 'wf_act_sverki_quarterly',\n 'acte_sverki_prepare': 'wf_supplier_sync',\n 'acte_sverki_relance': 'wf_act_sverki_quarterly',\n 'classify_supplier_email': 'wf_email_scan',\n 'update_supplier_db': 'wf_supplier_sync',\n 'send_email': 'wf_email_writer',\n 'log': 'wf_log_only',\n 'ping': 'wf_log_only',\n 'send_excel_validation': 'wf_supplier_sync',\n};\n\n// Vérification champs requis par intent\nconst REQUIRED = {\n 'acte_sverki_send': [],\n 'acte_sverki_prepare': [],\n 'acte_sverki_relance': ['supplier_id'],\n 'classify_supplier_email': ['thread_id'],\n 'update_supplier_db': ['supplier_id'],\n 'send_email': ['supplier_id', 'doc_id'],\n 'log': [],\n 'ping': [],\n};\n\nconst required = REQUIRED[intent] || [];\nconst missing = required.filter(k => !body[k]);\n\nif (!intent) {\n return [{ json: {\n route: 'ASK_MIN_CONTEXT',\n workflow: null,\n payload: {},\n ask: ['intent'],\n trace_id,\n state_update: { summary: 'Intent manquant.', important_ids: {} }\n }}];\n}\n\nif (missing.length > 0) {\n return [{ json: {\n route: 'ASK_MIN_CONTEXT',\n workflow: null,\n payload: {},\n ask: missing,\n trace_id,\n state_update: { summary: `Champs manquants pour ${intent}.`, important_ids: {} }\n }}];\n}\n\nconst workflow = ROUTES[intent] || 'wf_log_only';\n\n// Payload minimal\nconst payload = {\n trace_id,\n intent,\n language: constraints.language || 'ru',\n output_mode: constraints.output_mode || 'short',\n};\nif (supplier_id) payload.supplier_id = supplier_id;\nif (supplier_name) payload.supplier_name = supplier_name;\nif (thread_id) payload.thread_id = thread_id;\nif (message_id) payload.message_id = message_id;\nif (doc_id) payload.doc_id = doc_id;\nif (constraints.quarter) payload.quarter = constraints.quarter;\n\nconst route = workflow === 'wf_log_only' && intent === 'ping' ? 'DONE' : 'RUN_N8N';\n\nreturn [{ json: {\n route,\n workflow,\n payload,\n ask: [],\n trace_id,\n intent,\n state_update: {\n summary: `Route: ${route} → ${workflow} | intent: ${intent} | trace: ${trace_id}`,\n important_ids: {\n supplier_id: supplier_id || null,\n thread_id: thread_id || null,\n doc_id: doc_id || null\n }\n }\n}}];\n"}},{"id":"orch-switch","name":"Switch Workflow","type":"n8n-nodes-base.switch","typeVersion":3,"position":[640,400],"parameters":{"mode":"rules","rules":{"values":[{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_act_sverki_quarterly","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_supplier_sync","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_email_scan","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_email_writer","operator":{"type":"string","operation":"equals"}}]}}]},"options":{"fallbackOutput":"extra"}}},{"id":"orch-call-sverki","name":"Call ACT_SVERKI","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,200],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-send-requests","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.payload) }}","options":{"timeout":60000}}},{"id":"orch-call-sync","name":"Call SUPPLIER_SYNC","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,340],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.payload) }}","options":{"timeout":120000}}},{"id":"orch-call-scan","name":"Call EMAIL_SCAN","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,480],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/audit-append","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"email_scan_requested\",\"payload\":{{ JSON.stringify($json.payload) }}}","options":{"timeout":10000}}},{"id":"orch-call-writer","name":"Call EMAIL_WRITER","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,620],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/audit-append","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"email_writer_requested\",\"payload\":{{ JSON.stringify($json.payload) }}}","options":{"timeout":10000}}},{"id":"orch-fallback","name":"Log Only","type":"n8n-nodes-base.code","typeVersion":2,"position":[860,760],"parameters":{"jsCode":"return [{ json: { ...$input.item.json, wf_result: { status: 'logged', message: 'Route DONE ou wf_log_only' } } }];"}},{"id":"orch-format","name":"Format Response","type":"n8n-nodes-base.code","typeVersion":2,"position":[1100,400],"parameters":{"jsCode":"// Récupérer le contexte original du Router\nconst router = $('Router').item.json;\nconst wf_result = $input.item.json;\nreturn [{ json: {\n route: router.route,\n workflow: router.workflow,\n payload: router.payload,\n ask: router.ask,\n trace_id: router.trace_id,\n intent: router.intent,\n wf_result: wf_result,\n state_update: router.state_update\n}}];"}},{"id":"orch-respond","name":"Respond JSON","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1300,400],"parameters":{"respondWith":"json","responseBody":"={{ $json }}","options":{"responseCode":200}}},{"id":"orch-format-tg","name":"Format Telegram Msg","type":"n8n-nodes-base.code","typeVersion":2,"position":[1300,560],"parameters":{"jsCode":"var r = $input.item.json;\nvar route = r.route || \"unknown\";\nvar intent = r.intent || \"\";\nvar wfResult = r.wf_result || {};\nvar stateUpdate = r.state_update || {};\nvar msg = \"\";\n\nif (route === \"ASK_MIN_CONTEXT\") {\n msg = \"Traitement en cours avec les infos disponibles.\";\n} else if (wfResult.status === \"error\" || wfResult.error) {\n var errMsg = wfResult.error || wfResult.message || \"Erreur inconnue\";\n msg = \"Erreur: \" + errMsg;\n} else if (wfResult.summary) {\n msg = wfResult.summary;\n} else if (typeof wfResult.message === \"string\" && wfResult.message.length > 0) {\n msg = wfResult.message;\n} else if (stateUpdate.summary) {\n msg = stateUpdate.summary;\n} else {\n var labels = {\n \"acte_sverki_send\": \"Processus Act Sverki lance\",\n \"acte_sverki_prepare\": \"Preparation liste fournisseurs lancee\",\n \"acte_sverki_relance\": \"Relances fournisseurs envoyees\",\n \"classify_supplier_email\": \"Classification emails lancee\",\n \"send_email\": \"Envoi emails lance\",\n \"send_excel_validation\": \"Generation et envoi des fichiers Excel de validation lance\",\n \"ping\": \"Pong! Systeme OK.\",\n \"log\": \"Action enregistree.\"\n };\n msg = labels[intent] || (\"Action \" + (intent || \"inconnue\") + \" executee.\");\n}\n\nif (!msg || msg.length === 0) {\n msg = \"Workflow execute (intent: \" + intent + \", route: \" + route + \")\";\n}\n\nreturn [{ json: { telegram_text: msg } }];"}},{"id":"orch-send-tg","name":"Send Telegram","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1520,560],"parameters":{"method":"POST","url":"https://api.telegram.org/bot8617383997:AAHhvgJoaXsbi43hYQfkBY4gRBARfxTDrw4/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\"chat_id\": 889172188, \"text\": {{ JSON.stringify($json.telegram_text) }}}","options":{"timeout":10000}}}],"connections":{"Webhook Router":{"main":[[{"node":"Router","type":"main","index":0}]]},"Router":{"main":[[{"node":"Switch Workflow","type":"main","index":0}]]},"Switch Workflow":{"main":[[{"node":"Call ACT_SVERKI","type":"main","index":0}],[{"node":"Call SUPPLIER_SYNC","type":"main","index":0}],[{"node":"Call EMAIL_SCAN","type":"main","index":0}],[{"node":"Call EMAIL_WRITER","type":"main","index":0}],[{"node":"Log Only","type":"main","index":0}]]},"Call ACT_SVERKI":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call SUPPLIER_SYNC":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call EMAIL_SCAN":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call EMAIL_WRITER":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Log Only":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Format Response":{"main":[[{"node":"Respond JSON","type":"main","index":0}]]}},"settings":{"callerPolicy":"workflowsFromSameOwner","availableInMCP":true},"staticData":null,"meta":null,"pinData":null,"versionId":"8fc36c6b-3ffb-474c-a741-54c73858b660","activeVersionId":"8fc36c6b-3ffb-474c-a741-54c73858b660","triggerCount":1,"shared":[{"updatedAt":"2026-02-28T08:13:47.132Z","createdAt":"2026-02-28T08:13:47.132Z","role":"workflow:owner","workflowId":"aY6grkgKPKWqalHg","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-02T21:58:11.267Z","createdAt":"2026-03-02T21:58:11.267Z","versionId":"8fc36c6b-3ffb-474c-a741-54c73858b660","workflowId":"aY6grkgKPKWqalHg","nodes":[{"id":"fbc1817d-28ab-41b1-a0f7-ef139a802991","name":"Webhook Router","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,400],"parameters":{"path":"orchestrator","httpMethod":"POST","responseMode":"responseNode","options":{}},"webhookId":"fbc1817d-28ab-41b1-a0f7-ef139a802991"},{"id":"orch-router","name":"Router","type":"n8n-nodes-base.code","typeVersion":2,"position":[420,400],"parameters":{"jsCode":"\nconst body = $input.item.json.body || $input.item.json;\nconst {\n intent = '',\n supplier_id = null,\n supplier_name = null,\n thread_id = null,\n message_id = null,\n doc_id = null,\n constraints = {},\n last_state_summary = ''\n} = body;\n\nconst trace_id = 'trc-' + Date.now() + '-' + Math.random().toString(36).slice(2,7);\n\n// Mapping intent → workflow\nconst ROUTES = {\n 'acte_sverki_send': 'wf_act_sverki_quarterly',\n 'acte_sverki_prepare': 'wf_supplier_sync',\n 'acte_sverki_relance': 'wf_act_sverki_quarterly',\n 'classify_supplier_email': 'wf_email_scan',\n 'update_supplier_db': 'wf_supplier_sync',\n 'send_email': 'wf_email_writer',\n 'log': 'wf_log_only',\n 'ping': 'wf_log_only',\n 'send_excel_validation': 'wf_supplier_sync',\n};\n\n// Vérification champs requis par intent\nconst REQUIRED = {\n 'acte_sverki_send': [],\n 'acte_sverki_prepare': [],\n 'acte_sverki_relance': ['supplier_id'],\n 'classify_supplier_email': ['thread_id'],\n 'update_supplier_db': ['supplier_id'],\n 'send_email': ['supplier_id', 'doc_id'],\n 'log': [],\n 'ping': [],\n};\n\nconst required = REQUIRED[intent] || [];\nconst missing = required.filter(k => !body[k]);\n\nif (!intent) {\n return [{ json: {\n route: 'ASK_MIN_CONTEXT',\n workflow: null,\n payload: {},\n ask: ['intent'],\n trace_id,\n state_update: { summary: 'Intent manquant.', important_ids: {} }\n }}];\n}\n\nif (missing.length > 0) {\n return [{ json: {\n route: 'ASK_MIN_CONTEXT',\n workflow: null,\n payload: {},\n ask: missing,\n trace_id,\n state_update: { summary: `Champs manquants pour ${intent}.`, important_ids: {} }\n }}];\n}\n\nconst workflow = ROUTES[intent] || 'wf_log_only';\n\n// Payload minimal\nconst payload = {\n trace_id,\n intent,\n language: constraints.language || 'ru',\n output_mode: constraints.output_mode || 'short',\n};\nif (supplier_id) payload.supplier_id = supplier_id;\nif (supplier_name) payload.supplier_name = supplier_name;\nif (thread_id) payload.thread_id = thread_id;\nif (message_id) payload.message_id = message_id;\nif (doc_id) payload.doc_id = doc_id;\nif (constraints.quarter) payload.quarter = constraints.quarter;\n\nconst route = workflow === 'wf_log_only' && intent === 'ping' ? 'DONE' : 'RUN_N8N';\n\nreturn [{ json: {\n route,\n workflow,\n payload,\n ask: [],\n trace_id,\n intent,\n state_update: {\n summary: `Route: ${route} → ${workflow} | intent: ${intent} | trace: ${trace_id}`,\n important_ids: {\n supplier_id: supplier_id || null,\n thread_id: thread_id || null,\n doc_id: doc_id || null\n }\n }\n}}];\n"}},{"id":"orch-switch","name":"Switch Workflow","type":"n8n-nodes-base.switch","typeVersion":3,"position":[640,400],"parameters":{"mode":"rules","rules":{"values":[{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_act_sverki_quarterly","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_supplier_sync","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_email_scan","operator":{"type":"string","operation":"equals"}}]}},{"conditions":{"options":{"caseSensitive":false,"leftValue":"","typeValidation":"loose"},"combinator":"and","conditions":[{"leftValue":"={{ $json.workflow }}","rightValue":"wf_email_writer","operator":{"type":"string","operation":"equals"}}]}}]},"options":{"fallbackOutput":"extra"}}},{"id":"orch-call-sverki","name":"Call ACT_SVERKI","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,200],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-send-requests","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.payload) }}","options":{"timeout":60000}}},{"id":"orch-call-sync","name":"Call SUPPLIER_SYNC","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,340],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/act-sverki-p1-write-review","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.payload) }}","options":{"timeout":120000}}},{"id":"orch-call-scan","name":"Call EMAIL_SCAN","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,480],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/audit-append","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"email_scan_requested\",\"payload\":{{ JSON.stringify($json.payload) }}}","options":{"timeout":10000}}},{"id":"orch-call-writer","name":"Call EMAIL_WRITER","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[860,620],"parameters":{"method":"POST","url":"http://n8n:5678/webhook/audit-append","sendBody":true,"specifyBody":"json","jsonBody":"={\"action\":\"email_writer_requested\",\"payload\":{{ JSON.stringify($json.payload) }}}","options":{"timeout":10000}}},{"id":"orch-fallback","name":"Log Only","type":"n8n-nodes-base.code","typeVersion":2,"position":[860,760],"parameters":{"jsCode":"return [{ json: { ...$input.item.json, wf_result: { status: 'logged', message: 'Route DONE ou wf_log_only' } } }];"}},{"id":"orch-format","name":"Format Response","type":"n8n-nodes-base.code","typeVersion":2,"position":[1100,400],"parameters":{"jsCode":"// Récupérer le contexte original du Router\nconst router = $('Router').item.json;\nconst wf_result = $input.item.json;\nreturn [{ json: {\n route: router.route,\n workflow: router.workflow,\n payload: router.payload,\n ask: router.ask,\n trace_id: router.trace_id,\n intent: router.intent,\n wf_result: wf_result,\n state_update: router.state_update\n}}];"}},{"id":"orch-respond","name":"Respond JSON","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1300,400],"parameters":{"respondWith":"json","responseBody":"={{ $json }}","options":{"responseCode":200}}},{"id":"orch-format-tg","name":"Format Telegram Msg","type":"n8n-nodes-base.code","typeVersion":2,"position":[1300,560],"parameters":{"jsCode":"var r = $input.item.json;\nvar route = r.route || \"unknown\";\nvar intent = r.intent || \"\";\nvar wfResult = r.wf_result || {};\nvar stateUpdate = r.state_update || {};\nvar msg = \"\";\n\nif (route === \"ASK_MIN_CONTEXT\") {\n msg = \"Traitement en cours avec les infos disponibles.\";\n} else if (wfResult.status === \"error\" || wfResult.error) {\n var errMsg = wfResult.error || wfResult.message || \"Erreur inconnue\";\n msg = \"Erreur: \" + errMsg;\n} else if (wfResult.summary) {\n msg = wfResult.summary;\n} else if (typeof wfResult.message === \"string\" && wfResult.message.length > 0) {\n msg = wfResult.message;\n} else if (stateUpdate.summary) {\n msg = stateUpdate.summary;\n} else {\n var labels = {\n \"acte_sverki_send\": \"Processus Act Sverki lance\",\n \"acte_sverki_prepare\": \"Preparation liste fournisseurs lancee\",\n \"acte_sverki_relance\": \"Relances fournisseurs envoyees\",\n \"classify_supplier_email\": \"Classification emails lancee\",\n \"send_email\": \"Envoi emails lance\",\n \"send_excel_validation\": \"Generation et envoi des fichiers Excel de validation lance\",\n \"ping\": \"Pong! Systeme OK.\",\n \"log\": \"Action enregistree.\"\n };\n msg = labels[intent] || (\"Action \" + (intent || \"inconnue\") + \" executee.\");\n}\n\nif (!msg || msg.length === 0) {\n msg = \"Workflow execute (intent: \" + intent + \", route: \" + route + \")\";\n}\n\nreturn [{ json: { telegram_text: msg } }];"}},{"id":"orch-send-tg","name":"Send Telegram","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1520,560],"parameters":{"method":"POST","url":"https://api.telegram.org/bot8617383997:AAHhvgJoaXsbi43hYQfkBY4gRBARfxTDrw4/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\"chat_id\": 889172188, \"text\": {{ JSON.stringify($json.telegram_text) }}}","options":{"timeout":10000}}}],"connections":{"Webhook Router":{"main":[[{"node":"Router","type":"main","index":0}]]},"Router":{"main":[[{"node":"Switch Workflow","type":"main","index":0}]]},"Switch Workflow":{"main":[[{"node":"Call ACT_SVERKI","type":"main","index":0}],[{"node":"Call SUPPLIER_SYNC","type":"main","index":0}],[{"node":"Call EMAIL_SCAN","type":"main","index":0}],[{"node":"Call EMAIL_WRITER","type":"main","index":0}],[{"node":"Log Only","type":"main","index":0}]]},"Call ACT_SVERKI":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call SUPPLIER_SYNC":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call EMAIL_SCAN":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Call EMAIL_WRITER":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Log Only":{"main":[[{"node":"Format Response","type":"main","index":0}]]},"Format Response":{"main":[[{"node":"Respond JSON","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-01T20:18:36.285Z","createdAt":"2026-03-01T19:10:47.337Z","id":"cCDZi9VNVdo8Iztu","name":"AI Router v3 - Intelligent 3-Level","active":false,"isArchived":false,"nodes":[{"parameters":{"multipleMethods":false,"httpMethod":"POST","path":"ai-router","authentication":"none","responseMode":"responseNode","webhookNotice":"","options":{}},"id":"549ee175-e8c1-49bf-8544-1db638acd67b","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[224,464],"webhookId":"27618e8d-df6c-4a12-95c6-60d0f38557cf"},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"// Simple hash function (DJB2) - no crypto module needed\nfunction simpleHash(str) {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash) + str.charCodeAt(i);\n hash = hash & hash; // Convert to 32bit integer\n }\n return 'h_' + Math.abs(hash).toString(36);\n}\n\nconst prompt = $input.first().json.body.prompt || '';\nconst task_type = $input.first().json.body.task_type || 'other';\nconst hash = simpleHash(prompt + '_' + task_type);\n\n// Use workflow static data as cache (persists between executions)\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.cache) staticData.cache = {};\nif (!staticData.metrics) staticData.metrics = {};\n\nconst cache = staticData.cache;\nconst TTL = 86400000; // 24h\n\n// Clean old entries (older than 24h)\nconst now = Date.now();\nfor (const key of Object.keys(cache)) {\n if (now - (cache[key].timestamp || 0) > TTL) {\n delete cache[key];\n }\n}\n\n// Check cache\nif (cache[hash] && (now - cache[hash].timestamp < TTL)) {\n return [{\n json: {\n cached: true,\n response: cache[hash].response,\n hash,\n route: 'cache',\n tokens_saved: cache[hash].estimated_tokens || 0\n }\n }];\n}\n\nreturn [{\n json: {\n cached: false,\n prompt,\n context: $input.first().json.body.context || '',\n task_type,\n source: $input.first().json.body.source || 'unknown',\n hash\n }\n}];","notice":""},"id":"bceb598c-6d48-45b7-81fc-ed5d28388630","name":"Check Cache","type":"n8n-nodes-base.code","typeVersion":2,"position":[448,464]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"cache-hit","leftValue":"={{ $json.cached }}","rightValue":true,"operator":{"type":"boolean","operation":"true"}}],"combinator":"and"},"looseTypeValidation":false,"options":{}},"id":"1f679228-ce78-47b1-a13a-b3dee553c3a3","name":"Cache Hit?","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[672,464]},{"parameters":{"generalNotice":"","respondWith":"json","webhookNotice":"","responseBody":"={{ JSON.stringify({ response: $json.response, route: 'cache', cached: true, tokens_saved: $json.tokens_saved }) }}","options":{"responseCode":200}},"id":"b94325b1-4355-4f52-a256-028dd41952c8","name":"Respond Cache","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[912,352]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const prompt = $input.first().json.prompt.toLowerCase();\nconst taskType = $input.first().json.task_type || 'other';\nconst promptLength = $input.first().json.prompt.length;\n\n// ══════════════════════════════════════════════════\n// NIVEAU 1 — OLLAMA LOCAL (gratuit)\n// ══════════════════════════════════════════════════\nconst ollamaKeywords = [\n 'format json', 'formater', 'valider', 'validator',\n 'convertir', 'converter', 'traduire', 'translate',\n 'corriger syntaxe', 'fix syntax', 'lister',\n 'template', 'modèle', 'exemple simple',\n 'c\\'est quoi', 'what is', 'définition',\n 'résumer en une ligne', 'one line summary'\n];\nconst ollamaTaskTypes = ['format', 'validate', 'convert', 'translate'];\n\n// ══════════════════════════════════════════════════\n// NIVEAU 2 — CLAUDE HAIKU (économique)\n// ══════════════════════════════════════════════════\nconst haikuKeywords = [\n 'debug', 'erreur', 'error', 'bug', 'fix',\n 'corriger', 'pourquoi ça marche pas', 'doesn\\'t work',\n 'expliquer', 'explain', 'comment faire', 'how to',\n 'résumer', 'summarize', 'améliorer', 'improve',\n 'modifier', 'modify', 'adapter', 'adjust',\n 'node n8n', 'code node', 'expression n8n'\n];\nconst haikuTaskTypes = ['debug', 'explain', 'fix', 'modify'];\n\n// ══════════════════════════════════════════════════\n// NIVEAU 3 — CLAUDE SONNET (puissant)\n// ══════════════════════════════════════════════════\nconst sonnetKeywords = [\n 'créer un workflow', 'create workflow', 'nouveau workflow',\n 'architecture', 'concevoir', 'design',\n 'business process', 'stratégie', 'strategy',\n 'multi-étapes', 'multi-step', 'complexe',\n 'optimiser workflow', 'refactorer', 'refactor',\n 'analyser en profondeur', 'deep analysis',\n 'comparer et recommander', 'évaluer'\n];\nconst sonnetTaskTypes = ['create', 'architect', 'analyze', 'strategy'];\n\n// ══════════════════════════════════════════════════\n// LOGIQUE DE CLASSIFICATION\n// ══════════════════════════════════════════════════\nlet route = 'haiku';\nlet confidence = 0.5;\nlet maxTokens = 512;\nlet reason = 'default';\n\n// 1. Par task_type explicite\nif (ollamaTaskTypes.includes(taskType)) {\n route = 'ollama'; confidence = 0.9; maxTokens = 512;\n reason = `task_type=${taskType}`;\n}\nelse if (haikuTaskTypes.includes(taskType)) {\n route = 'haiku'; confidence = 0.9; maxTokens = 512;\n reason = `task_type=${taskType}`;\n}\nelse if (sonnetTaskTypes.includes(taskType)) {\n route = 'sonnet'; confidence = 0.9; maxTokens = 2048;\n reason = `task_type=${taskType}`;\n}\n\n// 2. Par mots-clés\nif (confidence < 0.7) {\n for (const kw of sonnetKeywords) {\n if (prompt.includes(kw)) {\n route = 'sonnet'; confidence = 0.8; maxTokens = 2048;\n reason = `keyword=\"${kw}\"`; break;\n }\n }\n}\nif (confidence < 0.7) {\n for (const kw of ollamaKeywords) {\n if (prompt.includes(kw)) {\n route = 'ollama'; confidence = 0.8; maxTokens = 512;\n reason = `keyword=\"${kw}\"`; break;\n }\n }\n}\nif (confidence < 0.7) {\n for (const kw of haikuKeywords) {\n if (prompt.includes(kw)) {\n route = 'haiku'; confidence = 0.75; maxTokens = 512;\n reason = `keyword=\"${kw}\"`; break;\n }\n }\n}\n\n// 3. Par longueur\nif (confidence < 0.6) {\n if (promptLength < 100) {\n route = 'ollama'; reason = 'short_prompt';\n } else if (promptLength > 500) {\n route = 'sonnet'; maxTokens = 2048; reason = 'long_prompt';\n } else {\n route = 'haiku'; reason = 'medium_prompt';\n }\n}\n\nreturn [{\n json: {\n route,\n confidence,\n maxTokens,\n reason,\n prompt: $input.first().json.prompt,\n context: $input.first().json.context || '',\n hash: $input.first().json.hash\n }\n}];","notice":""},"id":"cac24378-79da-4dbd-ab92-3664297a7393","name":"Router 3 Niveaux","type":"n8n-nodes-base.code","typeVersion":2,"position":[912,560]},{"parameters":{"mode":"rules","rules":{"values":[{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"leftValue":"={{ $json.route }}","rightValue":"ollama","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"renameOutput":"Ollama"},{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"leftValue":"={{ $json.route }}","rightValue":"haiku","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"renameOutput":"Haiku"},{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"leftValue":"={{ $json.route }}","rightValue":"sonnet","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"renameOutput":"Sonnet"}]},"looseTypeValidation":false,"options":{"fallbackOutput":"extra"}},"id":"1f9b0ef4-f0e2-4f3e-9c4a-b439cc6b3182","name":"Route Switch","type":"n8n-nodes-base.switch","typeVersion":3.2,"position":[1152,560]},{"parameters":{"curlImport":"","method":"POST","url":"http://172.17.0.1:11434/api/generate","authentication":"none","provideSslCertificates":false,"sendQuery":false,"sendHeaders":false,"sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify({ model: 'llama3.2:3b', prompt: $json.prompt, stream: false, options: { temperature: 0.3, num_predict: $json.maxTokens || 512 } }) }}","options":{"timeout":30000},"infoMessage":""},"id":"f0c6696d-9c3a-4601-8838-ff60479c8110","name":"Ollama Local","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1408,352]},{"parameters":{"curlImport":"","method":"POST","url":"https://api.anthropic.com/v1/messages","authentication":"none","provideSslCertificates":false,"sendQuery":false,"sendHeaders":true,"specifyHeaders":"keypair","headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $env.ANTHROPIC_API_KEY || $credentials.anthropicApi?.apiKey || '' }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]},"sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: $json.maxTokens || 512, system: [{ type: 'text', text: 'Tu es un assistant technique spécialisé en n8n et automatisation. Réponds de manière concise et directe. Donne uniquement le code corrigé ou la solution, sans explications longues sauf si explicitement demandé. Contexte architecture : Telegram > n8n > Ollama/Claude API. VPS Ubuntu 24.04, 4GB RAM.', cache_control: { type: 'ephemeral' } }], messages: [{ role: 'user', content: $json.prompt }] }) }}","options":{"timeout":30000},"infoMessage":""},"id":"30c409ed-982f-4547-b321-0569390527e1","name":"Claude Haiku","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1408,560]},{"parameters":{"curlImport":"","method":"POST","url":"https://api.anthropic.com/v1/messages","authentication":"none","provideSslCertificates":false,"sendQuery":false,"sendHeaders":true,"specifyHeaders":"keypair","headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $env.ANTHROPIC_API_KEY || $credentials.anthropicApi?.apiKey || '' }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]},"sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: $json.maxTokens || 2048, system: [{ type: 'text', text: 'Tu es un expert en automatisation n8n et architecture de business processes. Tu crées des workflows complets, optimisés et production-ready. Contexte : architecture Telegram > n8n > routeur intelligent (Ollama local + Claude Haiku + Claude Sonnet). VPS Hostinger Ubuntu 24.04, 4GB RAM, 1 CPU. n8n actif. Ollama (llama3.2:3b) actif.', cache_control: { type: 'ephemeral' } }], messages: [{ role: 'user', content: $json.prompt }] }) }}","options":{"timeout":60000},"infoMessage":""},"id":"61ae1766-faed-483d-8bad-8b05d1dada48","name":"Claude Sonnet","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1408,784]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const route = 'ollama';\nconst rawResponse = $input.first().json;\nconst response = rawResponse.response || '';\nconst hash = $('Router 3 Niveaux').first().json.hash;\nconst prompt = $('Router 3 Niveaux').first().json.prompt;\n\nreturn [{\n json: {\n response,\n route,\n hash,\n prompt\n }\n}];","notice":""},"id":"86a6e6e8-d9fd-4f58-b6d8-7e26776f0196","name":"Format Ollama","type":"n8n-nodes-base.code","typeVersion":2,"position":[1648,352]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const route = 'haiku';\nconst rawResponse = $input.first().json;\nconst response = rawResponse.content?.[0]?.text || '';\nconst hash = $('Router 3 Niveaux').first().json.hash;\nconst prompt = $('Router 3 Niveaux').first().json.prompt;\n\nreturn [{\n json: {\n response,\n route,\n hash,\n prompt\n }\n}];","notice":""},"id":"78bbedfa-2e4c-42e9-a678-14f59902718a","name":"Format Haiku","type":"n8n-nodes-base.code","typeVersion":2,"position":[1648,560]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const route = 'sonnet';\nconst rawResponse = $input.first().json;\nconst response = rawResponse.content?.[0]?.text || '';\nconst hash = $('Router 3 Niveaux').first().json.hash;\nconst prompt = $('Router 3 Niveaux').first().json.prompt;\n\nreturn [{\n json: {\n response,\n route,\n hash,\n prompt\n }\n}];","notice":""},"id":"caf3b2c0-d41f-47b8-9c1e-9301dd4d91aa","name":"Format Sonnet","type":"n8n-nodes-base.code","typeVersion":2,"position":[1648,784]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"// Save response to cache and update metrics - using staticData (no fs module)\nconst input = $input.first().json;\nconst hash = input.hash || 'unknown';\nconst route = input.route || 'unknown';\nconst response = input.response || '';\nconst prompt = input.prompt || '';\nconst estimated_tokens = Math.ceil((prompt.length + response.length) / 4);\n\n// Use workflow static data (persists between executions)\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.cache) staticData.cache = {};\nif (!staticData.metrics) staticData.metrics = {};\n\n// Save to cache\nstaticData.cache[hash] = {\n response,\n route,\n timestamp: Date.now(),\n estimated_tokens\n};\n\n// Keep cache under 500 entries\nconst cacheKeys = Object.keys(staticData.cache);\nif (cacheKeys.length > 500) {\n // Remove oldest entries\n const sorted = cacheKeys.sort((a, b) => \n (staticData.cache[a].timestamp || 0) - (staticData.cache[b].timestamp || 0)\n );\n for (let i = 0; i < sorted.length - 400; i++) {\n delete staticData.cache[sorted[i]];\n }\n}\n\n// Update metrics\nconst today = new Date().toISOString().split('T')[0];\nif (!staticData.metrics[today]) {\n staticData.metrics[today] = {\n cache_hits: 0,\n ollama_calls: 0,\n haiku_calls: 0,\n sonnet_calls: 0,\n total_requests: 0,\n estimated_cost_saved: 0\n };\n}\n\nconst m = staticData.metrics[today];\nm.total_requests = (m.total_requests || 0) + 1;\n\n// Pricing per 1M tokens (approx)\nconst PRICES = {\n sonnet_input: 3.0,\n sonnet_output: 15.0,\n haiku_input: 0.25,\n haiku_output: 1.25\n};\n\nif (route === 'ollama') {\n m.ollama_calls++;\n // Saved the cost of a Sonnet call\n m.estimated_cost_saved += (estimated_tokens / 1000000) * (PRICES.sonnet_input + PRICES.sonnet_output);\n} else if (route === 'haiku') {\n m.haiku_calls++;\n // Saved the difference between Sonnet and Haiku\n const haikuCost = (estimated_tokens / 1000000) * (PRICES.haiku_input + PRICES.haiku_output);\n const sonnetCost = (estimated_tokens / 1000000) * (PRICES.sonnet_input + PRICES.sonnet_output);\n m.estimated_cost_saved += (sonnetCost - haikuCost);\n} else if (route === 'sonnet') {\n m.sonnet_calls++;\n // No savings - this is the most expensive route\n}\n\n// Clean old metrics (keep last 30 days)\nconst metricKeys = Object.keys(staticData.metrics);\nif (metricKeys.length > 30) {\n const sorted = metricKeys.sort();\n for (let i = 0; i < sorted.length - 30; i++) {\n delete staticData.metrics[sorted[i]];\n }\n}\n\nreturn [{\n json: {\n route,\n cached: false,\n response,\n hash,\n estimated_tokens,\n metrics_today: staticData.metrics[today]\n }\n}];","notice":""},"id":"ef0a7303-c7ad-45a7-9d3e-649c083c5556","name":"Save Cache + Metrics","type":"n8n-nodes-base.code","typeVersion":2,"position":[1888,560]},{"parameters":{"generalNotice":"","respondWith":"json","webhookNotice":"","responseBody":"={{ JSON.stringify({ response: $json.response, route: $json.route, cached: $json.cached || false }) }}","options":{"responseCode":200}},"id":"b5bab92d-b6ea-4aa2-b9c1-b3a743e6a32c","name":"Respond Final","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2128,560]}],"connections":{"Webhook":{"main":[[{"node":"Check Cache","type":"main","index":0}]]},"Check Cache":{"main":[[{"node":"Cache Hit?","type":"main","index":0}]]},"Cache Hit?":{"main":[[{"node":"Respond Cache","type":"main","index":0}],[{"node":"Router 3 Niveaux","type":"main","index":0}]]},"Router 3 Niveaux":{"main":[[{"node":"Route Switch","type":"main","index":0}]]},"Route Switch":{"main":[[{"node":"Ollama Local","type":"main","index":0}],[{"node":"Claude Haiku","type":"main","index":0}],[{"node":"Claude Sonnet","type":"main","index":0}]]},"Ollama Local":{"main":[[{"node":"Format Ollama","type":"main","index":0}]]},"Claude Haiku":{"main":[[{"node":"Format Haiku","type":"main","index":0}]]},"Claude Sonnet":{"main":[[{"node":"Format Sonnet","type":"main","index":0}]]},"Format Ollama":{"main":[[{"node":"Save Cache + Metrics","type":"main","index":0}]]},"Format Haiku":{"main":[[{"node":"Save Cache + Metrics","type":"main","index":0}]]},"Format Sonnet":{"main":[[{"node":"Save Cache + Metrics","type":"main","index":0}]]},"Save Cache + Metrics":{"main":[[{"node":"Respond Final","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"staticData":null,"meta":null,"pinData":null,"versionId":"1239223a-4392-49f1-b718-a0afe9f215d1","activeVersionId":"1239223a-4392-49f1-b718-a0afe9f215d1","triggerCount":1,"shared":[{"updatedAt":"2026-03-01T19:10:47.370Z","createdAt":"2026-03-01T19:10:47.370Z","role":"workflow:owner","workflowId":"cCDZi9VNVdo8Iztu","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-01T20:03:09.899Z","createdAt":"2026-03-01T20:03:09.899Z","versionId":"1239223a-4392-49f1-b718-a0afe9f215d1","workflowId":"cCDZi9VNVdo8Iztu","nodes":[{"parameters":{"multipleMethods":false,"httpMethod":"POST","path":"ai-router","authentication":"none","responseMode":"responseNode","webhookNotice":"","options":{}},"id":"549ee175-e8c1-49bf-8544-1db638acd67b","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[224,464],"webhookId":"27618e8d-df6c-4a12-95c6-60d0f38557cf"},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"// Simple hash function (DJB2) - no crypto module needed\nfunction simpleHash(str) {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash) + str.charCodeAt(i);\n hash = hash & hash; // Convert to 32bit integer\n }\n return 'h_' + Math.abs(hash).toString(36);\n}\n\nconst prompt = $input.first().json.body.prompt || '';\nconst task_type = $input.first().json.body.task_type || 'other';\nconst hash = simpleHash(prompt + '_' + task_type);\n\n// Use workflow static data as cache (persists between executions)\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.cache) staticData.cache = {};\nif (!staticData.metrics) staticData.metrics = {};\n\nconst cache = staticData.cache;\nconst TTL = 86400000; // 24h\n\n// Clean old entries (older than 24h)\nconst now = Date.now();\nfor (const key of Object.keys(cache)) {\n if (now - (cache[key].timestamp || 0) > TTL) {\n delete cache[key];\n }\n}\n\n// Check cache\nif (cache[hash] && (now - cache[hash].timestamp < TTL)) {\n return [{\n json: {\n cached: true,\n response: cache[hash].response,\n hash,\n route: 'cache',\n tokens_saved: cache[hash].estimated_tokens || 0\n }\n }];\n}\n\nreturn [{\n json: {\n cached: false,\n prompt,\n context: $input.first().json.body.context || '',\n task_type,\n source: $input.first().json.body.source || 'unknown',\n hash\n }\n}];","notice":""},"id":"bceb598c-6d48-45b7-81fc-ed5d28388630","name":"Check Cache","type":"n8n-nodes-base.code","typeVersion":2,"position":[448,464]},{"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"cache-hit","leftValue":"={{ $json.cached }}","rightValue":true,"operator":{"type":"boolean","operation":"true"}}],"combinator":"and"},"looseTypeValidation":false,"options":{}},"id":"1f679228-ce78-47b1-a13a-b3dee553c3a3","name":"Cache Hit?","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[672,464]},{"parameters":{"generalNotice":"","respondWith":"json","webhookNotice":"","responseBody":"={{ JSON.stringify({ response: $json.response, route: 'cache', cached: true, tokens_saved: $json.tokens_saved }) }}","options":{"responseCode":200}},"id":"b94325b1-4355-4f52-a256-028dd41952c8","name":"Respond Cache","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[912,352]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const prompt = $input.first().json.prompt.toLowerCase();\nconst taskType = $input.first().json.task_type || 'other';\nconst promptLength = $input.first().json.prompt.length;\n\n// ══════════════════════════════════════════════════\n// NIVEAU 1 — OLLAMA LOCAL (gratuit)\n// ══════════════════════════════════════════════════\nconst ollamaKeywords = [\n 'format json', 'formater', 'valider', 'validator',\n 'convertir', 'converter', 'traduire', 'translate',\n 'corriger syntaxe', 'fix syntax', 'lister',\n 'template', 'modèle', 'exemple simple',\n 'c\\'est quoi', 'what is', 'définition',\n 'résumer en une ligne', 'one line summary'\n];\nconst ollamaTaskTypes = ['format', 'validate', 'convert', 'translate'];\n\n// ══════════════════════════════════════════════════\n// NIVEAU 2 — CLAUDE HAIKU (économique)\n// ══════════════════════════════════════════════════\nconst haikuKeywords = [\n 'debug', 'erreur', 'error', 'bug', 'fix',\n 'corriger', 'pourquoi ça marche pas', 'doesn\\'t work',\n 'expliquer', 'explain', 'comment faire', 'how to',\n 'résumer', 'summarize', 'améliorer', 'improve',\n 'modifier', 'modify', 'adapter', 'adjust',\n 'node n8n', 'code node', 'expression n8n'\n];\nconst haikuTaskTypes = ['debug', 'explain', 'fix', 'modify'];\n\n// ══════════════════════════════════════════════════\n// NIVEAU 3 — CLAUDE SONNET (puissant)\n// ══════════════════════════════════════════════════\nconst sonnetKeywords = [\n 'créer un workflow', 'create workflow', 'nouveau workflow',\n 'architecture', 'concevoir', 'design',\n 'business process', 'stratégie', 'strategy',\n 'multi-étapes', 'multi-step', 'complexe',\n 'optimiser workflow', 'refactorer', 'refactor',\n 'analyser en profondeur', 'deep analysis',\n 'comparer et recommander', 'évaluer'\n];\nconst sonnetTaskTypes = ['create', 'architect', 'analyze', 'strategy'];\n\n// ══════════════════════════════════════════════════\n// LOGIQUE DE CLASSIFICATION\n// ══════════════════════════════════════════════════\nlet route = 'haiku';\nlet confidence = 0.5;\nlet maxTokens = 512;\nlet reason = 'default';\n\n// 1. Par task_type explicite\nif (ollamaTaskTypes.includes(taskType)) {\n route = 'ollama'; confidence = 0.9; maxTokens = 512;\n reason = `task_type=${taskType}`;\n}\nelse if (haikuTaskTypes.includes(taskType)) {\n route = 'haiku'; confidence = 0.9; maxTokens = 512;\n reason = `task_type=${taskType}`;\n}\nelse if (sonnetTaskTypes.includes(taskType)) {\n route = 'sonnet'; confidence = 0.9; maxTokens = 2048;\n reason = `task_type=${taskType}`;\n}\n\n// 2. Par mots-clés\nif (confidence < 0.7) {\n for (const kw of sonnetKeywords) {\n if (prompt.includes(kw)) {\n route = 'sonnet'; confidence = 0.8; maxTokens = 2048;\n reason = `keyword=\"${kw}\"`; break;\n }\n }\n}\nif (confidence < 0.7) {\n for (const kw of ollamaKeywords) {\n if (prompt.includes(kw)) {\n route = 'ollama'; confidence = 0.8; maxTokens = 512;\n reason = `keyword=\"${kw}\"`; break;\n }\n }\n}\nif (confidence < 0.7) {\n for (const kw of haikuKeywords) {\n if (prompt.includes(kw)) {\n route = 'haiku'; confidence = 0.75; maxTokens = 512;\n reason = `keyword=\"${kw}\"`; break;\n }\n }\n}\n\n// 3. Par longueur\nif (confidence < 0.6) {\n if (promptLength < 100) {\n route = 'ollama'; reason = 'short_prompt';\n } else if (promptLength > 500) {\n route = 'sonnet'; maxTokens = 2048; reason = 'long_prompt';\n } else {\n route = 'haiku'; reason = 'medium_prompt';\n }\n}\n\nreturn [{\n json: {\n route,\n confidence,\n maxTokens,\n reason,\n prompt: $input.first().json.prompt,\n context: $input.first().json.context || '',\n hash: $input.first().json.hash\n }\n}];","notice":""},"id":"cac24378-79da-4dbd-ab92-3664297a7393","name":"Router 3 Niveaux","type":"n8n-nodes-base.code","typeVersion":2,"position":[912,560]},{"parameters":{"mode":"rules","rules":{"values":[{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"leftValue":"={{ $json.route }}","rightValue":"ollama","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"renameOutput":"Ollama"},{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"leftValue":"={{ $json.route }}","rightValue":"haiku","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"renameOutput":"Haiku"},{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"leftValue":"={{ $json.route }}","rightValue":"sonnet","operator":{"type":"string","operation":"equals"}}],"combinator":"and"},"renameOutput":"Sonnet"}]},"looseTypeValidation":false,"options":{"fallbackOutput":"extra"}},"id":"1f9b0ef4-f0e2-4f3e-9c4a-b439cc6b3182","name":"Route Switch","type":"n8n-nodes-base.switch","typeVersion":3.2,"position":[1152,560]},{"parameters":{"curlImport":"","method":"POST","url":"http://172.17.0.1:11434/api/generate","authentication":"none","provideSslCertificates":false,"sendQuery":false,"sendHeaders":false,"sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify({ model: 'llama3.2:3b', prompt: $json.prompt, stream: false, options: { temperature: 0.3, num_predict: $json.maxTokens || 512 } }) }}","options":{"timeout":30000},"infoMessage":""},"id":"f0c6696d-9c3a-4601-8838-ff60479c8110","name":"Ollama Local","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1408,352]},{"parameters":{"curlImport":"","method":"POST","url":"https://api.anthropic.com/v1/messages","authentication":"none","provideSslCertificates":false,"sendQuery":false,"sendHeaders":true,"specifyHeaders":"keypair","headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $env.ANTHROPIC_API_KEY || $credentials.anthropicApi?.apiKey || '' }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]},"sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: $json.maxTokens || 512, system: [{ type: 'text', text: 'Tu es un assistant technique spécialisé en n8n et automatisation. Réponds de manière concise et directe. Donne uniquement le code corrigé ou la solution, sans explications longues sauf si explicitement demandé. Contexte architecture : Telegram > n8n > Ollama/Claude API. VPS Ubuntu 24.04, 4GB RAM.', cache_control: { type: 'ephemeral' } }], messages: [{ role: 'user', content: $json.prompt }] }) }}","options":{"timeout":30000},"infoMessage":""},"id":"30c409ed-982f-4547-b321-0569390527e1","name":"Claude Haiku","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1408,560]},{"parameters":{"curlImport":"","method":"POST","url":"https://api.anthropic.com/v1/messages","authentication":"none","provideSslCertificates":false,"sendQuery":false,"sendHeaders":true,"specifyHeaders":"keypair","headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $env.ANTHROPIC_API_KEY || $credentials.anthropicApi?.apiKey || '' }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]},"sendBody":true,"contentType":"json","specifyBody":"json","jsonBody":"={{ JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: $json.maxTokens || 2048, system: [{ type: 'text', text: 'Tu es un expert en automatisation n8n et architecture de business processes. Tu crées des workflows complets, optimisés et production-ready. Contexte : architecture Telegram > n8n > routeur intelligent (Ollama local + Claude Haiku + Claude Sonnet). VPS Hostinger Ubuntu 24.04, 4GB RAM, 1 CPU. n8n actif. Ollama (llama3.2:3b) actif.', cache_control: { type: 'ephemeral' } }], messages: [{ role: 'user', content: $json.prompt }] }) }}","options":{"timeout":60000},"infoMessage":""},"id":"61ae1766-faed-483d-8bad-8b05d1dada48","name":"Claude Sonnet","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1408,784]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const route = 'ollama';\nconst rawResponse = $input.first().json;\nconst response = rawResponse.response || '';\nconst hash = $('Router 3 Niveaux').first().json.hash;\nconst prompt = $('Router 3 Niveaux').first().json.prompt;\n\nreturn [{\n json: {\n response,\n route,\n hash,\n prompt\n }\n}];","notice":""},"id":"86a6e6e8-d9fd-4f58-b6d8-7e26776f0196","name":"Format Ollama","type":"n8n-nodes-base.code","typeVersion":2,"position":[1648,352]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const route = 'haiku';\nconst rawResponse = $input.first().json;\nconst response = rawResponse.content?.[0]?.text || '';\nconst hash = $('Router 3 Niveaux').first().json.hash;\nconst prompt = $('Router 3 Niveaux').first().json.prompt;\n\nreturn [{\n json: {\n response,\n route,\n hash,\n prompt\n }\n}];","notice":""},"id":"78bbedfa-2e4c-42e9-a678-14f59902718a","name":"Format Haiku","type":"n8n-nodes-base.code","typeVersion":2,"position":[1648,560]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"const route = 'sonnet';\nconst rawResponse = $input.first().json;\nconst response = rawResponse.content?.[0]?.text || '';\nconst hash = $('Router 3 Niveaux').first().json.hash;\nconst prompt = $('Router 3 Niveaux').first().json.prompt;\n\nreturn [{\n json: {\n response,\n route,\n hash,\n prompt\n }\n}];","notice":""},"id":"caf3b2c0-d41f-47b8-9c1e-9301dd4d91aa","name":"Format Sonnet","type":"n8n-nodes-base.code","typeVersion":2,"position":[1648,784]},{"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"// Save response to cache and update metrics - using staticData (no fs module)\nconst input = $input.first().json;\nconst hash = input.hash || 'unknown';\nconst route = input.route || 'unknown';\nconst response = input.response || '';\nconst prompt = input.prompt || '';\nconst estimated_tokens = Math.ceil((prompt.length + response.length) / 4);\n\n// Use workflow static data (persists between executions)\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.cache) staticData.cache = {};\nif (!staticData.metrics) staticData.metrics = {};\n\n// Save to cache\nstaticData.cache[hash] = {\n response,\n route,\n timestamp: Date.now(),\n estimated_tokens\n};\n\n// Keep cache under 500 entries\nconst cacheKeys = Object.keys(staticData.cache);\nif (cacheKeys.length > 500) {\n // Remove oldest entries\n const sorted = cacheKeys.sort((a, b) => \n (staticData.cache[a].timestamp || 0) - (staticData.cache[b].timestamp || 0)\n );\n for (let i = 0; i < sorted.length - 400; i++) {\n delete staticData.cache[sorted[i]];\n }\n}\n\n// Update metrics\nconst today = new Date().toISOString().split('T')[0];\nif (!staticData.metrics[today]) {\n staticData.metrics[today] = {\n cache_hits: 0,\n ollama_calls: 0,\n haiku_calls: 0,\n sonnet_calls: 0,\n total_requests: 0,\n estimated_cost_saved: 0\n };\n}\n\nconst m = staticData.metrics[today];\nm.total_requests = (m.total_requests || 0) + 1;\n\n// Pricing per 1M tokens (approx)\nconst PRICES = {\n sonnet_input: 3.0,\n sonnet_output: 15.0,\n haiku_input: 0.25,\n haiku_output: 1.25\n};\n\nif (route === 'ollama') {\n m.ollama_calls++;\n // Saved the cost of a Sonnet call\n m.estimated_cost_saved += (estimated_tokens / 1000000) * (PRICES.sonnet_input + PRICES.sonnet_output);\n} else if (route === 'haiku') {\n m.haiku_calls++;\n // Saved the difference between Sonnet and Haiku\n const haikuCost = (estimated_tokens / 1000000) * (PRICES.haiku_input + PRICES.haiku_output);\n const sonnetCost = (estimated_tokens / 1000000) * (PRICES.sonnet_input + PRICES.sonnet_output);\n m.estimated_cost_saved += (sonnetCost - haikuCost);\n} else if (route === 'sonnet') {\n m.sonnet_calls++;\n // No savings - this is the most expensive route\n}\n\n// Clean old metrics (keep last 30 days)\nconst metricKeys = Object.keys(staticData.metrics);\nif (metricKeys.length > 30) {\n const sorted = metricKeys.sort();\n for (let i = 0; i < sorted.length - 30; i++) {\n delete staticData.metrics[sorted[i]];\n }\n}\n\nreturn [{\n json: {\n route,\n cached: false,\n response,\n hash,\n estimated_tokens,\n metrics_today: staticData.metrics[today]\n }\n}];","notice":""},"id":"ef0a7303-c7ad-45a7-9d3e-649c083c5556","name":"Save Cache + Metrics","type":"n8n-nodes-base.code","typeVersion":2,"position":[1888,560]},{"parameters":{"generalNotice":"","respondWith":"json","webhookNotice":"","responseBody":"={{ JSON.stringify({ response: $json.response, route: $json.route, cached: $json.cached || false }) }}","options":{"responseCode":200}},"id":"b5bab92d-b6ea-4aa2-b9c1-b3a743e6a32c","name":"Respond Final","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[2128,560]}],"connections":{"Webhook":{"main":[[{"node":"Check Cache","type":"main","index":0}]]},"Check Cache":{"main":[[{"node":"Cache Hit?","type":"main","index":0}]]},"Cache Hit?":{"main":[[{"node":"Respond Cache","type":"main","index":0}],[{"node":"Router 3 Niveaux","type":"main","index":0}]]},"Router 3 Niveaux":{"main":[[{"node":"Route Switch","type":"main","index":0}]]},"Route Switch":{"main":[[{"node":"Ollama Local","type":"main","index":0}],[{"node":"Claude Haiku","type":"main","index":0}],[{"node":"Claude Sonnet","type":"main","index":0}]]},"Ollama Local":{"main":[[{"node":"Format Ollama","type":"main","index":0}]]},"Claude Haiku":{"main":[[{"node":"Format Haiku","type":"main","index":0}]]},"Claude Sonnet":{"main":[[{"node":"Format Sonnet","type":"main","index":0}]]},"Format Ollama":{"main":[[{"node":"Save Cache + Metrics","type":"main","index":0}]]},"Format Haiku":{"main":[[{"node":"Save Cache + Metrics","type":"main","index":0}]]},"Format Sonnet":{"main":[[{"node":"Save Cache + Metrics","type":"main","index":0}]]},"Save Cache + Metrics":{"main":[[{"node":"Respond Final","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-09T07:15:12.762Z","createdAt":"2026-03-09T07:15:12.762Z","id":"cIt1GbuoYVyOAe7o","name":"ERP_DEBUGGER","active":true,"isArchived":false,"nodes":[{"id":"wh5","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-debugger","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd5","name":"Debugger","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const inp = $input.first().json;\n const deployLog = inp.deploy_log || [];\n const spec = inp.spec || {};\n const attempt = (inp.debug_attempt || 0) + 1;\n if (attempt > 3) return [{json:{...inp, status:\"debug_exhausted\", debug_attempt:attempt}}];\n\n const errors = deployLog.filter(l=>!l.ok).map(l=>`${l.step}: ${l.output}`).join(\"\\n\");\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const systemPrompt = `You are ERP_DEBUGGER. Fix deployment errors in a Fastify+TypeScript+Prisma ERP project.\\nGiven error logs, provide exact bash commands to fix the issue.\\nReturn ONLY valid JSON (no markdown):\\n{\"fix_commands\": [\"cmd1\", \"cmd2\"], \"explanation\": \"what was wrong\"}`;\n\n const body = JSON.stringify({\n model: \"claude-sonnet-4-5\",\n max_tokens: 2000,\n system: systemPrompt,\n messages: [{role:\"user\", content: \"Deployment failed (attempt \"+attempt+\"/3):\\n\"+errors+\"\\n\\nSpec module: \"+spec.module_name}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{...inp, status:\"debugger_api_error\"}}];\n const raw = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n\n let fix;\n try {\n const cleaned = raw.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n fix = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{...inp, status:\"debug_parse_error\", raw: raw.substring(0,300)}}];\n }\n\n const fixResults = [];\n for (const cmd of (fix.fix_commands || [])) {\n const res = await hostCmd(cmd, 60000);\n fixResults.push({cmd: cmd.substring(0,100), ok: !res.error || res.error===\"\", output: (res.output||\"\").substring(0,200)});\n }\n\n return [{json:{...inp, debug_attempt:attempt, fix_applied:fix, fix_results:fixResults, status:\"debug_applied\"}}];\n}\nreturn run();\n"}},{"id":"rsp5","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Debugger","type":"main","index":0}]]},"Debugger":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"09465be5-9185-4a2b-b264-ef25a4572a89","activeVersionId":"09465be5-9185-4a2b-b264-ef25a4572a89","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T07:15:12.764Z","createdAt":"2026-03-09T07:15:12.764Z","role":"workflow:owner","workflowId":"cIt1GbuoYVyOAe7o","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T07:15:12.768Z","createdAt":"2026-03-09T07:15:12.768Z","versionId":"09465be5-9185-4a2b-b264-ef25a4572a89","workflowId":"cIt1GbuoYVyOAe7o","nodes":[{"id":"wh5","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[200,300],"parameters":{"path":"erp-debugger","httpMethod":"POST","responseMode":"responseNode"}},{"id":"cd5","name":"Debugger","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"function hostCmd(cmd, timeoutMs) {\n return new Promise((resolve) => {\n const p = JSON.stringify({command: cmd, secret: \"mk_host_2026\"});\n const req = require(\"http\").request(\n {hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n res => { let b=\"\"; res.on(\"data\",c=>b+=c);\n res.on(\"end\",()=>{ try{resolve(JSON.parse(b))}catch(e){resolve({output:b,error:\"\"})} }); }\n );\n req.on(\"error\", e => resolve({output:\"\",error:\"ERR:\"+e.message}));\n if (timeoutMs) req.setTimeout(timeoutMs, ()=>{req.destroy();resolve({output:\"\",error:\"TIMEOUT\"});});\n req.write(p); req.end();\n });\n}\nasync function run() {\n const inp = $input.first().json;\n const deployLog = inp.deploy_log || [];\n const spec = inp.spec || {};\n const attempt = (inp.debug_attempt || 0) + 1;\n if (attempt > 3) return [{json:{...inp, status:\"debug_exhausted\", debug_attempt:attempt}}];\n\n const errors = deployLog.filter(l=>!l.ok).map(l=>`${l.step}: ${l.output}`).join(\"\\n\");\n\n const fs = require(\"fs\");\n const envRaw = fs.readFileSync(\"/proc/1/environ\",\"utf8\").split(\"\\0\");\n const apiKey = envRaw.find(e=>e.startsWith(\"ANTHROPIC_API_KEY=\")).split(\"=\").slice(1).join(\"=\");\n\n const systemPrompt = `You are ERP_DEBUGGER. Fix deployment errors in a Fastify+TypeScript+Prisma ERP project.\\nGiven error logs, provide exact bash commands to fix the issue.\\nReturn ONLY valid JSON (no markdown):\\n{\"fix_commands\": [\"cmd1\", \"cmd2\"], \"explanation\": \"what was wrong\"}`;\n\n const body = JSON.stringify({\n model: \"claude-sonnet-4-5\",\n max_tokens: 2000,\n system: systemPrompt,\n messages: [{role:\"user\", content: \"Deployment failed (attempt \"+attempt+\"/3):\\n\"+errors+\"\\n\\nSpec module: \"+spec.module_name}]\n });\n\n const apiRes = await new Promise((resolve,reject)=>{\n const r = require(\"https\").request(\n {hostname:\"api.anthropic.com\",path:\"/v1/messages\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"x-api-key\":apiKey,\n \"anthropic-version\":\"2023-06-01\",\"Content-Length\":Buffer.byteLength(body)}},\n res=>{let b=\"\";res.on(\"data\",c=>b+=c);res.on(\"end\",()=>{try{resolve(JSON.parse(b))}catch(e){resolve({error:b})}}}\n );\n r.on(\"error\",reject);r.write(body);r.end();\n });\n\n if (apiRes.error) return [{json:{...inp, status:\"debugger_api_error\"}}];\n const raw = (apiRes.content && apiRes.content[0] && apiRes.content[0].text) || \"\";\n\n let fix;\n try {\n const cleaned = raw.replace(/```json\\n?/g,\"\").replace(/```\\n?/g,\"\").trim();\n fix = JSON.parse(cleaned);\n } catch(e) {\n return [{json:{...inp, status:\"debug_parse_error\", raw: raw.substring(0,300)}}];\n }\n\n const fixResults = [];\n for (const cmd of (fix.fix_commands || [])) {\n const res = await hostCmd(cmd, 60000);\n fixResults.push({cmd: cmd.substring(0,100), ok: !res.error || res.error===\"\", output: (res.output||\"\").substring(0,200)});\n }\n\n return [{json:{...inp, debug_attempt:attempt, fix_applied:fix, fix_results:fixResults, status:\"debug_applied\"}}];\n}\nreturn run();\n"}},{"id":"rsp5","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[720,300],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"}}],"connections":{"Webhook":{"main":[[{"node":"Debugger","type":"main","index":0}]]},"Debugger":{"main":[[{"node":"Respond","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-08T23:25:12.458Z","createdAt":"2026-03-08T23:25:12.458Z","id":"chV5TppYuPruRiwO","name":"DISK_MONITOR","active":true,"isArchived":false,"nodes":[{"id":"s1","name":"Every Hour","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[200,300],"parameters":{"rule":{"interval":[{"field":"hours","hoursInterval":1}]}}},{"id":"c1","name":"Check Disk","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"const http = require(\"http\");\nfunction hc(cmd){\n return new Promise(res=>{\n const p=JSON.stringify({command:cmd,secret:\"mk_host_2026\"});\n const r=http.request({hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n x=>{let b=\"\";x.on(\"data\",c=>b+=c);x.on(\"end\",()=>{try{res(JSON.parse(b))}catch(e){res({output:b})}})});\n r.on(\"error\",e=>res({output:\"ERR:\"+e.message}));r.write(p);r.end();\n });\n}\nasync function run(){\n const r1=await hc(\"df / --output=pcent,avail,size | tail -1\");\n const line=(r1.output||\"\").trim();\n const m=line.match(/(\\d+)%\\s+(\\d+)\\s+(\\d+)/);\n const pct=m?parseInt(m[1]):0;\n const avail=m?(parseInt(m[2])/1048576).toFixed(1):\"?\";\n const total=m?(parseInt(m[3])/1048576).toFixed(1):\"?\";\n const r2=await hc(\"docker system df 2>/dev/null\");\n const dinfo=(r2.output||\"\").trim();\n const r3=await hc(\"find /var/lib/docker/volumes -name backup_*.json 2>/dev/null | wc -l\");\n const bcount=(r3.output||\"0\").trim();\n let st=\"OK\",lv=\"info\";\n if(pct>=90){st=\"CRITIQUE\";lv=\"critical\";}\n else if(pct>=85){st=\"ALERTE\";lv=\"warning\";}\n else if(pct>=75){st=\"ATTENTION\";lv=\"watch\";}\n return [{json:{usedPct:pct,availGB:avail,totalGB:total,status:st,level:lv,backupCount:bcount,dockerInfo:dinfo}}];\n}\nreturn run();"}},{"id":"i1","name":"Alert?","type":"n8n-nodes-base.if","typeVersion":2,"position":[720,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"x1","leftValue":"={{ $json.level }}","rightValue":"info","operator":{"type":"string","operation":"notEquals"}}],"combinator":"and"}}},{"id":"c2","name":"Alert ADMIN_AGENT","type":"n8n-nodes-base.code","typeVersion":2,"position":[980,180],"parameters":{"jsCode":"const http = require(\"http\");\nconst d=$input.first().json;\nconst emoji = d.level===\"critical\"?\"🔴\":d.level===\"warning\"?\"🟠\":\"🟡\";\nconst msg=[\n emoji+\" [DISK_MONITOR] \"+d.status,\n \"Disque: \"+d.usedPct+\"% (\"+d.availGB+\"GB libres / \"+d.totalGB+\"GB total)\",\n \"Backups n8n: \"+d.backupCount+\" fichiers\",\n \"Docker:\\n\"+d.dockerInfo.substring(0,280),\n d.level===\"critical\"?\">>> ACTION IMMEDIATE: docker system prune -f\":\">>> Surveiller evolution\"\n].join(\"\\n\");\nconst body=JSON.stringify({message:msg,chatId:\"889172188\"});\nreturn new Promise((resolve,reject)=>{\n const r=http.request({hostname:\"localhost\",port:5678,path:\"/webhook/admin-agent\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(body)}},\n x=>{let b=\"\";x.on(\"data\",c=>b+=c);x.on(\"end\",()=>resolve([{json:{sent:true}}]));});\n r.on(\"error\",reject);r.setTimeout(90000,()=>{r.destroy();reject(new Error(\"timeout\"));});\n r.write(body);r.end();\n});"}},{"id":"n1","name":"OK","type":"n8n-nodes-base.noOp","typeVersion":1,"position":[980,420],"parameters":{}}],"connections":{"Every Hour":{"main":[[{"node":"Check Disk","type":"main","index":0}]]},"Check Disk":{"main":[[{"node":"Alert?","type":"main","index":0}]]},"Alert?":{"main":[[{"node":"Alert ADMIN_AGENT","type":"main","index":0}],[{"node":"OK","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":{"node:Every Hour":{"recurrenceRules":[]}},"meta":null,"pinData":null,"versionId":"0926454d-3f4e-46b4-bc84-a0804a91e04e","activeVersionId":"0926454d-3f4e-46b4-bc84-a0804a91e04e","triggerCount":1,"shared":[{"updatedAt":"2026-03-08T23:25:12.460Z","createdAt":"2026-03-08T23:25:12.460Z","role":"workflow:owner","workflowId":"chV5TppYuPruRiwO","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-08T23:25:12.462Z","createdAt":"2026-03-08T23:25:12.462Z","versionId":"0926454d-3f4e-46b4-bc84-a0804a91e04e","workflowId":"chV5TppYuPruRiwO","nodes":[{"id":"s1","name":"Every Hour","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[200,300],"parameters":{"rule":{"interval":[{"field":"hours","hoursInterval":1}]}}},{"id":"c1","name":"Check Disk","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"const http = require(\"http\");\nfunction hc(cmd){\n return new Promise(res=>{\n const p=JSON.stringify({command:cmd,secret:\"mk_host_2026\"});\n const r=http.request({hostname:\"172.17.0.1\",port:9999,method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(p)}},\n x=>{let b=\"\";x.on(\"data\",c=>b+=c);x.on(\"end\",()=>{try{res(JSON.parse(b))}catch(e){res({output:b})}})});\n r.on(\"error\",e=>res({output:\"ERR:\"+e.message}));r.write(p);r.end();\n });\n}\nasync function run(){\n const r1=await hc(\"df / --output=pcent,avail,size | tail -1\");\n const line=(r1.output||\"\").trim();\n const m=line.match(/(\\d+)%\\s+(\\d+)\\s+(\\d+)/);\n const pct=m?parseInt(m[1]):0;\n const avail=m?(parseInt(m[2])/1048576).toFixed(1):\"?\";\n const total=m?(parseInt(m[3])/1048576).toFixed(1):\"?\";\n const r2=await hc(\"docker system df 2>/dev/null\");\n const dinfo=(r2.output||\"\").trim();\n const r3=await hc(\"find /var/lib/docker/volumes -name backup_*.json 2>/dev/null | wc -l\");\n const bcount=(r3.output||\"0\").trim();\n let st=\"OK\",lv=\"info\";\n if(pct>=90){st=\"CRITIQUE\";lv=\"critical\";}\n else if(pct>=85){st=\"ALERTE\";lv=\"warning\";}\n else if(pct>=75){st=\"ATTENTION\";lv=\"watch\";}\n return [{json:{usedPct:pct,availGB:avail,totalGB:total,status:st,level:lv,backupCount:bcount,dockerInfo:dinfo}}];\n}\nreturn run();"}},{"id":"i1","name":"Alert?","type":"n8n-nodes-base.if","typeVersion":2,"position":[720,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict"},"conditions":[{"id":"x1","leftValue":"={{ $json.level }}","rightValue":"info","operator":{"type":"string","operation":"notEquals"}}],"combinator":"and"}}},{"id":"c2","name":"Alert ADMIN_AGENT","type":"n8n-nodes-base.code","typeVersion":2,"position":[980,180],"parameters":{"jsCode":"const http = require(\"http\");\nconst d=$input.first().json;\nconst emoji = d.level===\"critical\"?\"🔴\":d.level===\"warning\"?\"🟠\":\"🟡\";\nconst msg=[\n emoji+\" [DISK_MONITOR] \"+d.status,\n \"Disque: \"+d.usedPct+\"% (\"+d.availGB+\"GB libres / \"+d.totalGB+\"GB total)\",\n \"Backups n8n: \"+d.backupCount+\" fichiers\",\n \"Docker:\\n\"+d.dockerInfo.substring(0,280),\n d.level===\"critical\"?\">>> ACTION IMMEDIATE: docker system prune -f\":\">>> Surveiller evolution\"\n].join(\"\\n\");\nconst body=JSON.stringify({message:msg,chatId:\"889172188\"});\nreturn new Promise((resolve,reject)=>{\n const r=http.request({hostname:\"localhost\",port:5678,path:\"/webhook/admin-agent\",method:\"POST\",\n headers:{\"Content-Type\":\"application/json\",\"Content-Length\":Buffer.byteLength(body)}},\n x=>{let b=\"\";x.on(\"data\",c=>b+=c);x.on(\"end\",()=>resolve([{json:{sent:true}}]));});\n r.on(\"error\",reject);r.setTimeout(90000,()=>{r.destroy();reject(new Error(\"timeout\"));});\n r.write(body);r.end();\n});"}},{"id":"n1","name":"OK","type":"n8n-nodes-base.noOp","typeVersion":1,"position":[980,420],"parameters":{}}],"connections":{"Every Hour":{"main":[[{"node":"Check Disk","type":"main","index":0}]]},"Check Disk":{"main":[[{"node":"Alert?","type":"main","index":0}]]},"Alert?":{"main":[[{"node":"Alert ADMIN_AGENT","type":"main","index":0}],[{"node":"OK","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-02T09:57:15.047Z","createdAt":"2026-03-02T09:57:15.047Z","id":"dZJ8tFwqREpaap5q","name":"_TEMP_IMPORT_55_SUPPLIERS","active":false,"isArchived":false,"nodes":[{"id":"trigger1","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[240,300],"parameters":{}},{"id":"code1","name":"Generate Rows","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"mode":"runOnceForAllItems","jsCode":"const suppliers = [{\"name\": \"\\u04105 \\u041e\\u041e\\u041e\", \"email\": \"ydik@a550.ru\"}, {\"name\": \"\\u0410\\u0411\\u0420\\u041e\\u0421\\u0418\\u041c\\u041e\\u0412 \\u0411\\u041e\\u0413\\u0414\\u0410\\u041d \\u0412\\u0410\\u041b\\u0415\\u0420\\u042c\\u0415\\u0412\\u0418\\u0427 \\u0418\\u041f\", \"email\": \"administrator@dcgaztverskoy.ru\"}, {\"name\": \"\\u0410\\u0412\\u0422\\u041e\\u0424\\u041e\\u0420\\u041c \\u041e\\u041e\\u041e\", \"email\": \"shtefan@balrt.ru\"}, {\"name\": \"\\u0410\\u0414\\u0412\\u0410\\u041d\\u0422\\u0410-\\u041c \\u041e\\u041e\\u041e\", \"email\": \"sverki@advanta-m.ru\"}, {\"name\": \"\\u0410\\u041b\\u0422\\u0415\\u0420\\u0412\\u0418\\u0410 \\u041e\\u041e\\u041e\", \"email\": \"b3@alterv.ru\"}, {\"name\": \"\\u0412\\u0418\\u041b\\u0421 \\u041e\\u041e\\u041e\", \"email\": \"sale@delprom.ru\"}, {\"name\": \"\\u0412\\u0418\\u041d\\u0418\\u041b\\u0424\\u041e\\u042e \\u041e\\u041e\\u041e\", \"email\": \"buh@vinyl4you.ru\"}, {\"name\": \"\\u0413\\u0438\\u0433\\u0430\\u0435\\u0432 \\u0421\\u0443\\u043b\\u0430\\u043c\\u0431\\u0435\\u043a \\u0421\\u0430\\u0439\\u0434-\\u041c\\u0430\\u0433\\u043e\\u043c\\u0435\\u0434\\u043e\\u0432\\u0438\\u0447\", \"email\": \"aleskerova.elenka@mail.ru\"}, {\"name\": \"\\u0413\\u041a \\u0421\\u0422\\u0420\\u041e\\u0419-\\u0418\\u041d\\u0414\\u0423\\u0421\\u0422\\u0420\\u0418\\u042f \\u041e\\u041e\\u041e\", \"email\": \"nadin@stroyind2000.ru\"}, {\"name\": \"\\u0413\\u0420\\u0423\\u041f\\u041f\\u0410 \\u041a\\u041e\\u041c\\u041f\\u0410\\u041d\\u0418\\u0419 \\u0414\\u0415\\u041c\\u0418\\u0414\\u041e\\u0412 \\u041e\\u041e\\u041e\", \"email\": \"tvr-sb04@ferost.ru\"}, {\"name\": \"\\u0414\\u041e\\u0414\\u0416\\u0415\\u0420 \\u041e\\u041e\\u041e\", \"email\": \"buhmettver@yandex.ru\"}, {\"name\": \"\\u0417\\u0410\\u0412\\u041e\\u0414 \\u0420\\u0422\\u0418 \\u0411\\u041e\\u0420\\u0415\\u041a\\u0421 \\u041e\\u041e\\u041e\", \"email\": \"td.rti@yandex.ru\"}, {\"name\": \"\\u0417\\u0410\\u0413\\u041b\\u0423\\u0428\\u041a\\u0410.\\u0420\\u0423 \\u041e\\u041e\\u041e\", \"email\": \"akulaeva@zaglushka.ru\"}, {\"name\": \"\\u0417\\u0410\\u041a\\u0418\\u041d\\u042f\\u041d \\u0412\\u0410\\u0420\\u0414\\u0410\\u041d \\u0413\\u0420\\u0410\\u0427\\u0418\\u041a\\u041e\\u0412\\u0418\\u0427\", \"email\": \"4109757@mfk-torg.ru\"}, {\"name\": \"\\u0417\\u0418\\u0422\\u0410\\u0420 \\u041e\\u041e\\u041e\", \"email\": \"oag@zitar.ru\"}, {\"name\": \"\\u041a\\u041b\\u0410\\u041c\\u041f\\u0422\\u0415\\u041a \\u041e\\u041e\\u041e\", \"email\": \"info@clamptek.ru\"}, {\"name\": \"\\u041a\\u043c\\u043e\\u0442\\u0440\\u0438\\u043a \\u0410\\u043d\\u043d\\u0430 \\u0421\\u0435\\u0440\\u0433\\u0435\\u0435\\u0432\\u043d\\u0430\", \"email\": \"info@bazaar-tex.ru\"}, {\"name\": \"\\u041a\\u041e\\u041c\\u041f\\u0410\\u041d\\u0418\\u042f \\u0410\\u041b\\u042c\\u042f\\u041d\\u0421-\\u041e\\u041f\\u0422 \\u041e\\u041e\\u041e\", \"email\": \"all-opt.tver@mail.ru\"}, {\"name\": \"\\u041a\\u041e\\u041c\\u041f\\u0410\\u041d\\u0418\\u042f \\u0424\\u0410\\u041c \\u041e\\u041e\\u041e\", \"email\": \"romanova@fam-drive.ru\"}, {\"name\": \"\\u041a\\u041e\\u041c\\u0423\\u0421 \\u041e\\u041e\\u041e\", \"email\": \"kc@komus.net\"}, {\"name\": \"\\u041a\\u041e\\u041f\\u041e\\u0421\\u041e\\u0412 \\u0414\\u041c\\u0418\\u0422\\u0420\\u0418\\u0419 \\u0421\\u0415\\u0420\\u0413\\u0415\\u0415\\u0412\\u0418\\u0427\", \"email\": \"sale@rosmagnit.ru\"}, {\"name\": \"\\u041a\\u041e\\u0420\\u041e\\u041b\\u0415\\u0412\\u0421\\u041a\\u0418\\u0419 \\u0422\\u0420\\u0423\\u0411\\u041d\\u042b\\u0419 \\u0417\\u0410\\u0412\\u041e\\u0414 \\u041e\\u041e\\u041e\", \"email\": \"melnik.e@ktzholding.ru\"}, {\"name\": \"\\u041a\\u0420\\u0426 \\u041e\\u041e\\u041e\", \"email\": \"infoallcasters@yandex.ru\"}, {\"name\": \"\\u041b\\u0415\\u041d\\u0422\\u0410 \\u041e\\u041e\\u041e\", \"email\": \"151@lenta.biz\"}, {\"name\": \"\\u041b\\u0418\\u041d\\u0418\\u042f \\u0421\\u0412\\u0415\\u0422\\u0410 \\u041e\\u041e\\u041e\", \"email\": \"info@liniyareza.ru\"}, {\"name\": \"\\u041c\\u0410\\u0418 \\u041e\\u041e\\u041e\", \"email\": \"autor-mebel@yandex.ru\"}, {\"name\": \"\\u041c\\u0415\\u0422\\u0410\\u041b\\u041b\\u0421\\u0415\\u0420\\u0412\\u0418\\u0421-\\u041c\\u041e\\u0421\\u041a\\u0412\\u0410 \\u041e\\u041e\\u041e\", \"email\": \"fin2@mc.ru\"}, {\"name\": \"\\u041c\\u0415\\u0422\\u0418\\u041d\\u0412\\u0415\\u0421\\u0422 \\u041e\\u041e\\u041e\", \"email\": \"base@delta-metall.ru\"}, {\"name\": \"\\u041c\\u0418\\u041b\\u0418\\u041e\\u0421 \\u0418\\u041d\\u0421\\u0422\\u0420\\u0423\\u041c\\u0415\\u041d\\u0422 \\u041e\\u041e\\u041e\", \"email\": \"buh@milios.ru\"}, {\"name\": \"\\u041d\\u0418\\u041a\\u041e\\u041b\\u0410\\u0415\\u0412 \\u0412\\u042f\\u0427\\u0415\\u0421\\u041b\\u0410\\u0412 \\u0412\\u0418\\u041a\\u0422\\u041e\\u0420\\u041e\\u0412\\u0418\\u0427\", \"email\": \"info@torteam.ru\"}, {\"name\": \"\\u041d\\u041e\\u0412\\u041e\\u0422\\u0415\\u0425 \\u0410\\u041e\", \"email\": \"petrovskaya_ev@resanta.ru\"}, {\"name\": \"\\u041f\\u041d\\u0415\\u0412\\u041c\\u041e\\u041c\\u0410\\u0428 \\u041e\\u041e\\u041e\", \"email\": \"pnevmomah2014@yandex.ru\"}, {\"name\": \"\\u041f\\u041e\\u0413\\u0420\\u0423\\u0417\\u0427\\u0418\\u041a-\\u0422\\u0412\\u0415\\u0420\\u042c \\u041e\\u041e\\u041e\", \"email\": \"pogruzchiktver@mail.ru\"}, {\"name\": \"\\u041f\\u041e\\u0414\\u041e\\u041b\\u042c\\u0421\\u041a\\u0418\\u0419 \\u0417\\u0410\\u0412\\u041e\\u0414 \\u041e\\u0411\\u041e\\u0420\\u0423\\u0414\\u041e\\u0412\\u0410\\u041d\\u0418\\u042f \\u041e\\u041e\\u041e\", \"email\": \"9653537328@p-z-o.ru\"}, {\"name\": \"\\u041f\\u043e\\u0434\\u044a\\u044f\\u043d\\u043e\\u0432\\u0430 \\u041e\\u043b\\u044c\\u0433\\u0430 \\u041d\\u0438\\u043a\\u043e\\u043b\\u0430\\u0435\\u0432\\u043d\\u0430\", \"email\": \"autoelling.tver@gmail.com\"}, {\"name\": \"\\u041f\\u041e\\u041b\\u0418\\u041c\\u0415\\u0420-\\u0413\\u0420\\u0423\\u041f\\u041f \\u041e\\u041e\\u041e\", \"email\": \"tital81@mail.ru\"}, {\"name\": \"\\u041f\\u041e\\u041b\\u0418\\u041c\\u0415\\u0420\\u0421\\u0415\\u0420\\u0412\\u0418\\u0421 \\u041e\\u041e\\u041e\", \"email\": \"bee@pplist.ru\"}, {\"name\": \"\\u041f\\u0420\\u0418\\u041e\\u0420\\u0418\\u0422\\u0415\\u0422 \\u041e\\u041e\\u041e\", \"email\": \"kypol_05w@mail.ru\"}, {\"name\": \"\\u041f\\u0420\\u041e\\u041c\\u0422\\u0420\\u042d\\u0419\\u0414 \\u041e\\u041e\\u041e\", \"email\": \"sergey.petrenko@kley24.ru\"}, {\"name\": \"\\u0420\\u0418\\u0412\\u0415\\u0422 \\u0413\\u0410\\u041d \\u041e\\u041e\\u041e\", \"email\": \"fin@rivetgun.ru\"}, {\"name\": \"\\u0421\\u0418\\u041d\\u0414\\u0418\\u041a-\\u0426\\u0415\\u041d\\u0422\\u0420 \\u041e\\u041e\\u041e\", \"email\": \"maxim.kapranov@syndic-c.ru\"}, {\"name\": \"\\u0421\\u041d\\u0410\\u0411\\u0411 \\u041e\\u041e\\u041e\", \"email\": \"im@snabb.ru\"}, {\"name\": \"\\u0421\\u0422\\u0410\\u041b\\u0415\\u0420 \\u041e\\u041e\\u041e \\u041a\\u041e\\u041c\\u041f\\u0410\\u041d\\u0418\\u042f\", \"email\": \"cpa@stalerplast.ru\"}, {\"name\": \"\\u0421\\u0443\\u0445\\u0430\\u0440\\u043d\\u0438\\u043a\\u043e\\u0432 \\u0410\\u043b\\u0435\\u043a\\u0441\\u0430\\u043d\\u0434\\u0440 \\u0410\\u043b\\u0435\\u043a\\u0441\\u0430\\u043d\\u0434\\u0440\\u043e\\u0432\\u0438\\u0447 \\u0418\\u041f\", \"email\": \"575-432@mail.ru\"}, {\"name\": \"\\u0422\\u0412\\u0415\\u0420\\u042c \\u0421\\u0418 \\u042d\\u041d \\u0421\\u0418 \\u041e\\u041e\\u041e\", \"email\": \"cnc-stanok@mail.ru\"}, {\"name\": \"\\u0422\\u0412\\u0415\\u0420\\u042c-\\u0422\\u0410\\u0420\\u0410 \\u041e\\u041e\\u041e\", \"email\": \"tver-tara@mail.ru\"}, {\"name\": \"\\u0422\\u0435\\u0445\\u043d\\u0438\\u043a\\u0430 \\u0434\\u043b\\u044f \\u0441\\u043a\\u043b\\u0430\\u0434\\u0430-\\u0422\\u0432\\u0435\\u0440\\u044c \\u041e\\u041e\\u041e\", \"email\": \"nik7469@mail.ru\"}, {\"name\": \"\\u0422\\u0415\\u0425\\u041f\\u0420\\u041e\\u0426\\u0415\\u0421\\u0421 \\u041e\\u041e\\u041e\", \"email\": \"buh@tprocess.ru\"}, {\"name\": \"\\u0422\\u0420\\u0415\\u0419\\u0414\\u0421\\u0415\\u0422 \\u041e\\u041e\\u041e\", \"email\": \"buh@tradeset.ru\"}, {\"name\": \"\\u0422\\u0421\\u041a \\u041f\\u041d\\u0415\\u0412\\u041c\\u041e\\u041c\\u0410\\u0428 \\u041e\\u041e\\u041e\", \"email\": \"info@airtver.ru\"}, {\"name\": \"\\u0422\\u0421\\u041e \\u041e\\u041e\\u041e\", \"email\": \"sjaros121@gmail.com\"}, {\"name\": \"\\u0426\\u0420\\u0418 \\u041e\\u041e\\u041e\", \"email\": \"turkin@woodwork.ru\"}, {\"name\": \"\\u042d\\u041b\\u0415\\u041a\\u0422\\u0420\\u041e\\u041c\\u0410\\u0420\\u041a\\u0415\\u0422 \\u041e\\u041e\\u041e\", \"email\": \"biw@tdm-e.ru\"}, {\"name\": \"\\u042e\\u041a\\u0410\\u041d \\u041e\\u041e\\u041e \\u0422\\u041a\\u0410\\u041d\\u0418 69\", \"email\": \"info@tkani69.ru\"}, {\"name\": \"\\u042e\\u041d\\u0413\\u0425\\u0410\\u0419\\u041d\\u0420\\u0418\\u0425 \\u041e\\u041e\\u041e\", \"email\": \"inna.minina@jh-lift.ru\"}];\nconst today = new Date().toISOString().split('T')[0];\nreturn suppliers.map(s => ({\n json: {\n supplier_name: s.name,\n email: s.email,\n quarter: '2026-Q1',\n status: 'pending',\n request_sent_at: '',\n last_reminder_at: '',\n reminder_count: 0,\n next_reminder_at: '',\n excluded: false,\n exclude_reason: '',\n last_inbound_class: '',\n notes: 'Ajouté Excel Снабжение ' + today,\n needs_1c_check: false,\n ai_confidence: '',\n ai_doc_type: '',\n row_id: '',\n supplier_id: '',\n document_number: '',\n document_date: '',\n debit: '',\n credit: '',\n balance: '',\n currency: '',\n comment: '',\n review_ts: '',\n merged_ts: '',\n resolved: false,\n resolution_comment: ''\n }\n}));"}},{"id":"sheets1","name":"Append to Tracking","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[680,300],"parameters":{"operation":"appendOrUpdate","documentId":{"__rl":true,"value":"1OHBgHa9pBNbkh1n4-IO2vzdIgpfrJbgUcGXRAg5ROi4","mode":"id"},"sheetName":{"__rl":true,"value":"act_sverki_tracking","mode":"name"},"columns":{"mappingMode":"defineBelow","value":{"supplier_name":"={{ $json.supplier_name }}","email":"={{ $json.email }}","quarter":"={{ $json.quarter }}","status":"={{ $json.status }}","request_sent_at":"={{ $json.request_sent_at }}","last_reminder_at":"={{ $json.last_reminder_at }}","reminder_count":"={{ $json.reminder_count }}","next_reminder_at":"={{ $json.next_reminder_at }}","excluded":"={{ $json.excluded }}","exclude_reason":"={{ $json.exclude_reason }}","last_inbound_class":"={{ $json.last_inbound_class }}","notes":"={{ $json.notes }}","needs_1c_check":"={{ $json.needs_1c_check }}"},"matchingColumns":["email","quarter"],"schema":[{"id":"supplier_name","displayName":"supplier_name","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"email","displayName":"email","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"quarter","displayName":"quarter","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"status","displayName":"status","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true},{"id":"notes","displayName":"notes","required":false,"defaultMatch":false,"display":true,"type":"string","canBeUsedToMatch":true}]}},"credentials":{"googleSheetsOAuth2Api":{"id":"syxMCnnYZhNGdol1","name":"Google Sheets account"}}}],"connections":{"Manual Trigger":{"main":[[{"node":"Generate Rows","type":"main","index":0}]]},"Generate Rows":{"main":[[{"node":"Append to Tracking","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"641c4fc0-428f-4b96-a87a-c4b15f8fb4ba","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-03-02T09:57:15.087Z","createdAt":"2026-03-02T09:57:15.087Z","role":"workflow:owner","workflowId":"dZJ8tFwqREpaap5q","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-02T09:55:33.800Z","createdAt":"2026-03-02T09:55:33.800Z","id":"eLo0odxNgFIn2BVV","name":"_TEMP_SEND_VERIFICATION_EMAIL","active":false,"isArchived":false,"nodes":[{"id":"trigger1","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[240,300],"parameters":{}},{"id":"code1","name":"Prepare Attachment","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"mode":"runOnceForAllItems","jsCode":"const b64 = 'UEsDBBQAAAAAAAAAAADxYTwnQgMAAEIDAAAaAAAAeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHM8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8UmVsYXRpb25zaGlwcyB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL3BhY2thZ2UvMjAwNi9yZWxhdGlvbnNoaXBzIj48UmVsYXRpb25zaGlwIElkPSJySWQxIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3dvcmtzaGVldCIgVGFyZ2V0PSJ3b3Jrc2hlZXRzL3NoZWV0MS54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQyIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3dvcmtzaGVldCIgVGFyZ2V0PSJ3b3Jrc2hlZXRzL3NoZWV0Mi54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQzIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3RoZW1lIiBUYXJnZXQ9InRoZW1lL3RoZW1lMS54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQ0IiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL3N0eWxlcyIgVGFyZ2V0PSJzdHlsZXMueG1sIi8+PFJlbGF0aW9uc2hpcCBJZD0icklkNSIgVHlwZT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcy9zaGVldE1ldGFkYXRhIiBUYXJnZXQ9Im1ldGFkYXRhLnhtbCIvPjwvUmVsYXRpb25zaGlwcz5QSwMEFAAAAAAAAAAAADAPiGveHQAA3h0AABMAAAB4bC90aGVtZS90aGVtZTEueG1sPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPGE6dGhlbWUgeG1sbnM6YT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL2RyYXdpbmdtbC8yMDA2L21haW4iIG5hbWU9Ik9mZmljZSBUaGVtZSI+PGE6dGhlbWVFbGVtZW50cz48YTpjbHJTY2hlbWUgbmFtZT0iT2ZmaWNlIj48YTpkazE+PGE6c3lzQ2xyIHZhbD0id2luZG93VGV4dCIgbGFzdENscj0iMDAwMDAwIi8+PC9hOmRrMT48YTpsdDE+PGE6c3lzQ2xyIHZhbD0id2luZG93IiBsYXN0Q2xyPSJGRkZGRkYiLz48L2E6bHQxPjxhOmRrMj48YTpzcmdiQ2xyIHZhbD0iMUY0OTdEIi8+PC9hOmRrMj48YTpsdDI+PGE6c3JnYkNsciB2YWw9IkVFRUNFMSIvPjwvYTpsdDI+PGE6YWNjZW50MT48YTpzcmdiQ2xyIHZhbD0iNEY4MUJEIi8+PC9hOmFjY2VudDE+PGE6YWNjZW50Mj48YTpzcmdiQ2xyIHZhbD0iQzA1MDREIi8+PC9hOmFjY2VudDI+PGE6YWNjZW50Mz48YTpzcmdiQ2xyIHZhbD0iOUJCQjU5Ii8+PC9hOmFjY2VudDM+PGE6YWNjZW50ND48YTpzcmdiQ2xyIHZhbD0iODA2NEEyIi8+PC9hOmFjY2VudDQ+PGE6YWNjZW50NT48YTpzcmdiQ2xyIHZhbD0iNEJBQ0M2Ii8+PC9hOmFjY2VudDU+PGE6YWNjZW50Nj48YTpzcmdiQ2xyIHZhbD0iRjc5NjQ2Ii8+PC9hOmFjY2VudDY+PGE6aGxpbms+PGE6c3JnYkNsciB2YWw9IjAwMDBGRiIvPjwvYTpobGluaz48YTpmb2xIbGluaz48YTpzcmdiQ2xyIHZhbD0iODAwMDgwIi8+PC9hOmZvbEhsaW5rPjwvYTpjbHJTY2hlbWU+PGE6Zm9udFNjaGVtZSBuYW1lPSJPZmZpY2UiPjxhOm1ham9yRm9udD48YTpsYXRpbiB0eXBlZmFjZT0iQ2FtYnJpYSIvPjxhOmVhIHR5cGVmYWNlPSIiLz48YTpjcyB0eXBlZmFjZT0iIi8+PGE6Zm9udCBzY3JpcHQ9IkpwYW4iIHR5cGVmYWNlPSLvvK3vvLMg77yw44K044K344OD44KvIi8+PGE6Zm9udCBzY3JpcHQ9IkhhbmciIHR5cGVmYWNlPSLrp5HsnYAg6rOg65SVIi8+PGE6Zm9udCBzY3JpcHQ9IkhhbnMiIHR5cGVmYWNlPSLlrovkvZMiLz48YTpmb250IHNjcmlwdD0iSGFudCIgdHlwZWZhY2U9IuaWsOe0sOaYjumrlCIvPjxhOmZvbnQgc2NyaXB0PSJBcmFiIiB0eXBlZmFjZT0iVGltZXMgTmV3IFJvbWFuIi8+PGE6Zm9udCBzY3JpcHQ9IkhlYnIiIHR5cGVmYWNlPSJUaW1lcyBOZXcgUm9tYW4iLz48YTpmb250IHNjcmlwdD0iVGhhaSIgdHlwZWZhY2U9IlRhaG9tYSIvPjxhOmZvbnQgc2NyaXB0PSJFdGhpIiB0eXBlZmFjZT0iTnlhbGEiLz48YTpmb250IHNjcmlwdD0iQmVuZyIgdHlwZWZhY2U9IlZyaW5kYSIvPjxhOmZvbnQgc2NyaXB0PSJHdWpyIiB0eXBlZmFjZT0iU2hydXRpIi8+PGE6Zm9udCBzY3JpcHQ9IktobXIiIHR5cGVmYWNlPSJNb29sQm9yYW4iLz48YTpmb250IHNjcmlwdD0iS25kYSIgdHlwZWZhY2U9IlR1bmdhIi8+PGE6Zm9udCBzY3JpcHQ9Ikd1cnUiIHR5cGVmYWNlPSJSYWF2aSIvPjxhOmZvbnQgc2NyaXB0PSJDYW5zIiB0eXBlZmFjZT0iRXVwaGVtaWEiLz48YTpmb250IHNjcmlwdD0iQ2hlciIgdHlwZWZhY2U9IlBsYW50YWdlbmV0IENoZXJva2VlIi8+PGE6Zm9udCBzY3JpcHQ9IllpaWkiIHR5cGVmYWNlPSJNaWNyb3NvZnQgWWkgQmFpdGkiLz48YTpmb250IHNjcmlwdD0iVGlidCIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBIaW1hbGF5YSIvPjxhOmZvbnQgc2NyaXB0PSJUaGFhIiB0eXBlZmFjZT0iTVYgQm9saSIvPjxhOmZvbnQgc2NyaXB0PSJEZXZhIiB0eXBlZmFjZT0iTWFuZ2FsIi8+PGE6Zm9udCBzY3JpcHQ9IlRlbHUiIHR5cGVmYWNlPSJHYXV0YW1pIi8+PGE6Zm9udCBzY3JpcHQ9IlRhbWwiIHR5cGVmYWNlPSJMYXRoYSIvPjxhOmZvbnQgc2NyaXB0PSJTeXJjIiB0eXBlZmFjZT0iRXN0cmFuZ2VsbyBFZGVzc2EiLz48YTpmb250IHNjcmlwdD0iT3J5YSIgdHlwZWZhY2U9IkthbGluZ2EiLz48YTpmb250IHNjcmlwdD0iTWx5bSIgdHlwZWZhY2U9IkthcnRpa2EiLz48YTpmb250IHNjcmlwdD0iTGFvbyIgdHlwZWZhY2U9IkRva0NoYW1wYSIvPjxhOmZvbnQgc2NyaXB0PSJTaW5oIiB0eXBlZmFjZT0iSXNrb29sYSBQb3RhIi8+PGE6Zm9udCBzY3JpcHQ9Ik1vbmciIHR5cGVmYWNlPSJNb25nb2xpYW4gQmFpdGkiLz48YTpmb250IHNjcmlwdD0iVmlldCIgdHlwZWZhY2U9IlRpbWVzIE5ldyBSb21hbiIvPjxhOmZvbnQgc2NyaXB0PSJVaWdoIiB0eXBlZmFjZT0iTWljcm9zb2Z0IFVpZ2h1ciIvPjxhOmZvbnQgc2NyaXB0PSJHZW9yIiB0eXBlZmFjZT0iU3lsZmFlbiIvPjwvYTptYWpvckZvbnQ+PGE6bWlub3JGb250PjxhOmxhdGluIHR5cGVmYWNlPSJDYWxpYnJpIi8+PGE6ZWEgdHlwZWZhY2U9IiIvPjxhOmNzIHR5cGVmYWNlPSIiLz48YTpmb250IHNjcmlwdD0iSnBhbiIgdHlwZWZhY2U9Iu+8re+8syDvvLDjgrTjgrfjg4Pjgq8iLz48YTpmb250IHNjcmlwdD0iSGFuZyIgdHlwZWZhY2U9IuunkeydgCDqs6DrlJUiLz48YTpmb250IHNjcmlwdD0iSGFucyIgdHlwZWZhY2U9IuWui+S9kyIvPjxhOmZvbnQgc2NyaXB0PSJIYW50IiB0eXBlZmFjZT0i5paw57Sw5piO6auUIi8+PGE6Zm9udCBzY3JpcHQ9IkFyYWIiIHR5cGVmYWNlPSJBcmlhbCIvPjxhOmZvbnQgc2NyaXB0PSJIZWJyIiB0eXBlZmFjZT0iQXJpYWwiLz48YTpmb250IHNjcmlwdD0iVGhhaSIgdHlwZWZhY2U9IlRhaG9tYSIvPjxhOmZvbnQgc2NyaXB0PSJFdGhpIiB0eXBlZmFjZT0iTnlhbGEiLz48YTpmb250IHNjcmlwdD0iQmVuZyIgdHlwZWZhY2U9IlZyaW5kYSIvPjxhOmZvbnQgc2NyaXB0PSJHdWpyIiB0eXBlZmFjZT0iU2hydXRpIi8+PGE6Zm9udCBzY3JpcHQ9IktobXIiIHR5cGVmYWNlPSJEYXVuUGVuaCIvPjxhOmZvbnQgc2NyaXB0PSJLbmRhIiB0eXBlZmFjZT0iVHVuZ2EiLz48YTpmb250IHNjcmlwdD0iR3VydSIgdHlwZWZhY2U9IlJhYXZpIi8+PGE6Zm9udCBzY3JpcHQ9IkNhbnMiIHR5cGVmYWNlPSJFdXBoZW1pYSIvPjxhOmZvbnQgc2NyaXB0PSJDaGVyIiB0eXBlZmFjZT0iUGxhbnRhZ2VuZXQgQ2hlcm9rZWUiLz48YTpmb250IHNjcmlwdD0iWWlpaSIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBZaSBCYWl0aSIvPjxhOmZvbnQgc2NyaXB0PSJUaWJ0IiB0eXBlZmFjZT0iTWljcm9zb2Z0IEhpbWFsYXlhIi8+PGE6Zm9udCBzY3JpcHQ9IlRoYWEiIHR5cGVmYWNlPSJNViBCb2xpIi8+PGE6Zm9udCBzY3JpcHQ9IkRldmEiIHR5cGVmYWNlPSJNYW5nYWwiLz48YTpmb250IHNjcmlwdD0iVGVsdSIgdHlwZWZhY2U9IkdhdXRhbWkiLz48YTpmb250IHNjcmlwdD0iVGFtbCIgdHlwZWZhY2U9IkxhdGhhIi8+PGE6Zm9udCBzY3JpcHQ9IlN5cmMiIHR5cGVmYWNlPSJFc3RyYW5nZWxvIEVkZXNzYSIvPjxhOmZvbnQgc2NyaXB0PSJPcnlhIiB0eXBlZmFjZT0iS2FsaW5nYSIvPjxhOmZvbnQgc2NyaXB0PSJNbHltIiB0eXBlZmFjZT0iS2FydGlrYSIvPjxhOmZvbnQgc2NyaXB0PSJMYW9vIiB0eXBlZmFjZT0iRG9rQ2hhbXBhIi8+PGE6Zm9udCBzY3JpcHQ9IlNpbmgiIHR5cGVmYWNlPSJJc2tvb2xhIFBvdGEiLz48YTpmb250IHNjcmlwdD0iTW9uZyIgdHlwZWZhY2U9Ik1vbmdvbGlhbiBCYWl0aSIvPjxhOmZvbnQgc2NyaXB0PSJWaWV0IiB0eXBlZmFjZT0iQXJpYWwiLz48YTpmb250IHNjcmlwdD0iVWlnaCIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBVaWdodXIiLz48YTpmb250IHNjcmlwdD0iR2VvciIgdHlwZWZhY2U9IlN5bGZhZW4iLz48L2E6bWlub3JGb250PjwvYTpmb250U2NoZW1lPjxhOmZtdFNjaGVtZSBuYW1lPSJPZmZpY2UiPjxhOmZpbGxTdHlsZUxzdD48YTpzb2xpZEZpbGw+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiLz48L2E6c29saWRGaWxsPjxhOmdyYWRGaWxsIHJvdFdpdGhTaGFwZT0iMSI+PGE6Z3NMc3Q+PGE6Z3MgcG9zPSIwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6dGludCB2YWw9IjUwMDAwIi8+PGE6c2F0TW9kIHZhbD0iMzAwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSIzNTAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSIzNzAwMCIvPjxhOnNhdE1vZCB2YWw9IjMwMDAwMCIvPjwvYTpzY2hlbWVDbHI+PC9hOmdzPjxhOmdzIHBvcz0iMTAwMDAwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6dGludCB2YWw9IjE1MDAwIi8+PGE6c2F0TW9kIHZhbD0iMzUwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOmxpbiBhbmc9IjE2MjAwMDAwIiBzY2FsZWQ9IjEiLz48L2E6Z3JhZEZpbGw+PGE6Z3JhZEZpbGwgcm90V2l0aFNoYXBlPSIxIj48YTpnc0xzdD48YTpncyBwb3M9IjAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTp0aW50IHZhbD0iMTAwMDAwIi8+PGE6c2hhZGUgdmFsPSIxMDAwMDAiLz48YTpzYXRNb2QgdmFsPSIxMzAwMDAiLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjEwMDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI1MDAwMCIvPjxhOnNoYWRlIHZhbD0iMTAwMDAwIi8+PGE6c2F0TW9kIHZhbD0iMzUwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOmxpbiBhbmc9IjE2MjAwMDAwIiBzY2FsZWQ9IjAiLz48L2E6Z3JhZEZpbGw+PC9hOmZpbGxTdHlsZUxzdD48YTpsblN0eWxlTHN0PjxhOmxuIHc9Ijk1MjUiIGNhcD0iZmxhdCIgY21wZD0ic25nIiBhbGduPSJjdHIiPjxhOnNvbGlkRmlsbD48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6c2hhZGUgdmFsPSI5NTAwMCIvPjxhOnNhdE1vZCB2YWw9IjEwNTAwMCIvPjwvYTpzY2hlbWVDbHI+PC9hOnNvbGlkRmlsbD48YTpwcnN0RGFzaCB2YWw9InNvbGlkIi8+PC9hOmxuPjxhOmxuIHc9IjI1NDAwIiBjYXA9ImZsYXQiIGNtcGQ9InNuZyIgYWxnbj0iY3RyIj48YTpzb2xpZEZpbGw+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiLz48L2E6c29saWRGaWxsPjxhOnByc3REYXNoIHZhbD0ic29saWQiLz48L2E6bG4+PGE6bG4gdz0iMzgxMDAiIGNhcD0iZmxhdCIgY21wZD0ic25nIiBhbGduPSJjdHIiPjxhOnNvbGlkRmlsbD48YTpzY2hlbWVDbHIgdmFsPSJwaENsciIvPjwvYTpzb2xpZEZpbGw+PGE6cHJzdERhc2ggdmFsPSJzb2xpZCIvPjwvYTpsbj48L2E6bG5TdHlsZUxzdD48YTplZmZlY3RTdHlsZUxzdD48YTplZmZlY3RTdHlsZT48YTplZmZlY3RMc3Q+PGE6b3V0ZXJTaGR3IGJsdXJSYWQ9IjQwMDAwIiBkaXN0PSIyMDAwMCIgZGlyPSI1NDAwMDAwIiByb3RXaXRoU2hhcGU9IjAiPjxhOnNyZ2JDbHIgdmFsPSIwMDAwMDAiPjxhOmFscGhhIHZhbD0iMzgwMDAiLz48L2E6c3JnYkNscj48L2E6b3V0ZXJTaGR3PjwvYTplZmZlY3RMc3Q+PC9hOmVmZmVjdFN0eWxlPjxhOmVmZmVjdFN0eWxlPjxhOmVmZmVjdExzdD48YTpvdXRlclNoZHcgYmx1clJhZD0iNDAwMDAiIGRpc3Q9IjIzMDAwIiBkaXI9IjU0MDAwMDAiIHJvdFdpdGhTaGFwZT0iMCI+PGE6c3JnYkNsciB2YWw9IjAwMDAwMCI+PGE6YWxwaGEgdmFsPSIzNTAwMCIvPjwvYTpzcmdiQ2xyPjwvYTpvdXRlclNoZHc+PC9hOmVmZmVjdExzdD48L2E6ZWZmZWN0U3R5bGU+PGE6ZWZmZWN0U3R5bGU+PGE6ZWZmZWN0THN0PjxhOm91dGVyU2hkdyBibHVyUmFkPSI0MDAwMCIgZGlzdD0iMjMwMDAiIGRpcj0iNTQwMDAwMCIgcm90V2l0aFNoYXBlPSIwIj48YTpzcmdiQ2xyIHZhbD0iMDAwMDAwIj48YTphbHBoYSB2YWw9IjM1MDAwIi8+PC9hOnNyZ2JDbHI+PC9hOm91dGVyU2hkdz48L2E6ZWZmZWN0THN0PjxhOnNjZW5lM2Q+PGE6Y2FtZXJhIHByc3Q9Im9ydGhvZ3JhcGhpY0Zyb250Ij48YTpyb3QgbGF0PSIwIiBsb249IjAiIHJldj0iMCIvPjwvYTpjYW1lcmE+PGE6bGlnaHRSaWcgcmlnPSJ0aHJlZVB0IiBkaXI9InQiPjxhOnJvdCBsYXQ9IjAiIGxvbj0iMCIgcmV2PSIxMjAwMDAwIi8+PC9hOmxpZ2h0UmlnPjwvYTpzY2VuZTNkPjxhOnNwM2Q+PGE6YmV2ZWxUIHc9IjYzNTAwIiBoPSIyNTQwMCIvPjwvYTpzcDNkPjwvYTplZmZlY3RTdHlsZT48L2E6ZWZmZWN0U3R5bGVMc3Q+PGE6YmdGaWxsU3R5bGVMc3Q+PGE6c29saWRGaWxsPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIi8+PC9hOnNvbGlkRmlsbD48YTpncmFkRmlsbCByb3RXaXRoU2hhcGU9IjEiPjxhOmdzTHN0PjxhOmdzIHBvcz0iMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI0MDAwMCIvPjxhOnNhdE1vZCB2YWw9IjM1MDAwMCIvPjwvYTpzY2hlbWVDbHI+PC9hOmdzPjxhOmdzIHBvcz0iNDAwMDAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTp0aW50IHZhbD0iNDUwMDAiLz48YTpzaGFkZSB2YWw9Ijk5MDAwIi8+PGE6c2F0TW9kIHZhbD0iMzUwMDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSIxMDAwMDAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTpzaGFkZSB2YWw9IjIwMDAwIi8+PGE6c2F0TW9kIHZhbD0iMjU1MDAwIi8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOnBhdGggcGF0aD0iY2lyY2xlIj48YTpmaWxsVG9SZWN0IGw9IjUwMDAwIiB0PSItODAwMDAiIHI9IjUwMDAwIiBiPSIxODAwMDAiLz48L2E6cGF0aD48L2E6Z3JhZEZpbGw+PGE6Z3JhZEZpbGwgcm90V2l0aFNoYXBlPSIxIj48YTpnc0xzdD48YTpncyBwb3M9IjAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTp0aW50IHZhbD0iODAwMDAiLz48YTpzYXRNb2QgdmFsPSIzMDAwMDAiLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjEwMDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnNoYWRlIHZhbD0iMzAwMDAiLz48YTpzYXRNb2QgdmFsPSIyMDAwMDAiLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48L2E6Z3NMc3Q+PGE6cGF0aCBwYXRoPSJjaXJjbGUiPjxhOmZpbGxUb1JlY3QgbD0iNTAwMDAiIHQ9IjUwMDAwIiByPSI1MDAwMCIgYj0iNTAwMDAiLz48L2E6cGF0aD48L2E6Z3JhZEZpbGw+PC9hOmJnRmlsbFN0eWxlTHN0PjwvYTpmbXRTY2hlbWU+PC9hOnRoZW1lRWxlbWVudHM+PGE6b2JqZWN0RGVmYXVsdHM+PGE6c3BEZWY+PGE6c3BQci8+PGE6Ym9keVByLz48YTpsc3RTdHlsZS8+PGE6c3R5bGU+PGE6bG5SZWYgaWR4PSIxIj48YTpzY2hlbWVDbHIgdmFsPSJhY2NlbnQxIi8+PC9hOmxuUmVmPjxhOmZpbGxSZWYgaWR4PSIzIj48YTpzY2hlbWVDbHIgdmFsPSJhY2NlbnQxIi8+PC9hOmZpbGxSZWY+PGE6ZWZmZWN0UmVmIGlkeD0iMiI+PGE6c2NoZW1lQ2xyIHZhbD0iYWNjZW50MSIvPjwvYTplZmZlY3RSZWY+PGE6Zm9udFJlZiBpZHg9Im1pbm9yIj48YTpzY2hlbWVDbHIgdmFsPSJsdDEiLz48L2E6Zm9udFJlZj48L2E6c3R5bGU+PC9hOnNwRGVmPjxhOmxuRGVmPjxhOnNwUHIvPjxhOmJvZHlQci8+PGE6bHN0U3R5bGUvPjxhOnN0eWxlPjxhOmxuUmVmIGlkeD0iMiI+PGE6c2NoZW1lQ2xyIHZhbD0iYWNjZW50MSIvPjwvYTpsblJlZj48YTpmaWxsUmVmIGlkeD0iMCI+PGE6c2NoZW1lQ2xyIHZhbD0iYWNjZW50MSIvPjwvYTpmaWxsUmVmPjxhOmVmZmVjdFJlZiBpZHg9IjEiPjxhOnNjaGVtZUNsciB2YWw9ImFjY2VudDEiLz48L2E6ZWZmZWN0UmVmPjxhOmZvbnRSZWYgaWR4PSJtaW5vciI+PGE6c2NoZW1lQ2xyIHZhbD0idHgxIi8+PC9hOmZvbnRSZWY+PC9hOnN0eWxlPjwvYTpsbkRlZj48L2E6b2JqZWN0RGVmYXVsdHM+PGE6ZXh0cmFDbHJTY2hlbWVMc3QvPjwvYTp0aGVtZT5QSwMEFAAAAAAAAAAAAFX0BJRaBAAAWgQAAA0AAAB4bC9zdHlsZXMueG1sPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPHN0eWxlU2hlZXQgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9zcHJlYWRzaGVldG1sLzIwMDYvbWFpbiIgeG1sbnM6dnQ9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L2RvY1Byb3BzVlR5cGVzIj48bnVtRm10cyBjb3VudD0iMSI+PG51bUZtdCBudW1GbXRJZD0iNTYiIGZvcm1hdENvZGU9IiZxdW90O+S4iuWNiC/kuIvljYggJnF1b3Q7aGgmcXVvdDvmmYImcXVvdDttbSZxdW90O+WIhiZxdW90O3NzJnF1b3Q756eSICZxdW90OyIvPjwvbnVtRm10cz48Zm9udHMgY291bnQ9IjEiPjxmb250PjxzeiB2YWw9IjEyIi8+PGNvbG9yIHRoZW1lPSIxIi8+PG5hbWUgdmFsPSJDYWxpYnJpIi8+PGZhbWlseSB2YWw9IjIiLz48c2NoZW1lIHZhbD0ibWlub3IiLz48L2ZvbnQ+PC9mb250cz48ZmlsbHMgY291bnQ9IjIiPjxmaWxsPjxwYXR0ZXJuRmlsbCBwYXR0ZXJuVHlwZT0ibm9uZSIvPjwvZmlsbD48ZmlsbD48cGF0dGVybkZpbGwgcGF0dGVyblR5cGU9ImdyYXkxMjUiLz48L2ZpbGw+PC9maWxscz48Ym9yZGVycyBjb3VudD0iMSI+PGJvcmRlcj48bGVmdC8+PHJpZ2h0Lz48dG9wLz48Ym90dG9tLz48ZGlhZ29uYWwvPjwvYm9yZGVyPjwvYm9yZGVycz48Y2VsbFN0eWxlWGZzIGNvdW50PSIxIj48eGYgbnVtRm10SWQ9IjAiIGZvbnRJZD0iMCIgZmlsbElkPSIwIiBib3JkZXJJZD0iMCIvPjwvY2VsbFN0eWxlWGZzPjxjZWxsWGZzIGNvdW50PSIxIj48eGYgbnVtRm10SWQ9IjAiIGZvbnRJZD0iMCIgZmlsbElkPSIwIiBib3JkZXJJZD0iMCIgeGZJZD0iMCIgYXBwbHlOdW1iZXJGb3JtYXQ9IjEiLz48L2NlbGxYZnM+PGNlbGxTdHlsZXMgY291bnQ9IjEiPjxjZWxsU3R5bGUgbmFtZT0iTm9ybWFsIiB4ZklkPSIwIiBidWlsdGluSWQ9IjAiLz48L2NlbGxTdHlsZXM+PGR4ZnMgY291bnQ9IjAiLz48dGFibGVTdHlsZXMgY291bnQ9IjAiIGRlZmF1bHRUYWJsZVN0eWxlPSJUYWJsZVN0eWxlTWVkaXVtOSIgZGVmYXVsdFBpdm90U3R5bGU9IlBpdm90U3R5bGVNZWRpdW00Ii8+PC9zdHlsZVNoZWV0PlBLAwQUAAAAAAAAAAAA+oDcDIEQAACBEAAAGAAAAHhsL3dvcmtzaGVldHMvc2hlZXQxLnhtbDw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04IiBzdGFuZGFsb25lPSJ5ZXMiPz4NCjx3b3Jrc2hlZXQgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9zcHJlYWRzaGVldG1sLzIwMDYvbWFpbiIgeG1sbnM6cj0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcyI+PGRpbWVuc2lvbiByZWY9IkExOkQxOCIvPjxzaGVldFZpZXdzPjxzaGVldFZpZXcgd29ya2Jvb2tWaWV3SWQ9IjAiLz48L3NoZWV0Vmlld3M+PGNvbHM+PGNvbCBtaW49IjEiIG1heD0iMSIgd2lkdGg9IjMyLjgzMjAzMTI1IiBjdXN0b21XaWR0aD0iMSIvPjxjb2wgbWluPSIyIiBtYXg9IjIiIHdpZHRoPSIyOC44MzIwMzEyNSIgY3VzdG9tV2lkdGg9IjEiLz48Y29sIG1pbj0iMyIgbWF4PSIzIiB3aWR0aD0iMzguODMyMDMxMjUiIGN1c3RvbVdpZHRoPSIxIi8+PGNvbCBtaW49IjQiIG1heD0iNCIgd2lkdGg9IjEyLjgzMjAzMTI1IiBjdXN0b21XaWR0aD0iMSIvPjwvY29scz48c2hlZXREYXRhPjxyb3cgcj0iMSI+PGMgcj0iQTEiIHQ9InN0ciI+PHY+RW1haWw8L3Y+PC9jPjxjIHI9IkIxIiB0PSJzdHIiPjx2Pk5vbSBhY3R1ZWwgKHRyYWNraW5nKTwvdj48L2M+PGMgcj0iQzEiIHQ9InN0ciI+PHY+VnJhaSBub20gKEV4Y2VsINCh0L3QsNCx0LbQtdC90LjQtSk8L3Y+PC9jPjxjIHI9IkQxIiB0PSJzdHIiPjx2PlN0YXR1dDwvdj48L2M+PC9yb3c+PHJvdyByPSIyIj48YyByPSJBMiIgdD0ic3RyIj48dj5lX3N1aG92YUBraXJlbGlzLnJ1PC92PjwvYz48YyByPSJCMiIgdD0ic3RyIj48dj5raXJlbGlzLnJ1PC92PjwvYz48YyByPSJDMiIgdD0ic3RyIj48dj7QmtCY0KDQldCb0JjQoSDQntCe0J48L3Y+PC9jPjxjIHI9IkQyIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMyI+PGMgcj0iQTMiIHQ9InN0ciI+PHY+NzEwMTE1dHZlckBtYWlsLnJ1PC92PjwvYz48YyByPSJCMyIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDMyIgdD0ic3RyIj48dj7QpdC+0LzRg9GC0L7QsiDQkNC90LTRgNC10Lkg0JDQvdC00YDQtdC10LLQuNGHINCY0J88L3Y+PC9jPjxjIHI9IkQzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iNCI+PGMgcj0iQTQiIHQ9InN0ciI+PHY+emFrYXpAbW9iaXhpbS5ydTwvdj48L2M+PGMgcj0iQjQiIHQ9InN0ciI+PHY+bW9iaXhpbS5ydTwvdj48L2M+PGMgcj0iQzQiIHQ9InN0ciI+PHY+0JzQntCR0JjQpdCY0Jwg0J7QntCePC92PjwvYz48YyByPSJENCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjUiPjxjIHI9IkE1IiB0PSJzdHIiPjx2PmNoZWNoZXZpY2luX3Rwc0BtYWlsLnJ1PC92PjwvYz48YyByPSJCNSIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDNSIgdD0ic3RyIj48dj7QotCf0KEg0J7QntCePC92PjwvYz48YyByPSJENSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjYiPjxjIHI9IkE2IiB0PSJzdHIiPjx2PnN2YXJrYXR2ZXJAeWFuZGV4LnJ1PC92PjwvYz48YyByPSJCNiIgdD0ic3RyIj48dj55YW5kZXgucnU8L3Y+PC9jPjxjIHI9IkM2IiB0PSJzdHIiPjx2PtCQ0JLQldCh0KLQkCDQntCe0J48L3Y+PC9jPjxjIHI9IkQ2IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iNyI+PGMgcj0iQTciIHQ9InN0ciI+PHY+b29vdHJla0BpbmJveC5ydTwvdj48L2M+PGMgcj0iQjciIHQ9InN0ciI+PHY+aW5ib3gucnU8L3Y+PC9jPjxjIHI9IkM3IiB0PSJzdHIiPjx2PtCi0KDQrdCaINCe0J7Qnjwvdj48L2M+PGMgcj0iRDciIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSI4Ij48YyByPSJBOCIgdD0ic3RyIj48dj51cmFsMUBjb2xvcnBvd2RlcmNvYXRpbmcuY29tPC92PjwvYz48YyByPSJCOCIgdD0ic3RyIj48dj5jb2xvcnBvd2RlcmNvYXRpbmcuY29tPC92PjwvYz48YyByPSJDOCIgdD0ic3RyIj48dj7QpNCt0JnQpdCe0J0g0J7QntCePC92PjwvYz48YyByPSJEOCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjkiPjxjIHI9IkE5IiB0PSJzdHIiPjx2PjE0NEBhdXRvcGFjay5ydTwvdj48L2M+PGMgcj0iQjkiIHQ9InN0ciI+PHY+YXV0b3BhY2sucnU8L3Y+PC9jPjxjIHI9IkM5IiB0PSJzdHIiPjx2PtCi0JQg0JDQktCi0J7Qn9CQ0Jog0J7QntCePC92PjwvYz48YyByPSJEOSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjEwIj48YyByPSJBMTAiIHQ9InN0ciI+PHY+bWFpbEBzaHlta2EucnU8L3Y+PC9jPjxjIHI9IkIxMCIgdD0ic3RyIj48dj5zaHlta2EucnU8L3Y+PC9jPjxjIHI9IkMxMCIgdD0ic3RyIj48dj7QqNCj0JzQmtCQINCe0J7QniDQmNCd0KLQldCg0J3QldCiLdCc0JDQk9CQ0JfQmNCdPC92PjwvYz48YyByPSJEMTAiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSIxMSI+PGMgcj0iQTExIiB0PSJzdHIiPjx2PmV2a29uZHJhdEBtYWlsLnJ1PC92PjwvYz48YyByPSJCMTEiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzExIiB0PSJzdHIiPjx2PtCQ0JLQotCe0JPQm9Ce0JHQo9ChINCe0J7Qnjwvdj48L2M+PGMgcj0iRDExIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTIiPjxjIHI9IkExMiIgdD0ic3RyIj48dj5kb2xnQGFrdmEtdml0YS5uZXQ8L3Y+PC9jPjxjIHI9IkIxMiIgdD0ic3RyIj48dj5ha3ZhLXZpdGEubmV0PC92PjwvYz48YyByPSJDMTIiIHQ9InN0ciI+PHY+0JAt0JIt0JrQntCc0J/QkNCd0Jgg0J7QntCePC92PjwvYz48YyByPSJEMTIiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSIxMyI+PGMgcj0iQTEzIiB0PSJzdHIiPjx2PnR2ZXI0NzU2NzhAbWFpbC5ydTwvdj48L2M+PGMgcj0iQjEzIiB0PSJzdHIiPjx2Pm1haWwucnU8L3Y+PC9jPjxjIHI9IkMxMyIgdD0ic3RyIj48dj7Qn9Cg0J7QnC3Qn9Ce0KHQotCQ0JLQmtCQINCe0J7Qnjwvdj48L2M+PGMgcj0iRDEzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTQiPjxjIHI9IkExNCIgdD0ic3RyIj48dj5vdi52aWtoYXJldmFAc2V2ZXJzdGFsLmNvbTwvdj48L2M+PGMgcj0iQjE0IiB0PSJzdHIiPjx2PnNldmVyc3RhbC5jb208L3Y+PC9jPjxjIHI9IkMxNCIgdD0ic3RyIj48dj7QrtCd0JjQpNCV0J3QoSDQntCe0J48L3Y+PC9jPjxjIHI9IkQxNCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjE1Ij48YyByPSJBMTUiIHQ9InN0ciI+PHY+aS5zaGFrdXJvdkBtZXRpemkucnU8L3Y+PC9jPjxjIHI9IkIxNSIgdD0ic3RyIj48dj5tZXRpemkucnU8L3Y+PC9jPjxjIHI9IkMxNSIgdD0ic3RyIj48dj7QkNCb0KzQpNCQINCg0JDQnNCV0J3QmtCQINCe0J7Qnjwvdj48L2M+PGMgcj0iRDE1IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTYiPjxjIHI9IkExNiIgdD0ic3RyIj48dj5zb3Jva2luX2JhdUBtYWlsLnJ1PC92PjwvYz48YyByPSJCMTYiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzE2IiB0PSJzdHIiPjx2PtCi0JLQldCg0KHQmtCY0JUg0J/QmNCb0J7QoNCQ0JzQqyDQntCe0J48L3Y+PC9jPjxjIHI9IkQxNiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48cm93IHI9IjE3Ij48YyByPSJBMTciIHQ9InN0ciI+PHY+c2VyZ2V5X29vb19oYXJkQG1haWwucnU8L3Y+PC9jPjxjIHI9IkIxNyIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDMTciIHQ9InN0ciI+PHY+0KXQkNCg0JTQktCj0JQg0J7QntCePC92PjwvYz48YyByPSJEMTciIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PC9yb3c+PHJvdyByPSIxOCI+PGMgcj0iQTE4IiB0PSJzdHIiPjx2PnRzQGF2YWxvbi5zdTwvdj48L2M+PGMgcj0iQjE4IiB0PSJzdHIiPjx2PmF2YWxvbi5zdTwvdj48L2M+PGMgcj0iQzE4IiB0PSJzdHIiPjx2PtCQ0JLQkNCb0J7QnS3QoSDQntCe0J48L3Y+PC9jPjxjIHI9IkQxOCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48L3Jvdz48L3NoZWV0RGF0YT48aWdub3JlZEVycm9ycz48aWdub3JlZEVycm9yIG51bWJlclN0b3JlZEFzVGV4dD0iMSIgc3FyZWY9IkExOkQxOCIvPjwvaWdub3JlZEVycm9ycz48L3dvcmtzaGVldD5QSwMEFAAAAAAAAAAAALeVZcljHQAAYx0AABgAAAB4bC93b3Jrc2hlZXRzL3NoZWV0Mi54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8d29ya3NoZWV0IHhtbG5zPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvc3ByZWFkc2hlZXRtbC8yMDA2L21haW4iIHhtbG5zOnI9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMiPjxkaW1lbnNpb24gcmVmPSJBMTpEMzUiLz48c2hlZXRWaWV3cz48c2hlZXRWaWV3IHdvcmtib29rVmlld0lkPSIwIi8+PC9zaGVldFZpZXdzPjxjb2xzPjxjb2wgbWluPSIxIiBtYXg9IjEiIHdpZHRoPSIzMi44MzIwMzEyNSIgY3VzdG9tV2lkdGg9IjEiLz48Y29sIG1pbj0iMiIgbWF4PSIyIiB3aWR0aD0iMjguODMyMDMxMjUiIGN1c3RvbVdpZHRoPSIxIi8+PGNvbCBtaW49IjMiIG1heD0iMyIgd2lkdGg9IjEyLjgzMjAzMTI1IiBjdXN0b21XaWR0aD0iMSIvPjxjb2wgbWluPSI0IiBtYXg9IjQiIHdpZHRoPSIzMC44MzIwMzEyNSIgY3VzdG9tV2lkdGg9IjEiLz48L2NvbHM+PHNoZWV0RGF0YT48cm93IHI9IjEiPjxjIHI9IkExIiB0PSJzdHIiPjx2PkVtYWlsPC92PjwvYz48YyByPSJCMSIgdD0ic3RyIj48dj5Ob20gYWN0dWVsICh0cmFja2luZyk8L3Y+PC9jPjxjIHI9IkMxIiB0PSJzdHIiPjx2PlN0YXR1dDwvdj48L2M+PGMgcj0iRDEiIHQ9InN0ciI+PHY+Tm90ZXMgZW52b2k8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMiI+PGMgcj0iQTIiIHQ9InN0ciI+PHY+Y2xpZW50LnNlcnZpY2VAZGVsbGluLnJ1PC92PjwvYz48YyByPSJCMiIgdD0ic3RyIj48dj5kZWxsaW4ucnU8L3Y+PC9jPjxjIHI9IkMyIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjMiPjxjIHI9IkEzIiB0PSJzdHIiPjx2PmdpbWVpbkBzYmVyYmFuay1tYWlsLmNvbTwvdj48L2M+PGMgcj0iQjMiIHQ9InN0ciI+PHY+c2JlcmJhbmstbWFpbC5jb208L3Y+PC9jPjxjIHI9IkMzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjQiPjxjIHI9IkE0IiB0PSJzdHIiPjx2PmFyY2hpYmFsZDgzQG1haWwucnU8L3Y+PC9jPjxjIHI9IkI0IiB0PSJzdHIiPjx2Pm1haWwucnU8L3Y+PC9jPjxjIHI9IkM0IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQ0IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjUiPjxjIHI9IkE1IiB0PSJzdHIiPjx2PmEuYmFidXJpZGlzQG1ldGl6aS5ydTwvdj48L2M+PGMgcj0iQjUiIHQ9InN0ciI+PHY+bWV0aXppLnJ1PC92PjwvYz48YyByPSJDNSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJENSIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSI2Ij48YyByPSJBNiIgdD0ic3RyIj48dj5wcm8xQGVjbzY5LnJ1PC92PjwvYz48YyByPSJCNiIgdD0ic3RyIj48dj5lY282OS5ydTwvdj48L2M+PGMgcj0iQzYiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDYiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iNyI+PGMgcj0iQTciIHQ9InN0ciI+PHY+bS51c3Ryb2toYW5vdkBna2F0bW9zLnJ1PC92PjwvYz48YyByPSJCNyIgdD0ic3RyIj48dj5na2F0bW9zLnJ1PC92PjwvYz48YyByPSJDNyIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJENyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSI4Ij48YyByPSJBOCIgdD0ic3RyIj48dj43MDdAbWV0YWxsLTc3LnJ1PC92PjwvYz48YyByPSJCOCIgdD0ic3RyIj48dj5tZXRhbGwtNzcucnU8L3Y+PC9jPjxjIHI9IkM4IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQ4IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjkiPjxjIHI9IkE5IiB0PSJzdHIiPjx2PnNlcnZpY2VAcml2ZXRndW4ucnU8L3Y+PC9jPjxjIHI9IkI5IiB0PSJzdHIiPjx2PnJpdmV0Z3VuLnJ1PC92PjwvYz48YyByPSJDOSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEOSIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxMCI+PGMgcj0iQTEwIiB0PSJzdHIiPjx2PmVrYkBrb2xlc2Fyb2xpa2kuY29tPC92PjwvYz48YyByPSJCMTAiIHQ9InN0ciI+PHY+a29sZXNhcm9saWtpLmNvbTwvdj48L2M+PGMgcj0iQzEwIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxMCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxMSI+PGMgcj0iQTExIiB0PSJzdHIiPjx2PmF1dG9waXRlckBhdXRvcGl0ZXIucnU8L3Y+PC9jPjxjIHI9IkIxMSIgdD0ic3RyIj48dj5hdXRvcGl0ZXIucnU8L3Y+PC9jPjxjIHI9IkMxMSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMTEiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTIiPjxjIHI9IkExMiIgdD0ic3RyIj48dj50dEB0dG9rYXJrYS5ydTwvdj48L2M+PGMgcj0iQjEyIiB0PSJzdHIiPjx2PnR0b2thcmthLnJ1PC92PjwvYz48YyByPSJDMTIiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDEyIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjEzIj48YyByPSJBMTMiIHQ9InN0ciI+PHY+cGFsbGV0MjAyNUBtYWlsLnJ1PC92PjwvYz48YyByPSJCMTMiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzEzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxMyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxNCI+PGMgcj0iQTE0IiB0PSJzdHIiPjx2PmVfYXN0YWZ1cm92YUBraXJlbGlzLnJ1PC92PjwvYz48YyByPSJCMTQiIHQ9InN0ciI+PHY+a2lyZWxpcy5ydTwvdj48L2M+PGMgcj0iQzE0IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxNCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxNSI+PGMgcj0iQTE1IiB0PSJzdHIiPjx2PmEuYnV0YWV2QGdrYXRtb3MucnU8L3Y+PC9jPjxjIHI9IkIxNSIgdD0ic3RyIj48dj5na2F0bW9zLnJ1PC92PjwvYz48YyByPSJDMTUiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDE1IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjE2Ij48YyByPSJBMTYiIHQ9InN0ciI+PHY+YnVoNkBvLXBpcnMucnU8L3Y+PC9jPjxjIHI9IkIxNiIgdD0ic3RyIj48dj5vLXBpcnMucnU8L3Y+PC9jPjxjIHI9IkMxNiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMTYiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMTciPjxjIHI9IkExNyIgdD0ic3RyIj48dj5rZG5Aeml0YXIucnU8L3Y+PC9jPjxjIHI9IkIxNyIgdD0ic3RyIj48dj56aXRhci5ydTwvdj48L2M+PGMgcj0iQzE3IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQxNyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIxOCI+PGMgcj0iQTE4IiB0PSJzdHIiPjx2PmJveWFyZC10dmVyQG1haWwucnU8L3Y+PC9jPjxjIHI9IkIxOCIgdD0ic3RyIj48dj5tYWlsLnJ1PC92PjwvYz48YyByPSJDMTgiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDE4IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjE5Ij48YyByPSJBMTkiIHQ9InN0ciI+PHY+c2FsZTEyQGF1dG9wYWNrLnJ1PC92PjwvYz48YyByPSJCMTkiIHQ9InN0ciI+PHY+YXV0b3BhY2sucnU8L3Y+PC9jPjxjIHI9IkMxOSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMTkiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjAiPjxjIHI9IkEyMCIgdD0ic3RyIj48dj5hLnZvbGtvbnNreUBzdDc3LnJ1PC92PjwvYz48YyByPSJCMjAiIHQ9InN0ciI+PHY+c3Q3Ny5ydTwvdj48L2M+PGMgcj0iQzIwIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyMCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIyMSI+PGMgcj0iQTIxIiB0PSJzdHIiPjx2Pm9wZXJhdG9yMkBzcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkIyMSIgdD0ic3RyIj48dj5zcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkMyMSIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjEiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjIiPjxjIHI9IkEyMiIgdD0ic3RyIj48dj5uZXpidXhAbWFpbC5ydTwvdj48L2M+PGMgcj0iQjIyIiB0PSJzdHIiPjx2Pm1haWwucnU8L3Y+PC9jPjxjIHI9IkMyMiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjIiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjMiPjxjIHI9IkEyMyIgdD0ic3RyIj48dj5idWhzdmVya2FAdGVjaG5peC1ydXMucnU8L3Y+PC9jPjxjIHI9IkIyMyIgdD0ic3RyIj48dj50ZWNobml4LXJ1cy5ydTwvdj48L2M+PGMgcj0iQzIzIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyMyIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIyNCI+PGMgcj0iQTI0IiB0PSJzdHIiPjx2Pm1hbmFnZXJAdGVjaG5peC1ydXMucnU8L3Y+PC9jPjxjIHI9IkIyNCIgdD0ic3RyIj48dj50ZWNobml4LXJ1cy5ydTwvdj48L2M+PGMgcj0iQzI0IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyNCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIyNSI+PGMgcj0iQTI1IiB0PSJzdHIiPjx2Pm1zazhAdHJ1Ym9wcm9kdWN0LnJ1PC92PjwvYz48YyByPSJCMjUiIHQ9InN0ciI+PHY+dHJ1Ym9wcm9kdWN0LnJ1PC92PjwvYz48YyByPSJDMjUiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDI1IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjI2Ij48YyByPSJBMjYiIHQ9InN0ciI+PHY+c252QHBsYXN0ZWxvLnJ1PC92PjwvYz48YyByPSJCMjYiIHQ9InN0ciI+PHY+cGxhc3RlbG8ucnU8L3Y+PC9jPjxjIHI9IkMyNiIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjYiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjciPjxjIHI9IkEyNyIgdD0ic3RyIj48dj50bUBrcmVwY28ucnU8L3Y+PC9jPjxjIHI9IkIyNyIgdD0ic3RyIj48dj5rcmVwY28ucnU8L3Y+PC9jPjxjIHI9IkMyNyIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMjciIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMjgiPjxjIHI9IkEyOCIgdD0ic3RyIj48dj5vcmRlcnNAbXZpZGVvLnJ1PC92PjwvYz48YyByPSJCMjgiIHQ9InN0ciI+PHY+bXZpZGVvLnJ1PC92PjwvYz48YyByPSJDMjgiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDI4IiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjI5Ij48YyByPSJBMjkiIHQ9InN0ciI+PHY+dHVuaW5ndHZlckBtYWlsLnJ1PC92PjwvYz48YyByPSJCMjkiIHQ9InN0ciI+PHY+bWFpbC5ydTwvdj48L2M+PGMgcj0iQzI5IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQyOSIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIzMCI+PGMgcj0iQTMwIiB0PSJzdHIiPjx2PnNhbGVzMkBnZW8tcGxhc3QuY29tPC92PjwvYz48YyByPSJCMzAiIHQ9InN0ciI+PHY+Z2VvLXBsYXN0LmNvbTwvdj48L2M+PGMgcj0iQzMwIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzMCIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIzMSI+PGMgcj0iQTMxIiB0PSJzdHIiPjx2PnMua29yem5pa292YUB6aXRhci5ydTwvdj48L2M+PGMgcj0iQjMxIiB0PSJzdHIiPjx2PnppdGFyLnJ1PC92PjwvYz48YyByPSJDMzEiIHQ9InN0ciI+PHY+cGVuZGluZzwvdj48L2M+PGMgcj0iRDMxIiB0PSJzdHIiPjx2PkF1dG8tcmVidWlsZCAyMDI2LTAzLTAxPC92PjwvYz48L3Jvdz48cm93IHI9IjMyIj48YyByPSJBMzIiIHQ9InN0ciI+PHY+bWl0c2theWFAbWV0YWxsb3RvcmcucnU8L3Y+PC9jPjxjIHI9IkIzMiIgdD0ic3RyIj48dj5tZXRhbGxvdG9yZy5ydTwvdj48L2M+PGMgcj0iQzMyIiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzMiIgdD0ic3RyIj48dj5BdXRvLXJlYnVpbGQgMjAyNi0wMy0wMTwvdj48L2M+PC9yb3c+PHJvdyByPSIzMyI+PGMgcj0iQTMzIiB0PSJzdHIiPjx2PnNhZHJldGRpbm92YUBzcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkIzMyIgdD0ic3RyIj48dj5zcHJpbmdzLXByb2plY3QucnU8L3Y+PC9jPjxjIHI9IkMzMyIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMzMiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMzQiPjxjIHI9IkEzNCIgdD0ic3RyIj48dj50ZWhuby1yZWFsQGJrLnJ1PC92PjwvYz48YyByPSJCMzQiIHQ9InN0ciI+PHY+YmsucnU8L3Y+PC9jPjxjIHI9IkMzNCIgdD0ic3RyIj48dj5wZW5kaW5nPC92PjwvYz48YyByPSJEMzQiIHQ9InN0ciI+PHY+QXV0by1yZWJ1aWxkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjxyb3cgcj0iMzUiPjxjIHI9IkEzNSIgdD0ic3RyIj48dj5tMkBzdGl2cHJvZmlsLnJ1PC92PjwvYz48YyByPSJCMzUiIHQ9InN0ciI+PHY+c3RpdnByb2ZpbC5ydTwvdj48L2M+PGMgcj0iQzM1IiB0PSJzdHIiPjx2PnBlbmRpbmc8L3Y+PC9jPjxjIHI9IkQzNSIgdD0ic3RyIj48dj5BdXRvLWFkZGVkIDIwMjYtMDMtMDE8L3Y+PC9jPjwvcm93Pjwvc2hlZXREYXRhPjxpZ25vcmVkRXJyb3JzPjxpZ25vcmVkRXJyb3IgbnVtYmVyU3RvcmVkQXNUZXh0PSIxIiBzcXJlZj0iQTE6RDM1Ii8+PC9pZ25vcmVkRXJyb3JzPjwvd29ya3NoZWV0PlBLAwQUAAAAAAAAAAAAYIAAgYgDAACIAwAADwAAAHhsL21ldGFkYXRhLnhtbDw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04IiBzdGFuZGFsb25lPSJ5ZXMiPz4NCjxtZXRhZGF0YSB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL3NwcmVhZHNoZWV0bWwvMjAwNi9tYWluIiB4bWxuczp4bHJkPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL29mZmljZS9zcHJlYWRzaGVldG1sLzIwMTcvcmljaGRhdGEiIHhtbG5zOnhkYT0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2Uvc3ByZWFkc2hlZXRtbC8yMDE3L2R5bmFtaWNhcnJheSI+CiAgPG1ldGFkYXRhVHlwZXMgY291bnQ9IjEiPgogICAgPG1ldGFkYXRhVHlwZSBuYW1lPSJYTERBUFIiIG1pblN1cHBvcnRlZFZlcnNpb249IjEyMDAwMCIgY29weT0iMSIgcGFzdGVBbGw9IjEiIHBhc3RlVmFsdWVzPSIxIiBtZXJnZT0iMSIgc3BsaXRGaXJzdD0iMSIgcm93Q29sU2hpZnQ9IjEiIGNsZWFyRm9ybWF0cz0iMSIgY2xlYXJDb21tZW50cz0iMSIgYXNzaWduPSIxIiBjb2VyY2U9IjEiIGNlbGxNZXRhPSIxIi8+CiAgPC9tZXRhZGF0YVR5cGVzPgogIDxmdXR1cmVNZXRhZGF0YSBuYW1lPSJYTERBUFIiIGNvdW50PSIxIj4KICAgIDxiaz4KICAgICAgPGV4dExzdD4KICAgICAgICA8ZXh0IHVyaT0ie2JkYmI4Y2RjLWZhMWUtNDk2ZS1hODU3LTNjM2YzMGMwMjljM30iPgogICAgICAgICAgPHhkYTpkeW5hbWljQXJyYXlQcm9wZXJ0aWVzIGZEeW5hbWljPSIxIiBmQ29sbGFwc2VkPSIwIi8+CiAgICAgICAgPC9leHQ+CiAgICAgIDwvZXh0THN0PgogICAgPC9iaz4KICA8L2Z1dHVyZU1ldGFkYXRhPgogIDxjZWxsTWV0YWRhdGEgY291bnQ9IjEiPgogICAgPGJrPgogICAgICA8cmMgdD0iMSIgdj0iMCIvPgogICAgPC9iaz4KICA8L2NlbGxNZXRhZGF0YT4KPC9tZXRhZGF0YT5QSwMEFAAAAAAAAAAAABu9lH6DAQAAgwEAAA8AAAB4bC93b3JrYm9vay54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8d29ya2Jvb2sgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9zcHJlYWRzaGVldG1sLzIwMDYvbWFpbiIgeG1sbnM6cj0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcyI+PHdvcmtib29rUHIgY29kZU5hbWU9IlRoaXNXb3JrYm9vayIvPjxzaGVldHM+PHNoZWV0IG5hbWU9IjE3X8OgX3bDqXJpZmllciIgc2hlZXRJZD0iMSIgcjppZD0icklkMSIvPjxzaGVldCBuYW1lPSIzNF90cmFja2luZ19vbmx5IiBzaGVldElkPSIyIiByOmlkPSJySWQyIi8+PC9zaGVldHM+PC93b3JrYm9vaz5QSwMEFAAAAAAAAAAAAEpqEflMAgAATAIAAAsAAABfcmVscy8ucmVsczw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04IiBzdGFuZGFsb25lPSJ5ZXMiPz4NCjxSZWxhdGlvbnNoaXBzIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvcGFja2FnZS8yMDA2L3JlbGF0aW9uc2hpcHMiPjxSZWxhdGlvbnNoaXAgSWQ9InJJZDIiIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9wYWNrYWdlLzIwMDYvcmVsYXRpb25zaGlwcy9tZXRhZGF0YS9jb3JlLXByb3BlcnRpZXMiIFRhcmdldD0iZG9jUHJvcHMvY29yZS54bWwiLz48UmVsYXRpb25zaGlwIElkPSJySWQzIiBUeXBlPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzL2V4dGVuZGVkLXByb3BlcnRpZXMiIFRhcmdldD0iZG9jUHJvcHMvYXBwLnhtbCIvPjxSZWxhdGlvbnNoaXAgSWQ9InJJZDEiIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMvb2ZmaWNlRG9jdW1lbnQiIFRhcmdldD0ieGwvd29ya2Jvb2sueG1sIi8+PC9SZWxhdGlvbnNoaXBzPlBLAwQUAAAAAAAAAAAAurdfBGACAABgAgAAEAAAAGRvY1Byb3BzL2FwcC54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8UHJvcGVydGllcyB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvZXh0ZW5kZWQtcHJvcGVydGllcyIgeG1sbnM6dnQ9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L2RvY1Byb3BzVlR5cGVzIj48QXBwbGljYXRpb24+U2hlZXRKUzwvQXBwbGljYXRpb24+PEhlYWRpbmdQYWlycz48dnQ6dmVjdG9yIHNpemU9IjIiIGJhc2VUeXBlPSJ2YXJpYW50Ij48dnQ6dmFyaWFudD48dnQ6bHBzdHI+V29ya3NoZWV0czwvdnQ6bHBzdHI+PC92dDp2YXJpYW50Pjx2dDp2YXJpYW50Pjx2dDppND4yPC92dDppND48L3Z0OnZhcmlhbnQ+PC92dDp2ZWN0b3I+PC9IZWFkaW5nUGFpcnM+PFRpdGxlc09mUGFydHM+PHZ0OnZlY3RvciBzaXplPSIyIiBiYXNlVHlwZT0ibHBzdHIiPjx2dDpscHN0cj4xN1/DoF92w6lyaWZpZXI8L3Z0Omxwc3RyPjx2dDpscHN0cj4zNF90cmFja2luZ19vbmx5PC92dDpscHN0cj48L3Z0OnZlY3Rvcj48L1RpdGxlc09mUGFydHM+PC9Qcm9wZXJ0aWVzPlBLAwQUAAAAAAAAAAAA1pJ8EVoBAABaAQAAEQAAAGRvY1Byb3BzL2NvcmUueG1sPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPGNwOmNvcmVQcm9wZXJ0aWVzIHhtbG5zOmNwPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvcGFja2FnZS8yMDA2L21ldGFkYXRhL2NvcmUtcHJvcGVydGllcyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpkY3Rlcm1zPSJodHRwOi8vcHVybC5vcmcvZGMvdGVybXMvIiB4bWxuczpkY21pdHlwZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlLyIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIvPlBLAwQUAAAAAAAAAAAAx3XJjZ0IAACdCAAAEwAAAFtDb250ZW50X1R5cGVzXS54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+DQo8VHlwZXMgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9wYWNrYWdlLzIwMDYvY29udGVudC10eXBlcyIgeG1sbnM6eHNkPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSI+PERlZmF1bHQgRXh0ZW5zaW9uPSJ4bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi94bWwiLz48RGVmYXVsdCBFeHRlbnNpb249ImJpbiIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5tcy1leGNlbC5zaGVldC5iaW5hcnkubWFjcm9FbmFibGVkLm1haW4iLz48RGVmYXVsdCBFeHRlbnNpb249InZtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC52bWxEcmF3aW5nIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJkYXRhIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50Lm1vZGVsK2RhdGEiLz48RGVmYXVsdCBFeHRlbnNpb249ImJtcCIgQ29udGVudFR5cGU9ImltYWdlL2JtcCIvPjxEZWZhdWx0IEV4dGVuc2lvbj0icG5nIiBDb250ZW50VHlwZT0iaW1hZ2UvcG5nIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJnaWYiIENvbnRlbnRUeXBlPSJpbWFnZS9naWYiLz48RGVmYXVsdCBFeHRlbnNpb249ImVtZiIgQ29udGVudFR5cGU9ImltYWdlL3gtZW1mIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJ3bWYiIENvbnRlbnRUeXBlPSJpbWFnZS94LXdtZiIvPjxEZWZhdWx0IEV4dGVuc2lvbj0ianBnIiBDb250ZW50VHlwZT0iaW1hZ2UvanBlZyIvPjxEZWZhdWx0IEV4dGVuc2lvbj0ianBlZyIgQ29udGVudFR5cGU9ImltYWdlL2pwZWciLz48RGVmYXVsdCBFeHRlbnNpb249InRpZiIgQ29udGVudFR5cGU9ImltYWdlL3RpZmYiLz48RGVmYXVsdCBFeHRlbnNpb249InRpZmYiIENvbnRlbnRUeXBlPSJpbWFnZS90aWZmIi8+PERlZmF1bHQgRXh0ZW5zaW9uPSJwZGYiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi9wZGYiLz48RGVmYXVsdCBFeHRlbnNpb249InJlbHMiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtcGFja2FnZS5yZWxhdGlvbnNoaXBzK3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL3dvcmtib29rLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLnNoZWV0Lm1haW4reG1sIi8+PE92ZXJyaWRlIFBhcnROYW1lPSIveGwvd29ya3NoZWV0cy9zaGVldDEueG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnNwcmVhZHNoZWV0bWwud29ya3NoZWV0K3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL3dvcmtzaGVldHMvc2hlZXQyLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLndvcmtzaGVldCt4bWwiLz48T3ZlcnJpZGUgUGFydE5hbWU9Ii94bC90aGVtZS90aGVtZTEueG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnRoZW1lK3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL3N0eWxlcy54bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtb2ZmaWNlZG9jdW1lbnQuc3ByZWFkc2hlZXRtbC5zdHlsZXMreG1sIi8+PE92ZXJyaWRlIFBhcnROYW1lPSIvZG9jUHJvcHMvY29yZS54bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtcGFja2FnZS5jb3JlLXByb3BlcnRpZXMreG1sIi8+PE92ZXJyaWRlIFBhcnROYW1lPSIvZG9jUHJvcHMvYXBwLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5leHRlbmRlZC1wcm9wZXJ0aWVzK3htbCIvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL3hsL21ldGFkYXRhLnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLnNoZWV0TWV0YWRhdGEreG1sIi8+PC9UeXBlcz5QSwECAAAUAAAAAAAAAAAA8WE8J0IDAABCAwAAGgAAAAAAAAAAAAAAAAAAAAAAeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHNQSwECAAAUAAAAAAAAAAAAMA+Ia94dAADeHQAAEwAAAAAAAAAAAAAAAAB6AwAAeGwvdGhlbWUvdGhlbWUxLnhtbFBLAQIAABQAAAAAAAAAAABV9ASUWgQAAFoEAAANAAAAAAAAAAAAAAAAAIkhAAB4bC9zdHlsZXMueG1sUEsBAgAAFAAAAAAAAAAAAPqA3AyBEAAAgRAAABgAAAAAAAAAAAAAAAAADiYAAHhsL3dvcmtzaGVldHMvc2hlZXQxLnhtbFBLAQIAABQAAAAAAAAAAAC3lWXJYx0AAGMdAAAYAAAAAAAAAAAAAAAAAMU2AAB4bC93b3Jrc2hlZXRzL3NoZWV0Mi54bWxQSwECAAAUAAAAAAAAAAAAYIAAgYgDAACIAwAADwAAAAAAAAAAAAAAAABeVAAAeGwvbWV0YWRhdGEueG1sUEsBAgAAFAAAAAAAAAAAABu9lH6DAQAAgwEAAA8AAAAAAAAAAAAAAAAAE1gAAHhsL3dvcmtib29rLnhtbFBLAQIAABQAAAAAAAAAAABKahH5TAIAAEwCAAALAAAAAAAAAAAAAAAAAMNZAABfcmVscy8ucmVsc1BLAQIAABQAAAAAAAAAAAC6t18EYAIAAGACAAAQAAAAAAAAAAAAAAAAADhcAABkb2NQcm9wcy9hcHAueG1sUEsBAgAAFAAAAAAAAAAAANaSfBFaAQAAWgEAABEAAAAAAAAAAAAAAAAAxl4AAGRvY1Byb3BzL2NvcmUueG1sUEsBAgAAFAAAAAAAAAAAAMd1yY2dCAAAnQgAABMAAAAAAAAAAAAAAAAAT2AAAFtDb250ZW50X1R5cGVzXS54bWxQSwUGAAAAAAsACwDBAgAAHWkAAAAA';\nconst buffer = Buffer.from(b64, 'base64');\nreturn [{\n json: { filename: 'verification_fournisseurs.xlsx', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },\n binary: {\n attachment: {\n data: b64,\n mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n fileName: 'verification_fournisseurs.xlsx',\n fileSize: buffer.length,\n fileExtension: 'xlsx'\n }\n }\n}];"}},{"id":"email1","name":"Send Email","type":"n8n-nodes-base.emailSend","typeVersion":2.1,"position":[680,300],"parameters":{"fromEmail":"purchase@metdesigntver.ru","toEmail":"gruart@cifem-ru.ru","subject":"Верификация списка поставщиков — Акт сверки Q1-2026","message":"Bonjour Louis,
Ci-joint le fichier Excel de vérification pour la liste des fournisseurs Акт сверки Q1-2026.
Feuille 1 — 17 à vérifier : fournisseurs déjà dans le tracking, avec le nom actuel (domaine) et le vrai nom issu du fichier Снабжение. Merci de confirmer ou corriger les vrais noms.
Feuille 2 — 34 tracking only : fournisseurs présents dans le tracking (issus de l'historique email Phase 1) mais absents du fichier officiel Снабжение. Merci de valider avec ton collègue lesquels garder.
Les 55 nouveaux fournisseurs du fichier Снабжение ont déjà été ajoutés au tracking (statut : pending, Q1-2026).
Joe ⚡
Assistant IA — MetallKart
Bonjour Louis,
Ci-joint le fichier de vérification pour la liste fournisseurs Акт сверки Q1-2026.
Feuille 1 — 17 à vérifier : fournisseurs déjà dans le tracking. Nom actuel (domaine email) → vrai nom issu de ton fichier Снабжение. Merci de confirmer ou corriger.
Feuille 2 — 34 tracking only : présents dans le tracking (historique email Phase 1) mais absents du fichier officiel. À valider avec ton collègue : garder ou exclure ?
Note : les 55 nouveaux fournisseurs ont déjà été ajoutés au tracking (statut pending, Q1-2026).
Joe ⚡ — MetallKart
","options":{"attachmentsUi":{"attachmentsBinary":[{"property":"attachment"}]}}},"credentials":{"smtp":{"id":"JTfjUcwtoiOr3XBB","name":"SMTP account"}}}],"connections":{"Manual Trigger":{"main":[[{"node":"Prepare Attachment","type":"main","index":0}]]},"Prepare Attachment":{"main":[[{"node":"Send Email","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"804a2f46-fba9-4f1f-9b15-9f37efe5b05b","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-03-02T09:55:51.931Z","createdAt":"2026-03-02T09:55:51.931Z","role":"workflow:owner","workflowId":"wwHF14T7rc98H7jn","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-09T10:15:52.807Z","createdAt":"2026-03-09T10:15:52.807Z","id":"xd6NYa9RZRCcypp1","name":"ERP_TELEGRAM_BOT","active":true,"isArchived":false,"nodes":[{"id":"tg1","name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","typeVersion":1.1,"position":[250,300],"parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}},{"id":"code1","name":"Process","type":"n8n-nodes-base.code","typeVersion":2,"position":[500,300],"parameters":{"jsCode":"const msg=.first().json.message;const chatId=msg.chat.id;const text=msg.text||\"\";;return [{json:{chatId,text,from:msg.from.first_name}}];"}},{"id":"tg2","name":"Reply","type":"n8n-nodes-base.telegram","typeVersion":1.2,"position":[750,300],"parameters":{"chatId":"={{.chatId}}","text":"=ERP Bot: {{.text}}","additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}}],"connections":{"Telegram Trigger":{"main":[[{"node":"Process","type":"main","index":0}]]},"Process":{"main":[[{"node":"Reply","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"2390b81a-63ea-4e11-8d95-37054b7968ba","activeVersionId":"2390b81a-63ea-4e11-8d95-37054b7968ba","triggerCount":1,"shared":[{"updatedAt":"2026-03-09T10:15:52.863Z","createdAt":"2026-03-09T10:15:52.863Z","role":"workflow:owner","workflowId":"xd6NYa9RZRCcypp1","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-09T10:15:52.910Z","createdAt":"2026-03-09T10:15:52.910Z","versionId":"2390b81a-63ea-4e11-8d95-37054b7968ba","workflowId":"xd6NYa9RZRCcypp1","nodes":[{"id":"tg1","name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","typeVersion":1.1,"position":[250,300],"parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}},{"id":"code1","name":"Process","type":"n8n-nodes-base.code","typeVersion":2,"position":[500,300],"parameters":{"jsCode":"const msg=.first().json.message;const chatId=msg.chat.id;const text=msg.text||\"\";;return [{json:{chatId,text,from:msg.from.first_name}}];"}},{"id":"tg2","name":"Reply","type":"n8n-nodes-base.telegram","typeVersion":1.2,"position":[750,300],"parameters":{"chatId":"={{.chatId}}","text":"=ERP Bot: {{.text}}","additionalFields":{}},"credentials":{"telegramApi":{"id":"9Gi8XKhMRmjdQi2D","name":"Telegram account"}}}],"connections":{"Telegram Trigger":{"main":[[{"node":"Process","type":"main","index":0}]]},"Process":{"main":[[{"node":"Reply","type":"main","index":0}]]}},"authors":"Cifem Vostok","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-02-14T19:39:41.655Z","createdAt":"2026-02-14T19:24:54.636Z","id":"yhz9iIA86f1vNYQJ","name":"Collect suppliers 12m","active":false,"isArchived":false,"nodes":[{"parameters":{},"type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,0],"id":"43a7e5d7-a690-4a9d-b675-aec2e3b2ee97","name":"When clicking ‘Execute workflow’"}],"connections":{},"settings":{"executionOrder":"v1","binaryMode":"separate","availableInMCP":false},"staticData":null,"meta":null,"pinData":{},"versionId":"96c1cd38-cce4-4599-a7dd-e08c657b24e3","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-02-15T15:04:40.881Z","createdAt":"2026-02-15T15:04:40.881Z","role":"workflow:owner","workflowId":"yhz9iIA86f1vNYQJ","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]},{"updatedAt":"2026-03-03T15:12:29.375Z","createdAt":"2026-03-03T13:24:50.319Z","id":"zRyrBSKnlbDPdDFj","name":"ADMIN_AGENT","active":true,"isArchived":false,"nodes":[{"id":"admin-webhook-001","name":"Admin Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[0,0],"parameters":{"httpMethod":"POST","path":"admin-agent","responseMode":"onReceived","options":{}},"webhookId":"admin-agent-wh"},{"id":"admin-agent-loop","name":"Agent Loop","type":"n8n-nodes-base.code","typeVersion":2,"position":[300,0],"parameters":{"jsCode":"\nconst MAX_ITERATIONS = 15;\nconst BOT_TOKEN = '8617383997:AAHhvgJoaXsbi43hYQfkBY4gRBARfxTDrw4';\nconst CHAT_ID = '889172188';\n\n// Get API key from credentials\n// Get API key from environment (configured in Docker)\nconst ANTHROPIC_API_KEY = $env.ANTHROPIC_API_KEY || '';\nif (!ANTHROPIC_API_KEY) { throw new Error('ANTHROPIC_API_KEY env var not set in n8n'); }\n\n// Message entrant\nconst userMessage = $input.first().json.message || $input.first().json.text || '';\nconst chatId = $input.first().json.chatId || CHAT_ID;\n\n// Définition des outils\nconst tools = [\n {\n name: \"execute_bash\",\n description: \"Exécuter une commande bash sur le VPS. Utilisé pour : lire des fichiers, lancer des scripts, vérifier l'état du système, installer des packages, etc.\",\n input_schema: {\n type: \"object\",\n properties: {\n command: { type: \"string\", description: \"La commande bash à exécuter\" }\n },\n required: [\"command\"]\n }\n },\n {\n name: \"n8n_get_workflow\",\n description: \"Lire la configuration complète d'un workflow n8n par son ID.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow n8n\" }\n },\n required: [\"workflow_id\"]\n }\n },\n {\n name: \"n8n_patch_workflow\",\n description: \"Modifier un workflow n8n. Envoie un PATCH avec les changements.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow\" },\n patch_body: { type: \"object\", description: \"Le body JSON du PATCH (nodes, connections, settings, etc.)\" }\n },\n required: [\"workflow_id\", \"patch_body\"]\n }\n },\n {\n name: \"n8n_cycle_workflow\",\n description: \"Désactiver puis réactiver un workflow (nécessaire pour les webhooks). Fait deactivate + activate.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow à cycler\" }\n },\n required: [\"workflow_id\"]\n }\n },\n {\n name: \"n8n_get_executions\",\n description: \"Lire les dernières exécutions d'un workflow pour analyser les erreurs.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow (optionnel, sinon toutes)\" },\n limit: { type: \"number\", description: \"Nombre d'exécutions à récupérer (défaut: 5)\" }\n },\n required: []\n }\n },\n {\n name: \"n8n_list_workflows\",\n description: \"Lister tous les workflows n8n avec leur statut.\",\n input_schema: {\n type: \"object\",\n properties: {},\n required: []\n }\n },\n {\n name: \"send_telegram\",\n description: \"Envoyer un message intermédiaire à Louis-Andre sur Telegram (pour donner des updates en cours de travail).\",\n input_schema: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"Le message à envoyer\" }\n },\n required: [\"text\"]\n }\n },\n {\n name: \"web_search\",\n description: \"Chercher sur internet. Utile pour trouver de la documentation, des solutions à des erreurs, etc.\",\n input_schema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"La requête de recherche\" }\n },\n required: [\"query\"]\n }\n },\n {\n name: \"web_fetch\",\n description: \"Lire le contenu d'une page web. Utile pour lire de la documentation technique.\",\n input_schema: {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"L'URL à lire\" }\n },\n required: [\"url\"]\n }\n },\n {\n name: \"read_file\",\n description: \"Lire le contenu d'un fichier sur le VPS.\",\n input_schema: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"Le chemin du fichier\" }\n },\n required: [\"path\"]\n }\n },\n {\n name: \"write_file\",\n description: \"Écrire du contenu dans un fichier sur le VPS.\",\n input_schema: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"Le chemin du fichier\" },\n content: { type: \"string\", description: \"Le contenu à écrire\" }\n },\n required: [\"path\", \"content\"]\n }\n }\n];\n\nconst systemPrompt = `Tu es le Directeur Technique (CTO) de MetallKart/CIFEM. Tu travailles DIRECTEMENT sur le VPS qui héberge n8n. Tu communiques avec Louis-Andre via Telegram.\n\n## TES RESPONSABILITÉS\n- Debugger les workflows n8n quand ils plantent\n- Modifier et améliorer les workflows existants\n- Créer de nouveaux workflows\n- Analyser les logs d'exécution pour détecter les problèmes\n- Chercher des solutions techniques sur internet\n- Proposer des améliorations d'architecture\n\n## WORKFLOWS EXISTANTS\n- pxDZmx3Kju4mugAw : AI Router v4 Fixed (routage IA)\n- telegram-bot : Telegram Bot (interface utilisateur)\n- aY6grkgKPKWqalHg : ORCHESTRATOR_ROUTER (exécution d'actions)\n- 6OlMHMXo4KdYO01Y : PREPARE_LIST Phase 1\n- vltkkEEgnMzDnNmG : PREPARE_LIST Phase 2\n- ADMIN_AGENT : Ce workflow (agent CTO)\n\n## RÈGLES\n- Toujours analyser avant d'agir\n- Tester après chaque modification\n- Envoyer des updates Telegram à Louis-Andre pendant le travail\n- Si un workflow webhook est modifié, le cycler (deactivate + activate)\n- Ne JAMAIS supprimer un workflow sans demander confirmation\n- Être concis dans les messages Telegram`;\n\n// Fonction d'exécution des outils\nasync function executeToolCall(toolName, input) {\n const N8N_BASE = 'http://localhost:5678';\n \n switch (toolName) {\n case \"execute_bash\": {\n // Bloquer les commandes dangereuses\n const dangerous = ['rm -rf /', 'shutdown', 'reboot', 'mkfs', 'dd if=/dev/zero', 'chmod -R 777 /', ':(){:|:&};:'];\n for (const d of dangerous) {\n if (input.command.includes(d)) {\n return 'REFUSÉ: Commande dangereuse détectée: ' + d;\n }\n }\n const { execSync } = require('child_process');\n const output = execSync(input.command, { timeout: 30000, maxBuffer: 1024 * 1024 }).toString();\n return output;\n }\n \n case \"n8n_get_workflow\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}`,\n headers: { 'Content-Type': 'application/json' },\n json: true\n });\n return JSON.stringify(resp).substring(0, 15000);\n }\n \n case \"n8n_patch_workflow\": {\n const resp = await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}`,\n body: input.patch_body,\n json: true\n });\n return JSON.stringify({ success: true, id: resp.data?.id });\n }\n \n case \"n8n_cycle_workflow\": {\n try {\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}/deactivate`,\n json: true\n });\n } catch(e) {}\n await new Promise(r => setTimeout(r, 1000));\n const act = await this.helpers.httpRequest({\n method: 'POST',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}/activate`,\n json: true\n });\n return JSON.stringify({ success: true, active: act.data?.active });\n }\n \n case \"n8n_get_executions\": {\n const params = new URLSearchParams();\n if (input.workflow_id) params.append('workflowId', input.workflow_id);\n params.append('limit', String(input.limit || 5));\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `${N8N_BASE}/rest/executions?${params}`,\n json: true\n });\n return JSON.stringify(resp).substring(0, 15000);\n }\n \n case \"n8n_list_workflows\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `${N8N_BASE}/rest/workflows`,\n json: true\n });\n const list = (resp.data || []).map(w => ({ id: w.id, name: w.name, active: w.active }));\n return JSON.stringify(list);\n }\n \n case \"send_telegram\": {\n // Limiter la taille du message\n let msgText = input.text || '';\n if (msgText.length > 4000) msgText = msgText.substring(0, 4000) + '...';\n await this.helpers.httpRequest({\n method: 'POST',\n url: `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,\n body: { chat_id: chatId, text: msgText, disable_web_page_preview: true },\n json: true\n });\n return \"Message envoyé\";\n }\n \n case \"web_search\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://html.duckduckgo.com/html/?q=${encodeURIComponent(input.query)}`,\n returnFullResponse: true\n });\n // Extract text content\n const body = resp.body || '';\n const textOnly = body.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n return textOnly.substring(0, 5000);\n }\n \n case \"web_fetch\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: input.url,\n returnFullResponse: true\n });\n const body = resp.body || '';\n const textOnly = body.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n return textOnly.substring(0, 10000);\n }\n \n case \"read_file\": {\n const fs = require('fs');\n return fs.readFileSync(input.path, 'utf8').substring(0, 10000);\n }\n \n case \"write_file\": {\n const fs = require('fs');\n fs.writeFileSync(input.path, input.content);\n return \"Fichier écrit: \" + input.path;\n }\n \n default:\n return \"Outil inconnu: \" + toolName;\n }\n}\n\n// Boucle agentique\nlet messages = [{ role: \"user\", content: userMessage }];\nlet iteration = 0;\nlet finalResponse = \"\";\n\nwhile (iteration < MAX_ITERATIONS) {\n iteration++;\n \n // Appel Claude Sonnet\n const apiResponse = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api.anthropic.com/v1/messages',\n headers: {\n 'x-api-key': ANTHROPIC_API_KEY,\n 'anthropic-version': '2023-06-01',\n 'content-type': 'application/json'\n },\n body: {\n model: 'claude-3-5-sonnet-20241022',\n max_tokens: 4096,\n system: systemPrompt,\n tools: tools,\n messages: messages\n },\n json: true\n });\n \n const content = apiResponse.content;\n let hasToolUse = false;\n let toolResults = [];\n \n for (const block of content) {\n if (block.type === \"text\") {\n finalResponse += block.text;\n }\n if (block.type === \"tool_use\") {\n hasToolUse = true;\n \n let result = \"\";\n try {\n result = await executeToolCall.call(this, block.name, block.input);\n } catch (e) {\n result = \"ERREUR: \" + e.message;\n }\n \n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: String(result).substring(0, 10000)\n });\n }\n }\n \n if (!hasToolUse) {\n break;\n }\n \n messages.push({ role: \"assistant\", content: content });\n messages.push({ role: \"user\", content: toolResults });\n}\n\n// Envoyer la réponse finale sur Telegram\nlet finalMsg = finalResponse || \"Tâche terminée (aucune réponse texte).\";\nif (finalMsg.length > 4000) finalMsg = finalMsg.substring(0, 4000) + '...';\n\nawait this.helpers.httpRequest({\n method: 'POST',\n url: `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,\n body: {\n chat_id: chatId,\n text: finalMsg,\n disable_web_page_preview: true\n },\n json: true\n});\n\nreturn [{ json: { response: finalResponse, iterations: iteration } }];\n"},"credentials":{"anthropicApi":{"id":"kF5Faea0kexqJVUT","name":"Anthropic account"}}}],"connections":{"Admin Webhook":{"main":[[{"node":"Agent Loop","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","availableInMCP":true},"staticData":null,"meta":null,"pinData":null,"versionId":"d883760d-ff9e-46e3-ad18-bef441599cd6","activeVersionId":"d883760d-ff9e-46e3-ad18-bef441599cd6","triggerCount":1,"shared":[{"updatedAt":"2026-03-03T13:24:50.322Z","createdAt":"2026-03-03T13:24:50.322Z","role":"workflow:owner","workflowId":"zRyrBSKnlbDPdDFj","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":{"updatedAt":"2026-03-03T14:35:02.277Z","createdAt":"2026-03-03T14:35:02.277Z","versionId":"d883760d-ff9e-46e3-ad18-bef441599cd6","workflowId":"zRyrBSKnlbDPdDFj","nodes":[{"id":"admin-webhook-001","name":"Admin Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[0,0],"parameters":{"httpMethod":"POST","path":"admin-agent","responseMode":"onReceived","options":{}},"webhookId":"admin-agent-wh"},{"id":"admin-agent-loop","name":"Agent Loop","type":"n8n-nodes-base.code","typeVersion":2,"position":[300,0],"parameters":{"jsCode":"\nconst MAX_ITERATIONS = 15;\nconst BOT_TOKEN = '8617383997:AAHhvgJoaXsbi43hYQfkBY4gRBARfxTDrw4';\nconst CHAT_ID = '889172188';\n\n// Get API key from credentials\n// Get API key from environment (configured in Docker)\nconst ANTHROPIC_API_KEY = $env.ANTHROPIC_API_KEY || '';\nif (!ANTHROPIC_API_KEY) { throw new Error('ANTHROPIC_API_KEY env var not set in n8n'); }\n\n// Message entrant\nconst userMessage = $input.first().json.message || $input.first().json.text || '';\nconst chatId = $input.first().json.chatId || CHAT_ID;\n\n// Définition des outils\nconst tools = [\n {\n name: \"execute_bash\",\n description: \"Exécuter une commande bash sur le VPS. Utilisé pour : lire des fichiers, lancer des scripts, vérifier l'état du système, installer des packages, etc.\",\n input_schema: {\n type: \"object\",\n properties: {\n command: { type: \"string\", description: \"La commande bash à exécuter\" }\n },\n required: [\"command\"]\n }\n },\n {\n name: \"n8n_get_workflow\",\n description: \"Lire la configuration complète d'un workflow n8n par son ID.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow n8n\" }\n },\n required: [\"workflow_id\"]\n }\n },\n {\n name: \"n8n_patch_workflow\",\n description: \"Modifier un workflow n8n. Envoie un PATCH avec les changements.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow\" },\n patch_body: { type: \"object\", description: \"Le body JSON du PATCH (nodes, connections, settings, etc.)\" }\n },\n required: [\"workflow_id\", \"patch_body\"]\n }\n },\n {\n name: \"n8n_cycle_workflow\",\n description: \"Désactiver puis réactiver un workflow (nécessaire pour les webhooks). Fait deactivate + activate.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow à cycler\" }\n },\n required: [\"workflow_id\"]\n }\n },\n {\n name: \"n8n_get_executions\",\n description: \"Lire les dernières exécutions d'un workflow pour analyser les erreurs.\",\n input_schema: {\n type: \"object\",\n properties: {\n workflow_id: { type: \"string\", description: \"L'ID du workflow (optionnel, sinon toutes)\" },\n limit: { type: \"number\", description: \"Nombre d'exécutions à récupérer (défaut: 5)\" }\n },\n required: []\n }\n },\n {\n name: \"n8n_list_workflows\",\n description: \"Lister tous les workflows n8n avec leur statut.\",\n input_schema: {\n type: \"object\",\n properties: {},\n required: []\n }\n },\n {\n name: \"send_telegram\",\n description: \"Envoyer un message intermédiaire à Louis-Andre sur Telegram (pour donner des updates en cours de travail).\",\n input_schema: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"Le message à envoyer\" }\n },\n required: [\"text\"]\n }\n },\n {\n name: \"web_search\",\n description: \"Chercher sur internet. Utile pour trouver de la documentation, des solutions à des erreurs, etc.\",\n input_schema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"La requête de recherche\" }\n },\n required: [\"query\"]\n }\n },\n {\n name: \"web_fetch\",\n description: \"Lire le contenu d'une page web. Utile pour lire de la documentation technique.\",\n input_schema: {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"L'URL à lire\" }\n },\n required: [\"url\"]\n }\n },\n {\n name: \"read_file\",\n description: \"Lire le contenu d'un fichier sur le VPS.\",\n input_schema: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"Le chemin du fichier\" }\n },\n required: [\"path\"]\n }\n },\n {\n name: \"write_file\",\n description: \"Écrire du contenu dans un fichier sur le VPS.\",\n input_schema: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"Le chemin du fichier\" },\n content: { type: \"string\", description: \"Le contenu à écrire\" }\n },\n required: [\"path\", \"content\"]\n }\n }\n];\n\nconst systemPrompt = `Tu es le Directeur Technique (CTO) de MetallKart/CIFEM. Tu travailles DIRECTEMENT sur le VPS qui héberge n8n. Tu communiques avec Louis-Andre via Telegram.\n\n## TES RESPONSABILITÉS\n- Debugger les workflows n8n quand ils plantent\n- Modifier et améliorer les workflows existants\n- Créer de nouveaux workflows\n- Analyser les logs d'exécution pour détecter les problèmes\n- Chercher des solutions techniques sur internet\n- Proposer des améliorations d'architecture\n\n## WORKFLOWS EXISTANTS\n- pxDZmx3Kju4mugAw : AI Router v4 Fixed (routage IA)\n- telegram-bot : Telegram Bot (interface utilisateur)\n- aY6grkgKPKWqalHg : ORCHESTRATOR_ROUTER (exécution d'actions)\n- 6OlMHMXo4KdYO01Y : PREPARE_LIST Phase 1\n- vltkkEEgnMzDnNmG : PREPARE_LIST Phase 2\n- ADMIN_AGENT : Ce workflow (agent CTO)\n\n## RÈGLES\n- Toujours analyser avant d'agir\n- Tester après chaque modification\n- Envoyer des updates Telegram à Louis-Andre pendant le travail\n- Si un workflow webhook est modifié, le cycler (deactivate + activate)\n- Ne JAMAIS supprimer un workflow sans demander confirmation\n- Être concis dans les messages Telegram`;\n\n// Fonction d'exécution des outils\nasync function executeToolCall(toolName, input) {\n const N8N_BASE = 'http://localhost:5678';\n \n switch (toolName) {\n case \"execute_bash\": {\n // Bloquer les commandes dangereuses\n const dangerous = ['rm -rf /', 'shutdown', 'reboot', 'mkfs', 'dd if=/dev/zero', 'chmod -R 777 /', ':(){:|:&};:'];\n for (const d of dangerous) {\n if (input.command.includes(d)) {\n return 'REFUSÉ: Commande dangereuse détectée: ' + d;\n }\n }\n const { execSync } = require('child_process');\n const output = execSync(input.command, { timeout: 30000, maxBuffer: 1024 * 1024 }).toString();\n return output;\n }\n \n case \"n8n_get_workflow\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}`,\n headers: { 'Content-Type': 'application/json' },\n json: true\n });\n return JSON.stringify(resp).substring(0, 15000);\n }\n \n case \"n8n_patch_workflow\": {\n const resp = await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}`,\n body: input.patch_body,\n json: true\n });\n return JSON.stringify({ success: true, id: resp.data?.id });\n }\n \n case \"n8n_cycle_workflow\": {\n try {\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}/deactivate`,\n json: true\n });\n } catch(e) {}\n await new Promise(r => setTimeout(r, 1000));\n const act = await this.helpers.httpRequest({\n method: 'POST',\n url: `${N8N_BASE}/rest/workflows/${input.workflow_id}/activate`,\n json: true\n });\n return JSON.stringify({ success: true, active: act.data?.active });\n }\n \n case \"n8n_get_executions\": {\n const params = new URLSearchParams();\n if (input.workflow_id) params.append('workflowId', input.workflow_id);\n params.append('limit', String(input.limit || 5));\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `${N8N_BASE}/rest/executions?${params}`,\n json: true\n });\n return JSON.stringify(resp).substring(0, 15000);\n }\n \n case \"n8n_list_workflows\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `${N8N_BASE}/rest/workflows`,\n json: true\n });\n const list = (resp.data || []).map(w => ({ id: w.id, name: w.name, active: w.active }));\n return JSON.stringify(list);\n }\n \n case \"send_telegram\": {\n // Limiter la taille du message\n let msgText = input.text || '';\n if (msgText.length > 4000) msgText = msgText.substring(0, 4000) + '...';\n await this.helpers.httpRequest({\n method: 'POST',\n url: `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,\n body: { chat_id: chatId, text: msgText, disable_web_page_preview: true },\n json: true\n });\n return \"Message envoyé\";\n }\n \n case \"web_search\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://html.duckduckgo.com/html/?q=${encodeURIComponent(input.query)}`,\n returnFullResponse: true\n });\n // Extract text content\n const body = resp.body || '';\n const textOnly = body.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n return textOnly.substring(0, 5000);\n }\n \n case \"web_fetch\": {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: input.url,\n returnFullResponse: true\n });\n const body = resp.body || '';\n const textOnly = body.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n return textOnly.substring(0, 10000);\n }\n \n case \"read_file\": {\n const fs = require('fs');\n return fs.readFileSync(input.path, 'utf8').substring(0, 10000);\n }\n \n case \"write_file\": {\n const fs = require('fs');\n fs.writeFileSync(input.path, input.content);\n return \"Fichier écrit: \" + input.path;\n }\n \n default:\n return \"Outil inconnu: \" + toolName;\n }\n}\n\n// Boucle agentique\nlet messages = [{ role: \"user\", content: userMessage }];\nlet iteration = 0;\nlet finalResponse = \"\";\n\nwhile (iteration < MAX_ITERATIONS) {\n iteration++;\n \n // Appel Claude Sonnet\n const apiResponse = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api.anthropic.com/v1/messages',\n headers: {\n 'x-api-key': ANTHROPIC_API_KEY,\n 'anthropic-version': '2023-06-01',\n 'content-type': 'application/json'\n },\n body: {\n model: 'claude-3-5-sonnet-20241022',\n max_tokens: 4096,\n system: systemPrompt,\n tools: tools,\n messages: messages\n },\n json: true\n });\n \n const content = apiResponse.content;\n let hasToolUse = false;\n let toolResults = [];\n \n for (const block of content) {\n if (block.type === \"text\") {\n finalResponse += block.text;\n }\n if (block.type === \"tool_use\") {\n hasToolUse = true;\n \n let result = \"\";\n try {\n result = await executeToolCall.call(this, block.name, block.input);\n } catch (e) {\n result = \"ERREUR: \" + e.message;\n }\n \n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: String(result).substring(0, 10000)\n });\n }\n }\n \n if (!hasToolUse) {\n break;\n }\n \n messages.push({ role: \"assistant\", content: content });\n messages.push({ role: \"user\", content: toolResults });\n}\n\n// Envoyer la réponse finale sur Telegram\nlet finalMsg = finalResponse || \"Tâche terminée (aucune réponse texte).\";\nif (finalMsg.length > 4000) finalMsg = finalMsg.substring(0, 4000) + '...';\n\nawait this.helpers.httpRequest({\n method: 'POST',\n url: `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,\n body: {\n chat_id: chatId,\n text: finalMsg,\n disable_web_page_preview: true\n },\n json: true\n});\n\nreturn [{ json: { response: finalResponse, iterations: iteration } }];\n"},"credentials":{"anthropicApi":{"id":"kF5Faea0kexqJVUT","name":"Anthropic account"}}}],"connections":{"Admin Webhook":{"main":[[{"node":"Agent Loop","type":"main","index":0}]]}},"authors":"Louis-Andre Admin","name":null,"description":null,"autosaved":false},"tags":[]},{"updatedAt":"2026-03-06T09:40:35.374Z","createdAt":"2026-03-06T09:40:35.374Z","id":"zy0C4rEFk54gMkJq","name":"test_ping","active":false,"isArchived":false,"nodes":[],"connections":{},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"d0968483-0744-4424-822b-5bbb84bc9d8c","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2026-03-06T09:40:35.378Z","createdAt":"2026-03-06T09:40:35.378Z","role":"workflow:owner","workflowId":"zy0C4rEFk54gMkJq","projectId":"HOY2s0Ej5Yh0WVMQ"}],"activeVersion":null,"tags":[]}],"nextCursor":null}