feat: окно «Показатели критериев значимости» по таблице ПП РФ № 127

- кнопка «📋 Заполнить по таблице ПП № 127» в вкладке «Безопасность»
- окно 860x640 со скроллом: 5 критериев (социальная, политическая,
  экономическая, экологическая, оборонная), 12 показателей,
  у каждого выбор значения 1-4 с описанием шкалы
- объединены показатели и обоснование в одном окне (общее поле)
- «🧮 Рассчитать категорию»: по максимальному баллу → 1/2/3/без категории
  (4→1-я, 3→2-я, 2→3-я), результат пишется в категорию значимости объекта
- «💾 Сохранить»: заполняет список показателей и обоснование
- словарь criteria_127.json (формулировки можно править файлом)
This commit is contained in:
2026-08-04 17:48:40 +04:00
parent 38eae4b16f
commit 5b318eef88
2 changed files with 269 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
[
{
"criteria": "Социальная значимость",
"indicators": [
{
"name": "Количество пользователей объекта КИИ, которым может быть причинён ущерб",
"scale": {
"4": "более 5 млн человек",
"3": "от 1 до 5 млн человек",
"2": "от 100 тыс. до 1 млн человек",
"1": "менее 100 тыс. человек"
}
},
{
"name": "Доля пользователей объекта КИИ, которым может быть причинён ущерб, от общего числа пользователей",
"scale": {
"4": "более 20%",
"3": "от 10% до 20%",
"2": "от 5% до 10%",
"1": "менее 5%"
}
},
{
"name": "Продолжительность нарушения безопасности объекта КИИ, которое может привести к причинению ущерба",
"scale": {
"4": "более 72 часов",
"3": "от 24 до 72 часов",
"2": "от 1 до 24 часов",
"1": "менее 1 часа"
}
}
]
},
{
"criteria": "Политическая значимость",
"indicators": [
{
"name": "Значимость объекта КИИ для обеспечения обороноспособности страны, безопасности государства и правопорядка",
"scale": {
"4": "критическая значимость",
"3": "высокая значимость",
"2": "средняя значимость",
"1": "низкая значимость"
}
},
{
"name": "Международная значимость объекта КИИ (используется при реализации международных договоров и соглашений)",
"scale": {
"4": "международное значение",
"3": "федеральное значение",
"2": "региональное значение",
"1": "местное значение"
}
}
]
},
{
"criteria": "Экономическая значимость",
"indicators": [
{
"name": "Размер возможного прямого ущерба в результате нарушения безопасности объекта КИИ",
"scale": {
"4": "более 5 млрд рублей",
"3": "от 1 до 5 млрд рублей",
"2": "от 100 млн до 1 млрд рублей",
"1": "менее 100 млн рублей"
}
},
{
"name": "Размер возможного косвенного ущерба (упущенная выгода, затраты на восстановление)",
"scale": {
"4": "более 10 млрд рублей",
"3": "от 1 до 10 млрд рублей",
"2": "от 100 млн до 1 млрд рублей",
"1": "менее 100 млн рублей"
}
},
{
"name": "Влияние нарушения безопасности объекта КИИ на экономическую безопасность государства",
"scale": {
"4": "критическое влияние",
"3": "высокое влияние",
"2": "среднее влияние",
"1": "низкое влияние"
}
}
]
},
{
"criteria": "Экологическая значимость",
"indicators": [
{
"name": "Площадь территории, на которой может быть оказано негативное воздействие на окружающую среду",
"scale": {
"4": "более 1000 кв. км",
"3": "от 100 до 1000 кв. км",
"2": "от 10 до 100 кв. км",
"1": "менее 10 кв. км"
}
},
{
"name": "Возможность нанесения ущерба объектам животного и растительного мира, водным объектам",
"scale": {
"4": "необратимый ущерб экосистемам",
"3": "значительный ущерб",
"2": "умеренный ущерб",
"1": "незначительный ущерб"
}
}
]
},
{
"criteria": "Значимость для обороны страны, безопасности государства и правопорядка",
"indicators": [
{
"name": "Влияние нарушения безопасности объекта КИИ на обороноспособность страны",
"scale": {
"4": "критическое влияние",
"3": "высокое влияние",
"2": "среднее влияние",
"1": "низкое влияние"
}
},
{
"name": "Влияние нарушения безопасности объекта КИИ на безопасность государства и правопорядок",
"scale": {
"4": "критическое влияние",
"3": "высокое влияние",
"2": "среднее влияние",
"1": "низкое влияние"
}
}
]
}
]
+134
View File
@@ -1038,6 +1038,8 @@ class DokoGenApp:
'is_incidents_listbox', 'incidents.json', height=2)
_sec_row(sec_right, "Показатели критериев значимости:",
'is_criteria_values_listbox', 'criteria_indicators.json', height=2)
ttk.Button(sec_right, text="📋 Заполнить по таблице ПП № 127",
command=self._open_criteria_127_dialog).pack(anchor=tk.W, pady=(0, 4))
_sec_row(sec_right, "Обоснование значений показателей:",
'is_criteria_justification_listbox', None, height=2)
_sec_row(sec_right, "Организационные меры:",
@@ -1733,6 +1735,138 @@ class DokoGenApp:
if user:
self.users_listbox.insert(tk.END, user)
def _open_criteria_127_dialog(self):
"""Окно заполнения показателей критериев значимости по таблице ПП РФ № 127.
Показатели и обоснование объединены в одном окне:
для каждого показателя выбирается значение (1-4), внизу — общее обоснование.
По максимальному баллу автоматически рассчитывается категория значимости:
4 → 1-я, 3 → 2-я, 2 → 3-я, иначе «без категории».
"""
import json as _json
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'criteria_127.json')
try:
with open(dict_path, encoding='utf-8') as f:
criteria = _json.load(f)
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось загрузить таблицу ПП 127: {e}")
return
dialog = tk.Toplevel(self.root)
dialog.title("Показатели критериев значимости (ПП РФ № 127)")
dialog.geometry("860x640")
dialog.transient(self.root)
dialog.grab_set()
outer = ttk.Frame(dialog, padding=8)
outer.pack(fill=tk.BOTH, expand=True)
canvas = tk.Canvas(outer, highlightthickness=0)
scrollbar = ttk.Scrollbar(outer, orient=tk.VERTICAL, command=canvas.yview)
body = ttk.Frame(canvas)
body.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=body, 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)
ttk.Label(body, text="Показатели критериев значимости объекта КИИ (ПП РФ № 127)",
font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=(0, 4))
ttk.Label(body, text="По каждому показателю выберите значение от 1 до 4 "
"(4 — максимальная значимость). Категория рассчитывается автоматически.",
foreground='gray', wraplength=780).pack(anchor=tk.W, pady=(0, 8))
# Текущие значения из listbox (чтобы предзаполнить, если уже заполнено)
cur_values = set()
if hasattr(self, 'is_criteria_values_listbox'):
for i in range(self.is_criteria_values_listbox.size()):
cur_values.add(str(self.is_criteria_values_listbox.get(i)))
rows = [] # (критерий, показатель, scale, var)
for crit in criteria:
grp = ttk.LabelFrame(body, text=crit.get('criteria', ''), padding=6)
grp.pack(fill=tk.X, pady=4)
for ind in crit.get('indicators', []):
name = ind.get('name', '')
scale = ind.get('scale', {})
row = ttk.Frame(grp)
row.pack(fill=tk.X, pady=2)
ttk.Label(row, text=name, wraplength=520, justify=tk.LEFT).pack(side=tk.LEFT)
var = tk.StringVar(value='')
combo = ttk.Combobox(row, textvariable=var, state='readonly', width=6,
values=['', '1', '2', '3', '4'])
combo.pack(side=tk.RIGHT, padx=6)
rows.append((crit.get('criteria', ''), name, scale, var))
ttk.Label(body, text="Обоснование полученных значений:", font=('Arial', 9, 'bold')).pack(
anchor=tk.W, pady=(10, 2))
justify_txt = tk.Text(body, width=90, height=5, wrap=tk.WORD)
justify_txt.pack(fill=tk.X, pady=(0, 6))
# предзаполнить обоснование из listbox, если есть
if hasattr(self, 'is_criteria_justification_listbox') and self.is_criteria_justification_listbox.size():
justify_txt.insert('1.0', '\n'.join(
self.is_criteria_justification_listbox.get(i)
for i in range(self.is_criteria_justification_listbox.size())))
result_lbl = ttk.Label(body, text="Категория значимости: —", font=('Arial', 10, 'bold'))
result_lbl.pack(anchor=tk.W, pady=(6, 0))
def _calc():
vals = []
for crit_name, ind_name, scale, var in rows:
v = var.get().strip()
if v:
desc = scale.get(v, '')
vals.append(f"{crit_name}: {ind_name}{v}" + (f" ({desc})" if desc else ""))
if vals:
self.is_criteria_values_listbox.delete(0, tk.END)
for line in vals:
self.is_criteria_values_listbox.insert(tk.END, line)
# Категория по максимальному баллу
nums = [int(var.get()) for _, _, _, var in rows if var.get().strip().isdigit()]
if nums:
mx = max(nums)
cat = {4: '1', 3: '2', 2: '3'}.get(mx, 'без категории')
result_lbl.config(text=f"Категория значимости: {cat} (макс. балл {mx})")
else:
result_lbl.config(text="Категория значимости: — (ничего не выбрано)")
def _save():
vals = []
for crit_name, ind_name, scale, var in rows:
v = var.get().strip()
if v:
desc = scale.get(v, '')
vals.append(f"{crit_name}: {ind_name}{v}" + (f" ({desc})" if desc else ""))
if hasattr(self, 'is_criteria_values_listbox'):
self.is_criteria_values_listbox.delete(0, tk.END)
for line in vals:
self.is_criteria_values_listbox.insert(tk.END, line)
# Обоснование
j = justify_txt.get('1.0', 'end-1c').strip()
if hasattr(self, 'is_criteria_justification_listbox'):
self.is_criteria_justification_listbox.delete(0, tk.END)
for line in j.split('\n'):
if line.strip():
self.is_criteria_justification_listbox.insert(tk.END, line.strip())
# Категория → в поле категории объекта
nums = [int(var.get()) for _, _, _, var in rows if var.get().strip().isdigit()]
if nums:
mx = max(nums)
cat = {4: '1', 3: '2', 2: '3'}.get(mx, 'без категории')
if hasattr(self, 'is_category_var'):
self.is_category_var.set(cat)
self.log(f"📋 Показатели критериев значимости сохранены ({len(vals)} шт.)")
dialog.destroy()
btn_frame = ttk.Frame(body)
btn_frame.pack(fill=tk.X, pady=(8, 0))
ttk.Button(btn_frame, text="🧮 Рассчитать категорию", command=_calc).pack(side=tk.LEFT, padx=2)
ttk.Button(btn_frame, text="💾 Сохранить", command=_save).pack(side=tk.LEFT, padx=2)
ttk.Button(btn_frame, text="Отмена", command=dialog.destroy).pack(side=tk.LEFT, padx=2)
dialog.wait_window()
def _download_fstec_threats(self):
"""Скачать перечень угроз с сайта ФСТЭК (БДУ) и обновить список."""
try: