From 3a4cd2fde40b286ddc99fe5f22a9daeee2ceb590 Mon Sep 17 00:00:00 2001 From: DokoGen Date: Mon, 3 Aug 2026 12:45:10 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BD=D0=BE=D1=80=D0=BC=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=9A=D0=90=D0=9F=D0=A1?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D0=BC=20?= =?UTF-8?q?(=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=20+=20=D0=B4=D0=BE=D0=BB=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - раньше: если в тексте есть аббревиатура (ООО, И.О.) или цифры (индекс в адресе) — весь текст оставался КАПСОМ - теперь: каждое слово нормализуется отдельно, аббревиатуры (АРМ, СКУД, ООО, ИНН и т.п.) и инициалы сохраняются - город: '350000, Г. КРАСНОДАР...' → 'Г. Краснодар' → город снова определяется при импорте - должность: 'ГЛАВА' → 'Глава', ФИО: 'ИВАНОВ ИВАН ИВАНОВИЧ' → 'Иванов Иван Иванович' --- dokogen/importer.py | 73 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/dokogen/importer.py b/dokogen/importer.py index b166d30..f28e5f6 100644 --- a/dokogen/importer.py +++ b/dokogen/importer.py @@ -15,6 +15,62 @@ except ImportError: from .import_settings import load_settings, parse_cell_ref, cell_value +# Известные аббревиатуры, которые сохраняются КАПСОМ при нормализации +_CAPS_ABBR = { + 'АРМ', 'СКУД', 'СЗИ', 'СКЗИ', 'СОВ', 'ЛВС', 'ИНН', 'КПП', 'ОГРН', 'ЦОД', + 'МБУ', 'МКУ', 'ООО', 'СНИЛС', 'СБИС', 'ЕМСЭД', 'ФИО', 'ИСПДН', 'ККТ', +} + + +def _normalize_caps(text: str) -> str: + """Приводит КАПС-текст к нормальному регистру по словам. + + Сохраняет: + - аббревиатуры из белого списка (АРМ, СКУД, ООО, ИНН и т.п.) + - инициалы и короткие служебные слова (И., Г., УЛ., №1) + - числа и номера (350000, №1) + - слова, уже содержащие строчные буквы + Длинные слова целиком заглавными (ДИРЕКТОР, КРАСНОДАР, ПЕТРОВ) → «Директор», «Краснодар». + """ + words = text.split() + result = [] + for w in words: + # отделяем пунктуацию по краям слова + pre = '' + post = '' + core = w + while core and not core[0].isalnum(): + pre += core[0] + core = core[1:] + while core and not core[-1].isalnum(): + post = core[-1] + post + core = core[:-1] + if not core: + result.append(w) + continue + # в слове уже есть строчные буквы — не трогаем + if any('а' <= c <= 'я' or c == 'ё' for c in core): + result.append(w) + continue + # нет заглавных кириллических букв (числа, латиница и т.п.) — не трогаем + if not any('А' <= c <= 'Я' or c == 'Ё' for c in core): + result.append(w) + continue + letters = [c for c in core if c.isalpha()] + # инициалы и служебные слова (1-2 буквы: И., Г., УЛ., ООО — но ООО в белом списке) + if len(letters) <= 2: + result.append(w) + continue + # известная аббревиатура — сохраняем как есть + if core.upper() in _CAPS_ABBR: + result.append(w) + continue + # обычное длинное слово: первая заглавная, остальные строчные + normalized = core[0].upper() + core[1:].lower() + result.append(pre + normalized + post) + return ' '.join(result) + + def clean_value(val): """Очистка и нормализация значения ячейки Excel.""" if val is None: @@ -33,20 +89,11 @@ def clean_value(val): text = str(val).strip() text = text.replace('\n', ' ').replace('\r', ' ') text = ' '.join(text.split()) - # Приведение регистра: если текст содержит только заглавные буквы РУ - # НО не трогаем аббревиатуры/номера (АРМ №1, ИС, ЕМСЭД, СКУД и т.п.) - if len(text) > 4 and any('А' <= c <= 'Я' or c == 'Ё' for c in text): + # Если весь текст КАПСОМ (нет строчных букв) — нормализуем по словам + if len(text) > 2 and any('А' <= c <= 'Я' or c == 'Ё' for c in text): has_lower = any('а' <= c <= 'я' or c == 'ё' for c in text) - has_digit_or_num = bool(re.search(r'[0-9№«»"\']', text)) - if not has_lower and not has_digit_or_num: - words = text.split() - # если есть короткая аббревиатура целиком заглавными — не трогаем - has_abbr = any(len(w) <= 4 and w.isupper() and any('А' <= c <= 'Я' for c in w) for w in words) - if not has_abbr: - if len(words) >= 3: - text = text.title() - else: - text = text.capitalize() + if not has_lower: + text = _normalize_caps(text) return text