Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -909,35 +909,56 @@ def reset_button_text_2():
|
|
909 |
|
910 |
def check_source_fields(description, product_name, benefits, key_message):
|
911 |
results = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
912 |
|
913 |
# Проверяем "Описание предложения"
|
914 |
desc_checks = perform_checks(description, "")
|
915 |
-
not_passed_desc = extract_failed_checks(desc_checks)
|
916 |
if not_passed_desc:
|
917 |
results.append(f"Описание предложения:\n{not_passed_desc}")
|
918 |
|
919 |
# Проверяем "Наименование продукта"
|
920 |
name_checks = perform_checks(product_name, "")
|
921 |
-
not_passed_name = extract_failed_checks(name_checks)
|
922 |
if not_passed_name:
|
923 |
results.append(f"Наименование продукта:\n{not_passed_name}")
|
924 |
|
925 |
# Проверяем "Преимущества"
|
926 |
ben_checks = perform_checks(benefits, "")
|
927 |
-
not_passed_ben = extract_failed_checks(ben_checks)
|
928 |
if not_passed_ben:
|
929 |
results.append(f"Преимущества:\n{not_passed_ben}")
|
930 |
|
931 |
# Проверяем "Ключевое сообщение"
|
932 |
km_checks = perform_checks(key_message, "")
|
933 |
-
not_passed_km = extract_failed_checks(km_checks)
|
934 |
if not_passed_km:
|
935 |
results.append(f"Ключевое сообщение:\n{not_passed_km}")
|
936 |
|
937 |
if not results:
|
938 |
-
return "Проверка исходных данных пройдена"
|
939 |
else:
|
940 |
-
|
|
|
941 |
|
942 |
|
943 |
def on_check_source_fields(description, product_name, benefits, key_message):
|
@@ -948,7 +969,16 @@ def on_check_source_fields(description, product_name, benefits, key_message):
|
|
948 |
|
949 |
|
950 |
def extract_failed_checks(checks_dict):
|
|
|
|
|
|
|
951 |
lines = []
|
|
|
|
|
|
|
|
|
|
|
|
|
952 |
for rule_key, result in checks_dict.items():
|
953 |
# Определяем, было ли нарушение
|
954 |
if isinstance(result, tuple):
|
@@ -989,12 +1019,29 @@ def rule_to_str(rule_key):
|
|
989 |
|
990 |
# ФУНКЦИИ ПРОВЕРОК (НАЧАЛО)
|
991 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
992 |
# 1. Запрещенные слова
|
993 |
|
994 |
-
def check_forbidden_words(message):
|
995 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
996 |
|
997 |
-
|
998 |
forbidden_patterns = [
|
999 |
r'№\s?1\b', r'номер\sодин\b', r'номер\s1\b',
|
1000 |
r'вкусный', r'дешёвый', r'продукт',
|
@@ -1003,169 +1050,233 @@ def check_forbidden_words(message):
|
|
1003 |
r'гарантия', r'успех', r'лидер', 'никакой'
|
1004 |
]
|
1005 |
|
1006 |
-
# Удаляем
|
1007 |
-
|
1008 |
|
1009 |
-
#
|
1010 |
placeholder = "заменабессроч"
|
1011 |
-
|
1012 |
-
flags=re.IGNORECASE)
|
1013 |
-
|
1014 |
-
# Проверка на наличие подстроки "лучш" (без учета регистра)
|
1015 |
-
if re.search(r'лучш', message_without_punctuation, re.IGNORECASE):
|
1016 |
-
return (False, 'Есть слово "лучший"')
|
1017 |
-
|
1018 |
-
# Лемматизация слов сообщения
|
1019 |
-
words = message_without_punctuation.split()
|
1020 |
-
lemmas = [morph.parse(word)[0].normal_form for word in words]
|
1021 |
|
1022 |
-
#
|
1023 |
-
|
1024 |
-
|
|
|
|
|
1025 |
|
1026 |
-
#
|
1027 |
for pattern in forbidden_patterns:
|
1028 |
-
|
1029 |
-
|
1030 |
-
|
|
|
|
|
|
|
|
|
|
|
1031 |
|
1032 |
return True
|
1033 |
|
1034 |
|
1035 |
# 2 и #3. Обращение к клиенту и приветствие клиента
|
1036 |
|
1037 |
-
def check_no_greeting(message):
|
1038 |
-
|
1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1040 |
greeting_patterns = [
|
1041 |
r"привет\b", r"здравствуй", r"добрый\s(день|вечер|утро)",
|
1042 |
r"дорогой\b", r"уважаемый\b", r"дорогая\b", r"уважаемая\b",
|
1043 |
r"господин\b", r"госпожа\b", r"друг\b", r"коллега\b",
|
1044 |
-
r"товарищ\b", r"приятель\b", r"
|
1045 |
]
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
|
1053 |
-
|
|
|
1054 |
return True
|
1055 |
|
1056 |
|
1057 |
# 4. Обещания и гарантии
|
1058 |
|
1059 |
-
def check_no_promises(message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1060 |
morph = pymorphy3.MorphAnalyzer()
|
1061 |
-
|
1062 |
-
"обещать", "обещание", "гарантировать", "обязаться", "обязать", "обязательство", "обязательный"
|
1063 |
-
]
|
1064 |
|
1065 |
words = message.split()
|
1066 |
-
lemmas = [morph.parse(
|
1067 |
|
1068 |
-
for
|
1069 |
-
if
|
1070 |
-
|
1071 |
-
|
1072 |
return True
|
1073 |
|
1074 |
|
1075 |
# 5. Составные конструкции из двух глаголов
|
1076 |
|
1077 |
-
def check_no_double_verbs(message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1078 |
morph = pymorphy3.MorphAnalyzer()
|
1079 |
-
# Разделяем текст по пробелам и знакам препинания
|
1080 |
words = re.split(r'\s+|[.!?]', message)
|
1081 |
-
|
1082 |
-
|
1083 |
-
|
1084 |
-
|
1085 |
-
|
1086 |
-
|
1087 |
-
|
1088 |
-
|
1089 |
-
|
1090 |
-
|
1091 |
-
|
|
|
|
|
|
|
|
|
|
|
1092 |
return True
|
1093 |
|
1094 |
|
1095 |
# 6. Причастия и причастные обороты
|
1096 |
|
1097 |
-
def check_no_participles(message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1098 |
morph = pymorphy3.MorphAnalyzer()
|
1099 |
words = message.split()
|
1100 |
-
|
1101 |
-
|
1102 |
-
|
1103 |
-
|
1104 |
-
|
1105 |
-
|
1106 |
-
|
1107 |
-
|
1108 |
return True
|
1109 |
|
1110 |
|
1111 |
# 7. Деепричастия и деепричастные обороты
|
1112 |
|
1113 |
-
def check_no_adverbial_participles(message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1114 |
morph = pymorphy3.MorphAnalyzer()
|
1115 |
words = message.split()
|
1116 |
-
|
1117 |
-
|
1118 |
-
|
1119 |
-
if
|
1120 |
-
|
1121 |
-
|
1122 |
return True
|
1123 |
|
1124 |
|
1125 |
# 8. Превосходная степень прилагательных
|
1126 |
|
1127 |
-
def check_no_superlative_adjectives(message):
|
1128 |
-
|
1129 |
-
|
1130 |
-
|
|
|
|
|
|
|
|
|
1131 |
|
1132 |
-
|
1133 |
-
|
1134 |
-
|
1135 |
-
|
|
|
|
|
|
|
1136 |
return True
|
1137 |
|
1138 |
|
1139 |
# 9. Страдательный залог
|
1140 |
|
1141 |
-
def check_no_passive_voice(message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1142 |
morph = pymorphy3.MorphAnalyzer()
|
1143 |
-
# Разбиваем сообщение на слова, игнорируя пунктуацию
|
1144 |
words = re.findall(r'\b\w+(?:-\w+)*\b', message.lower())
|
1145 |
|
1146 |
-
for
|
1147 |
-
|
1148 |
-
|
1149 |
-
|
|
|
|
|
1150 |
return True
|
1151 |
|
1152 |
|
1153 |
# 10. Порядковые числительные от 10 прописью
|
1154 |
|
1155 |
-
def check_no_written_out_ordinals(message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1156 |
morph = pymorphy3.MorphAnalyzer()
|
1157 |
ordinal_words = [
|
1158 |
-
"десятый", "одиннадцатый", "двенадцатый", "тринадцатый",
|
1159 |
-
"
|
|
|
1160 |
]
|
1161 |
-
|
1162 |
-
|
1163 |
-
|
1164 |
-
|
1165 |
-
|
1166 |
-
|
1167 |
-
print(f"Не пройдена проверка: Порядковые числительные от 10 прописью. Сообщение: {message}")
|
1168 |
-
return False, f'Не пройдена проверка на порядковые числительные: {word}'
|
1169 |
return True
|
1170 |
|
1171 |
|
@@ -1196,99 +1307,152 @@ def check_no_subordinate_clauses_chain(message):
|
|
1196 |
|
1197 |
# 12. Разделительные повторяющиеся союзы
|
1198 |
|
1199 |
-
def check_no_repeating_conjunctions(message):
|
1200 |
-
|
1201 |
-
|
|
|
|
|
|
|
|
|
|
|
1202 |
|
1203 |
-
|
1204 |
sentences = re.split(r'[.!?]\s*', message)
|
1205 |
-
|
1206 |
-
|
1207 |
-
|
1208 |
-
|
1209 |
-
|
1210 |
-
|
1211 |
return True
|
1212 |
|
1213 |
|
1214 |
# 13. Вводные конструкции
|
1215 |
|
1216 |
-
def check_no_introductory_phrases(message):
|
1217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1218 |
r'\b(во-первых|во-вторых|с одной стороны|по сути|по правде говоря)\b',
|
1219 |
r'\b(может быть|кстати|конечно|естественно|безусловно|возможно)\b'
|
1220 |
]
|
1221 |
-
|
1222 |
-
|
1223 |
-
if
|
1224 |
-
|
1225 |
-
|
|
|
1226 |
return True
|
1227 |
|
1228 |
|
1229 |
# 14. Усилители
|
1230 |
|
1231 |
-
def check_no_amplifiers(message):
|
1232 |
-
|
1233 |
-
|
1234 |
-
]
|
|
|
|
|
|
|
|
|
1235 |
|
1236 |
-
|
1237 |
-
|
1238 |
-
|
1239 |
-
|
|
|
|
|
|
|
|
|
1240 |
return True
|
1241 |
|
1242 |
# 15. Паразиты времени
|
1243 |
|
1244 |
-
def check_no_time_parasites(message):
|
1245 |
-
|
1246 |
-
|
1247 |
-
]
|
|
|
|
|
|
|
|
|
1248 |
|
1249 |
-
|
1250 |
-
|
1251 |
-
|
1252 |
-
|
|
|
|
|
|
|
|
|
1253 |
return True
|
1254 |
|
1255 |
|
1256 |
# 16. Несколько существительных подряд
|
1257 |
|
1258 |
-
def check_no_multiple_nouns(message):
|
1259 |
-
|
1260 |
-
|
1261 |
-
|
|
|
|
|
|
|
|
|
1262 |
|
1263 |
-
|
1264 |
-
|
|
|
|
|
1265 |
|
1266 |
-
|
1267 |
-
|
1268 |
-
|
1269 |
-
|
1270 |
-
|
1271 |
-
|
|
|
|
|
|
|
1272 |
else:
|
1273 |
-
|
|
|
1274 |
|
1275 |
-
if
|
1276 |
-
|
1277 |
-
|
|
|
1278 |
return True
|
1279 |
|
1280 |
|
1281 |
# 17. Производные предлоги
|
1282 |
|
1283 |
-
def check_no_derived_prepositions(message):
|
1284 |
-
|
1285 |
-
|
1286 |
-
]
|
1287 |
-
|
1288 |
-
|
1289 |
-
|
1290 |
-
|
1291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1292 |
return True
|
1293 |
|
1294 |
|
@@ -1312,84 +1476,78 @@ def check_no_compound_sentences(message):
|
|
1312 |
|
1313 |
# 20. Даты прописью
|
1314 |
|
1315 |
-
def check_no_dates_written_out(message):
|
1316 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1317 |
months = [
|
1318 |
"января", "февраля", "марта", "апреля", "мая", "июня",
|
1319 |
"июля", "августа", "сентября", "октября", "ноября", "декабря"
|
1320 |
]
|
1321 |
-
|
1322 |
-
|
1323 |
-
|
1324 |
-
r'
|
|
|
|
|
|
|
|
|
|
|
1325 |
]
|
1326 |
|
1327 |
-
for
|
1328 |
-
for
|
1329 |
-
|
1330 |
-
|
1331 |
-
|
1332 |
-
|
|
|
|
|
|
|
|
|
1333 |
return True
|
1334 |
|
1335 |
# Доп правило. Повторы слов
|
1336 |
|
1337 |
-
def check_no_word_repetitions(message, key_message):
|
1338 |
-
|
1339 |
-
|
1340 |
-
|
1341 |
-
|
1342 |
-
|
1343 |
-
|
1344 |
-
|
1345 |
-
'INTJ', # Междометия
|
1346 |
-
'NUMR', # Числительные
|
1347 |
-
'PART', # Частицы
|
1348 |
-
'NPRO'
|
1349 |
-
}
|
1350 |
|
1351 |
-
|
1352 |
-
|
1353 |
|
1354 |
-
|
1355 |
-
def normalize_word(word):
|
1356 |
-
parses = morph.parse(word)
|
1357 |
-
if not parses:
|
1358 |
-
return word # Если слово не распознано, возвращаем как есть
|
1359 |
-
parse = parses[0]
|
1360 |
-
return parse.normal_form, parse.tag.POS
|
1361 |
|
1362 |
-
#
|
1363 |
key_normalized = set()
|
1364 |
-
for
|
1365 |
-
|
1366 |
-
key_normalized.add(
|
1367 |
-
|
1368 |
-
|
1369 |
-
|
1370 |
-
|
1371 |
-
|
1372 |
-
|
1373 |
-
for word in words:
|
1374 |
-
norm, pos = normalize_word(word)
|
1375 |
-
|
1376 |
-
# Игнорируем слово, если оно относится к одной из игнорируемых частей речи
|
1377 |
-
if pos in ignore_pos:
|
1378 |
continue
|
1379 |
-
|
1380 |
-
# Игнорируем слово, если оно присутствует в ключевом сообщении
|
1381 |
-
if norm in key_normalized:
|
1382 |
continue
|
1383 |
-
|
1384 |
-
|
1385 |
-
if
|
1386 |
-
|
1387 |
-
|
1388 |
-
|
1389 |
-
# Добавляем слово в словарь для отслеживания повторов
|
1390 |
-
normalized_words[norm] = True
|
1391 |
-
|
1392 |
-
# Если повторов не найдено, возвращаем True
|
1393 |
return True
|
1394 |
|
1395 |
# ФУНКЦИИ ПРОВЕРОК (КОНЕЦ)
|
|
|
909 |
|
910 |
def check_source_fields(description, product_name, benefits, key_message):
|
911 |
results = []
|
912 |
+
exceptions_dict = {
|
913 |
+
"forbidden_words": set(),
|
914 |
+
"greetings": set(),
|
915 |
+
"promises": set(),
|
916 |
+
"double_verbs": set(),
|
917 |
+
"participles": set(),
|
918 |
+
"adverbial_participles": set(),
|
919 |
+
"superlative_adjectives": set(),
|
920 |
+
"passive_voice": set(),
|
921 |
+
"written_out_ordinals": set(),
|
922 |
+
"repeating_conjunctions": set(),
|
923 |
+
"introductory_phrases": set(),
|
924 |
+
"amplifiers": set(),
|
925 |
+
"time_parasites": set(),
|
926 |
+
"multiple_nouns": set(),
|
927 |
+
"derived_prepositions": set(),
|
928 |
+
"compound_sentences": set(),
|
929 |
+
"dates_written_out": set(),
|
930 |
+
"word_repetitions": set()
|
931 |
+
}
|
932 |
|
933 |
# Проверяем "Описание предложения"
|
934 |
desc_checks = perform_checks(description, "")
|
935 |
+
not_passed_desc = extract_failed_checks(desc_checks, exceptions_dict, context="Описание предложения")
|
936 |
if not_passed_desc:
|
937 |
results.append(f"Описание предложения:\n{not_passed_desc}")
|
938 |
|
939 |
# Проверяем "Наименование продукта"
|
940 |
name_checks = perform_checks(product_name, "")
|
941 |
+
not_passed_name = extract_failed_checks(name_checks, exceptions_dict, context="Наименование продукта")
|
942 |
if not_passed_name:
|
943 |
results.append(f"Наименование продукта:\n{not_passed_name}")
|
944 |
|
945 |
# Проверяем "Преимущества"
|
946 |
ben_checks = perform_checks(benefits, "")
|
947 |
+
not_passed_ben = extract_failed_checks(ben_checks, exceptions_dict, context="Преимущества")
|
948 |
if not_passed_ben:
|
949 |
results.append(f"Преимущества:\n{not_passed_ben}")
|
950 |
|
951 |
# Проверяем "Ключевое сообщение"
|
952 |
km_checks = perform_checks(key_message, "")
|
953 |
+
not_passed_km = extract_failed_checks(km_checks, exceptions_dict, context="Ключевое сообщение")
|
954 |
if not_passed_km:
|
955 |
results.append(f"Ключевое сообщение:\n{not_passed_km}")
|
956 |
|
957 |
if not results:
|
958 |
+
return "Проверка исходных данных пройдена", exceptions_dict
|
959 |
else:
|
960 |
+
report = "\n\n".join(results)
|
961 |
+
return report, exceptions_dict
|
962 |
|
963 |
|
964 |
def on_check_source_fields(description, product_name, benefits, key_message):
|
|
|
969 |
|
970 |
|
971 |
def extract_failed_checks(checks_dict):
|
972 |
+
|
973 |
+
morph = pymorphy3.MorphAnalyzer()
|
974 |
+
|
975 |
lines = []
|
976 |
+
|
977 |
+
def lemma_pair(word1, word2):
|
978 |
+
p1 = morph.parse(word1)[0].normal_form
|
979 |
+
p2 = morph.parse(word2)[0].normal_form
|
980 |
+
return (p1, p2)
|
981 |
+
|
982 |
for rule_key, result in checks_dict.items():
|
983 |
# Определяем, было ли нарушение
|
984 |
if isinstance(result, tuple):
|
|
|
1019 |
|
1020 |
# ФУНКЦИИ ПРОВЕРОК (НАЧАЛО)
|
1021 |
|
1022 |
+
def lemmatize_word(word, morph):
|
1023 |
+
"""
|
1024 |
+
Возвращает (lemma, POS) для переданного слова.
|
1025 |
+
"""
|
1026 |
+
parsed = morph.parse(word)
|
1027 |
+
if not parsed:
|
1028 |
+
return word, None
|
1029 |
+
best = parsed[0]
|
1030 |
+
return best.normal_form, best.tag.POS
|
1031 |
+
|
1032 |
# 1. Запрещенные слова
|
1033 |
|
1034 |
+
def check_forbidden_words(message, exceptions=None):
|
1035 |
+
"""
|
1036 |
+
Проверка на запрещённые слова.
|
1037 |
+
Если лемма «запрещённого слова» находится в exceptions['forbidden_words'],
|
1038 |
+
то пропускаем.
|
1039 |
+
"""
|
1040 |
+
if exceptions is None:
|
1041 |
+
exceptions = {}
|
1042 |
+
allowed_lemmas = exceptions.get("forbidden_words", set())
|
1043 |
|
1044 |
+
morph = pymorphy3.MorphAnalyzer()
|
1045 |
forbidden_patterns = [
|
1046 |
r'№\s?1\b', r'номер\sодин\b', r'номер\s1\b',
|
1047 |
r'вкусный', r'дешёвый', r'продукт',
|
|
|
1050 |
r'гарантия', r'успех', r'лидер', 'никакой'
|
1051 |
]
|
1052 |
|
1053 |
+
# Удаляем пунктуацию
|
1054 |
+
message_no_punct = message.translate(str.maketrans('', '', string.punctuation))
|
1055 |
|
1056 |
+
# Пример: «бессроч» => placeholder
|
1057 |
placeholder = "заменабессроч"
|
1058 |
+
message_no_punct = re.sub(r'\b\w*бессроч\w*\b', placeholder, message_no_punct, flags=re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1059 |
|
1060 |
+
# Лемматизируем все слова
|
1061 |
+
words = message_no_punct.split()
|
1062 |
+
lemmas = [morph.parse(w)[0].normal_form for w in words]
|
1063 |
+
lemmas = [re.sub(r'заменабессроч', 'бессроч', l) for l in lemmas]
|
1064 |
+
normalized_msg = ' '.join(lemmas)
|
1065 |
|
1066 |
+
# Для каждого pattern проверяем, нет ли совпадения
|
1067 |
for pattern in forbidden_patterns:
|
1068 |
+
found = re.search(pattern, normalized_msg, re.IGNORECASE)
|
1069 |
+
if found:
|
1070 |
+
# Получим саму найденную строку
|
1071 |
+
matched_str = found.group(0)
|
1072 |
+
# Лемматизируем
|
1073 |
+
lemma_found, _ = lemmatize_word(matched_str, morph)
|
1074 |
+
if lemma_found not in allowed_lemmas:
|
1075 |
+
return False, f"Запрещенное слово: {matched_str}"
|
1076 |
|
1077 |
return True
|
1078 |
|
1079 |
|
1080 |
# 2 и #3. Обращение к клиенту и приветствие клиента
|
1081 |
|
1082 |
+
def check_no_greeting(message, exceptions=None):
|
1083 |
+
"""
|
1084 |
+
Проверка на «приветствия».
|
1085 |
+
Если лемма слова среди exceptions['greetings'], пропускаем.
|
1086 |
+
"""
|
1087 |
+
if exceptions is None:
|
1088 |
+
exceptions = {}
|
1089 |
+
allowed_lemmas = exceptions.get("greetings", set())
|
1090 |
+
|
1091 |
greeting_patterns = [
|
1092 |
r"привет\b", r"здравствуй", r"добрый\s(день|вечер|утро)",
|
1093 |
r"дорогой\b", r"уважаемый\b", r"дорогая\b", r"уважаемая\b",
|
1094 |
r"господин\b", r"госпожа\b", r"друг\b", r"коллега\b",
|
1095 |
+
r"товарищ\b", r"приятель\b", r"подруга\b"
|
1096 |
]
|
1097 |
+
# Будем искать все совпадения паттернов
|
1098 |
+
for pat in greeting_patterns:
|
1099 |
+
match = re.search(pat, message, re.IGNORECASE)
|
1100 |
+
if match:
|
1101 |
+
found = match.group(0).lower() # «дорогая», «привет» и т.п.
|
1102 |
+
morph = pymorphy3.MorphAnalyzer()
|
1103 |
+
lemma, pos = lemmatize_word(found, morph)
|
1104 |
+
if lemma not in allowed_lemmas:
|
1105 |
+
return False, f"Есть приветствие: {found}"
|
1106 |
return True
|
1107 |
|
1108 |
|
1109 |
# 4. Обещания и гарантии
|
1110 |
|
1111 |
+
def check_no_promises(message, exceptions=None):
|
1112 |
+
"""
|
1113 |
+
Проверка на «обещания».
|
1114 |
+
Если lemma слова в exceptions['promises'], то пропускаем.
|
1115 |
+
"""
|
1116 |
+
if exceptions is None:
|
1117 |
+
exceptions = {}
|
1118 |
+
allowed_lemmas = exceptions.get("promises", set())
|
1119 |
+
|
1120 |
morph = pymorphy3.MorphAnalyzer()
|
1121 |
+
patterns = ["обещать", "обещание", "гарантировать", "обязаться", "обязать", "обязательство", "обязательный"]
|
|
|
|
|
1122 |
|
1123 |
words = message.split()
|
1124 |
+
lemmas = [morph.parse(w)[0].normal_form for w in words]
|
1125 |
|
1126 |
+
for patt in patterns:
|
1127 |
+
if patt in lemmas:
|
1128 |
+
if patt not in allowed_lemmas:
|
1129 |
+
return False, f"Не пройдена проверка: обещания => {patt}"
|
1130 |
return True
|
1131 |
|
1132 |
|
1133 |
# 5. Составные конструкции из двух глаголов
|
1134 |
|
1135 |
+
def check_no_double_verbs(message, exceptions=None):
|
1136 |
+
"""
|
1137 |
+
Проверка на 2 подряд глагола.
|
1138 |
+
Если (lemma1, lemma2) находится в exceptions['double_verbs'], то разрешаем.
|
1139 |
+
"""
|
1140 |
+
if exceptions is None:
|
1141 |
+
exceptions = {}
|
1142 |
+
allowed_pairs = exceptions.get("double_verbs", set())
|
1143 |
+
|
1144 |
morph = pymorphy3.MorphAnalyzer()
|
|
|
1145 |
words = re.split(r'\s+|[.!?]', message)
|
1146 |
+
|
1147 |
+
tokens = [w.strip() for w in words if w.strip()]
|
1148 |
+
parses = [morph.parse(tok)[0] for tok in tokens]
|
1149 |
+
|
1150 |
+
for i in range(len(parses) - 1):
|
1151 |
+
if (parses[i].tag.POS in {'VERB', 'INFN'}) and (parses[i+1].tag.POS in {'VERB', 'INFN'}):
|
1152 |
+
lemma1 = parses[i].normal_form
|
1153 |
+
lemma2 = parses[i+1].normal_form
|
1154 |
+
pair = (lemma1, lemma2)
|
1155 |
+
# Если разрешено
|
1156 |
+
if pair in allowed_pairs:
|
1157 |
+
continue
|
1158 |
+
# Если это "хотеть", "начинать", ...
|
1159 |
+
if lemma1 in ["хотеть", "начинать", "начать"]:
|
1160 |
+
continue
|
1161 |
+
return False, f"Не пройдена проверка на 2 глагола подряд: {parses[i].word} {parses[i+1].word}"
|
1162 |
return True
|
1163 |
|
1164 |
|
1165 |
# 6. Причастия и причастные обороты
|
1166 |
|
1167 |
+
def check_no_participles(message, exceptions=None):
|
1168 |
+
"""
|
1169 |
+
Проверка на причастия.
|
1170 |
+
Если lemma причастия в exceptions['participles'], разрешаем.
|
1171 |
+
"""
|
1172 |
+
if exceptions is None:
|
1173 |
+
exceptions = {}
|
1174 |
+
allowed_lemmas = exceptions.get("participles", set())
|
1175 |
+
|
1176 |
+
skip_lemmas = {"повысить", "увеличить", "понизить", "снизить"}
|
1177 |
+
|
1178 |
morph = pymorphy3.MorphAnalyzer()
|
1179 |
words = message.split()
|
1180 |
+
|
1181 |
+
for w in words:
|
1182 |
+
p = morph.parse(w)[0]
|
1183 |
+
lemma = p.normal_form
|
1184 |
+
if 'PRTF' in p.tag:
|
1185 |
+
# Проверяем исключения
|
1186 |
+
if lemma not in skip_lemmas and lemma not in allowed_lemmas:
|
1187 |
+
return False, f"Не пройдена проверка на причастие: {p.word}"
|
1188 |
return True
|
1189 |
|
1190 |
|
1191 |
# 7. Деепричастия и деепричастные обороты
|
1192 |
|
1193 |
+
def check_no_adverbial_participles(message, exceptions=None):
|
1194 |
+
"""
|
1195 |
+
Проверка на деепричастия.
|
1196 |
+
Если lemma в exceptions['adverbial_participles'], то не считае�� нарушением.
|
1197 |
+
"""
|
1198 |
+
if exceptions is None:
|
1199 |
+
exceptions = {}
|
1200 |
+
allowed_lemmas = exceptions.get("adverbial_participles", set())
|
1201 |
+
|
1202 |
morph = pymorphy3.MorphAnalyzer()
|
1203 |
words = message.split()
|
1204 |
+
for w in words:
|
1205 |
+
p = morph.parse(w)[0]
|
1206 |
+
lemma = p.normal_form
|
1207 |
+
if "GRND" in p.tag:
|
1208 |
+
if lemma not in allowed_lemmas:
|
1209 |
+
return False, f"Не пройдена проверка: деепричастие => {p.word}"
|
1210 |
return True
|
1211 |
|
1212 |
|
1213 |
# 8. Превосходная степень прилагательных
|
1214 |
|
1215 |
+
def check_no_superlative_adjectives(message, exceptions=None):
|
1216 |
+
"""
|
1217 |
+
Проверка на превосходную степень прилагательных.
|
1218 |
+
Если lemma прилагательного среди exceptions['superlative_adjectives'], разрешаем.
|
1219 |
+
"""
|
1220 |
+
if exceptions is None:
|
1221 |
+
exceptions = {}
|
1222 |
+
allowed_lemmas = exceptions.get("superlative_adjectives", set())
|
1223 |
|
1224 |
+
morph = pymorphy3.MorphAnalyzer()
|
1225 |
+
for w in message.split():
|
1226 |
+
p = morph.parse(w)[0]
|
1227 |
+
lemma = p.normal_form
|
1228 |
+
if 'Supr' in p.tag:
|
1229 |
+
if lemma not in allowed_lemmas:
|
1230 |
+
return False, f"Не пройдена проверка на превосходную степень: {p.word}"
|
1231 |
return True
|
1232 |
|
1233 |
|
1234 |
# 9. Страдательный залог
|
1235 |
|
1236 |
+
def check_no_passive_voice(message, exceptions=None):
|
1237 |
+
"""
|
1238 |
+
Проверка на страдательный залог.
|
1239 |
+
Если lemma в exceptions['passive_voice'], пропускаем.
|
1240 |
+
"""
|
1241 |
+
if exceptions is None:
|
1242 |
+
exceptions = {}
|
1243 |
+
allowed_lemmas = exceptions.get("passive_voice", set())
|
1244 |
+
|
1245 |
morph = pymorphy3.MorphAnalyzer()
|
|
|
1246 |
words = re.findall(r'\b\w+(?:-\w+)*\b', message.lower())
|
1247 |
|
1248 |
+
for w in words:
|
1249 |
+
p = morph.parse(w)[0]
|
1250 |
+
lemma = p.normal_form
|
1251 |
+
if 'pssv' in p.tag:
|
1252 |
+
if lemma not in allowed_lemmas:
|
1253 |
+
return False, f"Не пройдена проверка на страдательный залог: {w}"
|
1254 |
return True
|
1255 |
|
1256 |
|
1257 |
# 10. Порядковые числительные от 10 прописью
|
1258 |
|
1259 |
+
def check_no_written_out_ordinals(message, exceptions=None):
|
1260 |
+
"""
|
1261 |
+
Проверка на порядковые числительные, написанные прописью (десятый и т.д.).
|
1262 |
+
Если lemma в exceptions['written_out_ordinals'], пропускаем.
|
1263 |
+
"""
|
1264 |
+
if exceptions is None:
|
1265 |
+
exceptions = {}
|
1266 |
+
allowed_lemmas = exceptions.get("written_out_ordinals", set())
|
1267 |
+
|
1268 |
morph = pymorphy3.MorphAnalyzer()
|
1269 |
ordinal_words = [
|
1270 |
+
"десятый", "одиннадцатый", "двенадцатый", "тринадцатый",
|
1271 |
+
"четырнадцатый", "пятнадцатый", "шестнадцатый", "семнадцатый",
|
1272 |
+
"восемнадцатый", "девятнадцатый", "двадцатый"
|
1273 |
]
|
1274 |
+
tokens = message.split()
|
1275 |
+
lemmas = [morph.parse(t)[0].normal_form for t in tokens]
|
1276 |
+
for ow in ordinal_words:
|
1277 |
+
if ow in lemmas:
|
1278 |
+
if ow not in allowed_lemmas:
|
1279 |
+
return False, f"Не пройдена проверка на порядковые числительные: {ow}"
|
|
|
|
|
1280 |
return True
|
1281 |
|
1282 |
|
|
|
1307 |
|
1308 |
# 12. Разделительные повторяющиеся союзы
|
1309 |
|
1310 |
+
def check_no_repeating_conjunctions(message, exceptions=None):
|
1311 |
+
"""
|
1312 |
+
Проверка на повторяющиеся союзы 'и', 'или' и т.п.
|
1313 |
+
Если сам союз (в лемме) в exceptions['repeating_conjunctions'], пропускаем.
|
1314 |
+
"""
|
1315 |
+
if exceptions is None:
|
1316 |
+
exceptions = {}
|
1317 |
+
allowed_conjs = exceptions.get("repeating_conjunctions", set())
|
1318 |
|
1319 |
+
pattern = re.compile(r'\b(и|ни|то|не то|или|либо)\b\s*(.*?)\s*,\s*\b\1\b', re.IGNORECASE)
|
1320 |
sentences = re.split(r'[.!?]\s*', message)
|
1321 |
+
for s in sentences:
|
1322 |
+
m = pattern.search(s)
|
1323 |
+
if m:
|
1324 |
+
conj = m.group(1).lower()
|
1325 |
+
if conj not in allowed_conjs:
|
1326 |
+
return False, f"Не пройдена проверка на повторяющиеся союзы: {s}"
|
1327 |
return True
|
1328 |
|
1329 |
|
1330 |
# 13. Вводные конструкции
|
1331 |
|
1332 |
+
def check_no_introductory_phrases(message, exceptions=None):
|
1333 |
+
"""
|
1334 |
+
Проверка на вводные конструкции.
|
1335 |
+
Если exact фраза в exceptions['introductory_phrases'], пропускаем.
|
1336 |
+
"""
|
1337 |
+
if exceptions is None:
|
1338 |
+
exceptions = {}
|
1339 |
+
allowed_phrases = exceptions.get("introductory_phrases", set())
|
1340 |
+
|
1341 |
+
patterns = [
|
1342 |
r'\b(во-первых|во-вторых|с одной стороны|по сути|по правде говоря)\b',
|
1343 |
r'\b(может быть|кстати|конечно|естественно|безусловно|возможно)\b'
|
1344 |
]
|
1345 |
+
for pat in patterns:
|
1346 |
+
match = re.search(pat, message, re.IGNORECASE)
|
1347 |
+
if match:
|
1348 |
+
found = match.group(1).lower()
|
1349 |
+
if found not in allowed_phrases:
|
1350 |
+
return False, f"Не пройдена проверка на вводные конструкции: {found}"
|
1351 |
return True
|
1352 |
|
1353 |
|
1354 |
# 14. Усилители
|
1355 |
|
1356 |
+
def check_no_amplifiers(message, exceptions=None):
|
1357 |
+
"""
|
1358 |
+
Проверка на усилители (очень, крайне...).
|
1359 |
+
Если лемма в exceptions['amplifiers'], пропускаем.
|
1360 |
+
"""
|
1361 |
+
if exceptions is None:
|
1362 |
+
exceptions = {}
|
1363 |
+
allowed_lemmas = exceptions.get("amplifiers", set())
|
1364 |
|
1365 |
+
pattern = re.compile(r'\b(очень|крайне|чрезвычайно|совсем|полностью|чисто)\b', re.IGNORECASE)
|
1366 |
+
matches = pattern.findall(message)
|
1367 |
+
if matches:
|
1368 |
+
morph = pymorphy3.MorphAnalyzer()
|
1369 |
+
for m in matches:
|
1370 |
+
lemma, _ = lemmatize_word(m, morph)
|
1371 |
+
if lemma not in allowed_lemmas:
|
1372 |
+
return False, f"Не пройдена проверка на усилители: {m}"
|
1373 |
return True
|
1374 |
|
1375 |
# 15. Паразиты времени
|
1376 |
|
1377 |
+
def check_no_time_parasites(message, exceptions=None):
|
1378 |
+
"""
|
1379 |
+
Проверка на «паразиты времени» (немедленно, срочно...).
|
1380 |
+
Если лемма в exceptions['time_parasites'], пропускаем.
|
1381 |
+
"""
|
1382 |
+
if exceptions is None:
|
1383 |
+
exceptions = {}
|
1384 |
+
allowed_lemmas = exceptions.get("time_parasites", set())
|
1385 |
|
1386 |
+
pattern = re.compile(r'\b(немедленно|срочно|в данный момент)\b', re.IGNORECASE)
|
1387 |
+
matches = pattern.findall(message)
|
1388 |
+
if matches:
|
1389 |
+
morph = pymorphy3.MorphAnalyzer()
|
1390 |
+
for m in matches:
|
1391 |
+
lemma, _ = lemmatize_word(m, morph)
|
1392 |
+
if lemma not in allowed_lemmas:
|
1393 |
+
return False, f"Не пройдена проверка на паразитов времени: {m}"
|
1394 |
return True
|
1395 |
|
1396 |
|
1397 |
# 16. Несколько существительных подряд
|
1398 |
|
1399 |
+
def check_no_multiple_nouns(message, exceptions=None):
|
1400 |
+
"""
|
1401 |
+
Проверка на 3+ подряд существительных.
|
1402 |
+
Если конкретная цепочка лемм в exceptions['multiple_nouns'], пропускаем.
|
1403 |
+
"""
|
1404 |
+
if exceptions is None:
|
1405 |
+
exceptions = {}
|
1406 |
+
allowed_chains = exceptions.get("multiple_nouns", set()) # set of tuples
|
1407 |
|
1408 |
+
morph = pymorphy3.MorphAnalyzer()
|
1409 |
+
tokens = re.split(r'\s+|[.!?]', message)
|
1410 |
+
chain = []
|
1411 |
+
count = 0
|
1412 |
|
1413 |
+
for t in tokens:
|
1414 |
+
t = t.strip()
|
1415 |
+
if not t:
|
1416 |
+
continue
|
1417 |
+
p = morph.parse(t)[0]
|
1418 |
+
lemma = p.normal_form
|
1419 |
+
if 'NOUN' in p.tag:
|
1420 |
+
count += 1
|
1421 |
+
chain.append(lemma)
|
1422 |
else:
|
1423 |
+
count = 0
|
1424 |
+
chain = []
|
1425 |
|
1426 |
+
if count > 2:
|
1427 |
+
chain_tuple = tuple(chain) # например ('зачисление', 'зарплата', 'сотрудникам')
|
1428 |
+
if chain_tuple not in allowed_chains:
|
1429 |
+
return False, f"Несколько существительных подряд: {chain_tuple}"
|
1430 |
return True
|
1431 |
|
1432 |
|
1433 |
# 17. Производные предлоги
|
1434 |
|
1435 |
+
def check_no_derived_prepositions(message, exceptions=None):
|
1436 |
+
"""
|
1437 |
+
Проверка на производные предлоги.
|
1438 |
+
Если конкретный предлог в exceptions['derived_prepositions'], пропускаем.
|
1439 |
+
"""
|
1440 |
+
if exceptions is None:
|
1441 |
+
exceptions = {}
|
1442 |
+
allowed_preps = exceptions.get("derived_prepositions", set())
|
1443 |
+
|
1444 |
+
pattern_text = (r'\b(в течение|в ходе|вследствие|в связи с|по мере|при помощи|'
|
1445 |
+
r'согласно|вопреки|на основании|на случай|в продолжение|по причине|'
|
1446 |
+
r'вблизи|вдалеке|вокруг|внутри|вдоль|посередине|вне|снаружи|'
|
1447 |
+
r'благодаря|невзирая на|исходя из|благодаря)\b')
|
1448 |
+
pat = re.compile(pattern_text, re.IGNORECASE)
|
1449 |
+
|
1450 |
+
matches = pat.findall(message)
|
1451 |
+
if matches:
|
1452 |
+
for m in matches:
|
1453 |
+
low = m.lower()
|
1454 |
+
if low not in allowed_preps:
|
1455 |
+
return False, f"Не пройдена проверка на производные предлоги: {m}"
|
1456 |
return True
|
1457 |
|
1458 |
|
|
|
1476 |
|
1477 |
# 20. Даты прописью
|
1478 |
|
1479 |
+
def check_no_dates_written_out(message, exceptions=None):
|
1480 |
+
"""
|
1481 |
+
Проверка на даты прописью.
|
1482 |
+
Если (lemma_ordinal, lemma_month) в exceptions['dates_written_out'], пропускаем.
|
1483 |
+
"""
|
1484 |
+
if exceptions is None:
|
1485 |
+
exceptions = {}
|
1486 |
+
allowed_dates = exceptions.get("dates_written_out", set())
|
1487 |
+
|
1488 |
+
morph = pymorphy3.MorphAnalyzer()
|
1489 |
+
|
1490 |
months = [
|
1491 |
"января", "февраля", "марта", "апреля", "мая", "июня",
|
1492 |
"июля", "августа", "сентября", "октября", "ноября", "декабря"
|
1493 |
]
|
1494 |
+
date_patterns = [
|
1495 |
+
r'\b(первого|второго|третьего|четвертого|пятого|шестого|седьмого|'
|
1496 |
+
r'восьмого|девятого|десятого|одиннадцатого|двенадцатого|'
|
1497 |
+
r'тринадцатого|четырнадцатого|пятнадцатого|шестнадцатого|'
|
1498 |
+
r'семнадцатого|восемнадцатого|девятнадцатого|двадцатого|'
|
1499 |
+
r'двадцать первого|двадцать второго|двадцать третьего|'
|
1500 |
+
r'двадцать четвертого|двадцать пятого|двадцать шестого|'
|
1501 |
+
r'двадцать седьмого|двадцать восьмого|двадцать девятого|'
|
1502 |
+
r'тридцатого|тридцать первого)\b'
|
1503 |
]
|
1504 |
|
1505 |
+
for m in months:
|
1506 |
+
for patt in date_patterns:
|
1507 |
+
found = re.search(f"{patt}\\s{m}", message, re.IGNORECASE)
|
1508 |
+
if found:
|
1509 |
+
ordinal_str = found.group(1).lower() # например «пятнадцатого»
|
1510 |
+
lemma_ord, _ = lemmatize_word(ordinal_str, morph)
|
1511 |
+
lemma_month, _ = lemmatize_word(m, morph)
|
1512 |
+
pair = (lemma_ord, lemma_month) # («пятнадцатый», «июль»)
|
1513 |
+
if pair not in allowed_dates:
|
1514 |
+
return False, f"Не пройдена проверка на даты прописью: {found.group(0)}"
|
1515 |
return True
|
1516 |
|
1517 |
# Доп правило. Повторы слов
|
1518 |
|
1519 |
+
def check_no_word_repetitions(message, key_message, exceptions=None):
|
1520 |
+
"""
|
1521 |
+
Проверка на повторы слов (кроме определённых частей речи).
|
1522 |
+
Если lemma есть в exceptions['word_repetitions'], пропускаем.
|
1523 |
+
"""
|
1524 |
+
if exceptions is None:
|
1525 |
+
exceptions = {}
|
1526 |
+
allowed_lemmas = exceptions.get("word_repetitions", set())
|
|
|
|
|
|
|
|
|
|
|
1527 |
|
1528 |
+
morph = pymorphy3.MorphAnalyzer()
|
1529 |
+
ignore_pos = {'PREP', 'CONJ', 'PRON', 'INTJ', 'NUMR', 'PART', 'NPRO'}
|
1530 |
|
1531 |
+
msg_words = re.findall(r'\b\w+(?:-\w+)*\b', message.lower())
|
|
|
|
|
|
|
|
|
|
|
|
|
1532 |
|
1533 |
+
# Ключевое сообщение
|
1534 |
key_normalized = set()
|
1535 |
+
for kw in re.findall(r'\b\w+\b', key_message.lower()):
|
1536 |
+
lemma_k, pos_k = lemmatize_word(kw, morph)
|
1537 |
+
key_normalized.add(lemma_k)
|
1538 |
+
|
1539 |
+
seen = {}
|
1540 |
+
for w in msg_words:
|
1541 |
+
lemma, pos = lemmatize_word(w, morph)
|
1542 |
+
if (not pos) or (pos in ignore_pos):
|
|
|
|
|
|
|
|
|
|
|
|
|
1543 |
continue
|
1544 |
+
if lemma in key_normalized:
|
|
|
|
|
1545 |
continue
|
1546 |
+
if lemma in allowed_lemmas:
|
1547 |
+
continue
|
1548 |
+
if lemma in seen:
|
1549 |
+
return False, f"Не пройдена проверка на повторы слов: {lemma}"
|
1550 |
+
seen[lemma] = True
|
|
|
|
|
|
|
|
|
|
|
1551 |
return True
|
1552 |
|
1553 |
# ФУНКЦИИ ПРОВЕРОК (КОНЕЦ)
|