Spaces:
Sleeping
Sleeping
File size: 19,441 Bytes
fd485d9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Скрипт для сравнения результатов InjectionBuilder при использовании
ChunkRepository (SQLite) и InMemoryEntityRepository (предзагруженного из SQLite).
"""
import logging
import random
import sys
from pathlib import Path
from uuid import UUID
# --- SQLAlchemy ---
from sqlalchemy import and_, create_engine, select
from sqlalchemy.orm import sessionmaker
# --- Конфигурация ---
# !!! ЗАМЕНИ НА АКТУАЛЬНЫЙ ПУТЬ К ТВОЕЙ БД НА СЕРВЕРЕ !!!
DATABASE_URL = "sqlite:///../data/logs.db" # Пример пути, используй свой
# Имя таблицы сущностей
ENTITY_TABLE_NAME = "entity" # Исправь, если нужно
# Количество случайных чанков для теста
SAMPLE_SIZE = 300
# --- Настройка путей для импорта ---
SCRIPT_DIR = Path(__file__).parent.resolve()
PROJECT_ROOT = SCRIPT_DIR.parent # Перейти на уровень вверх (scripts -> project root)
LIB_EXTRACTOR_PATH = PROJECT_ROOT / "lib" / "extractor"
COMPONENTS_PATH = PROJECT_ROOT / "components" # Путь к компонентам
sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(LIB_EXTRACTOR_PATH))
sys.path.insert(0, str(COMPONENTS_PATH))
# Добавляем путь к ntr_text_fragmentation внутри lib/extractor
sys.path.insert(0, str(LIB_EXTRACTOR_PATH / "ntr_text_fragmentation"))
# --- Логирование ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# --- Импорты из проекта и библиотеки ---
try:
# Модели БД
from ntr_text_fragmentation.core.entity_repository import \
InMemoryEntityRepository # Импортируем InMemory Repo
from ntr_text_fragmentation.core.injection_builder import \
InjectionBuilder # Импортируем Builder
# Модели сущностей
from ntr_text_fragmentation.models import (Chunk, DocumentAsEntity,
LinkerEntity)
# Репозитории и билдер
from components.dbo.chunk_repository import \
ChunkRepository # Импортируем ChunkRepository
from components.dbo.models.acronym import \
Acronym # Импортируем модель из проекта
from components.dbo.models.dataset import \
Dataset # Импортируем модель из проекта
from components.dbo.models.dataset_document import \
DatasetDocument # Импортируем модель из проекта
from components.dbo.models.document import \
Document # Импортируем модель из проекта
from components.dbo.models.entity import \
EntityModel # Импортируем модель из проекта
# TableEntity если есть
# from ntr_text_fragmentation.models.table_entity import TableEntity
except ImportError as e:
logger.error(f"Ошибка импорта необходимых модулей: {e}")
logger.error("Убедитесь, что скрипт находится в папке scripts вашего проекта,")
logger.error("и структура проекта соответствует ожиданиям (наличие lib/extractor, components/dbo и т.д.).")
sys.exit(1)
# --- Вспомогательная функция для парсинга вывода ---
def parse_output_by_source(text: str) -> dict[str, str]:
"""Разбивает текст на блоки по маркерам '[Источник]'."""
blocks = {}
# Разделяем текст по маркеру
parts = text.split('[Источник]')
# Пропускаем первую часть (текст до первого маркера или пустая строка)
for part in parts[1:]:
part = part.strip() # Убираем лишние пробелы вокруг части
if not part:
continue
# Ищем первый перенос строки
newline_index = part.find('\n')
if newline_index != -1:
# Извлекаем заголовок ( - ИмяИсточника)
header = part[:newline_index].strip()
# Извлекаем контент
content = part[newline_index+1:].strip()
# Очищаем имя источника от " - " и пробелов
source_name = header.removeprefix('-').strip()
if source_name: # Убедимся, что имя источника не пустое
if source_name in blocks:
logger.warning(f"Найден дублирующийся источник '{source_name}' при парсинге split(). Контент будет перезаписан.")
blocks[source_name] = content
else:
logger.warning(f"Не удалось извлечь имя источника из заголовка: '{header}'")
else:
# Если переноса строки нет, вся часть может быть заголовком без контента?
logger.warning(f"Часть без переноса строки после '[Источник]': '{part[:100]}...'")
return blocks
# --- Основная функция сравнения ---
def compare_repositories():
logger.info(f"Подключение к базе данных: {DATABASE_URL}")
try:
engine = create_engine(DATABASE_URL)
# Определяем модель здесь, чтобы не зависеть от Base из другого места
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db_session = SessionLocal()
# 1. Инициализация ChunkRepository (нужен для доступа к _map_db_entity_to_linker_entity)
# Передаем фабрику сессий, чтобы он мог создавать свои сессии при необходимости
chunk_repo = ChunkRepository(db=SessionLocal)
# 2. Загрузка ВСЕХ сущностей НАПРЯМУЮ из БД
logger.info("Загрузка всех сущностей из БД через сессию...")
all_db_models = db_session.query(EntityModel).all()
logger.info(f"Загружено {len(all_db_models)} записей EntityModel.")
if not all_db_models:
logger.error("Не удалось загрузить сущности из базы данных. Проверьте подключение и наличие данных.")
db_session.close()
return
# Конвертация в LinkerEntity с использованием маппинга из ChunkRepository
logger.info("Конвертация EntityModel в LinkerEntity...")
all_linker_entities = [chunk_repo._map_db_entity_to_linker_entity(model) for model in all_db_models]
logger.info(f"Сконвертировано в {len(all_linker_entities)} LinkerEntity объектов.")
# 3. Инициализация InMemoryEntityRepository
logger.info("Инициализация InMemoryEntityRepository...")
in_memory_repo = InMemoryEntityRepository(entities=all_linker_entities)
logger.info(f"InMemoryEntityRepository инициализирован с {len(in_memory_repo.entities)} сущностями.")
# 4. Получение ID искомых чанков НАПРЯМУЮ из БД
logger.info("Получение ID искомых чанков из БД через сессию...")
query = select(EntityModel.uuid).where(
and_(
EntityModel.in_search_text.isnot(None),
)
)
results = db_session.execute(query).scalars().all()
searchable_chunk_ids = [UUID(res) for res in results]
logger.info(f"Найдено {len(searchable_chunk_ids)} сущностей для поиска.")
if not searchable_chunk_ids:
logger.warning("В базе данных не найдено сущностей для поиска (с in_search_text). Тест невозможен.")
db_session.close()
return
# 5. Выборка случайных ID чанков
actual_sample_size = min(SAMPLE_SIZE, len(searchable_chunk_ids))
if actual_sample_size < len(searchable_chunk_ids):
logger.info(f"Выбираем {actual_sample_size} случайных ID сущностей для поиска из {len(searchable_chunk_ids)}...")
sampled_chunk_ids = random.sample(searchable_chunk_ids, actual_sample_size)
else:
logger.info(f"Используем все {len(searchable_chunk_ids)} найденные ID сущностей для поиска (т.к. их меньше или равно {SAMPLE_SIZE}).")
sampled_chunk_ids = searchable_chunk_ids
# 6. Инициализация InjectionBuilders
logger.info("Инициализация InjectionBuilder для ChunkRepository...")
# Передаем ИМЕННО ЭКЗЕМПЛЯР chunk_repo, который мы создали
builder_chunk_repo = InjectionBuilder(repository=chunk_repo)
logger.info("Инициализация InjectionBuilder для InMemoryEntityRepository...")
builder_in_memory = InjectionBuilder(repository=in_memory_repo)
# 7. Сборка текста для обоих репозиториев
logger.info(f"\n--- Сборка текста для ChunkRepository ({actual_sample_size} ID)... ---")
try:
# Передаем список UUID
text_chunk_repo = builder_chunk_repo.build(filtered_entities=sampled_chunk_ids)
logger.info(f"Сборка для ChunkRepository завершена. Общая длина: {len(text_chunk_repo)}")
# --- Добавляем вывод начала текста ---
print("\n--- Начало текста (ChunkRepository, первые 1000 символов): ---")
print(text_chunk_repo[:1000])
print("--- Конец начала текста (ChunkRepository) ---")
# -------------------------------------
except Exception as e:
logger.error(f"Ошибка при сборке с ChunkRepository: {e}", exc_info=True)
text_chunk_repo = f"ERROR_ChunkRepo: {e}"
logger.info(f"\n--- Сборка текста для InMemoryEntityRepository ({actual_sample_size} ID)... ---")
try:
# Передаем список UUID
text_in_memory = builder_in_memory.build(filtered_entities=sampled_chunk_ids)
logger.info(f"Сборка для InMemoryEntityRepository завершена. Общая длина: {len(text_in_memory)}")
# --- Добавляем вывод начала текста ---
print("\n--- Начало текста (InMemory, первые 1000 символов): ---")
print(text_in_memory[:1000])
print("--- Конец начала текста (InMemory) ---")
# -------------------------------------
except Exception as e:
logger.error(f"Ошибка при сборке с InMemoryEntityRepository: {e}", exc_info=True)
text_in_memory = f"ERROR_InMemory: {e}"
# 8. Парсинг результатов по блокам
logger.info("\n--- Парсинг результатов по источникам ---")
blocks_chunk_repo = parse_output_by_source(text_chunk_repo)
blocks_in_memory = parse_output_by_source(text_in_memory)
logger.info(f"ChunkRepo: Найдено {len(blocks_chunk_repo)} блоков источников.")
logger.info(f"InMemory: Найдено {len(blocks_in_memory)} блоков источников.")
# 9. Сравнение блоков
logger.info("\n--- Сравнение блоков по источникам ---")
chunk_repo_keys = set(blocks_chunk_repo.keys())
in_memory_keys = set(blocks_in_memory.keys())
all_keys = chunk_repo_keys | in_memory_keys
mismatched_blocks = []
if chunk_repo_keys != in_memory_keys:
logger.warning("Наборы источников НЕ СОВПАДАЮТ!")
only_in_chunk = chunk_repo_keys - in_memory_keys
only_in_memory = in_memory_keys - chunk_repo_keys
if only_in_chunk:
logger.warning(f" Источники только в ChunkRepo: {sorted(list(only_in_chunk))}")
if only_in_memory:
logger.warning(f" Источники только в InMemory: {sorted(list(only_in_memory))}")
else:
logger.info("Наборы источников совпадают.")
logger.info("\n--- Сравнение содержимого общих источников ---")
common_keys = chunk_repo_keys & in_memory_keys
if not common_keys:
logger.warning("Нет общих источников для сравнения содержимого.")
else:
all_common_blocks_match = True
table_marker_found_in_any_chunk_repo = False
table_marker_found_in_any_in_memory = False
for key in sorted(list(common_keys)):
content_chunk = blocks_chunk_repo.get(key, "") # Используем .get для безопасности
content_memory = blocks_in_memory.get(key, "") # Используем .get для безопасности
# Проверка наличия маркера таблиц
has_tables_chunk = "###" in content_chunk
has_tables_memory = "###" in content_memory
if has_tables_chunk:
table_marker_found_in_any_chunk_repo = True
if has_tables_memory:
table_marker_found_in_any_in_memory = True
# Логируем наличие таблиц для КАЖДОГО блока (можно закомментировать, если много)
# logger.info(f" Источник: '{key}' - Таблицы (###) в ChunkRepo: {has_tables_chunk}, в InMemory: {has_tables_memory}")
if content_chunk != content_memory:
all_common_blocks_match = False
mismatched_blocks.append(key)
logger.warning(f" НЕСОВПАДЕНИЕ для источника: '{key}' (Таблицы в ChunkRepo: {has_tables_chunk}, в InMemory: {has_tables_memory})")
# Можно добавить вывод diff для конкретного блока, если нужно
# import difflib
# block_diff = difflib.unified_diff(
# content_chunk.splitlines(keepends=True),
# content_memory.splitlines(keepends=True),
# fromfile=f'{key}_ChunkRepo',
# tofile=f'{key}_InMemory',
# lineterm='',
# )
# print("\nDiff для блока:")
# sys.stdout.writelines(list(block_diff)[:20]) # Показать начало diff блока
# if len(list(block_diff)) > 20: print("...")
# else:
# # Логируем совпадение только если таблицы есть хоть где-то, для краткости
# if has_tables_chunk or has_tables_memory:
# logger.info(f" Совпадение для источника: '{key}' (Таблицы в ChunkRepo: {has_tables_chunk}, в InMemory: {has_tables_memory})")
# Выводим общую информацию о наличии таблиц
logger.info("--- Итог проверки таблиц (###) в общих блоках ---")
logger.info(f"Маркер таблиц '###' найден хотя бы в одном блоке ChunkRepo: {table_marker_found_in_any_chunk_repo}")
logger.info(f"Маркер таблиц '###' найден хотя бы в одном блоке InMemory: {table_marker_found_in_any_in_memory}")
logger.info("-------------------------------------------------")
if all_common_blocks_match:
logger.info("Содержимое ВСЕХ общих источников СОВПАДАЕТ.")
else:
logger.warning(f"Найдено НЕСОВПАДЕНИЕ содержимого для {len(mismatched_blocks)} источников: {sorted(mismatched_blocks)}")
logger.info("\n--- Итоговый вердикт ---")
if chunk_repo_keys == in_memory_keys and not mismatched_blocks:
logger.info("ПОЛНОЕ СОВПАДЕНИЕ: Наборы источников и их содержимое идентичны.")
elif chunk_repo_keys == in_memory_keys and mismatched_blocks:
logger.warning("ЧАСТИЧНОЕ СОВПАДЕНИЕ: Наборы источников совпадают, но содержимое некоторых блоков различается.")
else:
logger.warning("НЕСОВПАДЕНИЕ: Наборы источников различаются (и, возможно, содержимое общих тоже).")
except ImportError as e:
# Ловим ошибки импорта, возникшие внутри функций (маловероятно после старта)
logger.error(f"Критическая ошибка импорта: {e}")
except Exception as e:
logger.error(f"Произошла общая ошибка: {e}", exc_info=True)
finally:
if 'db_session' in locals() and db_session:
db_session.close()
logger.info("Сессия базы данных закрыта.")
# --- Запуск ---
if __name__ == "__main__":
# Используем Path для более надежного определения пути
db_path = Path(DATABASE_URL.replace("sqlite:///", ""))
if not db_path.exists():
print(f"!!! ОШИБКА: Файл базы данных НЕ НАЙДЕН по пути: {db_path.resolve()} !!!")
print(f"!!! Проверьте значение DATABASE_URL в скрипте. !!!")
elif "путь/к/твоей" in DATABASE_URL: # Доп. проверка на placeholder
print("!!! ПОЖАЛУЙСТА, УКАЖИТЕ ПРАВИЛЬНЫЙ ПУТЬ К БАЗЕ ДАННЫХ В ПЕРЕМЕННОЙ DATABASE_URL !!!")
else:
compare_repositories()
|