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

1 line
8.1 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-12T09:44:10.802Z","createdAt":"2025-12-12T09:43:57.181Z","id":"HeNuVT2DB61vKm6y","name":"Traffic Forecast Dynamic","active":false,"isArchived":false,"nodes":[{"parameters":{"updates":["message"],"additionalFields":{}},"name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","typeVersion":1,"position":[-592,-112],"id":"9bd9cd1c-102e-4aa8-8d6e-b69e5003931c","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,-112],"id":"f4e44340-4096-4c3d-b043-855f418977e0","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":[-144,-112],"id":"80ae83b6-aa9a-48fb-b2ac-18826df7f7ad"},{"parameters":{"jsCode":"// --- НАСТРОЙКИ МОДЕЛИ ---\n// Alpha (0.1 - 0.9). Чем больше, тем быстрее прогноз реагирует на последние изменения.\n// 0.1 - очень плавный прогноз (учитывает долгую историю)\n// 0.8 - нервный прогноз (повторяет вчерашний день)\nconst ALPHA = 0.3; \n\n// --- ПОЛУЧЕНИЕ ДАННЫХ ---\nconst items = $input.all();\nif (items.length === 0) return [];\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// --- ШАГ 0: ПАРСИНГ И ПОДГОТОВКА ---\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// Преобразуем в удобный массив объектов\nlet parsedRows = inputData.map(row => {\n const rawTraffic = row[trafficKey];\n const rawDate = row[dateKey];\n const traffic = parseCleanNumber(rawTraffic);\n \n let date;\n if (typeof rawDate === 'number') date = getJsDateFromExcel(rawDate);\n else date = new Date(rawDate);\n\n return {\n dateObj: date,\n originalDate: rawDate,\n traffic: traffic,\n hasTraffic: traffic > 0 && !isNaN(traffic),\n isValidDate: !isNaN(date.getTime())\n };\n}).filter(r => r.isValidDate);\n\n// ВАЖНО: Сортируем по дате от прошлого к будущему\nparsedRows.sort((a, b) => a.dateObj - b.dateObj);\n\n// --- ШАГ 1: РАСЧЕТ СЕЗОННОСТИ (Глобальные паттерны) ---\n// Считаем коэффициенты дней недели и месяцев по всей доступной истории\nconst statsDay = {};\nconst statsMonth = {};\nlet globalSum = 0;\nlet globalCount = 0;\n\nparsedRows.forEach(row => {\n if (!row.hasTraffic) return;\n \n const d = row.dateObj.getDay();\n const m = row.dateObj.getMonth();\n \n globalSum += row.traffic;\n globalCount++;\n\n statsDay[d] = (statsDay[d] || {s:0, c:0});\n statsDay[d].s += row.traffic;\n statsDay[d].c++;\n\n statsMonth[m] = (statsMonth[m] || {s:0, c:0});\n statsMonth[m].s += row.traffic;\n statsMonth[m].c++;\n});\n\nconst globalAvg = globalCount > 0 ? globalSum / globalCount : 0;\n\n// Вычисляем коэффициенты (K)\nconst K_Day = {};\nconst K_Month = {};\n\nfor(let i=0; i<7; i++) {\n K_Day[i] = statsDay[i] ? (statsDay[i].s / statsDay[i].c) / globalAvg : 1.0;\n}\nfor(let i=0; i<12; i++) {\n K_Month[i] = statsMonth[i] ? (statsMonth[i].s / statsMonth[i].c) / globalAvg : 1.0;\n}\n\n// --- ШАГ 2: АДАПТИВНЫЙ ПРОГНОЗ (ЭКСПОНЕНЦИАЛЬНОЕ СГЛАЖИВАНИЕ) ---\n// Идем по времени и обновляем \"Уровень\" трафика (Level)\n\n// Начальный уровень берем как глобальное среднее (или среднее первых N дней)\nlet currentLevel = globalAvg;\n\nconst finalOutput = parsedRows.map(row => {\n const d = row.dateObj.getDay();\n const m = row.dateObj.getMonth();\n \n // 1. Рассчитываем сезонный коэффициент для текущей точки\n const seasonality = (K_Day[d] || 1) * (K_Month[m] || 1);\n \n // 2. Делаем прогноз НА СЕГОДНЯ, используя уровень ВЧЕРАШНЕГО дня\n // Forecast = Level * Seasonality\n let forecastVal = Math.round(currentLevel * seasonality);\n \n // 3. Если у нас есть ФАКТ (История), мы корректируем уровень (Обучаемся)\n if (row.hasTraffic) {\n // Очищаем факт от сезонности, чтобы получить \"чистый уровень\"\n const deseasonalizedActual = row.traffic / seasonality;\n \n // Обновляем уровень: \n // Новый уровень = (Alpha * Текущий факт) + ((1-Alpha) * Старый уровень)\n currentLevel = (ALPHA * deseasonalizedActual) + ((1 - ALPHA) * currentLevel);\n }\n \n // Формируем красивую дату\n const dateStr = formatDate(row.dateObj);\n\n return {\n json: {\n \"Дата\": dateStr,\n \"День недели\": row.dateObj.toLocaleDateString('ru-RU', { weekday: 'short' }),\n \"Исторический трафик (Факт)\": row.traffic || 0,\n \"Прогнозный трафик\": forecastVal\n }\n };\n});\n\nreturn finalOutput;"},"name":"Calculate Forecast","type":"n8n-nodes-base.code","typeVersion":2,"position":[80,-112],"id":"17bebbe3-a7bd-4bfb-894b-1d4080b69ba7"},{"parameters":{"operation":"toFile","fileFormat":"xlsx","options":{}},"name":"Create CSV","type":"n8n-nodes-base.spreadsheetFile","typeVersion":2,"position":[304,-112],"id":"a19692de-39db-4622-a15e-cc52f3f5f5e7"},{"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":[528,-112],"id":"0804d9ba-d8bd-4986-b036-8fba1a95d151","webhookId":"b7e31308-df8c-44ee-9e89-87c4b1b585f7","credentials":{"telegramApi":{"id":"L9KHcMiteyKROF5r","name":"Telegram main n8n"}}}],"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":"be154c7b-ea55-4241-b0c8-6a3e6fc47d4f","activeVersionId":null,"triggerCount":0,"shared":[{"updatedAt":"2025-12-12T09:43:57.181Z","createdAt":"2025-12-12T09:43:57.181Z","role":"workflow:owner","workflowId":"HeNuVT2DB61vKm6y","projectId":"sYuiFAb87pAir6jV"}],"activeVersion":null,"tags":[]}