fix(L1): обрезать отпечатки равномерно по тексту, а не произвольно

winnow() возвращает set, поэтому list(fp)[:LIMIT] брал случайное подмножество:
у длинного документа целые куски оставались без отпечатков, и списывание
именно из них не находилось. Обнаружено при разборе того, почему фрагмент
статьи PMC не искался.

Добавлены winnow_ordered() — отпечатки в порядке появления в тексте, и
sample_evenly() — выборка каждого n-го элемента вместо первых N. Применено в
add_document и store_full_text.

7 тестов на главное свойство: выборка растянута по всей длине документа, шаг
ровный, порядок сохранён.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-09-05 19:13:01 +05:00
parent 9145de8e54
commit 476682741e
3 changed files with 123 additions and 4 deletions

View File

@@ -10,7 +10,7 @@ from sqlalchemy import func, select, update
from sqlalchemy.exc import IntegrityError
from app.algorithms.minhash import add_to_lsh, find_similar
from app.algorithms.winnowing import winnow
from app.algorithms.winnowing import sample_evenly, winnow, winnow_ordered
from app.celery_app import celery_app
from app.config import settings
from app.db import db_session, get_minio, refund_plagiarism_quota, update_task_status
@@ -284,8 +284,9 @@ def add_document(doc_data: dict[str, Any], dispatch_embed: bool = True) -> dict[
# Вычислить fingerprints
text = doc_data.get("full_text") or doc_data.get("abstract", "") or ""
if text:
fp = winnow(text)
fingerprints_to_add = list(fp)[: settings.MAX_FINGERPRINTS_PER_DOC]
fingerprints_to_add = sample_evenly(
winnow_ordered(text), settings.MAX_FINGERPRINTS_PER_DOC
)
for i, hash_val in enumerate(fingerprints_to_add):
session.add(Fingerprint(doc_id=doc_id, hash_value=hash_val, position=i))
@@ -375,7 +376,7 @@ def store_full_text(doc_id: int, text: str) -> dict[str, Any]:
content_type="text/plain; charset=utf-8",
)
hashes = list(winnow(text))[: settings.MAX_FINGERPRINTS_PER_DOC]
hashes = sample_evenly(winnow_ordered(text), settings.MAX_FINGERPRINTS_PER_DOC)
with db_session() as session:
doc = session.get(Document, doc_id)