Files
hotels/test_comfort_hotel.py
Фёдор 0cf3297290 Проект аудита отелей: основные скрипты и документация
- Краулеры: 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
2025-10-16 10:52:09 +03:00

365 lines
12 KiB
Python
Raw Permalink 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.

#!/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('🟰 Результаты одинаковые!')