From 74cbf1b8f45c221084caf9bfe19739cad12af36f Mon Sep 17 00:00:00 2001 From: jze9 Date: Tue, 25 Aug 2026 17:42:23 +0500 Subject: [PATCH] =?UTF-8?q?fix(plagiarism):=20=D0=BD=D0=B5=20=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D1=87=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D1=82=D0=B8?= =?UTF-8?q?=D0=B2=20=D1=87=D1=83=D0=B6=D0=B8=D1=85/=D1=81=D0=B2=D0=BE?= =?UTF-8?q?=D0=B8=D1=85=20=D0=B6=D0=B5=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Критично: L1/L2/L3 сравнивали фрагменты со ВСЕЙ базой documents, включая source=user_submission — работы, которые auto_approve_submission сам же затаскивал в корпус после проверки. Итог: студент, перепроверивший тот же файл дважды, получал 100% "точное совпадение" по всем фрагментам — против собственной же более ранней загрузки. Фикс: все три уровня исключают source=user_submission из кандидатов на совпадение. AUTO_APPROVE_SUBMISSIONS выключен по умолчанию — раньше рос корпус для будущего сравнения, но без защиты от self/cross-match это опаснее, чем полезно. --- services/worker-gpu/app/tasks/plagiarism.py | 4 ++++ services/worker-indexer/app/config.py | 10 ++++++---- services/worker-indexer/app/tasks/index.py | 13 ++++++++++--- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/services/worker-gpu/app/tasks/plagiarism.py b/services/worker-gpu/app/tasks/plagiarism.py index 7132f99..14fc74f 100644 --- a/services/worker-gpu/app/tasks/plagiarism.py +++ b/services/worker-gpu/app/tasks/plagiarism.py @@ -121,6 +121,10 @@ def check_plagiarism( if not doc_meta: continue + # user_submission — чужие непубличные загрузки (и свои же прошлые + # прогоны того же файла), не легитимный источник для сравнения. + if doc_meta["source"] == "user_submission": + continue llm_result = {"is_paraphrase": False, "confidence": 0.0, "reason": ""} if source_text: diff --git a/services/worker-indexer/app/config.py b/services/worker-indexer/app/config.py index dce4e51..09f7a88 100644 --- a/services/worker-indexer/app/config.py +++ b/services/worker-indexer/app/config.py @@ -61,10 +61,12 @@ class Settings(BaseSettings): # Автоматически добавлять проверенные работы студентов в базу для сравнения # (как в коммерческих системах — Антиплагиат.ру/Turnitin ловят списывание у - # предыдущих потоков именно так). Без этого каждая работа лежит в StagedWork - # и ждёт ручного одобрения админом — сейчас это дефолт для роста корпуса; - # выключить, если нужна модерация перед публикацией. - AUTO_APPROVE_SUBMISSIONS: bool = True + # предыдущих потоков именно так). Выключено по умолчанию: без ручной + # модерации студент, перепроверивший тот же файл дважды, получал 100% + # "плагиата" — против собственной же более ранней загрузки. L1/L2/L3 теперь + # и так исключают source=user_submission из сравнения, так что включать это + # обратно есть смысл только вместе с реальной защитой от self/cross-match. + AUTO_APPROVE_SUBMISSIONS: bool = False # App ENVIRONMENT: str = "development" diff --git a/services/worker-indexer/app/tasks/index.py b/services/worker-indexer/app/tasks/index.py index 12b5412..ba6130e 100644 --- a/services/worker-indexer/app/tasks/index.py +++ b/services/worker-indexer/app/tasks/index.py @@ -108,13 +108,20 @@ def extract_and_check( continue frag_hashes = list(frag_fp) - # Источник, разделяющий больше всего отпечатков с этим фрагментом + # Источник, разделяющий больше всего отпечатков с этим фрагментом. + # user_submission исключены: это чужие непубличные загрузки (и + # свои же прошлые прогоны того же файла) — сравнение с ними даёт + # ложные 100%-совпадения, а не реальный плагиат из источника. row = session.execute( select( Fingerprint.doc_id, func.count(Fingerprint.id).label("cnt"), ) - .where(Fingerprint.hash_value.in_(frag_hashes)) + .join(Document, Document.id == Fingerprint.doc_id) + .where( + Fingerprint.hash_value.in_(frag_hashes), + Document.source != "user_submission", + ) .group_by(Fingerprint.doc_id) .order_by(func.count(Fingerprint.id).desc()) .limit(1) @@ -164,7 +171,7 @@ def extract_and_check( try: doc_id = int(key.split(":")[-1]) doc = session.get(Document, doc_id) - if not doc: + if not doc or doc.source == "user_submission": continue level2_matches.append({