Files
anti-plagiarism/services/worker-gpu/tests/test_scoring.py
jze9 4c15f11efa refactor(gpu): вынести скоринг плагиата в чистый app.scoring + 8 тестов
Итоговый процент схожести (тот, что видит студент) и дедупликация совпадений
жили внутри Celery-задачи check_plagiarism и не тестировались. Вынес в чистую
функцию app.scoring.aggregate_results (без Celery/БД/сети) — правильная «высота»
(доменная логика отдельно от оркестрации) и теперь покрыто тестами:

- процент = доля УНИКАЛЬНЫХ помеченных позиций от всех фрагментов;
- одна позиция с несколькими источниками считается один раз (не раздувает %);
- точный дубль (source_title:pos) дедуплицируется;
- деление на ноль фрагментов; кап на 100%; округление до 2 знаков; by_method.

Поведение сохранено 1:1 (лог задачи переведён на result[...]). scoring.py добавлен
в mypy-гейт. Тестов всего: 61 (indexer 19, gost 17, gpu 25).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 20:13:27 +05:00

70 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Юнит-тесты скоринга плагиата (итоговый процент, который видит студент)."""
from app.scoring import aggregate_results
def _m(title: str, pos: int) -> dict:
return {"source_title": title, "position_start": pos}
def test_empty_input_is_zero():
out = aggregate_results([], [], [], total_fragments=10)
assert out["overall_similarity"] == 0.0
assert out["flagged_fragments"] == 0
assert out["matches"] == []
assert out["by_method"] == {"exact": 0, "fuzzy": 0, "semantic_llm": 0}
def test_zero_fragments_does_not_divide_by_zero():
out = aggregate_results([_m("A", 0)], [], [], total_fragments=0)
assert out["overall_similarity"] == 0.0
def test_percentage_is_share_of_flagged_positions():
# 2 из 4 фрагментов помечены → 50%
level1 = [_m("A", 0), _m("B", 5)]
out = aggregate_results(level1, [], [], total_fragments=4)
assert out["overall_similarity"] == 50.0
assert out["flagged_fragments"] == 2
def test_same_position_multiple_sources_counts_once():
# Одна позиция совпала с двумя разными источниками — доля не раздувается
level1 = [_m("A", 0)]
semantic = [_m("B", 0)] # та же позиция 0, другой источник
out = aggregate_results(level1, [], semantic, total_fragments=2)
assert out["flagged_fragments"] == 1
assert out["overall_similarity"] == 50.0
# оба совпадения сохранены в matches (дедуп только по паре title:pos)
assert len(out["matches"]) == 2
def test_exact_duplicate_match_is_deduplicated():
# Один и тот же источник в одной позиции пришёл дважды — остаётся один
dup = _m("A", 3)
out = aggregate_results([dict(dup)], [dict(dup)], [], total_fragments=5)
assert len(out["matches"]) == 1
def test_similarity_capped_at_100():
# Больше уникальных позиций, чем фрагментов (пограничный случай) → не выше 100%
matches = [_m(f"S{i}", i) for i in range(5)]
out = aggregate_results(matches, [], [], total_fragments=3)
assert out["overall_similarity"] == 100.0
def test_by_method_counts_raw_matches_per_level():
out = aggregate_results(
[_m("A", 0), _m("B", 1)], # exact = 2
[_m("C", 2)], # fuzzy = 1
[_m("D", 3), _m("E", 4), _m("F", 5)], # semantic_llm = 3
total_fragments=10,
)
assert out["by_method"] == {"exact": 2, "fuzzy": 1, "semantic_llm": 3}
def test_percentage_rounded_to_two_decimals():
# 1 из 3 → 33.333... → 33.33
out = aggregate_results([_m("A", 0)], [], [], total_fragments=3)
assert out["overall_similarity"] == 33.33