mawo-razdel 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mawo_razdel/__init__.py CHANGED
@@ -69,21 +69,47 @@ class Substring:
69
69
 
70
70
 
71
71
  def tokenize(text: str, use_enhanced: bool = True) -> list[Substring]:
72
- """Tokenize Russian text into tokens.
72
+ """Токенизация русского текста.
73
+
74
+ Улучшенная токенизация с правильной обработкой:
75
+ - Десятичных чисел (3.14, 3,14)
76
+ - Процентов (95.5%)
77
+ - Диапазонов (1995-1999, 10:30-11:00)
78
+ - Дробей (1/2, 3/4)
79
+ - Телефонов, ID и т.д.
73
80
 
74
81
  Args:
75
- text: Text to tokenize
76
- use_enhanced: Use enhanced patterns if available
82
+ text: Текст для токенизации
83
+ use_enhanced: Использовать улучшенные паттерны
77
84
 
78
85
  Returns:
79
- List of Substring objects (tokens)
86
+ Список объектов Substring (токенов)
87
+ """
88
+ # Улучшенный паттерн на основе современных практик NLP (2024-2025)
89
+ # Сохраняет целостность чисел при обработке русского текста
90
+ pattern = r"""
91
+ # Десятичные числа с точкой или запятой (3.14159 или 3,14159)
92
+ \d+[.,]\d+
93
+ # Диапазоны и временные интервалы (1995-1999, 10:30-11:00)
94
+ |\d+[-:]\d+(?:[-:]\d+)*
95
+ # Дроби (1/2, 3/4)
96
+ |\d+/\d+
97
+ # Проценты (с числом)
98
+ |\d+\s*%
99
+ # Обычные числа
100
+ |\d+
101
+ # Русские и латинские слова (включая ё)
102
+ |[\w\u0400-\u04FF]+
103
+ # Любой другой непробельный символ
104
+ |\S
80
105
  """
81
- # Simple but effective tokenization with Russian support
82
- pattern = r"\b[\w\u0400-\u04FF]+\b|\S"
83
106
 
84
107
  tokens: list[Substring] = []
85
- for match in re.finditer(pattern, text):
86
- tokens.append(Substring(match.start(), match.end(), match.group()))
108
+ for match in re.finditer(pattern, text, re.VERBOSE | re.UNICODE):
109
+ token_text = match.group()
110
+ # Пропускаем чистые пробелы (не должно совпадать, но проверяем)
111
+ if token_text.strip():
112
+ tokens.append(Substring(match.start(), match.end(), token_text))
87
113
 
88
114
  return tokens
89
115
 
@@ -33,109 +33,126 @@ class SegmentationRule:
33
33
  class SynTagRusPatterns:
34
34
  """SynTagRus-based patterns для сегментации предложений."""
35
35
 
36
- # Аббревиатуры, которые НЕ завершают предложение
37
- ABBREVIATIONS = {
38
- # Географические
39
- "г",
40
- "гг",
41
- "г-н",
42
- "г-жа", # Год, годы, господин, госпожа
36
+ # Головные аббревиатуры (HEAD) - идут ПЕРЕД именами/названиями
37
+ # После них может быть заглавная буква, но это не начало предложения
38
+ HEAD_ABBREVIATIONS = {
39
+ # Географические (перед названиями)
43
40
  "ул",
44
41
  "пр",
45
42
  "пл",
46
43
  "пер",
47
44
  "просп",
48
- "наб", # Улица, проспект, площадь...
49
- "д",
50
- "дом",
51
- "корп",
52
- "стр",
53
- "кв", # Дом, корпус, строение, квартира
45
+ "наб", # улица Тверская
46
+ "г",
47
+ "гор", # г. Москва (город, не год!)
54
48
  "обл",
55
49
  "р-н",
56
50
  "п",
57
51
  "с",
58
52
  "дер",
59
- "пос", # Область, район, посёлок...
60
- # Научные степени и звания
61
- "акад",
53
+ "пос", # область, район...
54
+ "им", # им. Пушкина
55
+ # Титулы и звания (перед именами)
56
+ "г-н",
57
+ "г-жа",
58
+ "гн",
59
+ "госп", # господин Иванов
62
60
  "проф",
63
- "доц",
64
- "к",
65
- "канд",
66
- "докт", # Академик, профессор...
67
- "м",
68
- "н",
69
- "мл",
70
- "ст", # Младший, старший научный сотрудник
71
- # Титулы
72
- "им",
61
+ "акад",
62
+ "доц", # профессор Петров
63
+ "св", # св. Иоанн
73
64
  "ген",
74
65
  "полк",
75
66
  "подп",
76
67
  "лейт",
77
- "кап", # Имени, генерал...
78
- # Временные
68
+ "кап", # генерал Иванов
69
+ }
70
+
71
+ # Хвостовые аббревиатуры (TAIL) - идут ПОСЛЕ чисел/слов
72
+ # После них НЕ должно быть заглавной буквы (иначе новое предложение)
73
+ TAIL_ABBREVIATIONS = {
74
+ # Года и века (после чисел)
75
+ "г",
76
+ "гг",
79
77
  "в",
80
78
  "вв",
81
- "р",
82
- "руб",
83
- "коп", # Век, рубль, копейка
79
+ "р", # 1799 г., XXI в., 250 г. до Р. Х.
80
+ # Адресные (после чисел)
81
+ "д",
82
+ "дом",
83
+ "корп",
84
+ "стр",
85
+ "кв", # д. 1, стр. 5
86
+ # Временные
84
87
  "ч",
85
88
  "час",
86
89
  "мин",
87
- "сек", # Час, минута, секунда
88
- # Общие сокращения
90
+ "сек", # 10 ч. 30 мин.
91
+ # Деньги и измерения (после чисел)
92
+ "руб",
93
+ "коп",
94
+ "тыс",
95
+ "млн",
96
+ "млрд",
97
+ "трлн",
98
+ "кг",
99
+ "мг",
100
+ "ц",
101
+ "л",
102
+ "мм",
103
+ "км",
104
+ "га",
105
+ "м",
106
+ # Страницы, тома (после чисел)
89
107
  "т",
90
108
  "тт",
109
+ "с",
91
110
  "пп",
92
111
  "рис",
93
112
  "илл",
94
- "табл", # Том, пункт, рисунок...
113
+ "табл", # стр уже в адресных
114
+ # Научные степени (инициалы перед)
115
+ "к",
116
+ "канд",
117
+ "докт",
118
+ "н", # к.т.н., д.ф.н.
119
+ # Общие (обычно внутри текста или в конце)
95
120
  "см",
96
121
  "ср",
97
122
  "напр",
98
- "в т.ч",
99
- "и т.д",
100
- "и т.п",
101
- "и др", # Смотри, сравни...
102
123
  "др",
103
124
  "проч",
104
125
  "прим",
105
- "примеч", # Другое, прочее, примечание
106
- # Измерения
107
- "кг",
108
- "мг",
109
- "ц",
110
- "л", # Килограмм, грамм...
111
- "мм",
112
- "км",
113
- "га", # Метр, сантиметр...
114
- "млн",
115
- "млрд",
116
- "тыс",
117
- "трлн", # Миллион, миллиард...
126
+ "примеч",
127
+ "т.е",
128
+ "т.д",
129
+ "т.п",
130
+ "т.к", # и т.д., и т.п.
118
131
  # Организационные
119
132
  "о-во",
120
133
  "о-ва",
121
134
  "о-ние",
122
- "о-ния", # Общество, общества...
135
+ "о-ния",
123
136
  "зам",
124
137
  "пом",
125
138
  "зав",
126
- "нач", # Министр, заместитель...
139
+ "нач",
127
140
  # Прочие
128
141
  "etc",
129
142
  "et al",
130
143
  "ibid",
131
- "op cit", # Латинские
144
+ "op cit",
132
145
  "англ",
133
146
  "нем",
134
147
  "франц",
135
148
  "итал",
136
- "исп", # Языки
149
+ "исп",
150
+ "лат", # Языки
137
151
  }
138
152
 
153
+ # Объединенный список всех аббревиатур
154
+ ABBREVIATIONS = HEAD_ABBREVIATIONS | TAIL_ABBREVIATIONS
155
+
139
156
  # Почетные звания и должности (часто перед ФИО)
140
157
  TITLES = {
141
158
  "президент",
@@ -224,43 +241,106 @@ class SynTagRusPatterns:
224
241
  self.sentence_end_pattern = re.compile(r"[.!?]+\s+[А-ЯЁ«\"\'(]")
225
242
 
226
243
  def is_abbreviation(self, text: str, pos: int) -> bool:
227
- """Проверяет, является ли точка в позиции pos частью аббревиатуры.
244
+ """Проверяет, является ли точка перед позицией pos частью аббревиатуры.
245
+
246
+ Улучшено с проверкой контекста - на основе современных практик NLP (2024-2025).
247
+ Проверяет ПЕРЕД и ПОСЛЕ точки, чтобы определить, действительно ли это аббревиатура,
248
+ которая должна блокировать границу предложения.
228
249
 
229
250
  Args:
230
- text: Text to check
231
- pos: Position of the dot
251
+ text: Текст для проверки
252
+ pos: Позиция ПОСЛЕ точки (граница)
232
253
 
233
254
  Returns:
234
- True if dot is part of abbreviation
255
+ True если точка - часть аббревиатуры, блокирующей границу предложения
235
256
  """
236
- if pos <= 0 or pos >= len(text):
257
+ if pos <= 1 or pos > len(text):
237
258
  return False
238
259
 
239
- # Look back for abbreviation
240
- # Check 1-10 characters before the dot
241
- for look_back in range(1, min(11, pos + 1)):
242
- preceding = text[pos - look_back : pos].lower().strip()
243
- if preceding in self.ABBREVIATIONS:
244
- return True
260
+ # Проверяем, что перед pos действительно точка
261
+ if text[pos - 1] != ".":
262
+ return False
245
263
 
246
- return False
264
+ # Ищем токен аббревиатуры ПЕРЕД точкой
265
+ # Извлекаем слово/токен перед точкой
266
+ before_match = re.search(r"(\w+)\.?$", text[: pos - 1])
267
+ if not before_match:
268
+ return False
269
+
270
+ preceding = before_match.group(1).lower()
271
+
272
+ # Проверяем, есть ли в нашем списке аббревиатур
273
+ if preceding not in self.ABBREVIATIONS:
274
+ return False
275
+
276
+ # КРИТИЧНО: Проверяем что идет ПОСЛЕ точки
277
+ # Это ключевое улучшение на основе современных практик NLP
278
+ remaining = text[pos:].lstrip()
279
+
280
+ if not remaining:
281
+ # Конец текста - аббревиатура в конце
282
+ return True
283
+
284
+ # Проверяем первый символ после пробелов
285
+ next_char = remaining[0]
286
+
287
+ # УЛУЧШЕНИЕ: Различаем HEAD и TAIL аббревиатуры
288
+ is_head = preceding in self.HEAD_ABBREVIATIONS
289
+ is_tail = preceding in self.TAIL_ABBREVIATIONS
290
+
291
+ # Если следующий символ - заглавная буква (не цифра)
292
+ if next_char.isupper() and next_char.isalpha():
293
+ # HEAD аббревиатуры (ул., г., проф.) могут идти перед заглавной буквой
294
+ # Например: "ул. Тверская", "г. Москва", "проф. Иванов"
295
+ if is_head:
296
+ return True # Не разбиваем
297
+
298
+ # TAIL аббревиатуры (г., в., д.) НЕ должны идти перед заглавной буквой
299
+ # Исключение: инициалы (А. С. Пушкин)
300
+ if is_tail:
301
+ # Проверяем инициалы: один символ + точка
302
+ if len(remaining) > 2 and remaining[1] == ".":
303
+ return True # Часть последовательности инициалов
304
+ # Иначе это начало нового предложения
305
+ return False
306
+
307
+ # Строчная буква, цифра или пунктуация после аббревиатуры - оставляем соединенными
308
+ return True
247
309
 
248
310
  def is_initials_context(self, text: str, pos: int) -> bool:
249
- """Проверяет, находится ли точка в контексте инициалов.
311
+ """Проверяет, находится ли точка непосредственно в контексте инициалов.
312
+
313
+ Улучшено: проверяем только если инициалы находятся РЯДОМ с границей,
314
+ а не в радиусе 20 символов.
250
315
 
251
316
  Args:
252
- text: Text to check
253
- pos: Position of the dot
317
+ text: Текст для проверки
318
+ pos: Позиция после точки
254
319
 
255
320
  Returns:
256
- True if in initials context
321
+ True если в непосредственном контексте инициалов
257
322
  """
258
- # Check surrounding context (±20 chars)
259
- start = max(0, pos - 20)
260
- end = min(len(text), pos + 20)
323
+ # Проверяем небольшой контекст: 5 символов до и 10 после
324
+ # Это достаточно для "А. С. Пушкин" но не захватывает далекие инициалы
325
+ start = max(0, pos - 5)
326
+ end = min(len(text), pos + 10)
261
327
  context = text[start:end]
262
328
 
263
- return bool(self.initials_pattern.search(context))
329
+ # Дополнительно: точка должна быть ВНУТРИ найденного паттерна инициалов
330
+ match = self.initials_pattern.search(context)
331
+ if not match:
332
+ return False
333
+
334
+ # Проверяем, что граница (pos) находится внутри найденного паттерна инициалов
335
+ # или сразу после него (с учетом смещения start)
336
+ match_start = start + match.start()
337
+ match_end = start + match.end()
338
+
339
+ # Граница должна быть внутри паттерна или максимум на 2 символа после
340
+ if match_start <= pos <= match_end + 2:
341
+ return True
342
+
343
+ return False
264
344
 
265
345
  def find_sentence_boundaries(self, text: str) -> list[int]:
266
346
  """Находит границы предложений в тексте.
@@ -287,8 +367,9 @@ class SynTagRusPatterns:
287
367
  # Check if this is a valid boundary
288
368
  is_valid_boundary = False
289
369
 
290
- # Case 1: Followed by whitespace and capital letter
291
- if re.match(r"\s+[А-ЯЁ«\"\'(]", remaining):
370
+ # Case 1: Followed by whitespace and capital letter (русская ИЛИ латинская)
371
+ # УЛУЧШЕНИЕ: добавлена поддержка латинских заглавных (для XXI, IV, и т.д.)
372
+ if re.match(r"\s+[А-ЯЁA-Z«\"\'(]", remaining):
292
373
  is_valid_boundary = True
293
374
 
294
375
  # Case 2: Followed by paragraph break
@@ -313,18 +394,20 @@ class SynTagRusPatterns:
313
394
  """Проверяет, блокируется ли граница высокоприоритетным правилом.
314
395
 
315
396
  Args:
316
- text: Text
317
- pos: Boundary position
397
+ text: Текст
398
+ pos: Позиция границы (после точки/знака)
318
399
 
319
400
  Returns:
320
- True if boundary is blocked
401
+ True если граница блокирована
321
402
  """
322
- # Check for abbreviation (точка после аббревиатуры)
403
+ # Проверка на аббревиатуру (точка после аббревиатуры)
404
+ # ВАЖНО: is_abbreviation уже проверяет контекст до И после точки
323
405
  if pos > 0 and text[pos - 1] == ".":
324
- if self.is_abbreviation(text, pos - 1):
406
+ # Передаем позицию ПОСЛЕ точки (pos), а не позицию точки
407
+ if self.is_abbreviation(text, pos):
325
408
  return True
326
409
 
327
- # Check for initials (А. С. Пушкин)
410
+ # Проверка на инициалы (А. С. Пушкин)
328
411
  if self.is_initials_context(text, pos):
329
412
  return True
330
413
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mawo-razdel
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Продвинутая токенизация для русского языка с SynTagRus паттернами и +25% точностью
5
5
  Author-email: MAWO Team <team@mawo.ru>
6
6
  Maintainer-email: MAWO Team <team@mawo.ru>
@@ -1,5 +1,5 @@
1
- mawo_razdel/__init__.py,sha256=F7OwT5NofxOWBOlzMggo9veVm2AexAXeZwxdnetqKvM,7120
2
- mawo_razdel/syntagrus_patterns.py,sha256=Ng0DqzeH6Hw8eJWP3QpDgzb_f5HN5qTjAQLRnXbys0A,13166
1
+ mawo_razdel/__init__.py,sha256=TDGqj1RnRWYHtTv14a__lwD_ke2l4a2XxprXjE2-QP0,8481
2
+ mawo_razdel/syntagrus_patterns.py,sha256=na90JObwtakS59qjzBJgmFLxh_rlhNok-JgkiVQpeM0,18363
3
3
  mawo_razdel/data/corpora_sents.txt.lzma,sha256=9g3tHoVAVWxZRBao3S9jSvDREK88tTHcW_HdIsUqOmo,3558884
4
4
  mawo_razdel/data/corpora_tokens.txt.lzma,sha256=32JAHq7qtQgX2EA88DelBDiAuCG8Q8vNVqCRakrcSXY,3785332
5
5
  mawo_razdel/data/gicrya_sents.txt.lzma,sha256=puRJ23GkU554Ed81yn8B7B35Zqjeqa4RKEtIEL56d6I,2189240
@@ -8,8 +8,8 @@ mawo_razdel/data/rnc_sents.txt.lzma,sha256=In5BVwCvotaWA-BZy446qLjhBAht4iLE2lv5v
8
8
  mawo_razdel/data/rnc_tokens.txt.lzma,sha256=7keKlZaZxHmw7D8ZtFLnCPiCS2hXPtxjt1vBeum2E54,2491824
9
9
  mawo_razdel/data/syntag_sents.txt.lzma,sha256=TrdCYsTWu9lG04cUGPDrEaOh4h-yLgAg3pOpMqsRWSk,2190388
10
10
  mawo_razdel/data/syntag_tokens.txt.lzma,sha256=KjVkGlrQBOItYa7lSZ4b5hCtoKNtvUuxv5RaZHDPg6Y,2212888
11
- mawo_razdel-1.0.1.dist-info/licenses/LICENSE,sha256=HxcBccBgl94zsrO98Iv1FqnG5cp8fSsnxfq3YDSi7Mg,1066
12
- mawo_razdel-1.0.1.dist-info/METADATA,sha256=v9dsNs8IxIkID9SzWgWjPRbCx__gqUFxt-q-taFXCEs,13039
13
- mawo_razdel-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- mawo_razdel-1.0.1.dist-info/top_level.txt,sha256=zjx6jdks6KA3fcXqFLPR_XQeF7-3anYoqlHs9kpiojA,12
15
- mawo_razdel-1.0.1.dist-info/RECORD,,
11
+ mawo_razdel-1.0.3.dist-info/licenses/LICENSE,sha256=HxcBccBgl94zsrO98Iv1FqnG5cp8fSsnxfq3YDSi7Mg,1066
12
+ mawo_razdel-1.0.3.dist-info/METADATA,sha256=1oL9HpjIB1sW8nmYvU2ZX0JGUT6RnxytNulmBJnj4nU,13039
13
+ mawo_razdel-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ mawo_razdel-1.0.3.dist-info/top_level.txt,sha256=zjx6jdks6KA3fcXqFLPR_XQeF7-3anYoqlHs9kpiojA,12
15
+ mawo_razdel-1.0.3.dist-info/RECORD,,