feat(plagiarism): показывать тематически близкие источники, а не только нарушения
All checks were successful
Deploy / test (push) Successful in 4m15s
Deploy / deploy (push) Successful in 37s

Кандидаты уровня 3 (FAISS), которые прошли порог семантической схожести,
но LLM не подтвердила заимствование, раньше молча отбрасывались. Теперь
это отдельный блок "recommendations" в отчёте — не плагиат, но источники,
полезные для раскрытия темы.
This commit is contained in:
jze9
2026-08-25 14:16:09 +05:00
parent 12eb954838
commit 53d0de3d71
6 changed files with 155 additions and 10 deletions

View File

@@ -99,6 +99,7 @@ def check_plagiarism(
ollama = OllamaClient()
vector_store = get_backend()
semantic_matches: list[dict] = []
related_candidates: list[dict] = []
for i, fragment in enumerate(fragments):
frag_text = fragment.get("text", "")
@@ -125,19 +126,29 @@ def check_plagiarism(
if source_text:
llm_result = ollama.check_paraphrase(source_text, frag_text)
candidate = {
"fragment": frag_text[:300],
"position_start": fragment.get("start", 0),
"position_end": fragment.get("end", len(frag_text)),
"similarity": round(score * 100, 1),
"source_title": doc_meta["title"],
"source_url": doc_meta["url"],
"source_db": doc_meta["source"],
}
if llm_result.get("is_paraphrase") and llm_result.get("confidence", 0.0) >= LLM_CONFIDENCE_THRESHOLD:
semantic_matches.append({
"fragment": frag_text[:300],
"position_start": fragment.get("start", 0),
"position_end": fragment.get("end", len(frag_text)),
"similarity": round(score * 100, 1),
**candidate,
"method": "semantic+llm",
"confidence": llm_result["confidence"],
"reason": llm_result.get("reason", ""),
"source_title": doc_meta["title"],
"source_url": doc_meta["url"],
"source_db": doc_meta["source"],
})
else:
# Похоже по смыслу, но LLM не подтвердила заимствование —
# не плагиат, но тематически близкая работа: кандидат в
# рекомендации "источники для раскрытия темы", а не в отчёт
# о нарушениях.
related_candidates.append(candidate)
if (i + 1) % 10 == 0:
logger.info(f"Проверено фрагментов: {i + 1}/{len(fragments)}")
@@ -146,7 +157,12 @@ def check_plagiarism(
from app.scoring import aggregate_results
result = aggregate_results(
level1_matches, level2_matches, semantic_matches, len(fragments), full_text=text
level1_matches,
level2_matches,
semantic_matches,
len(fragments),
full_text=text,
related_candidates=related_candidates,
)
# Сохранить результат