refactor(indexer): вынести разбиение на фрагменты в app.fragments + 5 тестов
_split_into_fragments жила в тяжёлой Celery-задаче (celery/sqlalchemy/minio) и не тестировалась, хотя именно она определяет, ЧТО проверяется на плагиат. Вынес в чистый app.fragments.split_into_fragments — тестируется изолированно: - пустой/пробельный текст → []; - текст короче 20 слов → отбрасывается порогом длины; - короткий текст → один фрагмент на весь объём, start=0; - скользящее окно: перекрытие ровно (window-step) слов, хвост==голова соседа; - start/end — корректные символьные офсеты в исходный текст. Поведение сохранено 1:1. Добавлен в mypy-гейт. Тестов всего: 66. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,60 +16,11 @@ from app.config import settings
|
||||
from app.db import db_session, get_minio, update_task_status
|
||||
from app.extractors.docx import extract_text_from_docx, extract_text_from_txt
|
||||
from app.extractors.pdf import extract_text_from_pdf
|
||||
from app.fragments import split_into_fragments
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
def _split_into_fragments(
|
||||
text: str,
|
||||
window: int = 200,
|
||||
overlap: int = 50,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Разбить текст на фрагменты для проверки плагиата.
|
||||
|
||||
Использует скользящее окно с перекрытием.
|
||||
|
||||
Args:
|
||||
text: Исходный текст
|
||||
window: Размер окна в словах
|
||||
overlap: Перекрытие между фрагментами в словах
|
||||
|
||||
Returns:
|
||||
Список словарей {"text": str, "start": int, "end": int}
|
||||
"""
|
||||
words = text.split()
|
||||
if not words:
|
||||
return []
|
||||
|
||||
fragments = []
|
||||
step = window - overlap
|
||||
char_positions = []
|
||||
|
||||
# Вычислить позиции символов для каждого слова
|
||||
pos = 0
|
||||
for word in words:
|
||||
char_positions.append(pos)
|
||||
pos += len(word) + 1 # +1 для пробела
|
||||
|
||||
for i in range(0, max(1, len(words) - window + 1), step):
|
||||
chunk_words = words[i : i + window]
|
||||
if len(chunk_words) < 20: # Пропустить слишком короткие фрагменты
|
||||
continue
|
||||
|
||||
start_char = char_positions[i]
|
||||
end_idx = min(i + window - 1, len(words) - 1)
|
||||
end_char = char_positions[end_idx] + len(words[end_idx])
|
||||
|
||||
fragments.append({
|
||||
"text": " ".join(chunk_words),
|
||||
"start": start_char,
|
||||
"end": end_char,
|
||||
})
|
||||
|
||||
return fragments
|
||||
|
||||
|
||||
@celery_app.task(
|
||||
name="index.extract_and_check",
|
||||
bind=True,
|
||||
@@ -133,7 +84,7 @@ def extract_and_check(
|
||||
_stage_work(task_id, minio_key, filename, text, word_count)
|
||||
|
||||
# Разбить на фрагменты
|
||||
fragments = _split_into_fragments(
|
||||
fragments = split_into_fragments(
|
||||
text,
|
||||
window=settings.FRAGMENT_WINDOW_WORDS,
|
||||
overlap=settings.FRAGMENT_OVERLAP_WORDS,
|
||||
|
||||
Reference in New Issue
Block a user