feat: подбор характеристик объекта КИИ — программа и ИИ

Вкладка «Основное», кнопки под полем типового объекта:
- ⚙️ «Подобрать типовой объект (программа)»: скоринг по совпадению
  значимых слов названия/назначения объекта с перечнем 392 типовых
  отраслевых объектов (Распоряжение 360-р), IDF-веса (редкие слова
  важнее общих) → окно с вариантами, оператор выбирает и применяет
  (заполняет сферу + типовой объект)
- 🤖 «Подобрать характеристики (ИИ)»: DeepSeek предлагает назначение,
  сферу, тип, архитектуру и типовой объект (из перечня 360-р) как
  рекомендацию → окно с полями, оператор подтверждает/правит

Примеры: СКУД → «ИС, АСУ, ИТКС, обеспечивающие контроль и управление
доступом на территорию»; 1С:УПП → «ИС для ведения бухгалтерского учета»
This commit is contained in:
2026-08-04 22:43:04 +04:00
parent b9968449ef
commit dadec02665
3 changed files with 389 additions and 0 deletions
+132
View File
@@ -342,3 +342,135 @@ def auto_fill_security(isys, threats=None):
res['tech_measures'] = '\n'.join(tech)
return res
# ---------------------------------------------------------------
# Подбор типового отраслевого объекта КИИ по характеристикам объекта
# ---------------------------------------------------------------
# Стоп-слова: не несут смысла при сопоставлении названия объекта и типового объекта
STOPWORDS = {
'информационные', 'информационно', 'телекоммуникационные', 'автоматизированные',
'системы', 'система', 'сети', 'сеть', 'обеспечивающие', 'обеспечивающих',
'предназначенные', 'предназначенных', 'управление', 'контроль', 'мониторинг',
'объекта', 'объектов', 'объект', 'объекты', 'кии', 'рф', 'российской',
'федерации', 'исполнительной', 'власти', 'органов', 'организаций', 'организации',
'подведомственных', 'функций', 'функции', 'осуществление', 'полномочий',
'обеспечение', 'процессов', 'процесса', 'процессы', 'данных', 'информации',
'сбор', 'хранение', 'обработка', 'представление', 'использование', 'передача',
'автоматизации', 'средств', 'программных', 'программно', 'аппаратных',
'технических', 'деятельность', 'деятельности', 'осуществляющих',
'размещенные', 'размещенных', 'расположенных', 'используемых', 'используемые',
'проведении', 'проведения', 'выполнении', 'выполнения', 'реализация', 'реализации',
'электронных', 'электронного', 'информационных', 'информационного',
# Общие словоформы, встречающиеся почти у всех типовых объектов:
'контроль', 'контроля', 'контролю', 'контролем', 'контроле',
'управление', 'управления', 'управлению', 'управлением', 'управлении',
'безопасность', 'безопасности', 'безопасностью', 'безопасном',
'обеспечение', 'обеспечения', 'обеспечению', 'обеспечением',
'предназначен', 'предназначена', 'предназначено', 'предназначены',
'предоставление', 'предоставления', 'предоставлению',
'взаимодействие', 'взаимодействия', 'взаимодействию',
'функционирование', 'функционирования', 'функционированию',
'деятельность', 'деятельности', 'деятельностью',
'автоматизирован', 'автоматизированной', 'автоматизированных',
'телекоммуникацион', 'телекоммуникационной', 'телекоммуникационных',
'информационн', 'информационной', 'информационных', 'информационное',
'осуществление', 'осуществления', 'осуществлению',
'для', 'систем', 'системы', 'система', 'ресурсов', 'ресурсы',
'целью', 'цели', 'помощи', 'помощь', 'также', 'или', 'иных', 'иное',
'при', 'на', 'по', 'в', 'с', 'и', 'не', 'то', 'числе',
}
def _tokenize(text):
"""Значимые слова текста (без стоп-слов)."""
import re as _re
words = _re.findall(r'[а-яёa-z0-9]+', (text or '').lower())
return [w for w in words if len(w) > 2 and w not in STOPWORDS]
def _load_all_typical(dict_dir=None):
"""Все типовые отраслевые объекты из typical_industry_objects.json (по сферам)."""
import os as _os
if dict_dir is None:
dict_dir = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), 'dictionaries')
path = _os.path.join(dict_dir, 'typical_industry_objects.json')
try:
with open(path, encoding='utf-8') as f:
data = json.load(f)
except Exception:
return []
out = []
for sphere, items in data.items():
if sphere == 'Иная сфера деятельности':
continue
for it in items:
out.append({'sphere': sphere, 'num': it.get('num', ''),
'name': it.get('name', '')})
return out
_IDF_CACHE = None
def _idf_weights(cands):
"""IDF-веса слов: чем реже слово в перечне типовых объектов, тем больше вес.
Общие слова («информационные», «управление», «контроль») встречаются почти
у всех 392 объектов и почти ничего не значат; редкие («доступом», «СКУД»,
«сигнализации») — сильно различают объекты.
"""
global _IDF_CACHE
if _IDF_CACHE is not None:
return _IDF_CACHE
import math
df = {}
for c in cands:
for w in set(_tokenize(c['name'])):
df[w] = df.get(w, 0) + 1
n = max(len(cands), 1)
weights = {w: math.log((n + 1) / (df.get(w, 0) + 1)) + 1.0 for w in df}
_IDF_CACHE = weights
return weights
def suggest_typical_object_program(isys, limit=10, sphere=None):
"""Подбор типового отраслевого объекта КИИ по характеристикам объекта.
Логика: значимые слова из названия/назначения/описания объекта КИИ
сопоставляются со значимыми словами названий типовых объектов
(перечень Распоряжения ПРФ № 360-р, 392 объекта). Скоринг по IDF:
редкие слова (например, «доступом», «сигнализации») весят больше общих.
Пример: объект «СКУД» / «контроль и управление доступом» →
«ИС, АСУ, ИТКС, обеспечивающие контроль и управление доступом на территорию».
Возвращает список dict: {sphere, num, name, score} — по убыванию релевантности.
"""
text = ' '.join([
getattr(isys, 'name', '') or '',
getattr(isys, 'purpose', '') or '',
getattr(isys, 'description', '') or '',
])
obj_words = set(_tokenize(text))
if not obj_words:
return []
cands = _load_all_typical()
if sphere:
cands = [c for c in cands if c['sphere'] == sphere]
weights = _idf_weights(cands)
scored = []
for c in cands:
tw = set(_tokenize(c['name']))
common = tw & obj_words
if common:
score = sum(weights.get(w, 1.0) for w in common)
scored.append({'sphere': c['sphere'], 'num': c['num'],
'name': c['name'], 'score': round(score, 2),
'matched': sorted(common)})
scored.sort(key=lambda x: (-x['score'], x['name']))
return scored[:limit]