fix: подбор угроз программой + сравнение программа↔ИИ

Проблемы:
1. Подбор программой возвращал 193 из 227 угроз (весь перечень) —
   теперь скоринг по ключевым словам объекта (тип, интернет, нарушитель,
   сеть), отсев нерелевантных: 72-82 угрозы вместо 193
2. Сравнение программа↔ИИ не подсвечивало совпадения: ИИ возвращает
   «1. УБИ.001 …» / с лишними пробелами — сравнение шло по точной строке.
   Теперь ключ сравнения — код УБИ (regex), иначе нормализованный текст;
   добавлена статистика «Совпало / только программа / только ИИ»
This commit is contained in:
2026-08-04 20:31:52 +04:00
parent 72f73d4c4f
commit 1ef8485b13
2 changed files with 56 additions and 10 deletions
+24 -2
View File
@@ -173,12 +173,34 @@ def suggest_threats_program(isys, threats=None, limit=None):
# — оставляем всё, кроме специфики каналов связи, если их нет # — оставляем всё, кроме специфики каналов связи, если их нет
pass pass
result = [] # --- Положительные ключевые слова: чем больше совпало — тем угроза актуальнее ---
pos = list(BASE_KEYWORDS)
if has_internet:
pos += INTERNET_KEYWORDS
else:
pos += ISOLATED_KEYWORDS
if is_asu:
pos += ASU_KEYWORDS
if is_network:
pos += NETWORK_KEYWORDS
if _is_insider(isys):
pos += INSIDER_KEYWORDS
else:
pos += EXTERNAL_KEYWORDS
# Убираем дубликаты
pos = list(dict.fromkeys(pos))
scored = []
for t in threats: for t in threats:
tl = str(t).lower() tl = str(t).lower()
if any(kw in tl for kw in exclude): if any(kw in tl for kw in exclude):
continue continue
result.append(t) sc = sum(1 for kw in pos if kw in tl)
scored.append((sc, t))
# Сортируем по релевантности (сначала — с наибольшим совпадением)
scored.sort(key=lambda x: -x[0])
result = [t for sc, t in scored if sc > 0]
if limit and len(result) > limit: if limit and len(result) > limit:
result = result[:limit] result = result[:limit]
+32 -8
View File
@@ -5,6 +5,7 @@ import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog, scrolledtext from tkinter import ttk, messagebox, filedialog, simpledialog, scrolledtext
import os import os
import json import json
import re
import sys import sys
import threading import threading
from datetime import datetime from datetime import datetime
@@ -2147,10 +2148,30 @@ class DokoGenApp:
messagebox.showinfo("Сравнение", "Сначала подберите угрозы (программой или ИИ).") messagebox.showinfo("Сравнение", "Сначала подберите угрозы (программой или ИИ).")
return return
# Нормализация строк угроз для сравнения:
# «1. УБИ.001 Угроза…», «УБИ.001 Угроза…» и «УБИ.001 Угроза…» — это одна и та же угроза.
# Ключ сравнения — код УБИ, если он есть (ИИ может обрезать/перефразировать название);
# иначе — нормализованный текст.
def _ubi_code(s):
m = re.search(r'уби\.?\s*(\d+)', str(s).lower())
return m.group(1) if m else None
def _norm(s):
s = re.sub(r'^\d+[.)]?\s*', '', str(s).strip()) # ведущий номер «1. »
s = re.sub(r'\s+', ' ', s).strip() # схлопнуть пробелы
return s.lower()
def _key(s):
code = _ubi_code(s)
return ('ubi', code) if code else ('name', _norm(s))
pset_norm = {_key(t) for t in program}
cset_norm = {_key(t) for t in current}
# Показываем окно: слева подобрано программой, справа текущий список # Показываем окно: слева подобрано программой, справа текущий список
dlg = tk.Toplevel(self.root) dlg = tk.Toplevel(self.root)
dlg.title("Сравнение угроз: программа ↔ ИИ/текущий") dlg.title("Сравнение угроз: программа ↔ ИИ/текущий")
dlg.geometry("760x420") dlg.geometry("780x460")
dlg.transient(self.root) dlg.transient(self.root)
frm = ttk.Frame(dlg, padding=8) frm = ttk.Frame(dlg, padding=8)
@@ -2158,7 +2179,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="⚙️ Программа", 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=14)
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:
@@ -2166,22 +2187,25 @@ 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="🤖 ИИ / текущий список", 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=14)
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)
# Подсветка совпадений # Подсветка совпадений (по коду УБИ / нормализованному виду)
pset = set(program) common = pset_norm & cset_norm
for i in range(lb_p.size()): for i in range(lb_p.size()):
if lb_p.get(i) in set(current): if _key(lb_p.get(i)) in common:
lb_p.itemconfig(i, bg='#c8e6c9') lb_p.itemconfig(i, bg='#c8e6c9')
for i in range(lb_c.size()): for i in range(lb_c.size()):
if lb_c.get(i) in pset: if _key(lb_c.get(i)) in common:
lb_c.itemconfig(i, bg='#c8e6c9') lb_c.itemconfig(i, bg='#c8e6c9')
info = ttk.Label(frm, text="Зелёным выделены совпавшие угрозы.", info = ttk.Label(frm,
text=f"Совпало угроз: {len(common)} (зелёным). "
f"Только программа: {len(program) - len(common)}, "
f"только ИИ/текущий: {len(current) - len(common)}.",
foreground='gray') foreground='gray')
info.pack(anchor=tk.W, pady=(4, 0)) info.pack(anchor=tk.W, pady=(4, 0))