{ "id": "GBPFFq8rmbdFrNn9", "name": "๐Ÿ“„ Paperless โ€” Upload Documento [Multi]", "nodes": [ { "parameters": { "updates": [ "message" ], "additionalFields": {} }, "id": "t_tg", "name": "Telegram Trigger", "type": "n8n-nodes-base.telegramTrigger", "typeVersion": 1.1, "position": [ -2208, 0 ], "webhookId": "tg-documento-multi", "credentials": { "telegramApi": { "id": "uTXHLqcCJxbOvqN3", "name": "Telegram account" } } }, { "parameters": { "httpMethod": "POST", "path": "paperless-upload", "responseMode": "responseNode", "options": {} }, "id": "t_wh", "name": "Webhook Email", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [ -2208, 304 ], "webhookId": "paperless-upload" }, { "parameters": { "jsCode": "const msg = $input.first().json.message;\nconst doc = msg.document;\nif (!doc) return [];\nconst caption = (msg.caption || '').trim();\nif (!caption.toLowerCase().startsWith('documento')) return [];\nreturn [{ json: {\n _source: 'telegram',\n file_id: doc.file_id,\n filename: doc.file_name || 'documento.pdf',\n mime_type: doc.mime_type || 'application/pdf',\n context_note: caption,\n chat_id: String(msg.chat.id),\n}}];" }, "id": "n_cc", "name": "Check Caption", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1696, 0 ] }, { "parameters": { "resource": "file", "fileId": "={{ $json.file_id }}", "additionalFields": {} }, "id": "n_tgdl", "name": "Telegram - Scarica File", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ -1440, 0 ], "webhookId": "ccbdaca8-c592-4c22-88a3-0e18b8272a26", "credentials": { "telegramApi": { "id": "uTXHLqcCJxbOvqN3", "name": "Telegram account" } } }, { "parameters": { "url": "=https://gmail.googleapis.com/gmail/v1/users/me/messages/{{ $json.body.email_id }}/attachments/{{ $json.body.attachment_id }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "gmailOAuth2", "options": {} }, "id": "n_gmdl", "name": "Gmail - Scarica Allegato", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ -1696, 304 ], "credentials": { "gmailOAuth2": { "id": "qvOikS6IF0H5khr8", "name": "Gmail account" } } }, { "parameters": { "respondWith": "json", "responseBody": "={{ JSON.stringify({received: true}) }}", "options": {} }, "id": "n_whr", "name": "Risposta Webhook", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [ -1456, 512 ] }, { "parameters": { "jsCode": "const data = $input.first().json.data;\nif (!data) throw new Error('Nessun dato binario dalla Gmail API');\nconst wb = $('Webhook Email').first().json.body;\nconst filename = wb.filename || 'documento.pdf';\nconst base64 = data.replace(/-/g, '+').replace(/_/g, '/');\nconst buffer = Buffer.from(base64, 'base64');\nconst binaryData = await this.helpers.prepareBinaryData(buffer, filename, 'application/pdf');\nreturn [{ json: {\n _source: 'email',\n filename,\n context_note: (wb.hint || wb.subject || ''),\n email_id: wb.email_id,\n from: wb.from || '',\n}, binary: { data: binaryData } }];" }, "id": "n_gmdec", "name": "Decodifica Allegato Gmail", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1456, 304 ] }, { "parameters": { "jsCode": "// Accetta output da Telegram - Scarica File OPPURE Decodifica Allegato Gmail\nlet item, source;\ntry {\n item = $('Telegram - Scarica File').first();\n source = $('Check Caption').first().json;\n return [{ json: {\n _source: 'telegram',\n filename: source.filename,\n context_note: source.context_note,\n chat_id: source.chat_id,\n }, binary: item.binary }];\n} catch(e) {\n item = $('Decodifica Allegato Gmail').first();\n return [{ json: {\n _source: 'email',\n filename: item.json.filename,\n context_note: item.json.context_note,\n chat_id: '-4814221197',\n email_id: item.json.email_id,\n from: item.json.from,\n }, binary: item.binary }];\n}" }, "id": "n_norm", "name": "Normalizza Input", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1200, 128 ] }, { "parameters": { "method": "POST", "url": "http://filewizard.home.svc.cluster.local:8000/ocr-pdf", "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "parameterType": "formBinaryData", "name": "file", "inputDataFieldName": "data" } ] }, "options": {} }, "id": "n_fwocr", "name": "FileWizard - Avvia OCR", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ -944, 128 ] }, { "parameters": { "amount": 25 }, "id": "n_w1", "name": "โณ Attendi OCR", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ -704, 128 ], "webhookId": "wait-ocr-multi" }, { "parameters": { "url": "=http://filewizard.home.svc.cluster.local:8000/job/{{ $json.job_id }}", "options": {} }, "id": "n_fwst", "name": "FileWizard - Stato OCR", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ -448, 128 ] }, { "parameters": { "url": "https://docs.mt-home.uk/api/correspondents/?page_size=50", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "options": {} }, "id": "n_plc", "name": "Paperless - Corrispondenti", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ -208, 128 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "url": "https://docs.mt-home.uk/api/document_types/?page_size=50", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "options": {} }, "id": "n_pld", "name": "Paperless - Tipi Doc", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 64, 128 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "url": "https://docs.mt-home.uk/api/tags/?page_size=50", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "options": {} }, "id": "n_plt", "name": "Paperless - Tag", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 304, 128 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "url": "https://docs.mt-home.uk/api/storage_paths/?page_size=50", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "options": {} }, "id": "n_plp", "name": "Paperless - Percorsi", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 560, 128 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "jsCode": "const fi = $('Normalizza Input').first().json;\nconst ocr = ($('FileWizard - Stato OCR').first().json.result_preview || '').substring(0, 2500);\nconst C = ($('Paperless - Corrispondenti').first().json.results || []).map(c => '[' + c.id + '] ' + c.name + ' (' + (c.document_count||0) + ' doc)').join('\\n');\nconst D = ($('Paperless - Tipi Doc').first().json.results || []).map(d => '[' + d.id + '] ' + d.name).join('\\n');\nconst T = ($('Paperless - Tag').first().json.results || []).map(t => '[' + t.id + '] ' + t.name).join('\\n');\nconst P = ($('Paperless - Percorsi').first().json.results || []).map(s => '[' + s.id + '] ' + s.name).join('\\n');\nconst sourceCtx = fi._source === 'email'\n ? `Documento ricevuto via EMAIL da: ${fi.from}\\nOggetto/hint: ${fi.context_note}`\n : `Documento inviato via TELEGRAM con caption: \\\"${fi.context_note}\\\"`;\nconst prompt = `Sei l'assistente di Martin che gestisce Paperless-NGX.\\n${sourceCtx}\\nNome file: ${fi.filename}\\n\\nTESTO ESTRATTO DAL PDF (OCR):\\n${ocr}\\n\\nCORRISPONDENTI DISPONIBILI:\\n${C}\\n\\nTIPI DOCUMENTO:\\n${D}\\n\\nTAG:\\n${T}\\n\\nPERCORSI:\\n${P}\\n\\nISTRUZIONI:\\n1. Se il documento non corrisponde a nessun tipo/tag esistente oppure non รจ un documento da archiviare: skip:true con motivazione\\n2. Altrimenti inferisci i metadati dal testo OCR (importo, periodo, data emissione...)\\n3. Titolo descrittivo con dettagli concreti (es: 'E.ON Bolletta Energia Mar2026 87.50EUR')\\n4. created_date = data emissione dal documento (non oggi)\\n\\nRispondi SOLO JSON valido:\\n{\\\\\\\"skip\\\\\\\":false,\\\\\\\"skip_reason\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"correspondent_id\\\\\\\":44,\\\\\\\"document_type_id\\\\\\\":2,\\\\\\\"tag_ids\\\\\\\":[3],\\\\\\\"storage_path_id\\\\\\\":2,\\\\\\\"title\\\\\\\":\\\\\\\"...\\\\\\\",\\\\\\\"created_date\\\\\\\":\\\\\\\"2026-03-01\\\\\\\",\\\\\\\"confidence_note\\\\\\\":\\\\\\\"...\\\\\\\"}`;\nreturn [{ json: { prompt, filename: fi.filename, context_note: fi.context_note }}];" }, "id": "n_bp", "name": "Build Prompt", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 800, 128 ] }, { "parameters": { "url": "https://api.github.com/copilot_internal/v2/token", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "options": {} }, "id": "n_cp", "name": "Ottieni Token Copilot", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1056, 128 ], "credentials": { "httpHeaderAuth": { "id": "vBwUxlzKrX3oDHyN", "name": "GitHub Copilot OAuth Token" } } }, { "parameters": { "method": "POST", "url": "https://api.githubcopilot.com/chat/completions", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "Authorization", "value": "={{ 'Bearer ' + $('Ottieni Token Copilot').first().json.token }}" }, { "name": "Content-Type", "value": "application/json" }, { "name": "Copilot-Integration-Id", "value": "vscode-chat" }, { "name": "Editor-Version", "value": "vscode/1.85.0" } ] }, "sendBody": true, "contentType": "raw", "rawContentType": "application/json", "body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Build Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":1024}) }}", "options": {} }, "id": "n_gpt", "name": "GPT-4.1 - Inferisci Metadati", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1312, 128 ] }, { "parameters": { "jsCode": "const raw = $input.first().json;\nconst content = (raw.choices || [])[0]?.message?.content || '{}';\nlet meta;\ntry { meta = JSON.parse(content); } catch(e) { throw new Error('GPT non JSON: ' + content.substring(0,300)); }\nconst fi = $('Normalizza Input').first().json;\nreturn [{ json: { ...meta, filename: fi.filename, context_note: fi.context_note, chat_id: fi.chat_id }}];" }, "id": "n_parse", "name": "Parse Risposta GPT", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1552, 128 ] }, { "parameters": { "conditions": { "options": {}, "combinator": "and", "conditions": [ { "id": "s1", "leftValue": "={{ $json.skip }}", "operator": { "type": "boolean", "operation": "true" }, "rightValue": "" } ] }, "options": {} }, "id": "n_skip", "name": "Skip?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 1808, 128 ] }, { "parameters": { "chatId": "-4814221197", "text": "={{ 'โš ๏ธ *Documento non archiviato*\\n\\n' + $json.skip_reason + '\\n\\nCaricalo manualmente su Paperless se necessario.' }}", "additionalFields": { "parse_mode": "Markdown" } }, "id": "n_tgskip", "name": "Telegram - Documento Non Riconosciuto", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 2048, -16 ], "webhookId": "60fefda0-215b-49e8-86ae-15ef4b03d89f", "credentials": { "telegramApi": { "id": "uTXHLqcCJxbOvqN3", "name": "Telegram account" } } }, { "parameters": { "jsCode": "const meta = $('Parse Risposta GPT').first().json;\nconst norm = $('Normalizza Input').first();\nconst filename = meta.filename || 'documento.pdf';\nconst uploadTitle = filename.replace(/\\.[^/.]+$/, '');\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') { try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; } }\nconst created_date = meta.created_date && meta.created_date !== 'None'\n ? String(meta.created_date).substring(0, 10)\n : new Date().toISOString().substring(0, 10);\nreturn [{ json: {\n title: meta.title || uploadTitle,\n upload_title: uploadTitle,\n filename,\n chat_id: meta.chat_id || '-4814221197',\n correspondent_id: meta.correspondent_id != null ? Number(meta.correspondent_id) : null,\n document_type_id: meta.document_type_id != null ? Number(meta.document_type_id) : null,\n storage_path_id: meta.storage_path_id != null ? Number(meta.storage_path_id) : null,\n tag_ids: Array.isArray(tag_ids) ? tag_ids.map(Number).filter(n => !isNaN(n)) : [],\n created_date,\n}, binary: norm.binary }];" }, "id": "n_prep", "name": "Prepara Upload", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2048, 304 ] }, { "parameters": { "method": "POST", "url": "https://docs.mt-home.uk/api/documents/post_document/", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "parameterType": "formBinaryData", "name": "document", "inputDataFieldName": "data" }, { "name": "title", "value": "={{ $json.upload_title }}" } ] }, "options": { "response": { "response": { "responseFormat": "text" } } } }, "id": "n_plup", "name": "Paperless - Upload Documento", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 2304, 304 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "jsCode": "const raw = $input.first().json;\nconst task_id = (raw.data || '').replace(/\"/g, '').trim();\nconst meta = $('Prepara Upload').first().json;\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') { try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; } }\nreturn [{ json: { task_id, ...meta, tag_ids } }];" }, "id": "n_tid", "name": "Estrai Task ID", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2528, 304 ] }, { "parameters": { "amount": 20 }, "id": "n_w2", "name": "โณ Attendi Paperless", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ 2752, 304 ], "webhookId": "wait-pl-multi" }, { "parameters": { "url": "https://docs.mt-home.uk/api/tasks/", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "task_id", "value": "={{ $json.task_id }}" } ] }, "options": {} }, "id": "n_plst", "name": "Paperless - Stato Task", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 2976, 304 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "jsCode": "const tasks = $input.first().json;\nconst task = Array.isArray(tasks) ? tasks[0] : tasks;\nif (!task) throw new Error('Nessun task');\nconst isDup = task.status === 'FAILURE' && String(task.result || '').includes('duplicate');\nif (!isDup && task.status !== 'SUCCESS') throw new Error('Task ' + task.status + ': ' + (task.result || ''));\nconst doc_id = parseInt(String(task.related_document || '').replace(/\\D/g, ''), 10);\nif (!doc_id) throw new Error('Doc ID non trovato: ' + JSON.stringify(task));\nconst meta = $('Prepara Upload').first().json;\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') { try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; } }\nreturn [{ json: {\n doc_id, is_duplicate: isDup, title: meta.title,\n filename: meta.filename, chat_id: meta.chat_id,\n correspondent_id: meta.correspondent_id != null ? Number(meta.correspondent_id) : null,\n document_type_id: meta.document_type_id != null ? Number(meta.document_type_id) : null,\n storage_path_id: meta.storage_path_id != null ? Number(meta.storage_path_id) : null,\n tag_ids: Array.isArray(tag_ids) ? tag_ids.map(Number).filter(n => !isNaN(n)) : [],\n created_date: meta.created_date ? String(meta.created_date).substring(0, 10) : null,\n} }];" }, "id": "n_did", "name": "Estrai Document ID", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3200, 304 ] }, { "parameters": { "method": "PATCH", "url": "=https://docs.mt-home.uk/api/documents/{{ $json.doc_id }}/", "authentication": "predefinedCredentialType", "nodeCredentialType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify(Object.fromEntries(Object.entries({correspondent:$json.correspondent_id,document_type:$json.document_type_id,storage_path:$json.storage_path_id,tags:$json.tag_ids&&$json.tag_ids.length>0?$json.tag_ids:undefined,created:$json.created_date||undefined}).filter(([_,v])=>v!==null&&v!==undefined))) }}", "options": {} }, "id": "n_patch", "name": "Paperless - Patch Metadati", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 3408, 304 ], "credentials": { "httpHeaderAuth": { "id": "uvGjLbrN5yQTQIzv", "name": "Paperless-NGX API" } } }, { "parameters": { "chatId": "-4814221197", "text": "={{ 'โœ… *Documento archiviato su Paperless!*\\n\\n๐Ÿ“„ *' + $('Prepara Upload').first().json.filename + '*\\n๐Ÿ—‚ ' + $('Prepara Upload').first().json.title + '\\n' + ($('Estrai Document ID').first().json.is_duplicate ? 'โš ๏ธ _Duplicato โ€” metadati aggiornati_\\n' : '') + '๐Ÿ”— https://docs.mt-home.uk/documents/' + $json.doc_id + '/details/' }}", "additionalFields": { "parse_mode": "Markdown" } }, "id": "n_tgok", "name": "Telegram - Conferma Upload", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 3632, 304 ], "webhookId": "3be53ead-f628-4ec4-99d4-f378224c57ed", "credentials": { "telegramApi": { "id": "uTXHLqcCJxbOvqN3", "name": "Telegram account" } } }, { "parameters": { "method": "POST", "url": "http://filewizard.home.svc.cluster.local:8000/settings/delete-files", "options": {} }, "id": "n_fwdel", "name": "FileWizard - Elimina File", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 3856, 304 ] }, { "parameters": { "method": "POST", "url": "http://filewizard.home.svc.cluster.local:8000/settings/clear-history", "options": {} }, "id": "n_fwclr", "name": "FileWizard - Pulisci Job", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 4080, 304 ] }, { "id": "n_memoria_code", "name": "๐Ÿง  Salva in Memoria", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3200, 400 ], "parameters": { "jsCode": "// โ”€โ”€โ”€ ๐Ÿง  Salva in Memoria: memory_facts + Qdrant knowledge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n// Eseguito in parallelo dopo Paperless - Patch Metadati\n// Non bloccante: errori vengono loggati ma non interrompono il flusso\n\nconst OLLAMA_URL = 'http://ollama.ai.svc.cluster.local:11434';\nconst QDRANT_URL = 'http://qdrant.persistence.svc.cluster.local:6333';\nconst QDRANT_KEY = '__Montecarlo00!';\nconst COLLECTION = 'knowledge';\n\nconst parsed = $('Parse Risposta GPT').first().json;\nconst docData = $('Estrai Document ID').first().json;\nconst ocrText = ($('FileWizard - Stato OCR').first().json.result_preview || '').substring(0, 3000);\nconst normIn = $('Normalizza Input').first().json;\n\nconst docId = docData.doc_id;\nconst title = parsed.title || docData.title || normIn.filename || 'Documento';\nconst sourceRef = `paperless-${docId}`;\n\n// TTL by doc type\nconst dtype = (parsed.document_type_name || '').toLowerCase();\nlet ttlDays = 365;\nif (dtype.includes('bolletta') || dtype.includes('fattura')) ttlDays = 180;\nelse if (dtype.includes('cedolino')) ttlDays = 730;\nelse if (dtype.includes('ricevuta')) ttlDays = 90;\nconst expiresAt = new Date(Date.now() + ttlDays * 86400000).toISOString();\n\n// Text to embed: title + OCR excerpt\nconst embedText = `${title}\\n\\n${ocrText}`.substring(0, 4000);\n\n// โ”€โ”€ 1. Embedding via Ollama โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nlet embedding = null;\ntry {\n const embedRes = await this.helpers.httpRequest({\n method: 'POST',\n url: `${OLLAMA_URL}/api/embed`,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ model: 'nomic-embed-text', input: embedText })\n });\n embedding = embedRes.embeddings?.[0] || null;\n} catch(e) {\n console.error('Ollama embed error:', e.message);\n}\n\n// โ”€โ”€ 2. Qdrant upsert โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nlet qdrantId = null;\nif (embedding && embedding.length === 768) {\n const { v4: uuidv4 } = require('uuid');\n qdrantId = uuidv4();\n try {\n await this.helpers.httpRequest({\n method: 'PUT',\n url: `${QDRANT_URL}/collections/${COLLECTION}/points`,\n headers: { 'content-type': 'application/json', 'api-key': QDRANT_KEY },\n body: JSON.stringify({ points: [{\n id: qdrantId,\n vector: embedding,\n payload: {\n user_id: 'martin',\n source: 'paperless',\n source_ref: sourceRef,\n doc_id: docId,\n title,\n category: 'finance',\n doc_type: parsed.document_type_name || '',\n correspondent: parsed.correspondent_name || '',\n created_date: parsed.created_date || '',\n tags: Array.isArray(parsed.tag_ids) ? parsed.tag_ids : [],\n date: new Date().toISOString(),\n action_required: false\n }\n }]})\n });\n } catch(e) {\n console.error('Qdrant upsert error:', e.message);\n qdrantId = null;\n }\n}\n\n// โ”€โ”€ 3. Prepare for Postgres node โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nconst pompeoNote = `Documento \"${title}\" archiviato su Paperless (doc_id=${docId}). ` +\n `Tipo: ${parsed.document_type_name || 'N/D'}. ` +\n `Corrispondente: ${parsed.correspondent_name || 'N/D'}. ` +\n `Fonte: ${normIn._source === 'email' ? 'email da ' + (normIn.from || '?') : 'Telegram'}.`;\n\nconst detail = {\n paperless_doc_id: docId,\n doc_type: parsed.document_type_name || '',\n correspondent: parsed.correspondent_name || '',\n created_date: parsed.created_date || '',\n tag_ids: Array.isArray(parsed.tag_ids) ? parsed.tag_ids : [],\n source: normIn._source,\n is_duplicate: docData.is_duplicate || false,\n qdrant_embedded: !!qdrantId\n};\n\nreturn [{ json: {\n source_ref: sourceRef,\n category: 'finance',\n subject: title.substring(0, 200).replace(/'/g, \"''\"),\n detail: JSON.stringify(detail),\n action_required: false,\n action_text: '',\n pompeo_note: pompeoNote.substring(0, 500).replace(/'/g, \"''\"),\n entity_refs: JSON.stringify({ people: [], places: [], products: [], amounts: [] }),\n expires_at: expiresAt,\n qdrant_id: qdrantId\n}}];" } }, { "id": "n_pg_memoria", "name": "๐Ÿ’พ Upsert Memoria", "type": "n8n-nodes-base.postgres", "typeVersion": 2.5, "position": [ 3420, 400 ], "credentials": { "postgres": { "id": "mRqzxhSboGscolqI", "name": "Pompeo โ€” PostgreSQL" } }, "parameters": { "operation": "executeQuery", "query": "INSERT INTO memory_facts\n (user_id, source, source_ref, category, subject, detail, action_required, action_text, pompeo_note, entity_refs, expires_at)\nVALUES (\n 'martin',\n 'email',\n '{{ $json.thread_id }}',\n '{{ $json.category }}',\n '{{ $json.subject }}',\n '{{ $json.detail }}',\n {{ $json.action_required }},\n '{{ $json.action_text }}',\n '{{ $json.pompeo_note }}',\n '{{ $json.entity_refs }}'::jsonb,\n {{ $json.expires_at ? \"'\" + $json.expires_at + \"'::timestamptz\" : \"NULL\" }}\n)\nON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL DO UPDATE SET\n subject = EXCLUDED.subject,\n detail = EXCLUDED.detail,\n action_required = EXCLUDED.action_required,\n action_text = EXCLUDED.action_text,\n pompeo_note = EXCLUDED.pompeo_note,\n entity_refs = EXCLUDED.entity_refs,\n expires_at = EXCLUDED.expires_at", "options": {} } } ], "connections": { "Telegram Trigger": { "main": [ [ { "node": "Check Caption", "type": "main", "index": 0 } ] ] }, "Check Caption": { "main": [ [ { "node": "Telegram - Scarica File", "type": "main", "index": 0 } ] ] }, "Telegram - Scarica File": { "main": [ [ { "node": "Normalizza Input", "type": "main", "index": 0 } ] ] }, "Webhook Email": { "main": [ [ { "node": "Gmail - Scarica Allegato", "type": "main", "index": 0 } ] ] }, "Gmail - Scarica Allegato": { "main": [ [ { "node": "Risposta Webhook", "type": "main", "index": 0 }, { "node": "Decodifica Allegato Gmail", "type": "main", "index": 0 } ] ] }, "Decodifica Allegato Gmail": { "main": [ [ { "node": "Normalizza Input", "type": "main", "index": 0 } ] ] }, "Normalizza Input": { "main": [ [ { "node": "FileWizard - Avvia OCR", "type": "main", "index": 0 } ] ] }, "FileWizard - Avvia OCR": { "main": [ [ { "node": "โณ Attendi OCR", "type": "main", "index": 0 } ] ] }, "โณ Attendi OCR": { "main": [ [ { "node": "FileWizard - Stato OCR", "type": "main", "index": 0 } ] ] }, "FileWizard - Stato OCR": { "main": [ [ { "node": "Paperless - Corrispondenti", "type": "main", "index": 0 } ] ] }, "Paperless - Corrispondenti": { "main": [ [ { "node": "Paperless - Tipi Doc", "type": "main", "index": 0 } ] ] }, "Paperless - Tipi Doc": { "main": [ [ { "node": "Paperless - Tag", "type": "main", "index": 0 } ] ] }, "Paperless - Tag": { "main": [ [ { "node": "Paperless - Percorsi", "type": "main", "index": 0 } ] ] }, "Paperless - Percorsi": { "main": [ [ { "node": "Build Prompt", "type": "main", "index": 0 } ] ] }, "Build Prompt": { "main": [ [ { "node": "Ottieni Token Copilot", "type": "main", "index": 0 } ] ] }, "Ottieni Token Copilot": { "main": [ [ { "node": "GPT-4.1 - Inferisci Metadati", "type": "main", "index": 0 } ] ] }, "GPT-4.1 - Inferisci Metadati": { "main": [ [ { "node": "Parse Risposta GPT", "type": "main", "index": 0 } ] ] }, "Parse Risposta GPT": { "main": [ [ { "node": "Skip?", "type": "main", "index": 0 } ] ] }, "Skip?": { "main": [ [ { "node": "Telegram - Documento Non Riconosciuto", "type": "main", "index": 0 } ], [ { "node": "Prepara Upload", "type": "main", "index": 0 } ] ] }, "Prepara Upload": { "main": [ [ { "node": "Paperless - Upload Documento", "type": "main", "index": 0 } ] ] }, "Paperless - Upload Documento": { "main": [ [ { "node": "Estrai Task ID", "type": "main", "index": 0 } ] ] }, "Estrai Task ID": { "main": [ [ { "node": "โณ Attendi Paperless", "type": "main", "index": 0 } ] ] }, "โณ Attendi Paperless": { "main": [ [ { "node": "Paperless - Stato Task", "type": "main", "index": 0 } ] ] }, "Paperless - Stato Task": { "main": [ [ { "node": "Estrai Document ID", "type": "main", "index": 0 } ] ] }, "Estrai Document ID": { "main": [ [ { "node": "Paperless - Patch Metadati", "type": "main", "index": 0 } ] ] }, "Paperless - Patch Metadati": { "main": [ [ { "node": "Telegram - Conferma Upload", "type": "main", "index": 0 }, { "node": "๐Ÿง  Salva in Memoria", "type": "main", "index": 0 } ] ] }, "Telegram - Conferma Upload": { "main": [ [ { "node": "FileWizard - Elimina File", "type": "main", "index": 0 } ] ] }, "FileWizard - Elimina File": { "main": [ [ { "node": "FileWizard - Pulisci Job", "type": "main", "index": 0 } ] ] }, "๐Ÿง  Salva in Memoria": { "main": [ [ { "node": "๐Ÿ’พ Upsert Memoria", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false }, "triggerCount": 2, "versionId": "f0e2579d-f423-4465-a144-9f03572ba94e", "owner": { "type": "personal", "projectId": "Hdttz401OqqtObPo", "projectName": "Martin Tahiraj ", "personalEmail": "tahiraj.martin@gmail.com" }, "parentFolderId": null, "isArchived": false }