fix: угрозы — фильтр по словарю УБИ для всего от ИИ (мусор отбрасывается, УБИ.069 дополняется названием), «Подобрать угрозы» — окно с галочками (синий — программа, красный — ИИ, зелёный — совпало), «Проверить через ИИ» — вердикт + точечное применение по словарю, возвращена кнопка «Загрузить угрозы ФСТЭК»

This commit is contained in:
LocoAgent
2026-08-05 11:03:28 +04:00
parent 033c21fccc
commit a8fa3f908c
2 changed files with 251 additions and 72 deletions
+69 -1
View File
@@ -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,