fix: угрозы — фильтр по словарю УБИ для всего от ИИ (мусор отбрасывается, УБИ.069 дополняется названием), «Подобрать угрозы» — окно с галочками (синий — программа, красный — ИИ, зелёный — совпало), «Проверить через ИИ» — вердикт + точечное применение по словарю, возвращена кнопка «Загрузить угрозы ФСТЭК»
This commit is contained in:
+181
-70
@@ -1092,8 +1092,10 @@ class DokoGenApp:
|
|||||||
command=self._suggest_threats_ai_click).pack(side=tk.LEFT, padx=2)
|
command=self._suggest_threats_ai_click).pack(side=tk.LEFT, padx=2)
|
||||||
ttk.Button(thr_actions, text="🤖 Проверить через ИИ",
|
ttk.Button(thr_actions, text="🤖 Проверить через ИИ",
|
||||||
command=self._check_threats_ai_click).pack(side=tk.LEFT, padx=2)
|
command=self._check_threats_ai_click).pack(side=tk.LEFT, padx=2)
|
||||||
ttk.Button(thr_actions, text="⚖️ Сравнить",
|
ttk.Button(thr_actions, text="🎯 Подобрать угрозы (программа+ИИ)",
|
||||||
command=self._compare_threats).pack(side=tk.LEFT, padx=2)
|
command=self._open_threats_picker).pack(side=tk.LEFT, padx=2)
|
||||||
|
ttk.Button(thr_actions, text="⬇️ Загрузить угрозы ФСТЭК",
|
||||||
|
command=self._download_fstec_threats).pack(side=tk.LEFT, padx=2)
|
||||||
self._threats_date_label = ttk.Label(thr_actions, text="", foreground='gray')
|
self._threats_date_label = ttk.Label(thr_actions, text="", foreground='gray')
|
||||||
self._threats_date_label.pack(side=tk.LEFT, padx=8)
|
self._threats_date_label.pack(side=tk.LEFT, padx=8)
|
||||||
|
|
||||||
@@ -2969,13 +2971,12 @@ class DokoGenApp:
|
|||||||
if not threats:
|
if not threats:
|
||||||
self._verify_log("❌ ИИ не вернул угрозы")
|
self._verify_log("❌ ИИ не вернул угрозы")
|
||||||
return
|
return
|
||||||
lb = self.is_threats_listbox
|
# Запоминаем рекомендации ИИ (уже отфильтрованы по словарю УБИ) —
|
||||||
lb.delete(0, tk.END)
|
# не заменяем список молча, а открываем окно подбора с галочками.
|
||||||
for t in threats:
|
self._last_ai_threats = list(threats)
|
||||||
lb.insert(tk.END, t)
|
self.log(f"🤖 ИИ рекомендует угроз: {len(threats)}")
|
||||||
self._collect_is() # сохранить в модель — галочки в словаре будут стоять
|
self._verify_log(f"🤖 ИИ: {len(threats)} угроз (отфильтровано по словарю УБИ)")
|
||||||
self.log(f"🤖 ИИ подобрал угроз: {len(threats)}")
|
self._open_threats_picker()
|
||||||
self._verify_log(f"🤖 ИИ: {len(threats)} угроз")
|
|
||||||
|
|
||||||
def _ask_amount(self, parent, title, prompt, initial=''):
|
def _ask_amount(self, parent, title, prompt, initial=''):
|
||||||
"""Свой диалог ввода суммы: принимает «121 118 270 руб», «1 000» и т.п.
|
"""Свой диалог ввода суммы: принимает «121 118 270 руб», «1 000» и т.п.
|
||||||
@@ -3032,23 +3033,25 @@ class DokoGenApp:
|
|||||||
parent.wait_window(dlg)
|
parent.wait_window(dlg)
|
||||||
return result['val']
|
return result['val']
|
||||||
|
|
||||||
def _compare_threats(self):
|
def _open_threats_picker(self):
|
||||||
"""Сравнить угрозы, подобранные программой и ИИ."""
|
"""🎯 Окно подбора угроз: программа (синий) + ИИ (красный) + совпавшие (зелёный).
|
||||||
|
|
||||||
|
Оператор отмечает галочками нужные угрозы (как в словаре) и жмёт
|
||||||
|
«✅ Применить выбранные» — список угроз заменяется выбранными.
|
||||||
|
По умолчанию отмечены все подобранные (программа + ИИ + текущие),
|
||||||
|
оператор снимает галочки с неугодных.
|
||||||
|
"""
|
||||||
if self._current_is_idx is None:
|
if self._current_is_idx is None:
|
||||||
messagebox.showinfo("Сравнение", "Сначала выберите объект КИИ в списке слева")
|
messagebox.showinfo("Угрозы", "Сначала выберите объект КИИ в списке слева")
|
||||||
return
|
return
|
||||||
self._collect_is()
|
self._collect_is()
|
||||||
isys = self.model.information_systems[self._current_is_idx]
|
isys = self.model.information_systems[self._current_is_idx]
|
||||||
from .threat_rules import suggest_threats_program
|
from .threat_rules import suggest_threats_program
|
||||||
program = suggest_threats_program(isys)
|
program = suggest_threats_program(isys)
|
||||||
current = [str(self.is_threats_listbox.get(i)) for i in range(self.is_threats_listbox.size())]
|
current = [str(self.is_threats_listbox.get(i)) for i in range(self.is_threats_listbox.size())]
|
||||||
if not current:
|
ai = list(getattr(self, '_last_ai_threats', None) or [])
|
||||||
messagebox.showinfo("Сравнение", "Сначала подберите угрозы (программой или ИИ).")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Нормализация строк угроз для сравнения:
|
# Нормализация строк угроз: ключ сравнения — код УБИ (если есть),
|
||||||
# «1. УБИ.001 Угроза…», «УБИ.001 Угроза…» и «УБИ.001 Угроза…» — это одна и та же угроза.
|
|
||||||
# Ключ сравнения — код УБИ, если он есть (ИИ может обрезать/перефразировать название);
|
|
||||||
# иначе — нормализованный текст.
|
# иначе — нормализованный текст.
|
||||||
def _ubi_code(s):
|
def _ubi_code(s):
|
||||||
m = re.search(r'уби\.?\s*(\d+)', str(s).lower())
|
m = re.search(r'уби\.?\s*(\d+)', str(s).lower())
|
||||||
@@ -3063,60 +3066,117 @@ class DokoGenApp:
|
|||||||
code = _ubi_code(s)
|
code = _ubi_code(s)
|
||||||
return ('ubi', code) if code else ('name', _norm(s))
|
return ('ubi', code) if code else ('name', _norm(s))
|
||||||
|
|
||||||
pset_norm = {_key(t) for t in program}
|
# Объединяем все источники: key → {text, src: {program, ai, current}}
|
||||||
cset_norm = {_key(t) for t in current}
|
merged = {}
|
||||||
|
def _add(t, src):
|
||||||
# Сортировка обоих списков по коду УБИ — чтобы совпадающие угрозы
|
if not str(t or '').strip():
|
||||||
# были на одних строках (удобно сверять).
|
return
|
||||||
def _sort_key(t):
|
k = _key(t)
|
||||||
code = _ubi_code(t)
|
if k not in merged:
|
||||||
return (0, int(code)) if code else (1, _norm(t))
|
merged[k] = {'text': str(t).strip(), 'src': set()}
|
||||||
program = sorted(program, key=_sort_key)
|
merged[k]['src'].add(src)
|
||||||
current = sorted(current, key=_sort_key)
|
|
||||||
|
|
||||||
# Показываем окно: слева подобрано программой, справа текущий список
|
|
||||||
dlg = tk.Toplevel(self.root)
|
|
||||||
dlg.title("Сравнение угроз: программа ↔ ИИ/текущий")
|
|
||||||
dlg.geometry("1400x700")
|
|
||||||
dlg.transient(self.root)
|
|
||||||
|
|
||||||
frm = ttk.Frame(dlg, padding=8)
|
|
||||||
frm.pack(fill=tk.BOTH, expand=True)
|
|
||||||
|
|
||||||
# Два списка на ~95% ширины окна (по ~47% + отступы)
|
|
||||||
left = ttk.Frame(frm)
|
|
||||||
left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
||||||
ttk.Label(left, text=f"⚙️ Программа ({len(program)}) — отсортировано по УБИ",
|
|
||||||
font=('Arial', 9, 'bold')).pack(anchor=tk.W)
|
|
||||||
lb_p = tk.Listbox(left, height=22, width=70, font=('Courier', 9))
|
|
||||||
lb_p.pack(fill=tk.BOTH, expand=True, pady=4)
|
|
||||||
for t in program:
|
for t in program:
|
||||||
lb_p.insert(tk.END, t)
|
_add(t, 'program')
|
||||||
|
|
||||||
right = ttk.Frame(frm)
|
|
||||||
right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0))
|
|
||||||
ttk.Label(right, text=f"🤖 ИИ / текущий ({len(current)}) — отсортировано по УБИ",
|
|
||||||
font=('Arial', 9, 'bold')).pack(anchor=tk.W)
|
|
||||||
lb_c = tk.Listbox(right, height=22, width=70, font=('Courier', 9))
|
|
||||||
lb_c.pack(fill=tk.BOTH, expand=True, pady=4)
|
|
||||||
for t in current:
|
for t in current:
|
||||||
lb_c.insert(tk.END, t)
|
_add(t, 'current')
|
||||||
|
for t in ai:
|
||||||
|
_add(t, 'ai')
|
||||||
|
|
||||||
# Подсветка совпадений (по коду УБИ / нормализованному виду)
|
items = sorted(merged.values(),
|
||||||
common = pset_norm & cset_norm
|
key=lambda v: (0, int(_ubi_code(v['text'])))
|
||||||
for i in range(lb_p.size()):
|
if _ubi_code(v['text']) else (1, _norm(v['text'])))
|
||||||
if _key(lb_p.get(i)) in common:
|
|
||||||
lb_p.itemconfig(i, bg='#c8e6c9')
|
|
||||||
for i in range(lb_c.size()):
|
|
||||||
if _key(lb_c.get(i)) in common:
|
|
||||||
lb_c.itemconfig(i, bg='#c8e6c9')
|
|
||||||
|
|
||||||
info = ttk.Label(frm,
|
# Окно с чекбоксами (как выбор из словаря)
|
||||||
text=f"Совпало угроз: {len(common)} (зелёным). "
|
dlg = tk.Toplevel(self.root)
|
||||||
f"Только программа: {len(program) - len(common)}, "
|
dlg.title(f"Подбор угроз: {isys.name or ''}")
|
||||||
f"только ИИ/текущий: {len(current) - len(common)}.",
|
dlg.geometry("980x700")
|
||||||
|
dlg.transient(self.root)
|
||||||
|
dlg.grab_set()
|
||||||
|
|
||||||
|
frame = ttk.Frame(dlg, padding=10)
|
||||||
|
frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
legend = ttk.Label(frame,
|
||||||
|
text="🟦 синий — рекомендация программы | 🟥 красный — рекомендация ИИ | "
|
||||||
|
"🟩 зелёный — совпало | ⬜ белый — уже было в списке",
|
||||||
foreground='gray')
|
foreground='gray')
|
||||||
info.pack(anchor=tk.W, pady=(4, 0))
|
legend.pack(anchor=tk.W, pady=(0, 5))
|
||||||
|
|
||||||
|
# Поиск
|
||||||
|
search_var = tk.StringVar()
|
||||||
|
search_entry = ttk.Entry(frame, textvariable=search_var)
|
||||||
|
search_entry.pack(fill=tk.X, pady=(0, 5))
|
||||||
|
search_entry.focus_set()
|
||||||
|
|
||||||
|
canvas = tk.Canvas(frame, highlightthickness=0)
|
||||||
|
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=canvas.yview)
|
||||||
|
check_frame = ttk.Frame(canvas)
|
||||||
|
check_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||||
|
canvas.create_window((0, 0), window=check_frame, anchor="nw")
|
||||||
|
canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
vars = {}
|
||||||
|
widgets = {}
|
||||||
|
for v in items:
|
||||||
|
src = v['src']
|
||||||
|
has_p = 'program' in src
|
||||||
|
has_a = 'ai' in src
|
||||||
|
if has_p and has_a:
|
||||||
|
bg = '#c8e6c9' # зелёный — совпало
|
||||||
|
elif has_p:
|
||||||
|
bg = '#b3d4fc' # синий — только программа
|
||||||
|
elif has_a:
|
||||||
|
bg = '#ffd0d0' # красный — только ИИ
|
||||||
|
else:
|
||||||
|
bg = '#ffffff' # белый — только текущий список
|
||||||
|
var = tk.BooleanVar(value=True) # по умолчанию отмечено всё
|
||||||
|
cb = tk.Checkbutton(check_frame, text=v['text'], variable=var, anchor=tk.W,
|
||||||
|
bg=bg, activebackground=bg, selectcolor='white',
|
||||||
|
justify=tk.LEFT, wraplength=900)
|
||||||
|
cb.pack(fill=tk.X, pady=1)
|
||||||
|
vars[v['text']] = var
|
||||||
|
widgets[v['text']] = cb
|
||||||
|
|
||||||
|
def _apply_search(*_):
|
||||||
|
q = search_var.get().strip().lower()
|
||||||
|
for text, cb in widgets.items():
|
||||||
|
if q and q not in text.lower():
|
||||||
|
cb.pack_forget()
|
||||||
|
else:
|
||||||
|
cb.pack(fill=tk.X, pady=1)
|
||||||
|
search_var.trace_add('write', _apply_search)
|
||||||
|
|
||||||
|
btn_frame = ttk.Frame(dlg, padding=10)
|
||||||
|
btn_frame.pack(fill=tk.X)
|
||||||
|
|
||||||
|
def _select_all():
|
||||||
|
for var in vars.values():
|
||||||
|
var.set(True)
|
||||||
|
|
||||||
|
def _clear_all():
|
||||||
|
for var in vars.values():
|
||||||
|
var.set(False)
|
||||||
|
|
||||||
|
def _apply():
|
||||||
|
selected = [t for t, var in vars.items() if var.get()]
|
||||||
|
selected.sort(key=lambda s: (0, int(_ubi_code(s)))
|
||||||
|
if _ubi_code(s) else (1, _norm(s)))
|
||||||
|
lb = self.is_threats_listbox
|
||||||
|
lb.delete(0, tk.END)
|
||||||
|
for t in selected:
|
||||||
|
lb.insert(tk.END, t)
|
||||||
|
self._collect_is()
|
||||||
|
self.log(f"🎯 Применено угроз: {len(selected)}")
|
||||||
|
dlg.destroy()
|
||||||
|
|
||||||
|
ttk.Button(btn_frame, text="Выбрать всё", command=_select_all).pack(side=tk.LEFT, padx=2)
|
||||||
|
ttk.Button(btn_frame, text="Снять всё", command=_clear_all).pack(side=tk.LEFT, padx=2)
|
||||||
|
ttk.Button(btn_frame, text="✅ Применить выбранные", command=_apply).pack(side=tk.RIGHT, padx=2)
|
||||||
|
ttk.Button(btn_frame, text="Отмена", command=dlg.destroy).pack(side=tk.RIGHT, padx=2)
|
||||||
|
|
||||||
|
dlg.wait_window()
|
||||||
|
|
||||||
def _check_threats_ai_click(self):
|
def _check_threats_ai_click(self):
|
||||||
"""🤖 Проверить текущий список угроз через ИИ (не подбирать заново).
|
"""🤖 Проверить текущий список угроз через ИИ (не подбирать заново).
|
||||||
@@ -3200,14 +3260,65 @@ class DokoGenApp:
|
|||||||
|
|
||||||
dlg = tk.Toplevel(self.root)
|
dlg = tk.Toplevel(self.root)
|
||||||
dlg.title(f"Проверка угроз ИИ — {obj_name}")
|
dlg.title(f"Проверка угроз ИИ — {obj_name}")
|
||||||
dlg.geometry("820x560")
|
dlg.geometry("860x600")
|
||||||
dlg.transient(self.root)
|
dlg.transient(self.root)
|
||||||
txt = tk.Text(dlg, wrap=tk.WORD, font=('Arial', 10))
|
txt = tk.Text(dlg, wrap=tk.WORD, font=('Arial', 10))
|
||||||
txt.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
|
txt.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
|
||||||
txt.insert('1.0', '\n'.join(lines))
|
txt.insert('1.0', '\n'.join(lines))
|
||||||
txt.config(state=tk.DISABLED)
|
txt.config(state=tk.DISABLED)
|
||||||
ttk.Button(dlg, text="Закрыть",
|
|
||||||
command=dlg.destroy).pack(pady=(0, 8))
|
def _ubi_code(s):
|
||||||
|
m = re.search(r'уби\.?\s*(\d+)', str(s).lower())
|
||||||
|
return m.group(1) if m else None
|
||||||
|
|
||||||
|
def _add_missing():
|
||||||
|
"""Добавить недостающие (только из словаря УБИ, полные названия)."""
|
||||||
|
lb = self.is_threats_listbox
|
||||||
|
existing = {(_ubi_code(lb.get(i)) or lb.get(i).strip().lower())
|
||||||
|
for i in range(lb.size())}
|
||||||
|
added = 0
|
||||||
|
for t in missing:
|
||||||
|
code = _ubi_code(t)
|
||||||
|
key = code or str(t).strip().lower()
|
||||||
|
if key in existing:
|
||||||
|
continue
|
||||||
|
lb.insert(tk.END, t)
|
||||||
|
existing.add(key)
|
||||||
|
added += 1
|
||||||
|
if added:
|
||||||
|
self._collect_is()
|
||||||
|
self.log(f"➕ Добавлено недостающих угроз (по словарю): {added}")
|
||||||
|
else:
|
||||||
|
self.log("➕ Недостающие уже есть в списке — ничего не добавлено")
|
||||||
|
dlg.destroy()
|
||||||
|
|
||||||
|
def _remove_extra():
|
||||||
|
"""Убрать лишние (только те, что реально есть в списке, по коду УБИ)."""
|
||||||
|
lb = self.is_threats_listbox
|
||||||
|
extra_codes = {c for c in (_ubi_code(t) for t in extra) if c}
|
||||||
|
removed = 0
|
||||||
|
for i in range(lb.size() - 1, -1, -1):
|
||||||
|
code = _ubi_code(lb.get(i))
|
||||||
|
if code and code in extra_codes:
|
||||||
|
lb.delete(i)
|
||||||
|
removed += 1
|
||||||
|
if removed:
|
||||||
|
self._collect_is()
|
||||||
|
self.log(f"➖ Убрано лишних угроз: {removed}")
|
||||||
|
else:
|
||||||
|
self.log("➖ Лишние не найдены в списке — ничего не убрано")
|
||||||
|
dlg.destroy()
|
||||||
|
|
||||||
|
btns = ttk.Frame(dlg)
|
||||||
|
btns.pack(pady=(0, 8))
|
||||||
|
if missing:
|
||||||
|
ttk.Button(btns, text="➕ Добавить недостающие (по словарю)",
|
||||||
|
command=_add_missing).pack(side=tk.LEFT, padx=4)
|
||||||
|
if extra:
|
||||||
|
ttk.Button(btns, text="➖ Убрать лишние",
|
||||||
|
command=_remove_extra).pack(side=tk.LEFT, padx=4)
|
||||||
|
ttk.Button(btns, text="Закрыть",
|
||||||
|
command=dlg.destroy).pack(side=tk.LEFT, padx=4)
|
||||||
self.log(f"🤖 Проверка угроз ИИ: лишних {len(extra)}, недостающих {len(missing)}")
|
self.log(f"🤖 Проверка угроз ИИ: лишних {len(extra)}, недостающих {len(missing)}")
|
||||||
self._verify_log('\n'.join(lines))
|
self._verify_log('\n'.join(lines))
|
||||||
|
|
||||||
|
|||||||
+69
-1
@@ -525,6 +525,66 @@ def compare_company_values(old_data, new_data):
|
|||||||
return diffs
|
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,
|
def suggest_threats_ai(security_data, api_key, model='deepseek-chat', base_url=None,
|
||||||
threats=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):
|
if isinstance(threats_out, str):
|
||||||
threats_out = [threats_out]
|
threats_out = [threats_out]
|
||||||
threats_out = [str(t).strip() for t in threats_out if str(t).strip()]
|
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
|
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)
|
js, err = _ai_json(api_key, model, base_url, system, user)
|
||||||
if err:
|
if err:
|
||||||
return None, 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,
|
def suggest_object_params_ai(object_data, api_key, model='deepseek-chat', base_url=None,
|
||||||
|
|||||||
Reference in New Issue
Block a user