🚀 Full project sync: Hotels RAG & Audit System

 Major Features:
- Complete RAG system for hotel website analysis
- Hybrid audit with BGE-M3 embeddings + Natasha NER
- Universal horizontal Excel reports with dashboards
- Multi-region processing (SPb, Orel, Chukotka, Kamchatka)

📊 Completed Regions:
- Орловская область: 100% (36/36)
- Чукотский АО: 100% (4/4)
- г. Санкт-Петербург: 93% (893/960)
- Камчатский край: 87% (89/102)

🔧 Infrastructure:
- PostgreSQL with pgvector extension
- BGE-M3 embeddings API
- Browserless for web scraping
- N8N workflows for automation
- S3/Nextcloud file storage

📝 Documentation:
- Complete DB schemas
- API documentation
- Setup guides
- Status reports
This commit is contained in:
Фёдор
2025-10-27 22:49:42 +03:00
parent 0cf3297290
commit 684fada337
94 changed files with 14891 additions and 911 deletions

160
n8n_code_check_regex.js Normal file
View File

@@ -0,0 +1,160 @@
// 🎯 CODE NODE: Проверка регулярными выражениями
// Размести эту ноду ПОСЛЕ AI Agent
// Она улучшит оценку, если найдёт точные форматы (ИНН, телефоны, email)
// Получаем данные от AI Agent
const aiResult = $input.item.json;
// Получаем текст из chunks (должен быть в контексте)
// Если у тебя есть отдельная нода для получения chunks - используй её
// Иначе - нужно сделать дополнительный запрос к PostgreSQL
const hotelText = $('Postgres1').all().map(item => item.json.text).join(' ');
// Регулярные выражения для каждого критерия
const regexPatterns = {
1: { // ИНН, ОГРН
patterns: [
/\b\d{10}\b/g, // ИНН юр.лица (10 цифр)
/\b\d{12}\b/g, // ИНН ИП (12 цифр)
/\b\d{13}\b/g, // ОГРН (13 цифр)
/\b\d{15}\b/g, // ОГРНИП (15 цифр)
/инн\s*:?\s*\d{10,12}/gi,
/огрн\s*:?\s*\d{13}/gi
],
weight: 1.0
},
2: { // Адрес
patterns: [
/\d{6}.*?ул\./gi,
/ул\.\s*[А-Яа-яёЁA-Za-z\s]+,?\s*\d+/gi,
/\d{6},?\s*г\.\s*[А-Яа-яёЁ-]+/gi
],
weight: 1.0
},
3: { // Контакты
patterns: [
/(?:\+7|8)\s*\(?\d{3,5}\)?\s*\d{1,3}[-\s]?\d{2}[-\s]?\d{2}/g, // Телефон
/[\w\.-]+@[\w\.-]+\.\w{2,}/g // Email
],
weight: 1.0
},
4: { // Режим работы
patterns: [
/(?:с|с\s+)\d{1,2}(?::|\.)\d{2}\s*(?:до|по)\s*\d{1,2}(?::|\.)\d{2}/gi,
/круглосуточно/gi,
/24\s*[/\-]\s*7/g
],
weight: 1.0
},
5: { // 152-ФЗ
patterns: [
/152[-\s]?фз/gi,
/политика\s+в\s+отношении\s+обработки\s+персональных\s+данных/gi
],
weight: 1.0
},
7: { // Договор-оферта
patterns: [
/публичная\s+оферта/gi,
/договор.*?оказани.*?услуг/gi,
/пользовательское\s+соглашение/gi
],
weight: 1.0
},
9: { // Цены
patterns: [
/\d+\s*(?:руб|₽)/g,
/(?:от|цена|стоимость)\s*\d+/gi
],
weight: 0.8
},
12: { // Онлайн-бронирование
patterns: [
/забронировать/gi,
/форма\s+(?:заявки|бронирования)/gi
],
weight: 0.8
}
};
// Функция проверки паттернов
function checkPatterns(text, patterns) {
const matches = [];
for (const pattern of patterns) {
const found = text.match(pattern);
if (found) {
matches.push(...found.slice(0, 3)); // Макс 3 совпадения на паттерн
}
}
return matches;
}
// Проверяем текущий критерий
const criterionId = aiResult.criterion_id || aiResult.id;
const regexConfig = regexPatterns[criterionId];
let regexScore = 0.0;
let regexMatches = [];
if (regexConfig && hotelText) {
regexMatches = checkPatterns(hotelText, regexConfig.patterns);
if (regexMatches.length > 0) {
regexScore = regexConfig.weight;
}
}
// ГИБРИДНАЯ ОЦЕНКА: берём максимум из AI и регулярок
const aiScore = parseFloat(aiResult.score) || 0.0;
const finalScore = Math.max(aiScore, regexScore);
// Определяем метод, который дал результат
let method = 'Не найдено';
if (finalScore > 0) {
if (aiScore > regexScore) {
method = 'AI Agent';
} else if (regexScore > aiScore) {
method = 'Регулярные выражения';
} else {
method = 'AI Agent + Регулярки';
}
}
// Возвращаем улучшенный результат
return {
json: {
criterion_id: criterionId,
criterion_name: aiResult.criterion_name || aiResult.name,
question: aiResult.question,
// Результаты AI Agent
ai_score: aiScore,
ai_found: aiResult.found,
ai_quote: aiResult.quote || '',
ai_url: aiResult.url || '',
// Результаты регулярок
regex_score: regexScore,
regex_matches: regexMatches.slice(0, 5), // Макс 5 совпадений
regex_found: regexMatches.length > 0,
// Итоговая оценка
final_score: finalScore,
method: method,
confidence: finalScore >= 0.8 ? 'Высокая' :
finalScore >= 0.5 ? 'Средняя' :
finalScore >= 0.3 ? 'Низкая' : 'Не найдено',
// Для отчёта
quote: aiResult.quote || (regexMatches.length > 0 ? `Найдено: ${regexMatches[0]}` : ''),
url: aiResult.url || '',
details: aiResult.details || ''
}
};