-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathtest_score.py
More file actions
125 lines (96 loc) · 8.36 KB
/
Copy pathtest_score.py
File metadata and controls
125 lines (96 loc) · 8.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env python3
"""Регресс-тесты score чистоты. Без pytest: чистые assert, запуск
`python scripts/test_score.py`.
Фиксируют калибровку, чтобы будущая правка весов не сломала разделение тихо:
- AI-текст и человеческий текст уверенно разделяются по score;
- плотный энциклопедический человеческий текст (много существительных, тире)
НЕ проваливается в «рерайт» из-за слабых сигналов (морфология/тире);
- полосы (чисто/правка/рерайт) соответствуют числу.
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "skills" / "humanizer-ru" / "scripts"))
from humanizer_metrics import analyze, cleanliness_score
passed = 0
def check(cond: bool, msg: str) -> None:
global passed
assert cond, f"FAIL: {msg}"
passed += 1
def score(text: str) -> int:
return cleanliness_score(analyze(text)).score
def band(text: str) -> str:
return cleanliness_score(analyze(text)).band
# --- AI и человек уверенно разделяются -----------------------------------
ai = ("В современном мире технология является ключевым инструментом. Стоит "
"отметить, что данный подход не просто меняет рынок, а открывает новые "
"горизонты. Таким образом, комплексное решение выводит бизнес на новый уровень.")
human = ("Я попробовал три раза. Не вышло. Потом понял, в чём дело: забыл про "
"кэш. Сбросил — заработало с первого раза.")
check(score(ai) < score(human), "score у AI должен быть ниже, чем у человека")
check(score(ai) < 40, f"насыщенный AI-текст должен быть низким, получено {score(ai)}")
check(score(human) >= 85, f"чистый человеческий текст ≥85, получено {score(human)}")
# --- плотный энциклопедический текст не должен проваливаться в рерайт ------
# Много существительных и тире, но без AI-оборотов: слабые сигналы не топят.
enc = ("Байкал — озеро тектонического происхождения на юге Восточной Сибири. "
"Площадь поверхности составляет около тридцати одной тысячи квадратных "
"километров. Максимальная глубина достигает тысячи шестисот сорока двух "
"метров. Озеро содержит крупнейший запас пресной воды на планете.")
check(band(enc) != "рерайт",
f"плотный энциклопедический текст без AI-оборотов не должен требовать рерайта, "
f"получено {score(enc)} [{band(enc)}]")
# --- полосы соответствуют числу ------------------------------------------
for text in (ai, human, enc):
s = score(text)
b = band(text)
expect = "чисто" if s >= 85 else ("правка" if s >= 60 else "рерайт")
check(b == expect, f"полоса {b} не совпадает со score {s} (ожидалось {expect})")
# --- score всегда в диапазоне 0..100 -------------------------------------
for text in (ai, human, enc, "", "одно слово"):
s = score(text)
check(0 <= s <= 100, f"score вне диапазона: {s}")
# --- document-level метрики (S1 листикл, S5 ровные абзацы) ----------------
def penalty_reasons(text: str) -> str:
return " | ".join(r for r, _ in cleanliness_score(analyze(text)).penalties)
# Листикл: 8 однотипных пунктов должны дать штраф «листикл».
listicle = "Преимущества\n\n" + "\n".join(f"- Пункт номер {i} про качество" for i in range(1, 9)) + "\n"
check("листикл" in penalty_reasons(listicle), "листикл из 8 пунктов должен штрафоваться")
# Ровные абзацы: 6 одинаковых по длине абзацев дают штраф «ровные абзацы».
para = "Это первое предложение абзаца. Это второе предложение абзаца."
uniform = "\n\n".join([para] * 6)
check("ровные абзацы" in penalty_reasons(uniform), "6 ровных абзацев должны штрафоваться (S5)")
# Инертность к короткой прозе: ни листикла, ни ровных абзацев на 1-2 абзацах без списков.
short_prose = "Я попробовал три раза. Не вышло. Потом понял: забыл про кэш."
check("листикл" not in penalty_reasons(short_prose), "короткая проза без списков не листикл")
check("ровные абзацы" not in penalty_reasons(short_prose), "по короткой прозе абзацы не судим")
# Меньше порога пунктов списка (3) — не листикл.
small_list = "Список\n\n- раз\n- два\n- три\n"
check("листикл" not in penalty_reasons(small_list), "3 пункта не дотягивают до порога листикла")
# Одиночное «является» — мягкий маркер, не хард-бан (порог >1/500 слов из SKILL.md).
single_yavl = ("Байкал является глубочайшим озером планеты. Вода прозрачная, "
"видно на сорок метров. Зимой лёд звенит под ногами.")
check("хард-баны" not in penalty_reasons(single_yavl),
"одиночное «является» не должно штрафоваться как хард-бан")
# --- фикстура: художественная проза не должна ложно штрафоваться -----------
# roshchin_pastiche.txt — стилизация под Толстого (провенанс неизвестен, см.
# eval/corpus/ATTRIBUTION.md). Стресс-кейс ложных срабатываний строкового
# детектора: омоним «таким образом, что» (образ действия, НЕ вводный вывод) и
# литературное тире. Умные метрики (рваный ритм, разные по длине абзацы) обязаны
# читать текст как человеческий. Был 82 [правка] из-за ложного бана на омониме,
# стал 94 [чисто] после сужения бана.
from humanizer_metrics.markers import scan_hard_bans # noqa: E402
_FIXTURE = (Path(__file__).resolve().parent.parent
/ "eval" / "corpus" / "literary" / "roshchin_pastiche.txt")
lit = _FIXTURE.read_text(encoding="utf-8")
lit_bans = {h.marker for h in scan_hard_bans(lit)}
check("Подводя итог / Таким образом" not in lit_bans,
"омоним «таким образом, что» в прозе не должен идти под HARD BAN")
check(band(lit) == "чисто",
f"художественная проза должна быть «чисто», получено {score(lit)} [{band(lit)}]")
check(score(lit) >= 88, f"проза не должна штрафоваться ниже 88, получено {score(lit)}")
check(analyze(lit).rhythm.cv_len >= 0.45,
"у живой прозы ритм рваный (CV высокий) — умная метрика читает человека")
check("ровные абзацы" not in penalty_reasons(lit),
"разные по длине абзацы прозы не должны штрафоваться (S5 молчит)")
if __name__ == "__main__":
print(f"OK — {passed} проверок прошли.")