ОКВЭД: полный справочник ОКВЭД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 = [
'удалённ', 'удаленн', 'сетев', 'фишинг', 'ddos', 'отказ в обслуживании',
@@ -153,6 +171,21 @@ def suggest_threats_program(isys, threats=None, limit=None):
is_asu = _is_asu(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 = []
@@ -167,6 +200,19 @@ def suggest_threats_program(isys, threats=None, limit=None):
if is_asu:
# АСУ: нет BIOS/виртуализации/браузера в привычном понимании
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:
# Обычная ИС: неактуальны чисто сетевые/телеком-угрозы без указания
+5 -1
View File
@@ -4546,7 +4546,11 @@ class DokoGenApp:
extra = data['okvedExtra']
if isinstance(extra, str):
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:
parts = [str(p).strip() for p in extra if str(p).strip()]
for p in parts:
+16 -10
View File
@@ -161,15 +161,20 @@ def normalize_address(text):
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 Название»).
2) Если не нашли запрашиваем DaData suggest okved2 (если есть токен).
3) Иначе возвращаем код как есть.
3) Иначе возвращаем нормализованную строку «код название» как есть.
"""
code = (code or '').strip()
if not code:
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) Словарь
try:
import os, json
@@ -178,12 +183,8 @@ def resolve_okved(code, dadata_token=None):
items = json.load(f)
for item in items:
item = str(item).strip()
# совпадение по началу: «27.32.2» или «27.32.2 - ...» / «27.32.2 ...»
if item.split()[0].rstrip('.') == code.rstrip('.'):
# нормализуем: «код - название»
rest = item[len(item.split()[0]):].strip().lstrip('- ').strip()
if rest:
return f"{code} - {rest}"
# совпадение по коду: «27.32.2» или «27.32.2 Название»
if item.split()[0].rstrip('.') == code_only.rstrip('.'):
return item
except Exception:
pass
@@ -191,7 +192,7 @@ def resolve_okved(code, dadata_token=None):
if dadata_token:
try:
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',
'Authorization': f'Token {dadata_token}'})
if not err and js:
@@ -200,7 +201,12 @@ def resolve_okved(code, dadata_token=None):
return sugg[0]['value']
except Exception:
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):