diff --git a/dokogen/dictionaries/criteria_127.json b/dokogen/dictionaries/criteria_127.json new file mode 100644 index 0000000..585b54a --- /dev/null +++ b/dokogen/dictionaries/criteria_127.json @@ -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": "низкое влияние" + } + } + ] + } +] diff --git a/dokogen/ui.py b/dokogen/ui.py index d57093a..d732168 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -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("", 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: