fix(plagiarism): не матчить против чужих/своих же загруженных работ
Критично: L1/L2/L3 сравнивали фрагменты со ВСЕЙ базой documents, включая source=user_submission — работы, которые auto_approve_submission сам же затаскивал в корпус после проверки. Итог: студент, перепроверивший тот же файл дважды, получал 100% "точное совпадение" по всем фрагментам — против собственной же более ранней загрузки. Фикс: все три уровня исключают source=user_submission из кандидатов на совпадение. AUTO_APPROVE_SUBMISSIONS выключен по умолчанию — раньше рос корпус для будущего сравнения, но без защиты от self/cross-match это опаснее, чем полезно.
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user