From ad54d3e0e20c4ed4066a2c6276f1e25a360da1cd Mon Sep 17 00:00:00 2001 From: LocoAgent Date: Wed, 5 Aug 2026 14:46:39 +0400 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=BC=D0=BF=D0=BE=D1=80=D1=82:=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BF.=20=D0=9E=D0=9A=D0=92=D0=AD=D0=94=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B4=D0=B0=D0=BC=20(=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=8B=20=D0=B2=D0=BD=D1=83=D1=82=D1=80=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D0=B9=20=D0=B1?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BD=D0=B5=20=D1=80=D0=B2?= =?UTF-8?q?=D1=83=D1=82=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8),=20=D1=83?= =?UTF-8?q?=D0=B3=D1=80=D0=BE=D0=B7=D1=8B=20=D1=81=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D1=81=D0=BE=D0=BC=20=D0=B2=D0=BD=D1=83=D1=82?= =?UTF-8?q?=D1=80=D0=B8=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=D0=BA=D0=BB=D0=B5=D0=B8=D0=B2=D0=B0=D1=8E=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20(=D0=A3=D0=91=D0=98.209=20=C2=AB=D0=BF=D0=B0=D0=BC?= =?UTF-8?q?=D1=8F=D1=82=D0=B8\r\n=D1=8F=D0=B4=D1=80=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=BE=D1=80=D0=B0=C2=BB=20?= =?UTF-8?q?=E2=86=92=20=D0=BE=D0=B4=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dokogen/ui.py | 26 ++++++++++---------------- dokogen/verify.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/dokogen/ui.py b/dokogen/ui.py index e29b1e7..23b3798 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -22,7 +22,7 @@ from . import ( from .verify import ( check_declension_morpher, check_company_dadata, check_declension_ai, check_company_ai, - COMPANY_FIELDS, compare_company_values, + COMPANY_FIELDS, compare_company_values, normalize_threats, ) from .api_settings import load_api_settings, save_api_settings @@ -761,8 +761,9 @@ class DokoGenApp: self._company_entries['okved'].insert(0, resolve_okved(comp['okved'], _tok3)) if want('okved_extra') and comp.get('okved_extra') and hasattr(self, 'okved_extra_listbox'): self.okved_extra_listbox.delete(0, tk.END) + from .verify import split_okved_extra _applied = 0 - for code in comp['okved_extra']: + for code in split_okved_extra(comp['okved_extra']): if str(code).strip(): self.okved_extra_listbox.insert(tk.END, resolve_okved(str(code).strip(), _tok3)) _applied += 1 @@ -4661,21 +4662,14 @@ class DokoGenApp: entry.insert(0, resolve_okved(data['okved'], _tok)) if data.get('okvedExtra'): if hasattr(self, 'okved_extra_listbox'): - from .verify import resolve_okved + from .verify import resolve_okved, split_okved_extra from .api_settings import load_api_settings as _las2 _tok2 = (_las2().get('dadata_token') or '') self.okved_extra_listbox.delete(0, tk.END) - extra = data['okvedExtra'] - if isinstance(extra, str): - import re as _re - # Перенос строки внутри одной записи («27.32.2 Производство\nсиловых кабелей») - # не должен создавать вторую запись — сначала склеиваем переносы в пробел, - # затем режем только по запятой / точке с запятой. - extra = _re.sub(r'\s*[\n\r]+\s*', ' ', extra) - parts = [p.strip() for p in _re.split(r'[;]+|\s*,\s*', extra) if p.strip()] - else: - parts = [str(p).strip() for p in extra if str(p).strip()] - for p in parts: + # Доп. ОКВЭД могут быть склеены с переносами внутри названий + # («25.93 — Производство изделий из проволоки\nцепей и пружин 32.12.1 — …») — + # нарезаем по кодам ОКВЭД, затем нормализуем каждый в «код название». + for p in split_okved_extra(data['okvedExtra']): self.okved_extra_listbox.insert(tk.END, resolve_okved(p, _tok2)) # Объекты КИИ @@ -4713,8 +4707,8 @@ class DokoGenApp: attacker_category=is_data.get('attacker_category', ''), attacker_level=is_data.get('attacker_level', ''), attacker_types=is_data.get('attacker_types', ''), - threats=is_data.get('threats', ''), - incidents=is_data.get('incidents', ''), + threats=normalize_threats(is_data.get('threats', '')), + incidents=normalize_threats(is_data.get('incidents', '')), criteria_values=is_data.get('criteria_values', ''), criteria_justification=is_data.get('criteria_justification', ''), org_measures=is_data.get('org_measures', ''), diff --git a/dokogen/verify.py b/dokogen/verify.py index 616afe0..2447374 100644 --- a/dokogen/verify.py +++ b/dokogen/verify.py @@ -544,6 +544,51 @@ def _norm_threat_text(text): return s.lower() +def normalize_threats(text): + """Склеить переносы внутри названий угроз. + + В опросниках встречается «УБИ.209 Угроза … памяти\r\nядра процессора» — + перенос строки внутри одного названия. Строка, не начинающаяся с «УБИ.», + считается продолжением предыдущей угрозы. + """ + if not text: + return '' + lines = [ln.strip() for ln in str(text).split('\n') if ln.strip()] + out = [] + for ln in lines: + if re.match(r'^уби\.?\s*\d+', ln, re.IGNORECASE): + out.append(ln) + elif out: + out[-1] = out[-1] + ' ' + ln + else: + out.append(ln) + return '\n'.join(out) + + +def split_okved_extra(value): + """Разбить okvedExtra (строка или список строк с переносами) на записи «код название». + + В опросниках доп. ОКВЭД часто идут склеенными: + «25.93 — Производство изделий из проволоки\nцепей и пружин 32.12.1 — …» + Здесь «цепей и пружин» — продолжение названия 25.93, а 32.12.1 — новый код. + Нарезаем по кодам ОКВЭД (двузначный код с точками в начале записи). + """ + if isinstance(value, (list, tuple)): + text = ' '.join(str(x) for x in value) + else: + text = str(value or '') + text = text.replace('\r', ' ').replace('\n', ' ') + # Нарезаем по началу кода ОКВЭД: «25.93», «32.12.1», «43.2» … + # Перед кодом не должно быть цифры или точки (чтобы не разрезать «25.93» на «25» и «93») + parts = re.split(r'(?