fix: угрозы — фильтр по словарю УБИ для всего от ИИ (мусор отбрасывается, УБИ.069 дополняется названием), «Подобрать угрозы» — окно с галочками (синий — программа, красный — ИИ, зелёный — совпало), «Проверить через ИИ» — вердикт + точечное применение по словарю, возвращена кнопка «Загрузить угрозы ФСТЭК»
This commit is contained in:
+69
-1
@@ -525,6 +525,66 @@ def compare_company_values(old_data, new_data):
|
||||
return diffs
|
||||
|
||||
|
||||
def _ubi_code(text):
|
||||
"""Извлекает код УБИ из строки: «УБИ.069 Название» → '069'."""
|
||||
m = re.search(r'уби\.?\s*(\d+)', str(text), re.IGNORECASE)
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def _norm_threat_text(text):
|
||||
"""Нормализация названия угрозы для сравнения: без ведущего номера, лишних пробелов."""
|
||||
s = re.sub(r'^\d+[.)]?\s*', '', str(text or '').strip())
|
||||
s = re.sub(r'\s+', ' ', s).strip()
|
||||
return s.lower()
|
||||
|
||||
|
||||
def filter_threats_by_dict(lines, threats_full=None):
|
||||
"""Приводит строки угроз (от ИИ или оператора) к каноническому виду по словарю УБИ.
|
||||
|
||||
Для каждой строки:
|
||||
- есть код УБИ → берём полную запись «УБИ.xxx Название» из словаря;
|
||||
- кода нет → ищем по названию (точное совпадение или вхождение);
|
||||
- не нашлось в словаре → отбрасываем (мусор от ИИ).
|
||||
|
||||
Возвращает уникальные канонические строки, отсортированные по коду УБИ.
|
||||
"""
|
||||
if threats_full is None:
|
||||
from .threat_rules import load_threats
|
||||
threats_full = load_threats()
|
||||
by_code = {}
|
||||
by_norm = {}
|
||||
for t in threats_full:
|
||||
t = str(t).strip()
|
||||
code = _ubi_code(t)
|
||||
if code:
|
||||
by_code.setdefault(code, t)
|
||||
by_norm.setdefault(_norm_threat_text(t), t)
|
||||
out = []
|
||||
seen = set()
|
||||
for line in lines or []:
|
||||
line = str(line or '').strip()
|
||||
if not line:
|
||||
continue
|
||||
code = _ubi_code(line)
|
||||
canon = None
|
||||
if code and code in by_code:
|
||||
canon = by_code[code]
|
||||
else:
|
||||
n = _norm_threat_text(line)
|
||||
if n in by_norm:
|
||||
canon = by_norm[n]
|
||||
else:
|
||||
for k, v in by_norm.items():
|
||||
if n and (n in k or k in n):
|
||||
canon = v
|
||||
break
|
||||
if canon and canon not in seen:
|
||||
seen.add(canon)
|
||||
out.append(canon)
|
||||
out.sort(key=lambda t: (int(_ubi_code(t)) if _ubi_code(t) else 10 ** 9, t))
|
||||
return out
|
||||
|
||||
|
||||
def suggest_threats_ai(security_data, api_key, model='deepseek-chat', base_url=None,
|
||||
threats=None):
|
||||
"""ИИ предлагает актуальные угрозы БИ для объекта КИИ по его характеристикам.
|
||||
@@ -571,6 +631,9 @@ def suggest_threats_ai(security_data, api_key, model='deepseek-chat', base_url=N
|
||||
if isinstance(threats_out, str):
|
||||
threats_out = [threats_out]
|
||||
threats_out = [str(t).strip() for t in threats_out if str(t).strip()]
|
||||
# Жёсткий фильтр по словарю: мусор отбрасываем, «УБИ.069» дополняем
|
||||
# полным названием из актуального перечня (если он передан).
|
||||
threats_out = filter_threats_by_dict(threats_out, threats)
|
||||
return threats_out, None
|
||||
|
||||
|
||||
@@ -606,7 +669,12 @@ def check_threats_ai(security_data, current_threats, api_key, model='deepseek-ch
|
||||
js, err = _ai_json(api_key, model, base_url, system, user)
|
||||
if err:
|
||||
return None, err
|
||||
return js, None
|
||||
# «Недостающие»/«лишние» — тоже через словарь: мусор отбрасываем,
|
||||
# неполные строки («УБИ.069») дополняем полными названиями.
|
||||
missing = filter_threats_by_dict(js.get('недостающие') or [])
|
||||
extra = filter_threats_by_dict(js.get('лишние') or [])
|
||||
comment = js.get('комментарий') or ''
|
||||
return {'лишние': extra, 'недостающие': missing, 'комментарий': comment}, None
|
||||
|
||||
|
||||
def suggest_object_params_ai(object_data, api_key, model='deepseek-chat', base_url=None,
|
||||
|
||||
Reference in New Issue
Block a user