Files
hotels/n8n_code_merge_audit_results.js

230 lines
10 KiB
JavaScript
Raw Normal View History

// ============================================================
// N8N CODE NODE: Объединение результатов AI Agent и Regex
// ============================================================
//
// INPUT: Массив из 34 элементов
// - Первые 17: результаты от AI Agent
// - Последние 17: результаты от Regex
//
// OUTPUT: Объединённые результаты с итоговой оценкой
// ============================================================
// Определяем 17 критериев
const CRITERIA = [
{ id: 1, name: "Юридическая идентификация и верификация", description: "ИНН, ОГРН, полное наименование организации" },
{ id: 2, name: "Адрес", description: "Юридический и фактический адрес, местонахождение" },
{ id: 3, name: "Контакты", description: "Телефон, email, форма обратной связи" },
{ id: 4, name: "Режим работы", description: "Часы работы, график приема, колл-центр" },
{ id: 5, name: "Политика ПДн (152-ФЗ)", description: "Политика персональных данных, обработка ПДн" },
{ id: 7, name: "Договор-оферта / Правила оказания услуг", description: "Публичная оферта, пользовательское соглашение" },
{ id: 8, name: "Рекламации и споры", description: "Претензии, возврат, обмен, жалобы" },
{ id: 9, name: "Цены/прайс", description: "Цены, стоимость, тарифы" },
{ id: 10, name: "Способы оплаты", description: "Наличные, карта, СБП" },
{ id: 11, name: "Онлайн-оплата", description: "Эквайринг, оплата онлайн" },
{ id: 12, name: "Онлайн-бронирование", description: "Забронировать, booking" },
{ id: 13, name: "FAQ", description: "Частые вопросы, вопрос-ответ" },
{ id: 14, name: "Доступность для ЛОВЗ", description: "Инвалиды, безбарьерная среда" },
{ id: 15, name: "Партнёры/бренды", description: "Партнеры, поставщики, сотрудничество" },
{ id: 16, name: "Команда/сотрудники", description: "Команда, персонал, руководство" },
{ id: 17, name: "Уголок потребителя", description: "Права потребителей, защита" },
{ id: 18, name: "Актуальность документов", description: "Дата обновления, версия" }
];
/**
* Рассчитывает итоговую уверенность
*/
function calculateFinalConfidence(aiConf, regexConf, aiFound, regexFound) {
// Если оба нашли - очень высокая
if (aiFound && regexFound) {
return "Очень высокая";
}
// Если один нашёл с высокой уверенностью
if ((aiFound && aiConf === "Высокая") || (regexFound && regexConf === "Высокая")) {
return "Высокая";
}
// Если один нашёл со средней уверенностью
if ((aiFound && aiConf === "Средняя") || (regexFound && regexConf === "Средняя")) {
return "Средняя";
}
// Если оба не нашли с высокой уверенностью - точно нет
if (!aiFound && !regexFound && aiConf === "Высокая" && regexConf === "Высокая") {
return "Высокая (не найдено)";
}
// Иначе - низкая
return "Низкая";
}
/**
* Объединяет результаты AI и Regex
*/
function mergeResults(allResults) {
// Разделяем на AI (первые 17) и Regex (последние 17)
const aiResults = allResults.slice(0, 17);
const regexResults = allResults.slice(17, 34);
const merged = [];
for (let i = 0; i < CRITERIA.length; i++) {
const criterion = CRITERIA[i];
// AI результаты
const aiItem = aiResults[i] || {};
const aiOutput = aiItem.output || {};
const aiFound = aiOutput.found || false;
const aiScore = aiOutput.score || 0;
const aiQuote = aiOutput.quote || '';
const aiUrl = aiOutput.url || '';
const aiDetails = aiOutput.details || '';
const aiConfidence = aiOutput.confidence || 'Не определена';
const aiCheckedPages = aiOutput.checked_pages || 0;
// Regex результаты
const regexItem = regexResults[i] || {};
const regexOutput = regexItem.output || {};
const regexFound = regexOutput.found || false;
const regexAnswer = regexOutput.answer || 'НЕТ';
const regexExtracted = regexOutput.extracted || '';
const regexConfidence = regexOutput.confidence || 'Не определена';
// Итоговый результат
const found = aiFound || regexFound;
const finalScore = Math.max(aiScore, regexFound ? 1 : 0);
const finalConfidence = calculateFinalConfidence(aiConfidence, regexConfidence, aiFound, regexFound);
// Собираем объединённый результат
const mergedItem = {
criterion_id: criterion.id,
criterion_name: criterion.name,
criterion_description: criterion.description,
// Общий результат
found: found,
status: found ? "НАЙДЕНО" : "НЕ НАЙДЕНО",
score: finalScore,
final_confidence: finalConfidence,
// AI Agent результаты
ai_agent: {
found: aiFound,
score: aiScore,
quote: aiQuote,
url: aiUrl,
details: aiDetails,
confidence: aiConfidence,
checked_pages: aiCheckedPages
},
// Regex результаты
regex: {
found: regexFound,
answer: regexAnswer,
extracted: regexExtracted,
confidence: regexConfidence
}
};
merged.push(mergedItem);
}
return merged;
}
/**
* Формирует итоговую сводку
*/
function formatSummary(mergedResults, hotelName, region) {
const total = mergedResults.length;
const foundCount = mergedResults.filter(r => r.found).length;
const notFoundCount = total - foundCount;
const compliancePercentage = Math.round((foundCount / total) * 100 * 10) / 10;
return {
hotel_name: hotelName || "Не указано",
region: region || "Не указано",
audit_date: new Date().toISOString().split('T')[0],
total_criteria: total,
found: foundCount,
not_found: notFoundCount,
compliance_percentage: compliancePercentage,
criteria_results: mergedResults
};
}
// ============================================================
// ГЛАВНЫЙ КОД
// ============================================================
// Получаем входные данные
const inputData = $input.all();
// Извлекаем массив результатов
let allResults = [];
if (Array.isArray(inputData) && inputData.length > 0) {
// Вариант 1: Aggregate вернул один item с массивом внутри
if (inputData.length === 1 && inputData[0].json && Array.isArray(inputData[0].json)) {
allResults = inputData[0].json;
}
// Вариант 2: Aggregate вернул один item с полем data (массив)
else if (inputData.length === 1 && inputData[0].json && Array.isArray(inputData[0].json.data)) {
allResults = inputData[0].json.data;
}
// Вариант 3: Пришло 34 отдельных items (без Aggregate)
else if (inputData.length === 34) {
allResults = inputData.map(item => item.json || item);
}
// Вариант 4: Пришло много items, берём все
else {
allResults = inputData.map(item => item.json || item);
}
} else {
throw new Error('Неверный формат входных данных. Ожидается массив из 34 элементов.');
}
// Отладочная информация
console.log(`📊 Получено элементов: ${allResults.length}`);
console.log(`📦 Формат входных данных: ${inputData.length} items`);
// Проверяем количество
if (allResults.length !== 34) {
console.log(`⚠️ Предупреждение: получено ${allResults.length} элементов вместо 34`);
console.log(`Первый элемент:`, JSON.stringify(allResults[0], null, 2).substring(0, 200));
}
// Объединяем результаты
const mergedResults = mergeResults(allResults);
// Получаем данные об отеле из первого элемента или workflow
let hotelName = "Неизвестный отель";
let region = "Неизвестный регион";
try {
// Пытаемся получить из первого input item
const firstItem = $input.first().json;
hotelName = firstItem.hotel_name || hotelName;
region = firstItem.region || region;
} catch (e) {
// Если не получилось, используем значения по умолчанию
console.log('Не удалось получить hotel_name и region из input');
}
// Формируем итоговую сводку
const summary = formatSummary(mergedResults, hotelName, region);
// Возвращаем результат
return [{ json: summary }];
// ============================================================
// ПРИМЕЧАНИЯ:
// ============================================================
// 1. Входные данные должны быть массивом из 34 элементов
// 2. Первые 17 - от AI Agent (с детальными ответами)
// 3. Последние 17 - от Regex (с простыми ДА/НЕТ)
// 4. На выходе - объединённый результат с итоговой оценкой
// ============================================================