appmind-nmumail/n8n_workflow.json

278 lines
11 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

{
"name": "NMU warning mail OCR and forward",
"nodes": [
{
"parameters": {
"content": "Настройте credentials для IMAP/Exchange ящика disp5@appm.ru.\n\nФильтры workflow:\n- From: iaocms@sevmeteo.ru\n- Subject: Предупреждение о НМУ\n\nЛокальная модель:\n- POST http://10.1.1.1:4000/v1/chat/completions\n- model: QWEN3.5-9B\n\nSMTP рассылка:\n- 10.0.3.22:25\n- без авторизации\n\nВ узле Build Forward Email замените список получателей NMU_RECIPIENTS.",
"height": 320,
"width": 420
},
"id": "b3d55a4f-6f6c-4e58-840c-7770e5019111",
"name": "README",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-760,
-280
]
},
{
"parameters": {
"mailbox": "INBOX",
"downloadAttachments": true,
"options": {
"customEmailConfig": "UNSEEN"
}
},
"id": "e73a579a-3a21-4684-96b0-e1d817208b57",
"name": "IMAP Email Trigger",
"type": "n8n-nodes-base.emailReadImap",
"typeVersion": 2,
"position": [
-760,
160
],
"credentials": {
"imap": {
"id": "",
"name": "disp5@appm.ru IMAP"
}
}
},
{
"parameters": {
"jsCode": "const FROM = 'iaocms@sevmeteo.ru';\nconst SUBJECT = 'Предупреждение о НМУ';\n\nreturn $input.all().map((item) => {\n const json = item.json || {};\n const fromText = String(json.from?.value?.[0]?.address || json.from?.text || json.from || '').toLowerCase();\n const subject = String(json.subject || '');\n const binaryKeys = Object.keys(item.binary || {});\n\n const matched = fromText.includes(FROM) && subject.trim() === SUBJECT && binaryKeys.length > 0;\n\n return {\n json: {\n ...json,\n nmu_should_process: matched,\n nmu_filter_reason: matched ? 'matched' : `from=${fromText}; subject=${subject}; attachments=${binaryKeys.length}`,\n nmu_binary_keys: binaryKeys\n },\n binary: item.binary\n };\n});"
},
"id": "6ea5d2c9-ed64-4e35-bab2-492482df028e",
"name": "Filter NMU Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-520,
160
]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.nmu_should_process }}",
"value2": true
}
]
}
},
"id": "86befd17-05e7-46c3-b16d-c5b67b820fe3",
"name": "Should Process?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
-280,
160
]
},
{
"parameters": {
"jsCode": "const item = $input.first();\nconst binary = item.binary || {};\nconst imageKey = Object.keys(binary).find((key) => {\n const mime = String(binary[key].mimeType || '').toLowerCase();\n return mime.startsWith('image/');\n});\n\nif (!imageKey) {\n throw new Error('В письме нет вложения-изображения. Если приходят PDF/TIFF, добавьте конвертацию перед OCR.');\n}\n\nconst attachment = binary[imageKey];\nconst mimeType = attachment.mimeType || 'image/jpeg';\nconst dataUrl = `data:${mimeType};base64,${attachment.data}`;\n\nconst openaiBody = {\n model: 'QWEN3.5-9B',\n temperature: 0,\n messages: [\n {\n role: 'system',\n content: 'Ты извлекаешь данные из скана официального письма. Верни только валидный JSON без markdown, пояснений и комментариев.'\n },\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: 'Распознай документ на изображении. Нужно извлечь: дату письма рядом с исходящим номером, исходящий номер после символа №, основной текст предупреждения НМУ и полный распознанный текст. Верни только валидный JSON в формате {\"date\":\"ДД.ММ.ГГГГ или null\",\"outgoing_number\":\"строка или null\",\"warning_text\":\"строка\",\"full_text\":\"строка\"}. Если поле не найдено, верни null. Не добавляй markdown.'\n },\n {\n type: 'image_url',\n image_url: {\n url: dataUrl\n }\n }\n ]\n }\n ]\n};\n\nreturn [{\n json: {\n message_id: item.json.messageId || item.json.message_id || null,\n original_subject: item.json.subject || '',\n original_from: item.json.from || '',\n attachment_key: imageKey,\n attachment_file_name: attachment.fileName || imageKey,\n attachment_mime_type: mimeType,\n openai_body: JSON.stringify(openaiBody)\n },\n binary: item.binary\n}];"
},
"id": "753f7228-2e78-4192-b82a-e3f6da3cbe9d",
"name": "Prepare Qwen Vision Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-20,
40
]
},
{
"parameters": {
"method": "POST",
"url": "http://10.1.1.1:4000/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ $json.openai_body }}",
"options": {
"timeout": 180000
}
},
"id": "c8992117-91e8-4855-9f71-089d2db07d6d",
"name": "OCR via QWEN3.5-9B",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
240,
40
]
},
{
"parameters": {
"jsCode": "function extractJson(text) {\n const clean = String(text || '').trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/```$/i, '')\n .trim();\n\n try {\n return JSON.parse(clean);\n } catch (error) {\n const match = clean.match(/\\{[\\s\\S]*\\}/);\n if (match) return JSON.parse(match[0]);\n throw error;\n }\n}\n\nconst response = $json;\nconst content = response.choices?.[0]?.message?.content || response.choices?.[0]?.text || response.content || '';\nconst parsed = extractJson(content);\n\nconst fullText = String(parsed.full_text || '');\nconst dateMatch = fullText.match(/\\b\\d{2}\\.\\d{2}\\.\\d{4}\\b/);\nconst numberMatch = fullText.match(/№\\s*([А-ЯA-Z0-9][А-ЯA-Z0-9\\/-]*)/i);\n\nconst date = parsed.date || dateMatch?.[0] || null;\nconst outgoingNumber = parsed.outgoing_number || numberMatch?.[1] || null;\n\nreturn [{\n json: {\n date,\n outgoing_number: outgoingNumber,\n warning_text: parsed.warning_text || null,\n full_text: parsed.full_text || content,\n raw_model_content: content\n }\n}];"
},
"id": "6f2e9dcc-dfc3-4838-8c42-8988fc433559",
"name": "Parse OCR JSON",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
40
]
},
{
"parameters": {
"jsCode": "const NMU_RECIPIENTS = [\n 'recipient1@appm.ru',\n 'recipient2@appm.ru'\n];\n\nconst date = $json.date || 'дата не распознана';\nconst number = $json.outgoing_number || 'номер не распознан';\nconst warningText = $json.warning_text || $json.full_text || 'Текст предупреждения не распознан.';\n\nconst subject = `Предупреждение о НМУ от ${date} № ${number}`;\nconst text = [\n `Дата письма: ${date}`,\n `Исходящий номер: ${number}`,\n '',\n 'Текст предупреждения:',\n warningText,\n '',\n 'Полный распознанный текст:',\n $json.full_text || ''\n].join('\\n');\n\nreturn [{\n json: {\n to_email: NMU_RECIPIENTS.join(','),\n from_email: 'disp5@appm.ru',\n subject,\n text,\n date: $json.date,\n outgoing_number: $json.outgoing_number\n }\n}];"
},
"id": "af0f0426-8e65-4040-ba56-3450c1d223f0",
"name": "Build Forward Email",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
760,
40
]
},
{
"parameters": {
"fromEmail": "={{ $json.from_email }}",
"toEmail": "={{ $json.to_email }}",
"subject": "={{ $json.subject }}",
"emailFormat": "text",
"text": "={{ $json.text }}",
"options": {}
},
"id": "3ff0b578-b265-4267-944f-8cb95e4a262a",
"name": "Send SMTP Email",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [
1020,
40
],
"credentials": {
"smtp": {
"id": "",
"name": "SMTP 10.0.3.22 no auth"
}
}
},
{
"parameters": {
"content": "Если узел HTTP Request вернет ошибку про image_url/base64, значит локальный OpenAI-compatible сервер не принимает data URL. Тогда нужен промежуточный HTTP-доступ к файлу или отдельный OCR endpoint, который принимает multipart/form-data.",
"height": 180,
"width": 360
},
"id": "f0631818-7c64-43dd-b901-08af5eb3de92",
"name": "Qwen note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
160,
-220
]
}
],
"pinData": {},
"connections": {
"IMAP Email Trigger": {
"main": [
[
{
"node": "Filter NMU Message",
"type": "main",
"index": 0
}
]
]
},
"Filter NMU Message": {
"main": [
[
{
"node": "Should Process?",
"type": "main",
"index": 0
}
]
]
},
"Should Process?": {
"main": [
[
{
"node": "Prepare Qwen Vision Request",
"type": "main",
"index": 0
}
],
[]
]
},
"Prepare Qwen Vision Request": {
"main": [
[
{
"node": "OCR via QWEN3.5-9B",
"type": "main",
"index": 0
}
]
]
},
"OCR via QWEN3.5-9B": {
"main": [
[
{
"node": "Parse OCR JSON",
"type": "main",
"index": 0
}
]
]
},
"Parse OCR JSON": {
"main": [
[
{
"node": "Build Forward Email",
"type": "main",
"index": 0
}
]
]
},
"Build Forward Email": {
"main": [
[
{
"node": "Send SMTP Email",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner"
},
"versionId": "68b03930-1a27-48e3-8a17-2c61433e4800",
"meta": {
"templateCredsSetupCompleted": false
},
"id": "NMU_QWEN_LOCAL_OCR",
"tags": []
}