feat(plagiarism): показывать тематически близкие источники, а не только нарушения
Кандидаты уровня 3 (FAISS), которые прошли порог семантической схожести, но LLM не подтвердила заимствование, раньше молча отбрасывались. Теперь это отдельный блок "recommendations" в отчёте — не плагиат, но источники, полезные для раскрытия темы.
This commit is contained in:
@@ -139,3 +139,56 @@ def test_no_full_text_means_nothing_marked_cited():
|
||||
out = aggregate_results([_m("A", 0)], [], [], total_fragments=2) # full_text не передан
|
||||
assert out["matches"][0]["cited"] is False
|
||||
assert out["overall_similarity"] == out["uncited_similarity"]
|
||||
|
||||
|
||||
# ─── aggregate_results: recommendations ──────────────────────────────────────
|
||||
|
||||
def _cand(title: str, similarity: float, url: str | None = None) -> dict:
|
||||
return {
|
||||
"fragment": "фрагмент",
|
||||
"position_start": 0,
|
||||
"position_end": 8,
|
||||
"similarity": similarity,
|
||||
"source_title": title,
|
||||
"source_url": url,
|
||||
"source_db": "openalex",
|
||||
}
|
||||
|
||||
|
||||
def test_no_related_candidates_means_empty_recommendations():
|
||||
out = aggregate_results([], [], [], total_fragments=10)
|
||||
assert out["recommendations"] == []
|
||||
|
||||
|
||||
def test_related_candidates_become_recommendations_sorted_by_similarity():
|
||||
out = aggregate_results(
|
||||
[], [], [], total_fragments=10,
|
||||
related_candidates=[_cand("Low", 60.0), _cand("High", 90.0)],
|
||||
)
|
||||
titles = [r["source_title"] for r in out["recommendations"]]
|
||||
assert titles == ["High", "Low"]
|
||||
|
||||
|
||||
def test_recommendations_dedup_keeps_best_per_source():
|
||||
out = aggregate_results(
|
||||
[], [], [], total_fragments=10,
|
||||
related_candidates=[_cand("A", 60.0, "url-a"), _cand("A", 85.0, "url-a")],
|
||||
)
|
||||
assert len(out["recommendations"]) == 1
|
||||
assert out["recommendations"][0]["similarity"] == 85.0
|
||||
|
||||
|
||||
def test_recommendations_capped_at_ten():
|
||||
candidates = [_cand(f"S{i}", float(i), f"url-{i}") for i in range(15)]
|
||||
out = aggregate_results([], [], [], total_fragments=10, related_candidates=candidates)
|
||||
assert len(out["recommendations"]) == 10
|
||||
|
||||
|
||||
def test_source_already_flagged_as_match_excluded_from_recommendations():
|
||||
semantic = [{**_cand("A", 90.0, "url-a"), "method": "semantic+llm"}]
|
||||
out = aggregate_results(
|
||||
[], [], semantic, total_fragments=10,
|
||||
related_candidates=[_cand("A", 60.0, "url-a"), _cand("B", 70.0, "url-b")],
|
||||
)
|
||||
titles = [r["source_title"] for r in out["recommendations"]]
|
||||
assert titles == ["B"]
|
||||
|
||||
Reference in New Issue
Block a user