Проект аудита отелей: основные скрипты и документация
- Краулеры: smart_crawler.py, regional_crawler.py - Аудит: audit_orel_to_excel.py, audit_chukotka_to_excel.py - РКН проверка: check_rkn_registry.py, recheck_unclear_rkn.py - Отчёты: create_orel_horizontal_report.py - Обработка: process_all_hotels_embeddings.py - Документация: README.md, DB_SCHEMA_REFERENCE.md
This commit is contained in:
364
test_comfort_hotel.py
Normal file
364
test_comfort_hotel.py
Normal file
@@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Тестирование гибридного аудита для отеля "Комфорт" (Камчатка)
|
||||
Сравнение с результатами n8n AI Agent
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
import requests
|
||||
import json
|
||||
from urllib.parse import unquote
|
||||
import re
|
||||
|
||||
# Natasha для NER
|
||||
from natasha import (
|
||||
Segmenter,
|
||||
MorphVocab,
|
||||
NewsEmbedding,
|
||||
NewsMorphTagger,
|
||||
NewsSyntaxParser,
|
||||
NewsNERTagger,
|
||||
Doc
|
||||
)
|
||||
|
||||
# Конфигурация БД
|
||||
DB_CONFIG = {
|
||||
'host': "147.45.189.234",
|
||||
'port': 5432,
|
||||
'database': "default_db",
|
||||
'user': "gen_user",
|
||||
'password': unquote("2~~9_%5EkVsU%3F2%5CS")
|
||||
}
|
||||
|
||||
# BGE-M3 API
|
||||
BGE_API_URL = "http://147.45.146.17:8002/embed"
|
||||
BGE_API_KEY = "22564b177aa73b6ac0b8642d7773350ff4c01d4983f028beff15ea247f09fa89"
|
||||
|
||||
# ID отеля Комфорт
|
||||
HOTEL_ID = "303958ee-c607-11ef-92da-f9f9e6a4072b"
|
||||
|
||||
# Инициализация Natasha
|
||||
print("🔧 Инициализация Natasha...")
|
||||
segmenter = Segmenter()
|
||||
morph_vocab = MorphVocab()
|
||||
emb = NewsEmbedding()
|
||||
morph_tagger = NewsMorphTagger(emb)
|
||||
syntax_parser = NewsSyntaxParser(emb)
|
||||
ner_tagger = NewsNERTagger(emb)
|
||||
print("✅ Natasha готова!\n")
|
||||
|
||||
# Результаты от n8n AI Agent для сравнения
|
||||
N8N_RESULTS = {
|
||||
1: {"score": 0.0, "found": False},
|
||||
2: {"score": 1.0, "found": True, "url": "https://hotelcomfort41.ru/o-kompanii"},
|
||||
3: {"score": 0.5, "found": True},
|
||||
4: {"score": 1.0, "found": True, "url": "https://hotelcomfort41.ru"},
|
||||
5: {"score": 0.5, "found": True},
|
||||
7: {"score": 1.0, "found": True},
|
||||
8: {"score": 0.0, "found": False},
|
||||
9: {"score": 0.5, "found": True},
|
||||
10: {"score": 0.0, "found": False},
|
||||
11: {"score": 0.0, "found": False},
|
||||
12: {"score": 1.0, "found": True},
|
||||
13: {"score": 0.0, "found": False},
|
||||
14: {"score": 0.0, "found": False},
|
||||
15: {"score": 0.0, "found": False},
|
||||
16: {"score": 0.0, "found": False},
|
||||
17: {"score": 0.0, "found": False},
|
||||
18: {"score": 0.0, "found": False}
|
||||
}
|
||||
|
||||
def get_db_connection():
|
||||
return psycopg2.connect(**DB_CONFIG, cursor_factory=RealDictCursor)
|
||||
|
||||
def get_all_hotel_text(hotel_id: str) -> list:
|
||||
"""Получить весь текст отеля из chunks"""
|
||||
conn = get_db_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT text, metadata->>'url' as url
|
||||
FROM hotel_website_chunks
|
||||
WHERE metadata->>'hotel_id' = %s
|
||||
ORDER BY id;
|
||||
""", (hotel_id,))
|
||||
|
||||
chunks = cur.fetchall()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
return chunks
|
||||
|
||||
def check_criterion_with_regex(chunks: list, criterion: dict) -> dict:
|
||||
"""Проверка критерия регулярными выражениями"""
|
||||
result = {
|
||||
'found': False,
|
||||
'matches': [],
|
||||
'urls': [],
|
||||
'quotes': []
|
||||
}
|
||||
|
||||
patterns = criterion.get('patterns', [])
|
||||
keywords = criterion.get('keywords', [])
|
||||
|
||||
for chunk in chunks:
|
||||
text = chunk['text']
|
||||
url = chunk.get('url', 'Нет URL')
|
||||
|
||||
# Проверяем регулярки
|
||||
for pattern in patterns:
|
||||
matches = re.findall(pattern, text, re.IGNORECASE)
|
||||
if matches:
|
||||
result['found'] = True
|
||||
result['matches'].extend(matches[:3])
|
||||
if url not in result['urls']:
|
||||
result['urls'].append(url)
|
||||
|
||||
# Находим контекст
|
||||
match_obj = re.search(pattern, text, re.IGNORECASE)
|
||||
if match_obj:
|
||||
idx = match_obj.start()
|
||||
start = max(0, idx - 100)
|
||||
end = min(len(text), idx + 200)
|
||||
quote = text[start:end].strip()
|
||||
result['quotes'].append({
|
||||
'url': url,
|
||||
'quote': quote,
|
||||
'match': matches[0]
|
||||
})
|
||||
|
||||
# Проверяем ключевые слова
|
||||
if not result['found']:
|
||||
text_lower = text.lower()
|
||||
for keyword in keywords:
|
||||
if keyword.lower() in text_lower:
|
||||
result['found'] = True
|
||||
if url not in result['urls']:
|
||||
result['urls'].append(url)
|
||||
|
||||
# Находим контекст вокруг ключевого слова
|
||||
idx = text_lower.find(keyword.lower())
|
||||
if idx >= 0:
|
||||
start = max(0, idx - 100)
|
||||
end = min(len(text), idx + 200)
|
||||
quote = text[start:end].strip()
|
||||
result['quotes'].append({
|
||||
'url': url,
|
||||
'quote': quote,
|
||||
'match': keyword
|
||||
})
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
# 17 критериев с регулярками
|
||||
CRITERIA = [
|
||||
{
|
||||
'id': 1,
|
||||
'name': 'Юридическая идентификация и верификация',
|
||||
'patterns': [
|
||||
r'\b\d{10}\b', # ИНН юр.лица
|
||||
r'\b\d{12}\b', # ИНН ИП
|
||||
r'\b\d{13}\b', # ОГРН
|
||||
r'инн\s*:?\s*\d{10,12}',
|
||||
r'огрн\s*:?\s*\d{13}',
|
||||
],
|
||||
'keywords': ['инн', 'огрн', 'егрюл', 'реквизиты']
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'name': 'Адрес',
|
||||
'patterns': [
|
||||
r'\d{6}.*?ул\.',
|
||||
r'ул\.\s*[А-Яа-яёЁA-Za-z\s]+,?\s*\d+',
|
||||
r'\d{6},?\s*г\.\s*[А-Яа-яёЁ-]+',
|
||||
],
|
||||
'keywords': ['адрес', 'местонахождение', 'г.', 'ул.']
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'name': 'Контакты',
|
||||
'patterns': [
|
||||
r'(?:\+7|8)\s*\(?\d{3,5}\)?\s*\d{1,3}[-\s]?\d{2}[-\s]?\d{2}',
|
||||
r'[\w\.-]+@[\w\.-]+\.\w{2,}',
|
||||
],
|
||||
'keywords': ['телефон', 'email', 'контакт']
|
||||
},
|
||||
{
|
||||
'id': 4,
|
||||
'name': 'Режим работы',
|
||||
'patterns': [
|
||||
r'(?:с|с\s+)\d{1,2}(?::|\.)\d{2}\s*(?:до|по)\s*\d{1,2}(?::|\.)\d{2}',
|
||||
r'круглосуточно',
|
||||
r'24\s*[/\-]\s*7',
|
||||
],
|
||||
'keywords': ['режим работы', 'часы работы', 'график']
|
||||
},
|
||||
{
|
||||
'id': 5,
|
||||
'name': 'Политика ПДн (152-ФЗ)',
|
||||
'patterns': [
|
||||
r'152[-\s]?фз',
|
||||
r'политика\s+в\s+отношении\s+обработки\s+персональных\s+данных',
|
||||
],
|
||||
'keywords': ['персональных данных', 'пдн', '152-фз', 'политика конфиденциальности']
|
||||
},
|
||||
{
|
||||
'id': 7,
|
||||
'name': 'Договор-оферта / Правила оказания услуг',
|
||||
'patterns': [
|
||||
r'публичная\s+оферта',
|
||||
r'договор.*?оказани.*?услуг',
|
||||
r'пользовательское\s+соглашение',
|
||||
],
|
||||
'keywords': ['оферта', 'договор', 'правила', 'соглашение']
|
||||
},
|
||||
{
|
||||
'id': 8,
|
||||
'name': 'Рекламации и споры',
|
||||
'patterns': [],
|
||||
'keywords': ['рекламация', 'претензия', 'жалоба', 'спор']
|
||||
},
|
||||
{
|
||||
'id': 9,
|
||||
'name': 'Цены/прайс',
|
||||
'patterns': [
|
||||
r'\d+\s*(?:руб|₽)',
|
||||
r'(?:от|цена|стоимость)\s*\d+',
|
||||
],
|
||||
'keywords': ['цена', 'прайс', 'стоимость', 'тариф']
|
||||
},
|
||||
{
|
||||
'id': 10,
|
||||
'name': 'Способы оплаты',
|
||||
'patterns': [],
|
||||
'keywords': ['оплата картой', 'наличные', 'безналичный', 'карта', 'visa', 'mastercard']
|
||||
},
|
||||
{
|
||||
'id': 11,
|
||||
'name': 'Онлайн-оплата',
|
||||
'patterns': [],
|
||||
'keywords': ['онлайн оплата', 'оплатить онлайн', 'эквайринг']
|
||||
},
|
||||
{
|
||||
'id': 12,
|
||||
'name': 'Онлайн-бронирование',
|
||||
'patterns': [],
|
||||
'keywords': ['забронировать', 'бронирование', 'booking', 'форма заявки']
|
||||
},
|
||||
{
|
||||
'id': 13,
|
||||
'name': 'FAQ',
|
||||
'patterns': [],
|
||||
'keywords': ['faq', 'частые вопросы', 'часто задаваемые']
|
||||
},
|
||||
{
|
||||
'id': 14,
|
||||
'name': 'Доступность для ЛОВЗ',
|
||||
'patterns': [],
|
||||
'keywords': ['ловз', 'инвалид', 'доступность', 'безбарьерная']
|
||||
},
|
||||
{
|
||||
'id': 15,
|
||||
'name': 'Партнёры/бренды',
|
||||
'patterns': [],
|
||||
'keywords': ['партнер', 'партнёр', 'бренд', 'сотрудничество']
|
||||
},
|
||||
{
|
||||
'id': 16,
|
||||
'name': 'Команда/сотрудники',
|
||||
'patterns': [],
|
||||
'keywords': ['команда', 'сотрудник', 'персонал', 'руководство']
|
||||
},
|
||||
{
|
||||
'id': 17,
|
||||
'name': 'Уголок потребителя',
|
||||
'patterns': [],
|
||||
'keywords': ['уголок потребителя', 'права потребителя', 'защита прав']
|
||||
},
|
||||
{
|
||||
'id': 18,
|
||||
'name': 'Актуальность документов',
|
||||
'patterns': [
|
||||
r'\d{4}[-/.]\d{1,2}[-/.]\d{1,2}', # Даты
|
||||
r'обновлено',
|
||||
r'актуализировано',
|
||||
],
|
||||
'keywords': ['дата обновления', 'актуально', 'обновлено']
|
||||
}
|
||||
]
|
||||
|
||||
print('🏨 ТЕСТИРОВАНИЕ ОТЕЛЯ: Городской отель \"Комфорт\" (Камчатский край)')
|
||||
print('='*80)
|
||||
print(f'ID отеля: {HOTEL_ID}')
|
||||
print()
|
||||
|
||||
# Получаем chunks
|
||||
chunks = get_all_hotel_text(HOTEL_ID)
|
||||
print(f'📊 Загружено {len(chunks)} chunks')
|
||||
print()
|
||||
|
||||
# Проверяем каждый критерий
|
||||
print('🔍 ПРОВЕРКА ПО РЕГУЛЯРКАМИ И КЛЮЧЕВЫМ СЛОВАМ:')
|
||||
print('='*80)
|
||||
|
||||
total_score = 0.0
|
||||
|
||||
for criterion in CRITERIA:
|
||||
crit_id = criterion['id']
|
||||
crit_name = criterion['name']
|
||||
print(f'\n{crit_id}. {crit_name}')
|
||||
print('-'*80)
|
||||
|
||||
result = check_criterion_with_regex(chunks, criterion)
|
||||
|
||||
# Оценка
|
||||
score = 0.0
|
||||
if result['found'] and result['matches']:
|
||||
score = 1.0
|
||||
elif result['found']:
|
||||
score = 0.5
|
||||
|
||||
total_score += score
|
||||
|
||||
# n8n результат для сравнения
|
||||
n8n_score = N8N_RESULTS.get(crit_id, {}).get('score', 0.0)
|
||||
|
||||
print(f'Регулярки: {score}/1.0')
|
||||
print(f'n8n AI: {n8n_score}/1.0')
|
||||
|
||||
if score != n8n_score:
|
||||
diff = score - n8n_score
|
||||
if diff > 0:
|
||||
print(f'✅ Регулярки ЛУЧШЕ на {diff:.1f}')
|
||||
else:
|
||||
print(f'❌ n8n AI ЛУЧШЕ на {-diff:.1f}')
|
||||
else:
|
||||
print('🟰 Одинаково')
|
||||
|
||||
if result['found']:
|
||||
print(f'Найдено совпадений: {len(result["matches"])}')
|
||||
if result['matches']:
|
||||
print(f'Примеры: {result["matches"][:3]}')
|
||||
if result['urls']:
|
||||
print(f'URL: {result["urls"][0]}')
|
||||
if result['quotes']:
|
||||
quote_text = result['quotes'][0]['quote'][:150]
|
||||
print(f'Цитата: {quote_text}...')
|
||||
else:
|
||||
print('❌ Не найдено')
|
||||
|
||||
print()
|
||||
print('='*80)
|
||||
print(f'📊 ИТОГО:')
|
||||
print(f'Регулярки: {total_score}/17 ({total_score/17*100:.1f}%)')
|
||||
print(f'n8n AI: 6.0/17 (35.3%)')
|
||||
print()
|
||||
|
||||
if total_score > 6.0:
|
||||
print(f'✅ Регулярки лучше на {total_score - 6.0:.1f} баллов!')
|
||||
elif total_score < 6.0:
|
||||
print(f'❌ n8n AI лучше на {6.0 - total_score:.1f} баллов!')
|
||||
else:
|
||||
print('🟰 Результаты одинаковые!')
|
||||
Reference in New Issue
Block a user