fix: окна авторасчёта/сравнения не обрезают текст; СЗИ перенесены в «Безопасность»; ИИ-рекомендации по СЗИ; ИИ-проверка категорий ПП 127; пояснения к полям 8/9; упрощение панели угроз
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"system": "Ты — эксперт по категорированию объектов критической информационной инфраструктуры (187-ФЗ, Постановление Правительства РФ № 127). Проверь корректность присвоенных категорий значимости по показателям критериев. Категория значимости объекта определяется по максимальной из категорий показателей: I — если хотя бы один показатель I; II — если есть II и нет I; III — если есть III и нет I/II; «—» — если показатели не присвоены. Учитывай сферу деятельности субъекта и применяемость показателей (показатель 8 — госкорпорации/ГУП/госкомпании/стратегические АО; показатель 9 — все субъекты; показатель 10 — финансовый рынок/банки; показатель 13 — оборонно-промышленный комплекс).",
|
||||||
|
"response_format": "Верни ТОЛЬКО JSON: {\"оценка\": \"ok|warning|error\", \"замечания\": [{\"показатель\": \"номер\", \"категория\": \"текущая\", \"комментарий\": \"что не так и как должно быть\"}], \"предложения\": {\"criteria_values\": \"исправленный список показателей с категориями\", \"criteria_justification\": \"исправленное обоснование\"}, \"итоговая_категория\": \"I|II|III|—\"}. В «предложениях» — только если нужно что-то исправить, иначе пустые строки."
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"system": "Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, приказ ФСТЭК России № 239 и № 236). По категории значимости объекта КИИ, актуальным угрозам и уже выбранным средствам защиты информации (СЗИ) определи, каких средств не хватает, и дай рекомендации. Учитывай требования по категориям: для I категории — максимальный набор (антивирус, СКЗИ, SIEM, межсетевые экраны, средства анти-APT и т.д.), для II — расширенный набор, для III — базовый набор. Выбирай СЗИ ТОЛЬКО из реально существующих классов средств защиты (антивирусная защита, СКЗИ, межсетевой экран, SIEM, DLP, IDS/IPS, средства доверенной загрузки, контроля целостности, управления доступом и т.п.).",
|
||||||
|
"response_format": "Верни ТОЛЬКО JSON: {\"рекомендации\": [{\"сзи\": \"название класса/средства\", \"зачем\": \"для чего нужно\"}], \"общие_советы\": [\"...\"], \"достаточно\": true|false}. Поле «достаточно» — true, если текущий набор СЗИ покрывает требования категории. Не придумывай конкретные торговые марки, если их нет в списке — указывай класс средства."
|
||||||
|
}
|
||||||
+238
-21
@@ -1076,13 +1076,11 @@ class DokoGenApp:
|
|||||||
|
|
||||||
thr_actions = ttk.Frame(sec_threats)
|
thr_actions = ttk.Frame(sec_threats)
|
||||||
thr_actions.pack(fill=tk.X, pady=(0, 6))
|
thr_actions.pack(fill=tk.X, pady=(0, 6))
|
||||||
ttk.Button(thr_actions, text="⬇️ Загрузить угрозы ФСТЭК",
|
ttk.Button(thr_actions, text="⚡ Подобрать угрозы автоматически",
|
||||||
command=self._download_fstec_threats).pack(side=tk.LEFT, padx=2)
|
|
||||||
ttk.Button(thr_actions, text="⚙️ Подобрать (программа)",
|
|
||||||
command=self._suggest_threats_program_click).pack(side=tk.LEFT, padx=2)
|
command=self._suggest_threats_program_click).pack(side=tk.LEFT, padx=2)
|
||||||
ttk.Button(thr_actions, text="🤖 Подобрать (ИИ)",
|
ttk.Button(thr_actions, text="🤖 Подобрать через ИИ",
|
||||||
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._compare_threats).pack(side=tk.LEFT, padx=2)
|
command=self._compare_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)
|
||||||
@@ -1127,8 +1125,12 @@ class DokoGenApp:
|
|||||||
'is_incidents_listbox', 'incidents.json', height=3)
|
'is_incidents_listbox', 'incidents.json', height=3)
|
||||||
_sec_row(sec_inc, "Показатели критериев значимости:",
|
_sec_row(sec_inc, "Показатели критериев значимости:",
|
||||||
'is_criteria_values_listbox', 'criteria_indicators.json', height=3)
|
'is_criteria_values_listbox', 'criteria_indicators.json', height=3)
|
||||||
ttk.Button(sec_inc, text="📋 Заполнить по таблице ПП № 127",
|
btns_inc = ttk.Frame(sec_inc)
|
||||||
command=self._open_criteria_127_dialog).pack(anchor=tk.W, pady=(0, 4))
|
btns_inc.pack(anchor=tk.W, pady=(0, 4))
|
||||||
|
ttk.Button(btns_inc, text="📋 Заполнить по таблице ПП № 127",
|
||||||
|
command=self._open_criteria_127_dialog).pack(side=tk.LEFT, padx=2)
|
||||||
|
ttk.Button(btns_inc, text="🤖 Проверить категории через ИИ",
|
||||||
|
command=self._check_criteria_ai).pack(side=tk.LEFT, padx=2)
|
||||||
_sec_row(sec_inc, "Обоснование значений показателей:",
|
_sec_row(sec_inc, "Обоснование значений показателей:",
|
||||||
'is_criteria_justification_listbox', None, height=3)
|
'is_criteria_justification_listbox', None, height=3)
|
||||||
|
|
||||||
@@ -1141,15 +1143,17 @@ class DokoGenApp:
|
|||||||
_sec_row(sec_meas, "Технические меры:",
|
_sec_row(sec_meas, "Технические меры:",
|
||||||
'is_tech_measures_listbox', 'tech_measures.json', height=5, allow_add=True)
|
'is_tech_measures_listbox', 'tech_measures.json', height=5, allow_add=True)
|
||||||
|
|
||||||
# ===== Вкладка «Средства защиты» =====
|
# --- Подвкладка 5: Средства защиты (СЗИ) — внутри «Безопасности» ---
|
||||||
def_tab = ttk.Frame(nb2)
|
sec_def = ttk.Frame(nb_sec, padding=8)
|
||||||
nb2.add(def_tab, text="Средства защиты")
|
nb_sec.add(sec_def, text="Средства защиты (СЗИ)")
|
||||||
def_btn_frame = ttk.Frame(def_tab)
|
def_btn_frame = ttk.Frame(sec_def)
|
||||||
def_btn_frame.pack(fill=tk.X)
|
def_btn_frame.pack(fill=tk.X, pady=(0, 4))
|
||||||
ttk.Button(def_btn_frame, text="+ Добавить", command=self._add_defense_item).pack(side=tk.LEFT, padx=2)
|
ttk.Button(def_btn_frame, text="+ Добавить", command=self._add_defense_item).pack(side=tk.LEFT, padx=2)
|
||||||
ttk.Button(def_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.defense_tools_listbox)).pack(side=tk.LEFT, padx=2)
|
ttk.Button(def_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.defense_tools_listbox)).pack(side=tk.LEFT, padx=2)
|
||||||
ttk.Button(def_btn_frame, text="Выбрать из списка", command=self._select_from_defense_list).pack(side=tk.LEFT, padx=2)
|
ttk.Button(def_btn_frame, text="Выбрать из списка", command=self._select_from_defense_list).pack(side=tk.LEFT, padx=2)
|
||||||
self.defense_tools_listbox = tk.Listbox(def_tab, height=6)
|
ttk.Button(def_btn_frame, text="🤖 Рекомендации ИИ по СЗИ",
|
||||||
|
command=self._suggest_defense_ai).pack(side=tk.LEFT, padx=2)
|
||||||
|
self.defense_tools_listbox = tk.Listbox(sec_def, height=6)
|
||||||
self.defense_tools_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
self.defense_tools_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
# ===== Вкладка «Пользователи ОКИИ» =====
|
# ===== Вкладка «Пользователи ОКИИ» =====
|
||||||
@@ -2197,7 +2201,7 @@ class DokoGenApp:
|
|||||||
# grab_set + lift — чтобы окно не уходило на задний план
|
# grab_set + lift — чтобы окно не уходило на задний план
|
||||||
w = tk.Toplevel(dialog)
|
w = tk.Toplevel(dialog)
|
||||||
w.title(f"🧮 Авторасчёт показателя {num} — {name[:60]}")
|
w.title(f"🧮 Авторасчёт показателя {num} — {name[:60]}")
|
||||||
w.geometry("820x700")
|
w.geometry("980x760")
|
||||||
w.transient(dialog)
|
w.transient(dialog)
|
||||||
w.lift()
|
w.lift()
|
||||||
w.focus_force()
|
w.focus_force()
|
||||||
@@ -2223,8 +2227,8 @@ class DokoGenApp:
|
|||||||
foreground='gray', wraplength=760, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
foreground='gray', wraplength=760, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
||||||
|
|
||||||
def _row(label, key, default=''):
|
def _row(label, key, default=''):
|
||||||
ttk.Label(frm, text=label).pack(anchor=tk.W, pady=(4, 0))
|
ttk.Label(frm, text=label, wraplength=900, justify=tk.LEFT).pack(anchor=tk.W, pady=(4, 0))
|
||||||
e = ttk.Entry(frm, width=70)
|
e = ttk.Entry(frm, width=80)
|
||||||
e.insert(0, str(default or ''))
|
e.insert(0, str(default or ''))
|
||||||
e.pack(fill=tk.X)
|
e.pack(fill=tk.X)
|
||||||
return e
|
return e
|
||||||
@@ -2232,8 +2236,14 @@ class DokoGenApp:
|
|||||||
e_profit = _row("Прибыль субъекта КИИ за прошедший год (руб.):", 'profit', profit)
|
e_profit = _row("Прибыль субъекта КИИ за прошедший год (руб.):", 'profit', profit)
|
||||||
e_tax = _row("Налог на прибыль за прошедший год (руб.):", 'tax', profit_tax)
|
e_tax = _row("Налог на прибыль за прошедший год (руб.):", 'tax', profit_tax)
|
||||||
e_dincome = _row("Снижение уровня доходов ΔДоходов (руб., наихудший сценарий):", 'dincome', '')
|
e_dincome = _row("Снижение уровня доходов ΔДоходов (руб., наихудший сценарий):", 'dincome', '')
|
||||||
|
ttk.Label(frm, text="⚠️ ΔДоходов — экспертная оценка снижения доходов субъекта при наихудшем сценарии "
|
||||||
|
"нарушения (в опроснике её нет, вводится вручную; можно приравнять к ущербу из показателя 8).",
|
||||||
|
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
|
||||||
e_budget = _row("Доход федерального бюджета, усреднённый за 3 года Бусредн (руб.):", 'budget',
|
e_budget = _row("Доход федерального бюджета, усреднённый за 3 года Бусредн (руб.):", 'budget',
|
||||||
'21228053357700')
|
'21228053357700')
|
||||||
|
ttk.Label(frm, text="⚠️ Бусредн — средний доход федерального бюджета за 3 года (данные Минфина, "
|
||||||
|
"подставлено значение по умолчанию; при необходимости уточните).",
|
||||||
|
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
|
||||||
ttk.Label(frm, text="Прочие составляющие ΔБ (если применимы, иначе 0):",
|
ttk.Label(frm, text="Прочие составляющие ΔБ (если применимы, иначе 0):",
|
||||||
font=('', 9, 'bold')).pack(anchor=tk.W, pady=(8, 0))
|
font=('', 9, 'bold')).pack(anchor=tk.W, pady=(8, 0))
|
||||||
e_ndpi = _row("Снижение выплат от налога на добычу ПИ ΔНДПИ (руб.):", 'ndpi', '0')
|
e_ndpi = _row("Снижение выплат от налога на добычу ПИ ΔНДПИ (руб.):", 'ndpi', '0')
|
||||||
@@ -2250,8 +2260,8 @@ class DokoGenApp:
|
|||||||
foreground='gray', wraplength=760, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
foreground='gray', wraplength=760, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
||||||
|
|
||||||
def _row(label, key, default=''):
|
def _row(label, key, default=''):
|
||||||
ttk.Label(frm, text=label).pack(anchor=tk.W, pady=(4, 0))
|
ttk.Label(frm, text=label, wraplength=900, justify=tk.LEFT).pack(anchor=tk.W, pady=(4, 0))
|
||||||
e = ttk.Entry(frm, width=70)
|
e = ttk.Entry(frm, width=80)
|
||||||
e.insert(0, str(default or ''))
|
e.insert(0, str(default or ''))
|
||||||
e.pack(fill=tk.X)
|
e.pack(fill=tk.X)
|
||||||
return e
|
return e
|
||||||
@@ -2264,8 +2274,14 @@ class DokoGenApp:
|
|||||||
_d_loss = _num(getattr(self.model, 'income_loss', '') or '')
|
_d_loss = _num(getattr(self.model, 'income_loss', '') or '')
|
||||||
e_dincome = _row("Суммарный экономический ущерб от нарушения критических процессов (руб.):",
|
e_dincome = _row("Суммарный экономический ущерб от нарушения критических процессов (руб.):",
|
||||||
'dincome', f"{_d_loss:,.0f}" if _d_loss else '')
|
'dincome', f"{_d_loss:,.0f}" if _d_loss else '')
|
||||||
|
ttk.Label(frm, text="⚠️ Ущерб — поле «Суммарный ущерб (показатель 8)» из вкладки «Организация» "
|
||||||
|
"(подставлено автоматически, можно поправить).",
|
||||||
|
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
|
||||||
e_budget = _row("Доходы субъекта КИИ, усреднённые за 5 лет (руб.):",
|
e_budget = _row("Доходы субъекта КИИ, усреднённые за 5 лет (руб.):",
|
||||||
'budget', f"{_d_avg:,.0f}" if _d_avg else '')
|
'budget', f"{_d_avg:,.0f}" if _d_avg else '')
|
||||||
|
ttk.Label(frm, text="⚠️ Доходы за 5 лет — среднее по полям «Доход за …» из вкладки «Организация» "
|
||||||
|
"(подставлено автоматически, можно поправить).",
|
||||||
|
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
|
||||||
result = tk.Text(frm, height=8, wrap=tk.WORD, state=tk.NORMAL)
|
result = tk.Text(frm, height=8, wrap=tk.WORD, state=tk.NORMAL)
|
||||||
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
|
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
|
||||||
|
|
||||||
@@ -2934,7 +2950,7 @@ class DokoGenApp:
|
|||||||
# Показываем окно: слева подобрано программой, справа текущий список
|
# Показываем окно: слева подобрано программой, справа текущий список
|
||||||
dlg = tk.Toplevel(self.root)
|
dlg = tk.Toplevel(self.root)
|
||||||
dlg.title("Сравнение угроз: программа ↔ ИИ/текущий")
|
dlg.title("Сравнение угроз: программа ↔ ИИ/текущий")
|
||||||
dlg.geometry("780x460")
|
dlg.geometry("960x600")
|
||||||
dlg.transient(self.root)
|
dlg.transient(self.root)
|
||||||
|
|
||||||
frm = ttk.Frame(dlg, padding=8)
|
frm = ttk.Frame(dlg, padding=8)
|
||||||
@@ -2943,7 +2959,7 @@ class DokoGenApp:
|
|||||||
left = ttk.Frame(frm)
|
left = ttk.Frame(frm)
|
||||||
left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
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)
|
ttk.Label(left, text=f"⚙️ Программа ({len(program)})", font=('Arial', 9, 'bold')).pack(anchor=tk.W)
|
||||||
lb_p = tk.Listbox(left, height=14)
|
lb_p = tk.Listbox(left, height=18)
|
||||||
lb_p.pack(fill=tk.BOTH, expand=True, pady=4)
|
lb_p.pack(fill=tk.BOTH, expand=True, pady=4)
|
||||||
for t in program:
|
for t in program:
|
||||||
lb_p.insert(tk.END, t)
|
lb_p.insert(tk.END, t)
|
||||||
@@ -2951,7 +2967,7 @@ class DokoGenApp:
|
|||||||
right = ttk.Frame(frm)
|
right = ttk.Frame(frm)
|
||||||
right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0))
|
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)
|
ttk.Label(right, text=f"🤖 ИИ / текущий ({len(current)})", font=('Arial', 9, 'bold')).pack(anchor=tk.W)
|
||||||
lb_c = tk.Listbox(right, height=14)
|
lb_c = tk.Listbox(right, height=18)
|
||||||
lb_c.pack(fill=tk.BOTH, expand=True, pady=4)
|
lb_c.pack(fill=tk.BOTH, expand=True, pady=4)
|
||||||
for t in current:
|
for t in current:
|
||||||
lb_c.insert(tk.END, t)
|
lb_c.insert(tk.END, t)
|
||||||
@@ -3290,6 +3306,207 @@ class DokoGenApp:
|
|||||||
dialog.wait_window()
|
dialog.wait_window()
|
||||||
return result.get('val')
|
return result.get('val')
|
||||||
|
|
||||||
|
def _suggest_defense_ai(self):
|
||||||
|
"""🤖 Рекомендации ИИ по средствам защиты (чего не хватает для категории)."""
|
||||||
|
if self._current_is_idx is None:
|
||||||
|
messagebox.showinfo("СЗИ", "Сначала выберите объект КИИ в списке слева")
|
||||||
|
return
|
||||||
|
settings = load_api_settings()
|
||||||
|
if not settings.get('ai_api_key'):
|
||||||
|
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||||
|
return
|
||||||
|
self._collect_is()
|
||||||
|
isys = self.model.information_systems[self._current_is_idx]
|
||||||
|
data = {
|
||||||
|
'name': isys.name or '',
|
||||||
|
'category': isys.category or '',
|
||||||
|
'sphere': isys.sphere or '',
|
||||||
|
'object_type': isys.object_type or '',
|
||||||
|
'threats': isys.threats or '',
|
||||||
|
'attacker_category': isys.attacker_category or '',
|
||||||
|
'defense_tools': list(self.defense_tools_listbox.get(0, tk.END)) or [],
|
||||||
|
}
|
||||||
|
self._verify_log("🤖 Запрос к ИИ: рекомендации по СЗИ…")
|
||||||
|
self.log(f"🤖 Запрос к ИИ: СЗИ для «{isys.name}» (категория {isys.category or '—'})")
|
||||||
|
|
||||||
|
def _run():
|
||||||
|
try:
|
||||||
|
from .verify import suggest_defense_ai
|
||||||
|
res, err = suggest_defense_ai(
|
||||||
|
data, settings['ai_api_key'], settings.get('ai_model'),
|
||||||
|
settings.get('ai_base_url'))
|
||||||
|
self.root.after(0, lambda: self._suggest_defense_ai_done(res, err))
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, lambda: self._suggest_defense_ai_done(None, str(e)))
|
||||||
|
|
||||||
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
|
|
||||||
|
def _suggest_defense_ai_done(self, res, err):
|
||||||
|
if err:
|
||||||
|
self._verify_log(f"❌ {err}")
|
||||||
|
self.log(f"❌ Рекомендации ИИ по СЗИ: {err}")
|
||||||
|
return
|
||||||
|
if not res:
|
||||||
|
self._verify_log("❌ ИИ не вернул рекомендации")
|
||||||
|
return
|
||||||
|
data = res.get('data', {})
|
||||||
|
lines = [f"🤖 Рекомендации ИИ по СЗИ (категория: {self.model.information_systems[self._current_is_idx].category or '—'})", ""]
|
||||||
|
recs = data.get('рекомендации') or []
|
||||||
|
if recs:
|
||||||
|
lines.append("Чего не хватает:")
|
||||||
|
for r in recs:
|
||||||
|
if isinstance(r, dict):
|
||||||
|
lines.append(f"• {r.get('сзи', '')} — {r.get('зачем', '')}")
|
||||||
|
else:
|
||||||
|
lines.append(f"• {r}")
|
||||||
|
else:
|
||||||
|
lines.append("ИИ не указал конкретных средств.")
|
||||||
|
tips = data.get('общие_советы') or []
|
||||||
|
if tips:
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Общие советы:")
|
||||||
|
for t in tips:
|
||||||
|
lines.append(f"• {t}")
|
||||||
|
self._show_security_ai_result(lines)
|
||||||
|
self._verify_log("🤖 Рекомендации ИИ по СЗИ получены")
|
||||||
|
self.log(f"🤖 Рекомендации ИИ по СЗИ: {len(recs)} позиций")
|
||||||
|
|
||||||
|
def _suggest_defense_ai(self):
|
||||||
|
"""🤖 Рекомендации ИИ по средствам защиты (чего не хватает для категории)."""
|
||||||
|
if self._current_is_idx is None:
|
||||||
|
messagebox.showinfo("СЗИ", "Сначала выберите объект КИИ в списке слева")
|
||||||
|
return
|
||||||
|
settings = load_api_settings()
|
||||||
|
if not settings.get('ai_api_key'):
|
||||||
|
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||||
|
return
|
||||||
|
self._collect_is()
|
||||||
|
isys = self.model.information_systems[self._current_is_idx]
|
||||||
|
data = {
|
||||||
|
'name': isys.name or '',
|
||||||
|
'category': isys.category or '',
|
||||||
|
'sphere': isys.sphere or '',
|
||||||
|
'object_type': isys.object_type or '',
|
||||||
|
'threats': isys.threats or '',
|
||||||
|
'attacker_category': isys.attacker_category or '',
|
||||||
|
'defense_tools': list(self.defense_tools_listbox.get(0, tk.END)) or [],
|
||||||
|
}
|
||||||
|
self._verify_log("🤖 Запрос к ИИ: рекомендации по СЗИ…")
|
||||||
|
self.log(f"🤖 Запрос к ИИ: СЗИ для «{isys.name}» (категория {isys.category or '—'})")
|
||||||
|
|
||||||
|
def _run():
|
||||||
|
try:
|
||||||
|
from .verify import suggest_defense_ai
|
||||||
|
res, err = suggest_defense_ai(
|
||||||
|
data, settings['ai_api_key'], settings.get('ai_model'),
|
||||||
|
settings.get('ai_base_url'))
|
||||||
|
self.root.after(0, lambda: self._suggest_defense_ai_done(res, err))
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, lambda: self._suggest_defense_ai_done(None, str(e)))
|
||||||
|
|
||||||
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
|
|
||||||
|
def _suggest_defense_ai_done(self, res, err):
|
||||||
|
if err:
|
||||||
|
self._verify_log(f"❌ {err}")
|
||||||
|
self.log(f"❌ Рекомендации ИИ по СЗИ: {err}")
|
||||||
|
return
|
||||||
|
if not res:
|
||||||
|
self._verify_log("❌ ИИ не вернул рекомендации")
|
||||||
|
return
|
||||||
|
data = res.get('data', {})
|
||||||
|
lines = [f"🤖 Рекомендации ИИ по СЗИ (категория: {self.model.information_systems[self._current_is_idx].category or '—'})", ""]
|
||||||
|
recs = data.get('рекомендации') or []
|
||||||
|
if recs:
|
||||||
|
lines.append("Чего не хватает:")
|
||||||
|
for r in recs:
|
||||||
|
if isinstance(r, dict):
|
||||||
|
lines.append(f"• {r.get('сзи', '')} — {r.get('зачем', '')}")
|
||||||
|
else:
|
||||||
|
lines.append(f"• {r}")
|
||||||
|
else:
|
||||||
|
lines.append("ИИ не указал конкретных средств.")
|
||||||
|
tips = data.get('общие_советы') or []
|
||||||
|
if tips:
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Общие советы:")
|
||||||
|
for t in tips:
|
||||||
|
lines.append(f"• {t}")
|
||||||
|
self._show_security_ai_result(lines)
|
||||||
|
self._verify_log("🤖 Рекомендации ИИ по СЗИ получены")
|
||||||
|
self.log(f"🤖 Рекомендации ИИ по СЗИ: {len(recs)} позиций")
|
||||||
|
|
||||||
|
def _check_criteria_ai(self):
|
||||||
|
"""🤖 Проверка категорий значимости по ПП 127 через ИИ."""
|
||||||
|
if self._current_is_idx is None:
|
||||||
|
messagebox.showinfo("Категории", "Сначала выберите объект КИИ в списке слева")
|
||||||
|
return
|
||||||
|
settings = load_api_settings()
|
||||||
|
if not settings.get('ai_api_key'):
|
||||||
|
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||||
|
return
|
||||||
|
self._collect_is()
|
||||||
|
isys = self.model.information_systems[self._current_is_idx]
|
||||||
|
data = {
|
||||||
|
'name': isys.name or '',
|
||||||
|
'description': isys.description or '',
|
||||||
|
'category': isys.category or '',
|
||||||
|
'sphere': isys.sphere or '',
|
||||||
|
'object_type': isys.object_type or '',
|
||||||
|
'criteria_values': isys.criteria_values or '',
|
||||||
|
'criteria_justification': isys.criteria_justification or '',
|
||||||
|
}
|
||||||
|
self._verify_log("🤖 Запрос к ИИ: проверка категорий по ПП 127…")
|
||||||
|
self.log(f"🤖 Запрос к ИИ: категории ПП 127 для «{isys.name}»")
|
||||||
|
|
||||||
|
def _run():
|
||||||
|
try:
|
||||||
|
from .verify import check_criteria_ai
|
||||||
|
res, err = check_criteria_ai(
|
||||||
|
data, settings['ai_api_key'], settings.get('ai_model'),
|
||||||
|
settings.get('ai_base_url'))
|
||||||
|
self.root.after(0, lambda: self._check_criteria_ai_done(res, err))
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, lambda: self._check_criteria_ai_done(None, str(e)))
|
||||||
|
|
||||||
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
|
|
||||||
|
def _check_criteria_ai_done(self, res, err):
|
||||||
|
if err:
|
||||||
|
self._verify_log(f"❌ {err}")
|
||||||
|
self.log(f"❌ Проверка категорий ИИ: {err}")
|
||||||
|
return
|
||||||
|
if not res:
|
||||||
|
self._verify_log("❌ ИИ не вернул результат")
|
||||||
|
return
|
||||||
|
data = res.get('data', {})
|
||||||
|
isys = self.model.information_systems[self._current_is_idx]
|
||||||
|
lines = [f"🤖 Проверка категорий по ПП № 127 — «{isys.name}»", ""]
|
||||||
|
lines.append(f"Оценка: {data.get('rating', 'warning')}")
|
||||||
|
if data.get('final_category'):
|
||||||
|
lines.append(f"Итоговая категория (по мнению ИИ): {data['final_category']}")
|
||||||
|
notes = data.get('notes') or []
|
||||||
|
if notes:
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Замечания:")
|
||||||
|
for n in notes:
|
||||||
|
if isinstance(n, dict):
|
||||||
|
lines.append(f"• Показатель {n.get('показатель', '?')} ({n.get('категория', '—')}): "
|
||||||
|
f"{n.get('комментарий', '')}")
|
||||||
|
else:
|
||||||
|
lines.append(f"• {n}")
|
||||||
|
props = data.get('proposals') or {}
|
||||||
|
if props.get('criteria_values') or props.get('criteria_justification'):
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Предложения ИИ (можно применить вручную):")
|
||||||
|
if props.get('criteria_values'):
|
||||||
|
lines.append(f"Показатели: {props['criteria_values']}")
|
||||||
|
if props.get('criteria_justification'):
|
||||||
|
lines.append(f"Обоснование: {props['criteria_justification']}")
|
||||||
|
self._show_security_ai_result(lines)
|
||||||
|
self._verify_log("🤖 Проверка категорий ИИ завершена")
|
||||||
|
self.log("🤖 Проверка категорий ИИ завершена")
|
||||||
|
|
||||||
def _select_from_defense_list(self):
|
def _select_from_defense_list(self):
|
||||||
"""Выбор СЗИ из JSON словаря."""
|
"""Выбор СЗИ из JSON словаря."""
|
||||||
import json
|
import json
|
||||||
|
|||||||
@@ -733,3 +733,86 @@ def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None
|
|||||||
},
|
},
|
||||||
'apply': {'company': {k: v for k, v in proposals.items() if v}},
|
'apply': {'company': {k: v for k, v in proposals.items() if v}},
|
||||||
}, None
|
}, None
|
||||||
|
|
||||||
|
|
||||||
|
def suggest_defense_ai(defense_data, api_key, model='deepseek-chat', base_url=None):
|
||||||
|
"""ИИ-рекомендации по средствам защиты информации (СЗИ).
|
||||||
|
|
||||||
|
По категории значимости, угрозам и текущему списку СЗИ ИИ подсказывает,
|
||||||
|
чего не хватает (например, антивирус, СКЗИ, SIEM и т.п.).
|
||||||
|
Возвращает (результат, ошибка).
|
||||||
|
"""
|
||||||
|
if not api_key:
|
||||||
|
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
|
||||||
|
pr = _load_prompt('suggest_defense')
|
||||||
|
system = pr.get('system', (
|
||||||
|
"Ты — эксперт по безопасности КИИ (187-ФЗ, приказ ФСТЭК № 239). "
|
||||||
|
"По категории значимости объекта и актуальным угрозам определи, каких "
|
||||||
|
"средств защиты информации (СЗИ) не хватает, и дай рекомендации."
|
||||||
|
))
|
||||||
|
system = system + " " + pr.get('response_format',
|
||||||
|
'Верни ТОЛЬКО JSON: {"рекомендации": [{"сзи": "...", "зачем": "..."}], '
|
||||||
|
'"общие_советы": ["..."]}')
|
||||||
|
user = json.dumps(defense_data, ensure_ascii=False)
|
||||||
|
js, err = _ai_json(api_key, model, base_url, system, user)
|
||||||
|
if err:
|
||||||
|
return None, err
|
||||||
|
recs = js.get('рекомендации') or []
|
||||||
|
if not isinstance(recs, list):
|
||||||
|
recs = []
|
||||||
|
tips = js.get('общие_советы') or []
|
||||||
|
if isinstance(tips, str):
|
||||||
|
tips = [tips]
|
||||||
|
return {
|
||||||
|
'source': 'ИИ',
|
||||||
|
'source_url': 'openai-совместимый API',
|
||||||
|
'disclaimer': AI_DISCLAIMER,
|
||||||
|
'kind': 'defense',
|
||||||
|
'data': {
|
||||||
|
'рекомендации': recs,
|
||||||
|
'общие_советы': tips,
|
||||||
|
},
|
||||||
|
}, None
|
||||||
|
|
||||||
|
|
||||||
|
def check_criteria_ai(criteria_data, api_key, model='deepseek-chat', base_url=None):
|
||||||
|
"""ИИ-проверка категорий значимости по ПП РФ № 127.
|
||||||
|
|
||||||
|
По данным объекта КИИ (назначение, сфера, категории по показателям,
|
||||||
|
обоснования) ИИ проверяет корректность присвоенных категорий.
|
||||||
|
Возвращает (результат, ошибка).
|
||||||
|
"""
|
||||||
|
if not api_key:
|
||||||
|
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
|
||||||
|
pr = _load_prompt('check_criteria')
|
||||||
|
system = pr.get('system', (
|
||||||
|
"Ты — эксперт по категорированию объектов КИИ (187-ФЗ, ПП РФ № 127). "
|
||||||
|
"Проверь присвоенные категории значимости по показателям критериев."
|
||||||
|
))
|
||||||
|
system = system + " " + pr.get('response_format',
|
||||||
|
'Верни ТОЛЬКО JSON: {"оценка": "ok|warning|error", '
|
||||||
|
'"замечания": [{"показатель": "...", "категория": "...", "комментарий": "..."}], '
|
||||||
|
'"предложения": {"criteria_values": "...", "criteria_justification": "..."}, '
|
||||||
|
'"итоговая_категория": "I|II|III|—"}')
|
||||||
|
user = json.dumps(criteria_data, ensure_ascii=False)
|
||||||
|
js, err = _ai_json(api_key, model, base_url, system, user)
|
||||||
|
if err:
|
||||||
|
return None, err
|
||||||
|
notes = js.get('замечания') or []
|
||||||
|
if isinstance(notes, str):
|
||||||
|
notes = [notes]
|
||||||
|
proposals = js.get('предложения') or {}
|
||||||
|
if not isinstance(proposals, dict):
|
||||||
|
proposals = {}
|
||||||
|
return {
|
||||||
|
'source': 'ИИ',
|
||||||
|
'source_url': 'openai-совместимый API',
|
||||||
|
'disclaimer': AI_DISCLAIMER,
|
||||||
|
'kind': 'criteria',
|
||||||
|
'data': {
|
||||||
|
'rating': js.get('оценка', 'warning'),
|
||||||
|
'notes': notes,
|
||||||
|
'proposals': proposals,
|
||||||
|
'final_category': js.get('итоговая_категория', ''),
|
||||||
|
},
|
||||||
|
}, None
|
||||||
|
|||||||
Reference in New Issue
Block a user