feat: автоподбор применимости показателей критериев значимости по сфере

- dictionaries/criteria_applicability.json: для каждой сферы перечень применимых
  показателей ПП РФ № 127, построен из признаков значимости Распоряжения ПРФ
  № 360-р (например: ОПК → только 13 оборонзаказ, показатель 1 «ущерб жизни
  и здоровью» не применим; банки → только 10; связь → только 4)
- окно ПП 127: неприменимые для сферы показатели блокируются и помечаются
  серым «(не применим для сферы)», исключаются из расчёта категории;
  если применимых показателей для сферы нет — категория «без категории»
This commit is contained in:
2026-08-04 20:22:38 +04:00
parent 5bbd9313f5
commit 07f0800b73
2 changed files with 207 additions and 8 deletions
@@ -0,0 +1,162 @@
{
"Здравоохранение": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Наука": [
"1",
"11",
"13",
"7",
"8",
"9"
],
"Транспорт": [
"10",
"11",
"3"
],
"Связь": [
"4"
],
"Энергетика": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Государственная регистрация прав": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Банковская сфера и финансовый рынок": [
"10"
],
"Топливно-энергетический комплекс": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Атомная энергетика": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Оборонная промышленность": [
"13"
],
"Ракетно-космическая промышленность": [
"10"
],
"Горнодобывающая промышленность": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Металлургическая промышленность": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Химическая промышленность": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Иная сфера деятельности": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
]
}
+45 -8
View File
@@ -1831,6 +1831,18 @@ class DokoGenApp:
messagebox.showerror("Ошибка", f"Не удалось загрузить таблицу ПП 127: {e}") messagebox.showerror("Ошибка", f"Не удалось загрузить таблицу ПП 127: {e}")
return return
# Применимость показателей по сфере объекта (Распоряжение ПРФ № 360-р)
applicable = None # None = все показатели применимы
try:
_app_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'criteria_applicability.json')
with open(_app_path, encoding='utf-8') as _f:
_app = _json.load(_f)
_sphere = self.is_sphere_combo.get() if hasattr(self, 'is_sphere_combo') else ''
if _sphere and _sphere in _app:
applicable = set(_app[_sphere])
except Exception:
applicable = None
dialog = tk.Toplevel(self.root) dialog = tk.Toplevel(self.root)
dialog.title("Показатели критериев значимости (ПП РФ № 127)") dialog.title("Показатели критериев значимости (ПП РФ № 127)")
dialog.geometry("980x700") dialog.geometry("980x700")
@@ -1855,10 +1867,16 @@ class DokoGenApp:
"объектов критической информационной инфраструктуры РФ» (приложение). " "объектов критической информационной инфраструктуры РФ» (приложение). "
"По каждому показателю выберите значение, которому соответствует объект. " "По каждому показателю выберите значение, которому соответствует объект. "
"Категория определяется по наивысшей из выбранных.", "Категория определяется по наивысшей из выбранных.",
foreground='gray', wraplength=920, justify=tk.LEFT).pack(anchor=tk.W, pady=(0, 8)) foreground='gray', wraplength=920, justify=tk.LEFT).pack(anchor=tk.W, pady=(0, 4))
if applicable is not None:
_sphere_txt = self.is_sphere_combo.get() if hasattr(self, 'is_sphere_combo') else ''
ttk.Label(body, text=f"Сфера объекта: «{_sphere_txt}». Показатели, не применимые для этой сферы "
f"(по Распоряжению ПРФ № 360-р), отмечены серым и исключены из расчёта.",
foreground='#1a6e1a', wraplength=920, justify=tk.LEFT).pack(anchor=tk.W, pady=(0, 8))
rows = [] # (num, name, values{III:..,II:..,I:..}, var) rows = [] # (num, name, values{III:..,II:..,I:..}, var, applicable)
import re as _re
for crit in criteria: for crit in criteria:
grp = ttk.LabelFrame(body, text=crit.get('criteria', ''), padding=6) grp = ttk.LabelFrame(body, text=crit.get('criteria', ''), padding=6)
grp.pack(fill=tk.X, pady=4) grp.pack(fill=tk.X, pady=4)
@@ -1866,12 +1884,18 @@ class DokoGenApp:
num = ind.get('num', '') num = ind.get('num', '')
name = ind.get('name', '') name = ind.get('name', '')
values = ind.get('values', {}) values = ind.get('values', {})
# Группа показателя: '1'→'1', '2а'→'2', '10.1'→'10', '13б'→'13'
m = _re.match(r'(\d+)', num)
grp_num = m.group(1) if m else num
applicable_here = applicable is None or grp_num in applicable
row = ttk.Frame(grp) row = ttk.Frame(grp)
row.pack(fill=tk.X, pady=2) row.pack(fill=tk.X, pady=2)
# Номер показателя # Номер показателя
ttk.Label(row, text=num, width=4, font=('Arial', 9, 'bold')).pack(side=tk.LEFT) lbl_num = ttk.Label(row, text=num, width=4, font=('Arial', 9, 'bold'))
lbl_num.pack(side=tk.LEFT)
# Текст показателя # Текст показателя
ttk.Label(row, text=name, wraplength=640, justify=tk.LEFT).pack(side=tk.LEFT) lbl_name = ttk.Label(row, text=name, wraplength=640, justify=tk.LEFT)
lbl_name.pack(side=tk.LEFT)
# Выбор значения (категория) # Выбор значения (категория)
var = tk.StringVar(value='') var = tk.StringVar(value='')
options = [''] options = ['']
@@ -1882,7 +1906,15 @@ class DokoGenApp:
combo = ttk.Combobox(row, textvariable=var, state='readonly', width=34, combo = ttk.Combobox(row, textvariable=var, state='readonly', width=34,
values=options) values=options)
combo.pack(side=tk.RIGHT, padx=6) combo.pack(side=tk.RIGHT, padx=6)
rows.append((num, name, values, var)) if not applicable_here:
# Показатель не применим для сферы объекта — блокируем
combo.config(state='disabled')
lbl_num.config(foreground='gray')
lbl_name.config(foreground='gray')
if hasattr(lbl_name, 'config'):
pass
lbl_name.config(text=name + ' (не применим для сферы)')
rows.append((num, name, values, var, applicable_here))
# Обоснование # Обоснование
ttk.Label(body, text="Обоснование полученных значений:", font=('Arial', 9, 'bold')).pack( ttk.Label(body, text="Обоснование полученных значений:", font=('Arial', 9, 'bold')).pack(
@@ -1898,9 +1930,11 @@ class DokoGenApp:
result_lbl.pack(anchor=tk.W, pady=(6, 0)) result_lbl.pack(anchor=tk.W, pady=(6, 0))
def _selected(): def _selected():
"""Список выбранных: [(num, name, категория, значение)].""" """Список выбранных: [(num, name, категория, значение)] — только применимые показатели."""
out = [] out = []
for num, name, values, var in rows: for num, name, values, var, applicable_here in rows:
if not applicable_here:
continue
s = var.get().strip() s = var.get().strip()
if not s: if not s:
continue continue
@@ -1923,7 +1957,10 @@ class DokoGenApp:
def _calc(): def _calc():
selected = _selected() selected = _selected()
if not selected: if not selected:
result_lbl.config(text="Категория значимости: — (ничего не выбрано)") if applicable is not None and not any(a for _, _, _, _, a in rows):
result_lbl.config(text="Категория значимости: без категории (для этой сферы нет применимых показателей)")
else:
result_lbl.config(text="Категория значимости: — (ничего не выбрано)")
return return
cat = _calc_category(selected) cat = _calc_category(selected)
result_lbl.config(text=f"Категория значимости: {cat} (выбрано показателей: {len(selected)})") result_lbl.config(text=f"Категория значимости: {cat} (выбрано показателей: {len(selected)})")