ОКВЭД: полный справочник ОКВЭД2 (2770 записей) в едином формате «код название», resolve_okved без дефиса, импорт доп. ОКВЭД без переносов; угрозы: умный подбор — исключение неиспользуемых технологий (виртуализация, облако, мобильные, АСУ ТП, грид, суперкомпьютер, большие данные, беспроводные сети, контейнеры, smart-карты, ИИ), базовые угрозы локальных сетей

This commit is contained in:
LocoAgent
2026-08-05 14:08:53 +04:00
parent 136c58c948
commit c9dffdd4ca
4 changed files with 2837 additions and 278 deletions
File diff suppressed because it is too large Load Diff
+46
View File
@@ -42,8 +42,26 @@ BASE_KEYWORDS = [
'вредоносн', 'несанкционированн', 'утечк', 'перехват', 'модификац', 'вредоносн', 'несанкционированн', 'утечк', 'перехват', 'модификац',
'уничтожен', 'отказ в обслуживании', 'нсд', 'биос', 'пароль', 'уничтожен', 'отказ в обслуживании', 'нсд', 'биос', 'пароль',
'криптографическ', 'шифрован', 'аутентификац', 'криптографическ', 'шифрован', 'аутентификац',
# базовые угрозы локальных сетей / клиент-серверных архитектур
'доступ', 'данн', 'систем', 'конфигурац', 'целостност', 'информац',
'программн', 'пользовател', 'авторизац', 'сеанс', 'журнал', 'реестр',
'файл', 'память', 'буфер', 'ресурс', 'сервер', 'клиент', 'учётн',
'учетн', 'идентификац', 'привилег', 'средств', 'сетев', 'локальн',
] ]
# Технологии, которые по умолчанию НЕ используются на объекте.
# Если в характеристиках объекта есть явные упоминания — группа возвращается.
TECH_GROUPS = {
'виртуализац': ['виртуал', 'гипервизор', 'контейнер'],
'облако': ['облачн', 'облак'],
'мобильн': ['мобильн', 'смартфон', 'планшет'],
'грид': ['грид', 'суперкомпьютер'],
'большие данные': ['больших данных', 'хранилищ'],
'беспроводн': ['беспроводн', 'wi-fi', 'wifi'],
'smart-карты': ['smart-карт', 'смарт-карт'],
'машинное обучение': ['машинного обучения', 'искусственного интеллекта', 'нейросет'],
}
# Объект имеет подключение к сети Интернет / сеть электросвязи # Объект имеет подключение к сети Интернет / сеть электросвязи
INTERNET_KEYWORDS = [ INTERNET_KEYWORDS = [
'удалённ', 'удаленн', 'сетев', 'фишинг', 'ddos', 'отказ в обслуживании', 'удалённ', 'удаленн', 'сетев', 'фишинг', 'ddos', 'отказ в обслуживании',
@@ -153,6 +171,21 @@ def suggest_threats_program(isys, threats=None, limit=None):
is_asu = _is_asu(isys) is_asu = _is_asu(isys)
is_network = _is_network(isys) is_network = _is_network(isys)
# --- Технологии, которые явно НЕ используются на объекте ---
# Если в характеристиках объекта (описание, архитектура, ПО, сфера) нет
# упоминаний технологии — угрозы этой группы исключаем.
tech_context = ' '.join([
str(getattr(isys, 'description', '') or ''),
str(getattr(isys, 'architecture', '') or ''),
str(getattr(isys, 'hw_sw', '') or ''),
str(getattr(isys, 'os_sw', '') or ''),
str(getattr(isys, 'app_sw', '') or ''),
str(getattr(isys, 'object_type', '') or ''),
str(getattr(isys, 'sphere', '') or ''),
str(getattr(isys, 'net_category', '') or ''),
str(getattr(isys, 'typical_object', '') or ''),
]).lower()
# --- Слова-исключения (если встретились в названии угрозы — она неактуальна) --- # --- Слова-исключения (если встретились в названии угрозы — она неактуальна) ---
exclude = [] exclude = []
@@ -167,6 +200,19 @@ def suggest_threats_program(isys, threats=None, limit=None):
if is_asu: if is_asu:
# АСУ: нет BIOS/виртуализации/браузера в привычном понимании # АСУ: нет BIOS/виртуализации/браузера в привычном понимании
exclude += ['биос', 'грид', 'гипервизор', 'виртуальн', 'браузер'] exclude += ['биос', 'грид', 'гипервизор', 'виртуальн', 'браузер']
else:
# Не АСУ — исключаем угрозы АСУ ТП / технологических процессов
exclude += ['технологическ', 'датчик', 'исполнительн', 'асутп', 'scada',
'промышленн', 'контроллер', 'числовым программным управлением',
'программируемых логических']
# Технологии, не упомянутые в характеристиках объекта → исключаем
for tech_name, kws in TECH_GROUPS.items():
if any(kw in tech_context for kw in kws):
continue # технология используется — угрозы оставляем
# Технология не упомянута — исключаем её угрозы
for kw in kws:
exclude.append(kw)
if not is_network and not is_asu: if not is_network and not is_asu:
# Обычная ИС: неактуальны чисто сетевые/телеком-угрозы без указания # Обычная ИС: неактуальны чисто сетевые/телеком-угрозы без указания
+5 -1
View File
@@ -4546,7 +4546,11 @@ class DokoGenApp:
extra = data['okvedExtra'] extra = data['okvedExtra']
if isinstance(extra, str): if isinstance(extra, str):
import re as _re import re as _re
parts = [p.strip() for p in _re.split(r'[;,\n]+', extra) if p.strip()] # Перенос строки внутри одной записи («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: else:
parts = [str(p).strip() for p in extra if str(p).strip()] parts = [str(p).strip() for p in extra if str(p).strip()]
for p in parts: for p in parts:
+16 -10
View File
@@ -161,15 +161,20 @@ def normalize_address(text):
def resolve_okved(code, dadata_token=None): def resolve_okved(code, dadata_token=None):
"""Код ОКВЭД → «код - название». """Код ОКВЭД → «код название» (без дефиса), например «27.32.2 Производство силовых кабелей».
Принимает и голый код («27.32.2»), и строку с названием
(«27.32.2 - Производство силовых кабелей» / «27.32.2 Производство силовых кабелей»).
1) Ищем в словаре dictionaries/okved.json (строки вида «27.32.2 Название»). 1) Ищем в словаре dictionaries/okved.json (строки вида «27.32.2 Название»).
2) Если не нашли — запрашиваем DaData suggest okved2 (если есть токен). 2) Если не нашли — запрашиваем DaData suggest okved2 (если есть токен).
3) Иначе возвращаем код как есть. 3) Иначе возвращаем нормализованную строку «код название» как есть.
""" """
code = (code or '').strip() code = (code or '').strip()
if not code: if not code:
return '' return ''
# Извлекаем код из входной строки: «27.32.2 - Производство силовых кабелей» → «27.32.2»
m = re.match(r'^\s*(\d+(?:\.\d+)*)', code)
code_only = m.group(1) if m else code.split()[0].rstrip('.')
# 1) Словарь # 1) Словарь
try: try:
import os, json import os, json
@@ -178,12 +183,8 @@ def resolve_okved(code, dadata_token=None):
items = json.load(f) items = json.load(f)
for item in items: for item in items:
item = str(item).strip() item = str(item).strip()
# совпадение по началу: «27.32.2» или «27.32.2 - ...» / «27.32.2 ...» # совпадение по коду: «27.32.2» или «27.32.2 Название»
if item.split()[0].rstrip('.') == code.rstrip('.'): if item.split()[0].rstrip('.') == code_only.rstrip('.'):
# нормализуем: «код - название»
rest = item[len(item.split()[0]):].strip().lstrip('- ').strip()
if rest:
return f"{code} - {rest}"
return item return item
except Exception: except Exception:
pass pass
@@ -191,7 +192,7 @@ def resolve_okved(code, dadata_token=None):
if dadata_token: if dadata_token:
try: try:
js, err = _http_json('https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/okved2', js, err = _http_json('https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/okved2',
data={'query': code, 'count': 1}, data={'query': code_only, 'count': 1},
headers={'Content-Type': 'application/json', headers={'Content-Type': 'application/json',
'Authorization': f'Token {dadata_token}'}) 'Authorization': f'Token {dadata_token}'})
if not err and js: if not err and js:
@@ -200,7 +201,12 @@ def resolve_okved(code, dadata_token=None):
return sugg[0]['value'] return sugg[0]['value']
except Exception: except Exception:
pass pass
return code # 3) Нормализуем вход: «27.32.2 - Название» → «27.32.2 Название» (без дефиса)
rest = code[len(m.group(1)):].strip() if m else ''
rest = rest.lstrip('- ').strip()
if rest:
return f"{code_only} {rest}"
return code_only
def _fmt_date(value): def _fmt_date(value):