feat: ИИ-проверка объекта по частям (временные файлы вход/выход по блокам)

This commit is contained in:
2026-08-05 01:33:43 +04:00
parent d376a28184
commit b3ae4c22cf
2 changed files with 123 additions and 0 deletions
+79
View File
@@ -1066,6 +1066,8 @@ class DokoGenApp:
command=self._auto_fill_security_click).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_main_btns, text="🤖 Проверить безопасность через ИИ",
command=self._check_security_ai).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_main_btns, text="🧩 Проверить объект по частям (ИИ)",
command=self._check_object_partial_click).pack(side=tk.LEFT, padx=2)
ttk.Label(sec_main_btns,
text="Подбор по данным объекта — как рекомендация, оператор подтверждает.",
foreground='gray').pack(side=tk.LEFT, padx=8)
@@ -3507,6 +3509,83 @@ class DokoGenApp:
self._verify_log("🤖 Проверка категорий ИИ завершена")
self.log("🤖 Проверка категорий ИИ завершена")
def _check_object_partial_click(self):
"""🧩 ИИ-проверка всего объекта по частям: каждый блок — отдельный запрос,
данные и ответы ИИ пишутся во временную папку (dokogen_ai_*)."""
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_company()
self._collect_is()
c = self.model
isys = self.model.information_systems[self._current_is_idx]
blocks = [
{'name': 'Реквизиты организации', 'prompt': 'check_company',
'data': {'full_name': c.full_name, 'short_name': c.short_name, 'address': c.address,
'inn': c.inn, 'kpp': c.kpp, 'ogrn': c.ogrn, 'ogrn_date': c.ogrn_date,
'chief_position': c.chief_position, 'chief_fio': c.chief_fio}},
{'name': 'Характеристики объекта КИИ', 'prompt': 'suggest_object_params',
'data': {'name': isys.name, 'description': isys.description, 'purpose': isys.purpose,
'sphere': isys.sphere, 'object_type': isys.object_type,
'architecture': isys.architecture, 'typical_object': isys.typical_object,
'net_category': isys.net_category, 'net_purpose': isys.net_purpose}},
{'name': 'Безопасность (нарушитель, угрозы, меры)', 'prompt': 'check_security',
'data': {'name': isys.name, 'description': isys.description, 'category': isys.category,
'sphere': isys.sphere, 'attacker_category': isys.attacker_category,
'attacker_level': isys.attacker_level, 'attacker_types': isys.attacker_types,
'threats': isys.threats, 'incidents': isys.incidents,
'org_measures': isys.org_measures, 'tech_measures': isys.tech_measures}},
{'name': 'Категории значимости (ПП 127)', 'prompt': 'check_criteria',
'data': {'name': isys.name, 'description': isys.description, 'category': isys.category,
'sphere': isys.sphere, 'object_type': isys.object_type,
'criteria_values': isys.criteria_values,
'criteria_justification': isys.criteria_justification}},
{'name': 'Средства защиты (СЗИ)', 'prompt': 'suggest_defense',
'data': {'name': isys.name, 'category': isys.category, 'sphere': isys.sphere,
'threats': isys.threats,
'defense_tools': list(self.defense_tools_listbox.get(0, tk.END)) or []}},
]
self._verify_log(f"🧩 ИИ-проверка объекта «{isys.name}» по частям ({len(blocks)} блоков)…")
self.log(f"🧩 ИИ-проверка объекта «{isys.name}» по частям: {len(blocks)} блоков")
def _run():
try:
from .verify import check_object_partial
res, err = check_object_partial(
blocks, settings['ai_api_key'], settings.get('ai_model'),
settings.get('ai_base_url'))
self.root.after(0, lambda: self._check_object_partial_done(res, err, isys.name))
except Exception as e:
self.root.after(0, lambda: self._check_object_partial_done(None, str(e), isys.name))
threading.Thread(target=_run, daemon=True).start()
def _check_object_partial_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
lines = [f"🧩 ИИ-проверка объекта «{obj_name}» по частям", ""]
for r in res:
status = "" if r.get('ok') else ""
lines.append(f"{status} {r['name']}")
if r.get('files', {}).get('out'):
lines.append(f" ответ ИИ: {r['files']['out']}")
lines.append("")
lines.append("Полные ответы ИИ по каждому блоку — во временных файлах выше.")
lines.append("Рекомендации применяются по кнопкам разделов (по галочкам).")
self._show_security_ai_result(lines)
self._verify_log("🧩 Проверка объекта по частям завершена")
self.log("🧩 Проверка объекта по частям завершена")
def _select_from_defense_list(self):
"""Выбор СЗИ из JSON словаря."""
import json
+44
View File
@@ -816,3 +816,47 @@ def check_criteria_ai(criteria_data, api_key, model='deepseek-chat', base_url=No
'final_category': js.get('итоговая_категория', ''),
},
}, None
def check_object_partial(blocks, api_key, model='deepseek-chat', base_url=None, workdir=None):
"""ИИ-проверка объекта КИИ по частям.
blocks: список [{'name': 'Реквизиты организации', 'prompt': 'check_company', 'data': {...}}, ...]
Для каждого блока: данные пишутся во временный файл (вход), вызывается ИИ,
ответ пишется во временный файл (выход). Возвращает (results, errors):
results [{name, ok, text, files: {in: путь, out: путь}}].
"""
import tempfile
import os
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
if workdir is None:
workdir = tempfile.mkdtemp(prefix='dokogen_ai_')
results = []
for i, blk in enumerate(blocks, 1):
name = blk.get('name', f'Блок {i}')
prompt_name = blk.get('prompt', 'check_security')
data = blk.get('data', {})
f_in = os.path.join(workdir, f'block_{i}_input.json')
f_out = os.path.join(workdir, f'block_{i}_output.json')
try:
with open(f_in, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception:
pass
pr = _load_prompt(prompt_name)
system = pr.get('system', 'Ты — эксперт по КИИ (187-ФЗ). Проверь данные и дай рекомендации.')
system = system + ' ' + pr.get('response_format', 'Верни ТОЛЬКО JSON с результатом проверки.')
user = json.dumps(data, ensure_ascii=False)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
results.append({'name': name, 'ok': False, 'text': f'Ошибка: {err}', 'files': {'in': f_in, 'out': f_out}})
continue
try:
with open(f_out, 'w', encoding='utf-8') as f:
json.dump(js, f, ensure_ascii=False, indent=2)
except Exception:
pass
text = json.dumps(js, ensure_ascii=False, indent=2)
results.append({'name': name, 'ok': True, 'text': text, 'files': {'in': f_in, 'out': f_out}})
return results, None