Files
n8n/backups/2025-12-27/Traffic Forecast via Telegram.json

1 line
13 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"updatedAt":"2025-12-11T08:19:23.847Z","createdAt":"2025-12-11T07:26:21.150Z","id":"VCPJdSaOUiAvXjBI","name":"Traffic Forecast via Telegram","active":false,"isArchived":false,"nodes":[{"parameters":{"updates":["message"],"additionalFields":{}},"name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","typeVersion":1,"position":[-592,-96],"id":"8971e1d6-fca4-4f4e-8e1d-c7e38823f716","webhookId":"traffic-forecast-bot","credentials":{"telegramApi":{"id":"L9KHcMiteyKROF5r","name":"Telegram main n8n"}}},{"parameters":{"resource":"file","fileId":"={{ $json.message.document.file_id }}","additionalFields":{}},"name":"Get File","type":"n8n-nodes-base.telegram","typeVersion":1,"position":[-368,-96],"id":"54455673-f9ca-4f5a-982e-b4f94d97aed7","webhookId":"b572f623-3010-47da-b480-eae5ae111b38","credentials":{"telegramApi":{"id":"L9KHcMiteyKROF5r","name":"Telegram main n8n"}}},{"parameters":{"options":{}},"name":"Parse File","type":"n8n-nodes-base.spreadsheetFile","typeVersion":2,"position":[-160,-96],"id":"a7cf8415-5a2e-44d7-8211-ae63de825efd"},{"parameters":{"jsCode":"// --- ПОЛУЧЕНИЕ ДАННЫХ ---\nconst items = $input.all();\nconst inputData = items.map(item => item.json);\n\n// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---\nfunction parseCleanNumber(value) {\n if (typeof value === 'number') return value;\n if (!value) return 0;\n const cleanStr = String(value).replace(/\\s/g, '').replace(/,/g, '.');\n const parsed = parseFloat(cleanStr);\n return isNaN(parsed) ? 0 : parsed;\n}\n\nfunction getJsDateFromExcel(serial) {\n const utc_days = Math.floor(serial - 25569);\n const utc_value = utc_days * 86400; \n const date_info = new Date(utc_value * 1000);\n return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate());\n}\n\nfunction formatDate(date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n// --- ШАГ 1: АНАЛИЗ СТАТИСТИКИ ---\n\nlet totalTrafficSum = 0;\nlet totalCount = 0;\n\nconst trafficByDay = {}; // Дни недели (0-6)\nconst trafficByMonth = {}; // Месяцы (0-11)\nconst trafficByYear = {}; // Годы (2022, 2023...) -> ЭТО НОВОЕ\n\nif (inputData.length === 0) return [];\nconst keys = Object.keys(inputData[0]);\nconst dateKey = keys.find(k => /date|дата|день/i.test(k)) || keys[0];\nconst trafficKey = keys.find(k => /traffic|трафик|кол-во|value/i.test(k)) || keys[1];\n\n// 1. Сбор данных\nfor (const row of inputData) {\n const rawTraffic = row[trafficKey];\n const rawDate = row[dateKey];\n const traffic = parseCleanNumber(rawTraffic);\n \n // Игнорируем нули, чтобы не портить среднее\n if (traffic === 0) continue;\n\n let date;\n if (typeof rawDate === 'number') date = getJsDateFromExcel(rawDate);\n else date = new Date(rawDate);\n \n if (isNaN(date.getTime())) continue;\n\n totalTrafficSum += traffic;\n totalCount++;\n\n const d = date.getDay();\n const m = date.getMonth();\n const y = date.getFullYear(); // Получаем год\n\n // Суммы по дням недели\n trafficByDay[d] = (trafficByDay[d] || {s:0, c:0});\n trafficByDay[d].s += traffic;\n trafficByDay[d].c++;\n\n // Суммы по месяцам\n trafficByMonth[m] = (trafficByMonth[m] || {s:0, c:0});\n trafficByMonth[m].s += traffic;\n trafficByMonth[m].c++;\n\n // Суммы по годам (Тренд)\n trafficByYear[y] = (trafficByYear[y] || {s:0, c:0});\n trafficByYear[y].s += traffic;\n trafficByYear[y].c++;\n}\n\nconst avgTotalTraffic = totalCount > 0 ? totalTrafficSum / totalCount : 0;\n\n// 2. Расчет коэффициентов\nconst coeffWeekday = {};\nconst coeffMonth = {};\nconst coeffYear = {}; // Коэффициент силы года\n\n// Дни недели\nfor (let i = 0; i < 7; i++) {\n const data = trafficByDay[i];\n coeffWeekday[i] = data ? (data.s / data.c) / avgTotalTraffic : 1.0;\n}\n// Месяцы\nfor (let i = 0; i < 12; i++) {\n const data = trafficByMonth[i];\n coeffMonth[i] = data ? (data.s / data.c) / avgTotalTraffic : 1.0;\n}\n// Годы (Новое!)\nfor (const y in trafficByYear) {\n const data = trafficByYear[y];\n // Если среднее за 2024 год = 300, а общее среднее = 200, то K_Year = 1.5\n coeffYear[y] = data ? (data.s / data.c) / avgTotalTraffic : 1.0;\n}\n\n// --- ШАГ 2: РАСЧЕТ ПРОГНОЗА ---\n\nconst finalOutput = inputData.map(row => {\n const rawDate = row[dateKey];\n const rawTraffic = row[trafficKey];\n \n let date;\n let formattedDate = rawDate;\n\n if (typeof rawDate === 'number') {\n date = getJsDateFromExcel(rawDate);\n formattedDate = formatDate(date);\n } else {\n date = new Date(rawDate);\n if (!isNaN(date.getTime())) formattedDate = formatDate(date);\n }\n\n let forecastValue = 0;\n\n if (date && !isNaN(date.getTime()) && avgTotalTraffic > 0) {\n const d = date.getDay();\n const m = date.getMonth();\n const y = date.getFullYear();\n \n const K_DN = coeffWeekday[d] || 1;\n const K_SEZ = coeffMonth[m] || 1;\n \n // Добавляем коэффициент года. \n // Если для этого года нет статистики (будущее), берем последний известный год или 1.0\n let K_YEAR = coeffYear[y];\n \n // Если год неизвестен (например, 2026, а статистики нет), берем коэф последнего года из данных\n if (!K_YEAR) {\n const knownYears = Object.keys(coeffYear).sort();\n const lastYear = knownYears[knownYears.length - 1];\n K_YEAR = coeffYear[lastYear] || 1.0;\n }\n \n // Новая формула\n forecastValue = Math.round(avgTotalTraffic * K_DN * K_SEZ * K_YEAR);\n }\n\n return {\n json: {\n \"Дата\": formattedDate,\n \"Исторический трафик (Факт)\": rawTraffic,\n \"Прогнозный трафик\": forecastValue\n }\n };\n});\n\nreturn finalOutput;"},"name":"Calculate Forecast","type":"n8n-nodes-base.code","typeVersion":2,"position":[64,-96],"id":"100a6bbb-fe43-4f65-a185-df835b3f90da"},{"parameters":{"operation":"toFile","fileFormat":"xlsx","options":{}},"name":"Create CSV","type":"n8n-nodes-base.spreadsheetFile","typeVersion":2,"position":[288,-96],"id":"f0bcaa19-0c67-49c4-bd16-ff5b44276049"},{"parameters":{"operation":"sendDocument","chatId":"={{ $('Telegram Trigger').first().json.message.chat.id }}","binaryData":true,"additionalFields":{}},"name":"Send Result","type":"n8n-nodes-base.telegram","typeVersion":1,"position":[512,-96],"id":"1b4771a2-dd94-4053-88c2-04bd9e12cbad","webhookId":"b7e31308-df8c-44ee-9e89-87c4b1b585f7","credentials":{"telegramApi":{"id":"L9KHcMiteyKROF5r","name":"Telegram main n8n"}}},{"parameters":{"jsCode":"// --- ПОЛУЧЕНИЕ ДАННЫХ ---\nconst items = $input.all();\nconst inputData = items.map(item => item.json);\n\n// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---\n\nfunction parseCleanNumber(value) {\n if (typeof value === 'number') return value;\n if (!value) return 0;\n // Удаляем пробелы и меняем запятую на точку\n const cleanStr = String(value).replace(/\\s/g, '').replace(/,/g, '.');\n const parsed = parseFloat(cleanStr);\n return isNaN(parsed) ? 0 : parsed;\n}\n\n// Конвертация Excel-даты (44562 -> Date)\nfunction getJsDateFromExcel(serial) {\n const utc_days = Math.floor(serial - 25569);\n const utc_value = utc_days * 86400; \n const date_info = new Date(utc_value * 1000);\n return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate());\n}\n\n// Красивый вывод даты YYYY-MM-DD\nfunction formatDate(date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n// --- ШАГ 1: АНАЛИЗ ДАННЫХ И РАСЧЕТ КОЭФФИЦИЕНТОВ ---\n// Нам нужно сначала пройтись по всем данным, чтобы понять \"норму\" (среднее, сезонность)\n\nlet totalTrafficSum = 0;\nlet totalCount = 0;\nconst trafficByDay = {}; // Статистика по дням недели\nconst trafficByMonth = {}; // Статистика по месяцам\n\n// Находим названия колонок один раз\nif (inputData.length === 0) return [];\nconst keys = Object.keys(inputData[0]);\n// Ищем колонку даты (содержит date, дата, день) и трафика\nconst dateKey = keys.find(k => /date|дата|день/i.test(k)) || keys[0];\nconst trafficKey = keys.find(k => /traffic|трафик|кол-во|value/i.test(k)) || keys[1];\n\n// Первый проход: Считаем суммы\nfor (const row of inputData) {\n const rawTraffic = row[trafficKey];\n const rawDate = row[dateKey];\n\n const traffic = parseCleanNumber(rawTraffic);\n \n // Если трафика нет (0 или пусто), мы НЕ учитываем это в расчете среднего,\n // иначе прогноз занизится.\n if (traffic === 0) continue;\n\n let date;\n if (typeof rawDate === 'number') date = getJsDateFromExcel(rawDate);\n else date = new Date(rawDate);\n \n if (isNaN(date.getTime())) continue;\n\n totalTrafficSum += traffic;\n totalCount++;\n\n const d = date.getDay();\n const m = date.getMonth();\n\n trafficByDay[d] = (trafficByDay[d] || {s:0, c:0});\n trafficByDay[d].s += traffic;\n trafficByDay[d].c++;\n\n trafficByMonth[m] = (trafficByMonth[m] || {s:0, c:0});\n trafficByMonth[m].s += traffic;\n trafficByMonth[m].c++;\n}\n\n// Если данных нет, возвращаем как есть\nconst avgTotalTraffic = totalCount > 0 ? totalTrafficSum / totalCount : 0;\n\n// Расчет коэффициентов\nconst coeffWeekday = {};\nconst coeffMonth = {};\n\nfor (let i = 0; i < 7; i++) {\n const data = trafficByDay[i];\n // Если данных по этому дню нет, коэффициент = 1\n coeffWeekday[i] = data ? (data.s / data.c) / avgTotalTraffic : 1.0;\n}\nfor (let i = 0; i < 12; i++) {\n const data = trafficByMonth[i];\n coeffMonth[i] = data ? (data.s / data.c) / avgTotalTraffic : 1.0;\n}\n\n// --- ШАГ 2: ЗАПОЛНЕНИЕ КОЛОНКИ ПРОГНОЗ ---\n// Теперь идем по тем же строкам и дописываем прогноз\n\nconst finalOutput = inputData.map(row => {\n const rawDate = row[dateKey];\n const rawTraffic = row[trafficKey];\n const factTraffic = parseCleanNumber(rawTraffic); // Факт может быть 0\n\n let date;\n let formattedDate = rawDate; // По умолчанию оставляем как было\n\n // Нормализуем дату для расчета\n if (typeof rawDate === 'number') {\n date = getJsDateFromExcel(rawDate);\n formattedDate = formatDate(date);\n } else {\n date = new Date(rawDate);\n if (!isNaN(date.getTime())) {\n formattedDate = formatDate(date);\n }\n }\n\n let forecastValue = 0;\n\n // Если дата валидная, считаем прогноз\n if (date && !isNaN(date.getTime()) && avgTotalTraffic > 0) {\n const d = date.getDay();\n const m = date.getMonth();\n \n const K_DN = coeffWeekday[d];\n const K_SEZ = coeffMonth[m];\n \n forecastValue = Math.round(avgTotalTraffic * K_DN * K_SEZ);\n }\n\n // Возвращаем объект в нужном порядке\n return {\n json: {\n \"Дата\": formattedDate,\n \"Исторический трафик (Факт)\": rawTraffic, // Оставляем оригинальное значение\n \"Прогнозный трафик\": forecastValue\n }\n };\n});\n\nreturn finalOutput;"},"name":"Calculate Forecast1","type":"n8n-nodes-base.code","typeVersion":2,"position":[64,80],"id":"26c95bbe-ef72-4331-ae6e-70dfe4fcc86e","disabled":true}],"connections":{"Telegram Trigger":{"main":[[{"node":"Get File","type":"main","index":0}]]},"Get File":{"main":[[{"node":"Parse File","type":"main","index":0}]]},"Parse File":{"main":[[{"node":"Calculate Forecast","type":"main","index":0}]]},"Calculate Forecast":{"main":[[{"node":"Create CSV","type":"main","index":0}]]},"Create CSV":{"main":[[{"node":"Send Result","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"staticData":null,"meta":{"templateCredsSetupCompleted":true},"pinData":{},"versionId":"4b8d3db7-ff4d-44c7-91f9-dab154b2dae2","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2025-12-11T07:26:21.150Z","createdAt":"2025-12-11T07:26:21.150Z","role":"workflow:owner","workflowId":"VCPJdSaOUiAvXjBI","projectId":"sYuiFAb87pAir6jV"}],"activeVersion":null,"tags":[]}