230 lines
10 KiB
JavaScript
230 lines
10 KiB
JavaScript
|
|
// ============================================================
|
|||
|
|
// 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. На выходе - объединённый результат с итоговой оценкой
|
|||
|
|
// ============================================================
|
|||
|
|
|