Проект аудита отелей: основные скрипты и документация
- Краулеры: 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:
248
test_semantic_search_chukotka.py
Normal file
248
test_semantic_search_chukotka.py
Normal file
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Тест семантического поиска по Чукотскому автономному округу
|
||||
на основе готовой базы с эмбеддингами
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
from urllib.parse import unquote
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
# API настройки
|
||||
BGE_API_URL = "http://147.45.146.17:8002/embed"
|
||||
BGE_API_KEY = "22564b177aa73b6ac0b8642d7773350ff4c01d4983f028beff15ea247f09fa89"
|
||||
|
||||
class ChukotkaAnalyzer:
|
||||
def __init__(self):
|
||||
self.conn = None
|
||||
self.cur = None
|
||||
self.connect_db()
|
||||
|
||||
def connect_db(self):
|
||||
"""Подключение к базе данных"""
|
||||
try:
|
||||
self.conn = psycopg2.connect(
|
||||
host='147.45.189.234',
|
||||
port=5432,
|
||||
database='default_db',
|
||||
user='gen_user',
|
||||
password=unquote('2~~9_%5EkVsU%3F2%5CS')
|
||||
)
|
||||
self.conn.autocommit = True
|
||||
self.cur = self.conn.cursor()
|
||||
print("✅ Подключение к БД установлено")
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка подключения к БД: {e}")
|
||||
raise
|
||||
|
||||
def get_chukotka_stats(self):
|
||||
"""Получение статистики по Чукотке"""
|
||||
self.cur.execute("""
|
||||
SELECT
|
||||
COUNT(DISTINCT metadata->>'hotel_id') as hotels_count,
|
||||
COUNT(*) as total_chunks,
|
||||
AVG(LENGTH(text)) as avg_chunk_length
|
||||
FROM hotel_website_chunks
|
||||
WHERE metadata->>'region_name' = 'Чукотский автономный округ';
|
||||
""")
|
||||
|
||||
result = self.cur.fetchone()
|
||||
return {
|
||||
'hotels_count': result[0],
|
||||
'total_chunks': result[1],
|
||||
'avg_chunk_length': result[2]
|
||||
}
|
||||
|
||||
def get_chukotka_hotels(self):
|
||||
"""Получение списка отелей Чукотки"""
|
||||
self.cur.execute("""
|
||||
SELECT DISTINCT
|
||||
metadata->>'hotel_id' as hotel_id,
|
||||
metadata->>'hotel_name' as hotel_name,
|
||||
COUNT(*) as chunks_count
|
||||
FROM hotel_website_chunks
|
||||
WHERE metadata->>'region_name' = 'Чукотский автономный округ'
|
||||
GROUP BY metadata->>'hotel_id', metadata->>'hotel_name'
|
||||
ORDER BY chunks_count DESC;
|
||||
""")
|
||||
|
||||
return self.cur.fetchall()
|
||||
|
||||
def generate_query_embedding(self, query: str):
|
||||
"""Генерация эмбеддинга для поискового запроса"""
|
||||
try:
|
||||
headers = {
|
||||
"X-API-Key": BGE_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {"text": query}
|
||||
response = requests.post(BGE_API_URL, json=payload, headers=headers, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
return result.get('embeddings', [[]])[0]
|
||||
else:
|
||||
print(f"❌ Ошибка API: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка генерации эмбеддинга: {e}")
|
||||
return None
|
||||
|
||||
def search_chukotka(self, query: str, limit: int = 5):
|
||||
"""Семантический поиск по Чукотке"""
|
||||
query_embedding = self.generate_query_embedding(query)
|
||||
if not query_embedding:
|
||||
return []
|
||||
|
||||
embedding_str = json.dumps(query_embedding)
|
||||
|
||||
self.cur.execute("""
|
||||
SELECT
|
||||
metadata->>'hotel_name' as hotel_name,
|
||||
metadata->>'url' as url,
|
||||
LEFT(text, 150) as sample_text,
|
||||
LENGTH(text) as text_length,
|
||||
embedding <-> %s::vector as distance
|
||||
FROM hotel_website_chunks
|
||||
WHERE metadata->>'region_name' = 'Чукотский автономный округ'
|
||||
AND embedding IS NOT NULL
|
||||
ORDER BY embedding <-> %s::vector
|
||||
LIMIT %s;
|
||||
""", (embedding_str, embedding_str, limit))
|
||||
|
||||
return self.cur.fetchall()
|
||||
|
||||
def analyze_hotel_criteria(self, hotel_id: str):
|
||||
"""Анализ отеля по критериям аудита"""
|
||||
criteria_queries = {
|
||||
'Юридическая идентификация': 'инн огрн егрюл организация',
|
||||
'Контактная информация': 'телефон адрес email контакты',
|
||||
'Политика конфиденциальности': 'политика конфиденциальности персональные данные',
|
||||
'Условия бронирования': 'бронирование условия отмена возврат',
|
||||
'Услуги отеля': 'услуги сервис завтрак wi-fi парковка',
|
||||
'Доступность': 'доступность инвалиды коляска лифт'
|
||||
}
|
||||
|
||||
results = {}
|
||||
|
||||
for criteria, query in criteria_queries.items():
|
||||
self.cur.execute("""
|
||||
SELECT
|
||||
embedding <-> %s::vector as distance,
|
||||
LEFT(text, 200) as sample_text
|
||||
FROM hotel_website_chunks
|
||||
WHERE metadata->>'hotel_id' = %s
|
||||
AND embedding IS NOT NULL
|
||||
ORDER BY embedding <-> %s::vector
|
||||
LIMIT 1;
|
||||
""", (json.dumps(self.generate_query_embedding(query)), hotel_id, json.dumps(self.generate_query_embedding(query))))
|
||||
|
||||
result = self.cur.fetchone()
|
||||
if result:
|
||||
distance, sample_text = result
|
||||
relevance = "🟢 Высокая" if distance < 0.9 else "🟡 Средняя" if distance < 1.0 else "🔴 Низкая"
|
||||
results[criteria] = {
|
||||
'distance': distance,
|
||||
'relevance': relevance,
|
||||
'sample_text': sample_text
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
def close(self):
|
||||
"""Закрытие соединения с БД"""
|
||||
if self.cur:
|
||||
self.cur.close()
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
|
||||
def main():
|
||||
print("="*70)
|
||||
print("🏔️ АНАЛИЗ ЧУКОТСКОГО АВТОНОМНОГО ОКРУГА")
|
||||
print("="*70)
|
||||
|
||||
analyzer = ChukotkaAnalyzer()
|
||||
|
||||
try:
|
||||
# Статистика по региону
|
||||
stats = analyzer.get_chukotka_stats()
|
||||
print(f"\n📊 СТАТИСТИКА ПО ЧУКОТКЕ:")
|
||||
print(f" Отелей: {stats['hotels_count']}")
|
||||
print(f" Chunks: {stats['total_chunks']}")
|
||||
print(f" Средняя длина chunk: {stats['avg_chunk_length']:.0f} символов")
|
||||
|
||||
# Список отелей
|
||||
hotels = analyzer.get_chukotka_hotels()
|
||||
print(f"\n🏨 ОТЕЛИ ЧУКОТКИ:")
|
||||
print("-" * 70)
|
||||
for hotel_id, hotel_name, chunks_count in hotels:
|
||||
print(f" 🏨 {hotel_name}")
|
||||
print(f" ID: {hotel_id}")
|
||||
print(f" Chunks: {chunks_count}")
|
||||
print()
|
||||
|
||||
# Тестовые поисковые запросы
|
||||
test_queries = [
|
||||
"телефон отеля",
|
||||
"услуги и сервисы",
|
||||
"бронирование номеров",
|
||||
"адрес и контакты",
|
||||
"политика конфиденциальности",
|
||||
"завтрак и питание"
|
||||
]
|
||||
|
||||
print("🔍 ТЕСТИРОВАНИЕ СЕМАНТИЧЕСКОГО ПОИСКА:")
|
||||
print("-" * 70)
|
||||
|
||||
for query in test_queries:
|
||||
print(f"\n🔍 Запрос: '{query}'")
|
||||
results = analyzer.search_chukotka(query, 3)
|
||||
|
||||
for i, (hotel_name, url, sample_text, text_length, distance) in enumerate(results, 1):
|
||||
if distance < 0.9:
|
||||
relevance = "🟢 Отлично"
|
||||
elif distance < 1.0:
|
||||
relevance = "🟡 Хорошо"
|
||||
else:
|
||||
relevance = "🔴 Слабо"
|
||||
|
||||
print(f" {i}. Distance: {distance:.4f} {relevance}")
|
||||
print(f" Отель: {hotel_name[:50]}...")
|
||||
print(f" Текст: {sample_text}...")
|
||||
print()
|
||||
|
||||
# Анализ одного отеля по критериям
|
||||
if hotels:
|
||||
test_hotel_id, test_hotel_name, _ = hotels[0]
|
||||
print(f"\n📋 АНАЛИЗ ОТЕЛЯ ПО КРИТЕРИЯМ:")
|
||||
print(f"🏨 {test_hotel_name}")
|
||||
print("-" * 70)
|
||||
|
||||
criteria_results = analyzer.analyze_hotel_criteria(test_hotel_id)
|
||||
|
||||
for criteria, data in criteria_results.items():
|
||||
print(f"{data['relevance']} {criteria}")
|
||||
print(f" Distance: {data['distance']:.4f}")
|
||||
print(f" Найденный текст: {data['sample_text'][:100]}...")
|
||||
print()
|
||||
|
||||
print("="*70)
|
||||
print("✅ АНАЛИЗ ЗАВЕРШЁН!")
|
||||
print("="*70)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка анализа: {e}")
|
||||
finally:
|
||||
analyzer.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user