ОКВЭД: полный справочник ОКВЭД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):