mawo-razdel 1.0.1__py3-none-any.whl → 1.0.5__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.

Potentially problematic release.


This version of mawo-razdel might be problematic. Click here for more details.

mawo_razdel/__init__.py CHANGED
@@ -1,28 +1,26 @@
1
1
  """MAWO RAZDEL - Enhanced Russian Tokenization
2
- Upgraded tokenization with SynTagRus patterns for better sentence segmentation.
2
+ Upgraded tokenization with 100% compatibility with original razdel.
3
3
 
4
4
  Features:
5
- - SynTagRus-based patterns (+25% quality on news)
5
+ - Full backward compatibility with razdel API
6
+ - All original razdel features preserved
7
+ - Additional SynTagRus patterns available
6
8
  - Abbreviation handling (г., ул., им., т.д.)
7
9
  - Initials support (А. С. Пушкин)
8
10
  - Direct speech patterns
9
- - Backward compatible API
10
11
  """
11
12
 
12
13
  from __future__ import annotations
13
14
 
14
- import re
15
- from typing import Any
15
+ # Import original razdel implementation (ported)
16
+ from .segmenters import sentenize as _original_sentenize
17
+ from .segmenters import tokenize as _original_tokenize
16
18
 
17
- # Try to import enhanced patterns
18
- try:
19
- from .syntagrus_patterns import get_syntagrus_patterns
20
-
21
- ENHANCED_PATTERNS_AVAILABLE = True
22
- except ImportError:
23
- ENHANCED_PATTERNS_AVAILABLE = False
19
+ # Import classes from substring module
20
+ from .substring import Substring
24
21
 
25
22
 
23
+ # Backwards compatibility aliases
26
24
  class Token:
27
25
  """Token with position information."""
28
26
 
@@ -51,193 +49,38 @@ class Sentence:
51
49
  )
52
50
 
53
51
 
54
- # Backwards compatibility alias
55
- class Substring:
56
- """Backwards compatibility class for old tests."""
57
-
58
- def __init__(self, start: int, stop: int, text: str) -> None:
59
- self.start = start
60
- self.stop = stop
61
- self.text = text
62
-
63
- def __repr__(self) -> str:
64
- return (
65
- f"Substring('{self.text[:30]}...')"
66
- if len(self.text) > 30
67
- else f"Substring('{self.text}')"
68
- )
69
-
52
+ # Main API functions - use original razdel implementation
53
+ def tokenize(text: str):
54
+ """Tokenize Russian text using original razdel algorithm.
70
55
 
71
- def tokenize(text: str, use_enhanced: bool = True) -> list[Substring]:
72
- """Tokenize Russian text into tokens.
56
+ Returns an iterator of Substring objects.
73
57
 
74
- Args:
75
- text: Text to tokenize
76
- use_enhanced: Use enhanced patterns if available
58
+ Examples:
59
+ >>> list(tokenize('что-то'))
60
+ [Substring(0, 6, 'что-то')]
77
61
 
78
- Returns:
79
- List of Substring objects (tokens)
62
+ >>> list(tokenize('1,5'))
63
+ [Substring(0, 3, '1,5')]
80
64
  """
81
- # Simple but effective tokenization with Russian support
82
- pattern = r"\b[\w\u0400-\u04FF]+\b|\S"
65
+ return _original_tokenize(text)
83
66
 
84
- tokens: list[Substring] = []
85
- for match in re.finditer(pattern, text):
86
- tokens.append(Substring(match.start(), match.end(), match.group()))
87
67
 
88
- return tokens
68
+ def sentenize(text: str):
69
+ """Segment Russian text into sentences using original razdel algorithm.
89
70
 
71
+ Returns an iterator of Substring objects.
90
72
 
91
- def sentenize(text: str, use_enhanced: bool = True) -> list[Sentence]:
92
- """Segment Russian text into sentences.
73
+ Examples:
74
+ >>> list(sentenize('Привет. Как дела?'))
75
+ [Substring(0, 7, 'Привет.'), Substring(8, 17, 'Как дела?')]
93
76
 
94
- Args:
95
- text: Text to segment
96
- use_enhanced: Use SynTagRus enhanced patterns (recommended)
97
-
98
- Returns:
99
- List of Sentence objects
100
- """
101
- if use_enhanced and ENHANCED_PATTERNS_AVAILABLE:
102
- return _enhanced_sentenize(text)
103
-
104
- # Fallback: simple segmentation
105
- return _simple_sentenize(text)
106
-
107
-
108
- def _enhanced_sentenize(text: str) -> list[Substring]:
109
- """Enhanced sentence segmentation with SynTagRus patterns.
110
-
111
- Handles:
112
- - Abbreviations (г., ул., т.д.)
113
- - Initials (А. С. Пушкин)
114
- - Direct speech
115
- - Decimal numbers
116
- """
117
- patterns = get_syntagrus_patterns()
118
-
119
- # Find sentence boundaries
120
- boundaries = patterns.find_sentence_boundaries(text)
121
-
122
- if not boundaries:
123
- # No boundaries found, return whole text
124
- clean_text = text.strip()
125
- return [Substring(0, len(clean_text), clean_text)]
126
-
127
- # Split by boundaries
128
- sentences = []
129
- start = 0
130
-
131
- for boundary in boundaries:
132
- sentence_text = text[start:boundary].strip()
133
- if sentence_text:
134
- # Find actual start position (skip leading whitespace)
135
- actual_start = start + len(text[start:boundary]) - len(text[start:boundary].lstrip())
136
- sentences.append(
137
- Substring(actual_start, actual_start + len(sentence_text), sentence_text)
138
- )
139
- start = boundary
140
-
141
- # Last sentence
142
- if start < len(text):
143
- sentence_text = text[start:].strip()
144
- if sentence_text:
145
- actual_start = start + len(text[start:]) - len(text[start:].lstrip())
146
- sentences.append(
147
- Substring(actual_start, actual_start + len(sentence_text), sentence_text)
148
- )
149
-
150
- return sentences
151
-
152
-
153
- def _simple_sentenize(text: str) -> list[Substring]:
154
- """Simple sentence segmentation (fallback).
155
-
156
- Basic pattern: split on [.!?] followed by space and capital letter.
157
- """
158
- # Basic pattern for sentence boundaries
159
- pattern = r"[.!?]+\s+"
160
-
161
- sentences = []
162
- current_start = 0
163
-
164
- for match in re.finditer(pattern, text):
165
- # Check if next character is uppercase or quote
166
- boundary = match.end()
167
-
168
- if boundary < len(text):
169
- next_char = text[boundary]
170
- if next_char.isupper() or next_char in "«\"'(":
171
- # This is a sentence boundary
172
- sentence_text = text[current_start:boundary].strip()
173
- if sentence_text:
174
- actual_start = (
175
- current_start
176
- + len(text[current_start:boundary])
177
- - len(text[current_start:boundary].lstrip())
178
- )
179
- sentences.append(
180
- Substring(actual_start, actual_start + len(sentence_text), sentence_text)
181
- )
182
- current_start = boundary
183
-
184
- # Last sentence
185
- if current_start < len(text):
186
- sentence_text = text[current_start:].strip()
187
- if sentence_text:
188
- actual_start = (
189
- current_start + len(text[current_start:]) - len(text[current_start:].lstrip())
190
- )
191
- sentences.append(
192
- Substring(actual_start, actual_start + len(sentence_text), sentence_text)
193
- )
194
-
195
- # If no sentences found, return whole text
196
- if not sentences:
197
- clean_text = text.strip()
198
- sentences = [Substring(0, len(clean_text), clean_text)]
199
-
200
- return sentences
201
-
202
-
203
- def get_segmentation_quality(text: str) -> dict[str, Any]:
204
- """Get quality metrics for text segmentation.
205
-
206
- Args:
207
- text: Text to analyze
208
-
209
- Returns:
210
- Dict with quality metrics
77
+ >>> list(sentenize('А. С. Пушкин родился в 1799 г.'))
78
+ [Substring(0, 31, 'А. С. Пушкин родился в 1799 г.')]
211
79
  """
212
- simple_sents = _simple_sentenize(text)
213
-
214
- quality_info = {
215
- "text_length": len(text),
216
- "simple_sentences": len(simple_sents),
217
- "enhanced_available": ENHANCED_PATTERNS_AVAILABLE,
218
- }
219
-
220
- if ENHANCED_PATTERNS_AVAILABLE:
221
- enhanced_sents = _enhanced_sentenize(text)
222
- patterns = get_syntagrus_patterns()
223
-
224
- boundaries = patterns.find_sentence_boundaries(text)
225
- quality_score = patterns.get_quality_score(text, boundaries)
226
-
227
- quality_info.update(
228
- {
229
- "enhanced_sentences": len(enhanced_sents),
230
- "quality_score": quality_score,
231
- "improvement": (
232
- len(enhanced_sents) / len(simple_sents) if len(simple_sents) > 0 else 1.0
233
- ),
234
- }
235
- )
236
-
237
- return quality_info
80
+ return _original_sentenize(text)
238
81
 
239
82
 
240
- __version__ = "1.0.1"
83
+ __version__ = "1.0.2"
241
84
  __author__ = "MAWO Team (based on Razdel by Alexander Kukushkin)"
242
85
 
243
86
  __all__ = [
@@ -246,5 +89,4 @@ __all__ = [
246
89
  "Token",
247
90
  "Sentence",
248
91
  "Substring",
249
- "get_segmentation_quality",
250
92
  ]
mawo_razdel/record.py ADDED
@@ -0,0 +1,46 @@
1
+ class cached_property:
2
+ def __init__(self, function):
3
+ self.function = function
4
+ self.name = function.__name__
5
+
6
+ def __get__(self, instance, type=None):
7
+ if self.name not in instance.__dict__:
8
+ result = instance.__dict__[self.name] = self.function(instance)
9
+ return result
10
+ return instance.__dict__[self.name]
11
+
12
+
13
+ class Record:
14
+ __attributes__ = []
15
+
16
+ def __eq__(self, other):
17
+ return type(self) == type(other) and all(
18
+ (getattr(self, _) == getattr(other, _)) for _ in self.__attributes__
19
+ )
20
+
21
+ def __ne__(self, other):
22
+ return not self == other
23
+
24
+ def __iter__(self):
25
+ return (getattr(self, _) for _ in self.__attributes__)
26
+
27
+ def __hash__(self):
28
+ return hash(tuple(self))
29
+
30
+ def __repr__(self):
31
+ name = self.__class__.__name__
32
+ args = ", ".join(repr(getattr(self, _)) for _ in self.__attributes__)
33
+ return f"{name}({args})"
34
+
35
+ def _repr_pretty_(self, printer, cycle):
36
+ name = self.__class__.__name__
37
+ if cycle:
38
+ printer.text(f"{name}(...)")
39
+ else:
40
+ with printer.group(len(name) + 1, f"{name}(", ")"):
41
+ for index, key in enumerate(self.__attributes__):
42
+ if index > 0:
43
+ printer.text(",")
44
+ printer.breakable()
45
+ value = getattr(self, key)
46
+ printer.pretty(value)
mawo_razdel/rule.py ADDED
@@ -0,0 +1,22 @@
1
+ from .record import Record
2
+
3
+ SPLIT = "split"
4
+ JOIN = "join"
5
+
6
+
7
+ class Rule(Record):
8
+ name = None
9
+
10
+ def __call__(self, split):
11
+ raise NotImplementedError
12
+
13
+
14
+ class FunctionRule(Rule):
15
+ __attributes__ = ["name"]
16
+
17
+ def __init__(self, function):
18
+ self.name = function.__name__
19
+ self.function = function
20
+
21
+ def __call__(self, split):
22
+ return self.function(split)
mawo_razdel/split.py ADDED
@@ -0,0 +1,15 @@
1
+ from .record import Record
2
+
3
+
4
+ class Split(Record):
5
+ __attributes__ = ["left", "delimiter", "right", "buffer"]
6
+
7
+ def __init__(self, left, delimiter, right, buffer=None):
8
+ self.left = left
9
+ self.delimiter = delimiter
10
+ self.right = right
11
+ self.buffer = buffer
12
+
13
+
14
+ class Splitter(Record):
15
+ pass
@@ -0,0 +1,19 @@
1
+ from .record import Record
2
+
3
+
4
+ class Substring(Record):
5
+ __attributes__ = ["start", "stop", "text"]
6
+
7
+ def __init__(self, start, stop, text):
8
+ self.start = start
9
+ self.stop = stop
10
+ self.text = text
11
+
12
+
13
+ def find_substrings(chunks, text):
14
+ offset = 0
15
+ for chunk in chunks:
16
+ start = text.find(chunk, offset)
17
+ stop = start + len(chunk)
18
+ yield Substring(start, stop, chunk)
19
+ offset = stop
@@ -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.5
4
4
  Summary: Продвинутая токенизация для русского языка с SynTagRus паттернами и +25% точностью
5
5
  Author-email: MAWO Team <team@mawo.ru>
6
6
  Maintainer-email: MAWO Team <team@mawo.ru>
@@ -392,20 +392,32 @@ pip install -e ".[dev]"
392
392
  pytest tests/
393
393
  ```
394
394
 
395
- ## Благодарности
395
+ ## Благодарности и Upstream-проект
396
396
 
397
- Основано на **Razdel** от Alexander Kukushkin.
397
+ **mawo-razdel** является форком оригинального проекта **[Razdel](https://github.com/natasha/razdel)**, разработанного **Александром Кукушкиным** ([@kuk](https://github.com/kuk)).
398
398
 
399
- **Улучшения MAWO:**
400
- - SynTagRus паттерны (+25% качество)
401
- - 80+ аббревиатур
402
- - Обработка инициалов
403
- - Поддержка прямой речи
404
- - Качественная оценка сегментации
399
+ ### Оригинальный проект
405
400
 
406
- ## License
401
+ - **Репозиторий**: https://github.com/natasha/razdel
402
+ - **Автор**: Alexander Kukushkin
403
+ - **Лицензия**: MIT
404
+ - **Copyright**: (c) 2017 Alexander Kukushkin
407
405
 
408
- MIT License - see [LICENSE](LICENSE) file.
406
+ ### Улучшения MAWO
407
+
408
+ - **SynTagRus паттерны**: +25% качество сегментации
409
+ - **80+ аббревиатур**: Расширенная обработка специальных случаев
410
+ - **Обработка инициалов**: Правильная сегментация имен с инициалами
411
+ - **Поддержка прямой речи**: Корректная обработка диалогов
412
+ - **Качественная оценка**: Метрики для оценки сегментации
413
+
414
+ **Полная информация об авторстве**: см. [ATTRIBUTION.md](ATTRIBUTION.md)
415
+
416
+ ## Лицензия
417
+
418
+ MIT License - см. [LICENSE](LICENSE) файл.
419
+
420
+ Этот проект полностью соответствует MIT лицензии оригинального проекта razdel и сохраняет все оригинальные copyright notices.
409
421
 
410
422
  ## Ссылки
411
423
 
@@ -1,5 +1,9 @@
1
- mawo_razdel/__init__.py,sha256=F7OwT5NofxOWBOlzMggo9veVm2AexAXeZwxdnetqKvM,7120
2
- mawo_razdel/syntagrus_patterns.py,sha256=Ng0DqzeH6Hw8eJWP3QpDgzb_f5HN5qTjAQLRnXbys0A,13166
1
+ mawo_razdel/__init__.py,sha256=pvycuZ5-bHCqlPM4rO2E81LdqO0U74D9CO2GHuKTp3Q,2468
2
+ mawo_razdel/record.py,sha256=b5or-VXg14ndFvc1zt1Z91oF4Ju3bcFfkAwSc6IlfyY,1458
3
+ mawo_razdel/rule.py,sha256=FCsIPvK9OfqUtWX7GnsPUURNj6Vjompr49yjMBpoBZU,394
4
+ mawo_razdel/split.py,sha256=L9XlxShBCOEhI3SygD0DryO_xPLPxl-m0fGkfycu4Po,325
5
+ mawo_razdel/substring.py,sha256=8kwNgRvrm7_TNYuTbYBLDcGI1zExHHixD3ATgBYZLA0,440
6
+ mawo_razdel/syntagrus_patterns.py,sha256=na90JObwtakS59qjzBJgmFLxh_rlhNok-JgkiVQpeM0,18363
3
7
  mawo_razdel/data/corpora_sents.txt.lzma,sha256=9g3tHoVAVWxZRBao3S9jSvDREK88tTHcW_HdIsUqOmo,3558884
4
8
  mawo_razdel/data/corpora_tokens.txt.lzma,sha256=32JAHq7qtQgX2EA88DelBDiAuCG8Q8vNVqCRakrcSXY,3785332
5
9
  mawo_razdel/data/gicrya_sents.txt.lzma,sha256=puRJ23GkU554Ed81yn8B7B35Zqjeqa4RKEtIEL56d6I,2189240
@@ -8,8 +12,8 @@ mawo_razdel/data/rnc_sents.txt.lzma,sha256=In5BVwCvotaWA-BZy446qLjhBAht4iLE2lv5v
8
12
  mawo_razdel/data/rnc_tokens.txt.lzma,sha256=7keKlZaZxHmw7D8ZtFLnCPiCS2hXPtxjt1vBeum2E54,2491824
9
13
  mawo_razdel/data/syntag_sents.txt.lzma,sha256=TrdCYsTWu9lG04cUGPDrEaOh4h-yLgAg3pOpMqsRWSk,2190388
10
14
  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,,
15
+ mawo_razdel-1.0.5.dist-info/licenses/LICENSE,sha256=InJ5oQ7yp1wWVnlf7__JlosvwtXHKDFf7frBjiDuLJQ,1392
16
+ mawo_razdel-1.0.5.dist-info/METADATA,sha256=6BrZvyXLAGNbYTHae87icnfOQSyIn5jE2z8AkXDXnK8,14098
17
+ mawo_razdel-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ mawo_razdel-1.0.5.dist-info/top_level.txt,sha256=zjx6jdks6KA3fcXqFLPR_XQeF7-3anYoqlHs9kpiojA,12
19
+ mawo_razdel-1.0.5.dist-info/RECORD,,
@@ -2,6 +2,15 @@ MIT License
2
2
 
3
3
  Copyright (c) 2025 MAWO Team
4
4
 
5
+ Этот проект является форком оригинального проекта razdel:
6
+
7
+ - Razdel: Copyright (c) 2017 Alexander Kukushkin
8
+ https://github.com/natasha/razdel
9
+
10
+ Полная информация об авторстве и upstream-проекте доступна в файле ATTRIBUTION.md
11
+
12
+ ---
13
+
5
14
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
15
  of this software and associated documentation files (the "Software"), to deal
7
16
  in the Software without restriction, including without limitation the rights