feat: подбор характеристик объекта КИИ — программа и ИИ
Вкладка «Основное», кнопки под полем типового объекта: - ⚙️ «Подобрать типовой объект (программа)»: скоринг по совпадению значимых слов названия/назначения объекта с перечнем 392 типовых отраслевых объектов (Распоряжение 360-р), IDF-веса (редкие слова важнее общих) → окно с вариантами, оператор выбирает и применяет (заполняет сферу + типовой объект) - 🤖 «Подобрать характеристики (ИИ)»: DeepSeek предлагает назначение, сферу, тип, архитектуру и типовой объект (из перечня 360-р) как рекомендацию → окно с полями, оператор подтверждает/правит Примеры: СКУД → «ИС, АСУ, ИТКС, обеспечивающие контроль и управление доступом на территорию»; 1С:УПП → «ИС для ведения бухгалтерского учета»
This commit is contained in:
+200
@@ -868,6 +868,16 @@ class DokoGenApp:
|
||||
self._typical_objects_all = ['']
|
||||
self.is_typical_object_combo['values'] = self._typical_objects_all
|
||||
|
||||
# Кнопки подбора характеристик: программа (типовой объект) и ИИ (всё)
|
||||
suggest_row = ttk.Frame(main_frame)
|
||||
suggest_row.grid(row=5, column=0, columnspan=2, sticky=tk.W, pady=(4, 0))
|
||||
ttk.Button(suggest_row, text="⚙️ Подобрать типовой объект (программа)",
|
||||
command=self._suggest_typical_program_click).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(suggest_row, text="🤖 Подобрать характеристики (ИИ)",
|
||||
command=self._suggest_object_params_ai_click).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Label(suggest_row, text="Подбор по названию/назначению объекта — как рекомендация, оператор подтверждает.",
|
||||
foreground='gray').pack(side=tk.LEFT, padx=8)
|
||||
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# ===== Вкладка «Субъект» =====
|
||||
@@ -1554,6 +1564,196 @@ class DokoGenApp:
|
||||
def _set_is_edit_state(self, enabled):
|
||||
state = 'normal' if enabled else 'disabled'
|
||||
|
||||
def _suggest_typical_program_click(self):
|
||||
"""⚙️ Подобрать типовой отраслевой объект программой (по названию/назначению объекта)."""
|
||||
if self._current_is_idx is None:
|
||||
messagebox.showinfo("Подбор", "Сначала выберите объект КИИ в списке слева")
|
||||
return
|
||||
self._collect_is()
|
||||
isys = self.model.information_systems[self._current_is_idx]
|
||||
try:
|
||||
from .threat_rules import suggest_typical_object_program
|
||||
sphere = self.is_sphere_combo.get() or None
|
||||
results = suggest_typical_object_program(isys, limit=12, sphere=sphere)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", str(e))
|
||||
return
|
||||
if not results:
|
||||
messagebox.showinfo("Подбор",
|
||||
"Не удалось подобрать типовой объект по названию/назначению.\n"
|
||||
"Попробуйте кнопку «🤖 Подобрать характеристики (ИИ)».")
|
||||
return
|
||||
|
||||
dlg = tk.Toplevel(self.root)
|
||||
dlg.title("⚙️ Подбор типового отраслевого объекта")
|
||||
dlg.geometry("860x520")
|
||||
dlg.transient(self.root)
|
||||
frm = ttk.Frame(dlg, padding=8)
|
||||
frm.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frm, text=f"Объект: {isys.name}",
|
||||
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
|
||||
ttk.Label(frm, text="Наиболее подходящие типовые отраслевые объекты (по совпадению слов "
|
||||
"с названием/назначением). Выберите и нажмите «Применить».",
|
||||
foreground='gray', wraplength=820, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
||||
|
||||
lb = tk.Listbox(frm, height=16, exportselection=False)
|
||||
lb.pack(fill=tk.BOTH, expand=True)
|
||||
for r in results:
|
||||
lb.insert(tk.END, f"[{r['score']}] ({r['sphere']}) {r['name']}")
|
||||
|
||||
btn_row = ttk.Frame(frm)
|
||||
btn_row.pack(fill=tk.X, pady=(8, 0))
|
||||
ttk.Button(btn_row, text="✔️ Применить выбранное",
|
||||
command=lambda: self._apply_typical_selection(lb, results, dlg)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(btn_row, text="Отмена", command=dlg.destroy).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
def _apply_typical_selection(self, lb, results, dlg):
|
||||
sel = lb.curselection()
|
||||
if not sel:
|
||||
messagebox.showinfo("Подбор", "Выберите типовой объект из списка")
|
||||
return
|
||||
r = results[sel[0]]
|
||||
# Устанавливаем сферу и типовой объект
|
||||
if hasattr(self, 'is_sphere_combo'):
|
||||
self.is_sphere_combo.set(r['sphere'])
|
||||
self._update_typical_objects_by_sphere()
|
||||
if hasattr(self, 'is_typical_object_combo'):
|
||||
self.is_typical_object_combo.set(r['name'])
|
||||
self._collect_is()
|
||||
self.log(f"⚙️ Типовой отраслевой объект: {r['name'][:70]}")
|
||||
dlg.destroy()
|
||||
|
||||
def _suggest_object_params_ai_click(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]
|
||||
# Перечень типовых объектов для выбора ИИ (всех сфер)
|
||||
typical_list = []
|
||||
if hasattr(self, '_typical_industry'):
|
||||
for sph, items in self._typical_industry.items():
|
||||
if sph == 'Иная сфера деятельности':
|
||||
continue
|
||||
for it in items:
|
||||
typical_list.append(it.get('name', ''))
|
||||
data = {
|
||||
'name': isys.name or '',
|
||||
'description': isys.description or '',
|
||||
'purpose': isys.purpose or '',
|
||||
'sphere': isys.sphere or '',
|
||||
'object_type': isys.object_type or '',
|
||||
}
|
||||
self.log(f"🤖 Запрос к ИИ: подбор характеристик для «{isys.name}»")
|
||||
self._verify_log("Подбор характеристик через ИИ…")
|
||||
|
||||
def _run():
|
||||
try:
|
||||
from .verify import suggest_object_params_ai
|
||||
res, err = suggest_object_params_ai(
|
||||
data, settings['ai_api_key'], settings.get('ai_model'),
|
||||
settings.get('ai_base_url'), typical_objects=typical_list)
|
||||
self.root.after(0, lambda: self._suggest_object_params_ai_done(res, err, isys.name))
|
||||
except Exception as e:
|
||||
self.root.after(0, lambda: self._suggest_object_params_ai_done(None, str(e), isys.name))
|
||||
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _suggest_object_params_ai_done(self, res, err, obj_name):
|
||||
if err:
|
||||
self._verify_log(f"❌ {err}")
|
||||
self.log(f"❌ Подбор характеристик ИИ: {err}")
|
||||
return
|
||||
if not res:
|
||||
self._verify_log("❌ ИИ не вернул характеристики")
|
||||
return
|
||||
|
||||
dlg = tk.Toplevel(self.root)
|
||||
dlg.title("🤖 Рекомендации ИИ по характеристикам объекта")
|
||||
dlg.geometry("860x560")
|
||||
dlg.transient(self.root)
|
||||
frm = ttk.Frame(dlg, padding=8)
|
||||
frm.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frm, text=f"Объект: {obj_name} — рекомендации ИИ (подтвердите или скорректируйте)",
|
||||
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
|
||||
|
||||
rows = [
|
||||
('назначение', 'Назначение объекта'),
|
||||
('сфера', 'Сфера деятельности'),
|
||||
('тип', 'Тип объекта'),
|
||||
('архитектура', 'Архитектура объекта'),
|
||||
('типовой_объект', 'Типовой отраслевой объект КИИ'),
|
||||
]
|
||||
entries = {}
|
||||
for key, label in rows:
|
||||
ttk.Label(frm, text=label + ":").pack(anchor=tk.W, pady=(6, 0))
|
||||
e = tk.Text(frm, height=2 if key == 'типовой_объект' else 1, wrap=tk.WORD)
|
||||
e.pack(fill=tk.X)
|
||||
e.insert('1.0', res.get(key, '') or '')
|
||||
entries[key] = e
|
||||
|
||||
ttk.Label(frm, text="Обоснование ИИ:").pack(anchor=tk.W, pady=(6, 0))
|
||||
just = tk.Text(frm, height=2, wrap=tk.WORD)
|
||||
just.pack(fill=tk.X)
|
||||
just.insert('1.0', res.get('обоснование', '') or '')
|
||||
|
||||
btn_row = ttk.Frame(frm)
|
||||
btn_row.pack(fill=tk.X, pady=(8, 0))
|
||||
ttk.Button(btn_row, text="✔️ Применить",
|
||||
command=lambda: self._apply_ai_params(entries, dlg)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(btn_row, text="Отмена", command=dlg.destroy).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
def _apply_ai_params(self, entries, dlg):
|
||||
"""Применить рекомендации ИИ к полям объекта."""
|
||||
def _get(e):
|
||||
return e.get('1.0', 'end-1c').strip()
|
||||
|
||||
purpose = _get(entries['назначение'])
|
||||
sphere = _get(entries['сфера'])
|
||||
otype = _get(entries['тип'])
|
||||
arch = _get(entries['архитектура'])
|
||||
typical = _get(entries['типовой_объект'])
|
||||
|
||||
if purpose and hasattr(self, 'is_purpose_entry'):
|
||||
self.is_purpose_entry.delete(0, tk.END)
|
||||
self.is_purpose_entry.insert(0, purpose)
|
||||
|
||||
# Сфера — проверим, есть ли в списке
|
||||
if sphere and hasattr(self, 'is_sphere_combo'):
|
||||
values = list(self.is_sphere_combo['values'])
|
||||
exact = next((v for v in values if v.lower() == sphere.lower()), None)
|
||||
if exact:
|
||||
self.is_sphere_combo.set(exact)
|
||||
self._update_typical_objects_by_sphere()
|
||||
else:
|
||||
self.is_sphere_combo.set(sphere)
|
||||
|
||||
if otype and hasattr(self, 'is_object_type_combo'):
|
||||
values = list(self.is_object_type_combo['values'])
|
||||
exact = next((v for v in values if v.lower() == otype.lower()), None)
|
||||
self.is_object_type_combo.set(exact or otype)
|
||||
|
||||
if arch and hasattr(self, 'is_architecture_combo'):
|
||||
values = list(self.is_architecture_combo['values'])
|
||||
exact = next((v for v in values if v.lower() == arch.lower()), None)
|
||||
self.is_architecture_combo.set(exact or arch)
|
||||
|
||||
if typical and hasattr(self, 'is_typical_object_combo'):
|
||||
values = list(self.is_typical_object_combo['values'])
|
||||
exact = next((v for v in values if v.lower() == typical.lower()), None)
|
||||
self.is_typical_object_combo.set(exact or typical)
|
||||
|
||||
self._collect_is()
|
||||
self.log("🤖 Рекомендации ИИ применены к характеристикам объекта")
|
||||
dlg.destroy()
|
||||
|
||||
def _on_sphere_changed(self, event=None):
|
||||
"""При смене сферы объекта — подставляем типовые отраслевые объекты этой сферы (Распоряжение ПРФ № 360-р)."""
|
||||
self._update_typical_objects_by_sphere()
|
||||
|
||||
Reference in New Issue
Block a user