Авторасчёт остальных показателей ПП-127 (1–7, 11–14): количественные — ввод значения → категория по порогам (человек, тыс. чел., часы, %), качественные — выбор варианта III/II/I из справочника; кнопка «🧮» для всех показателей
This commit is contained in:
+77
-5
@@ -2204,6 +2204,8 @@ class DokoGenApp:
|
|||||||
критериев экономической значимости» ФСТЭК (2021). Показывает расчёт
|
критериев экономической значимости» ФСТЭК (2021). Показывает расчёт
|
||||||
и предлагает категорию как рекомендацию."""
|
и предлагает категорию как рекомендацию."""
|
||||||
import re as _re2
|
import re as _re2
|
||||||
|
from .variables import (CRITERION_QUANT_THRESHOLDS, CRITERION_CHOICE_INDICATORS,
|
||||||
|
CRITERION_QUANT_UNITS)
|
||||||
|
|
||||||
def _num(s):
|
def _num(s):
|
||||||
# «121 118 270 руб», «1 000», «906 779 руб», «2 212.5» → число
|
# «121 118 270 руб», «1 000», «906 779 руб», «2 212.5» → число
|
||||||
@@ -2245,7 +2247,51 @@ class DokoGenApp:
|
|||||||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
if num == '10' or str(num).startswith('10.'):
|
if (num in CRITERION_QUANT_THRESHOLDS or num in CRITERION_CHOICE_INDICATORS):
|
||||||
|
# Остальные показатели (1–7, 11–14): количественные — ввод значения,
|
||||||
|
# качественные — выбор варианта (III/II/I) из справочника ПП-127
|
||||||
|
from .variables import (CRITERION_QUANT_THRESHOLDS, CRITERION_CHOICE_INDICATORS,
|
||||||
|
CRITERION_QUANT_UNITS)
|
||||||
|
is_choice = num in CRITERION_CHOICE_INDICATORS
|
||||||
|
ttk.Label(frm, text=f"Показатель {num} — по порогам ПП-127",
|
||||||
|
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
|
||||||
|
if is_choice:
|
||||||
|
ttk.Label(frm, text="Выберите один из вариантов значений (соответствует III/II/I категории):",
|
||||||
|
foreground='gray', wraplength=760, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
||||||
|
else:
|
||||||
|
_unit = CRITERION_QUANT_UNITS.get(num, '')
|
||||||
|
ttk.Label(frm, text=f"Введите значение показателя ({_unit}): категория определяется "
|
||||||
|
"по пороговым значениям ПП-127.",
|
||||||
|
foreground='gray', wraplength=760, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
|
||||||
|
|
||||||
|
def _row(label, key, default=''):
|
||||||
|
ttk.Label(frm, text=label, wraplength=900, justify=tk.LEFT).pack(anchor=tk.W, pady=(4, 0))
|
||||||
|
e = ttk.Entry(frm, width=80)
|
||||||
|
e.insert(0, str(default or ''))
|
||||||
|
e.pack(fill=tk.X)
|
||||||
|
return e
|
||||||
|
|
||||||
|
if is_choice:
|
||||||
|
# Варианты значений для качественного показателя (из criteria_127.json)
|
||||||
|
_opts = []
|
||||||
|
for _g in (criteria if isinstance(criteria, list) else []):
|
||||||
|
for _ind in _g.get('indicators', []):
|
||||||
|
if str(_ind.get('num', '')) == num:
|
||||||
|
_vals = _ind.get('values', {}) or {}
|
||||||
|
_opts = [str(v) for v in _vals.values() if str(v).strip()]
|
||||||
|
break
|
||||||
|
e_choice = ttk.Combobox(frm, width=90, state='readonly', values=_opts)
|
||||||
|
e_choice.pack(fill=tk.X, pady=4)
|
||||||
|
else:
|
||||||
|
e_value = _row(f"Значение показателя ({_unit}):", 'value', '')
|
||||||
|
result_frame = ttk.Frame(frm)
|
||||||
|
result_frame.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
|
||||||
|
result = tk.Text(result_frame, height=10, wrap=tk.WORD, state=tk.NORMAL)
|
||||||
|
result_scroll = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=result.yview)
|
||||||
|
result.configure(yscrollcommand=result_scroll.set)
|
||||||
|
result.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
result_scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
elif num == '10' or str(num).startswith('10.'):
|
||||||
# Показатель 10 — прекращение финансовых операций (банковская сфера)
|
# Показатель 10 — прекращение финансовых операций (банковская сфера)
|
||||||
is_base = str(num) == '10'
|
is_base = str(num) == '10'
|
||||||
if is_base:
|
if is_base:
|
||||||
@@ -2412,7 +2458,30 @@ class DokoGenApp:
|
|||||||
|
|
||||||
def _calc():
|
def _calc():
|
||||||
lines = []
|
lines = []
|
||||||
if num == '10' or str(num).startswith('10.'):
|
if (num in CRITERION_QUANT_THRESHOLDS or num in CRITERION_CHOICE_INDICATORS):
|
||||||
|
from .variables import (CRITERION_QUANT_THRESHOLDS, CRITERION_CHOICE_INDICATORS,
|
||||||
|
CRITERION_QUANT_UNITS, _calc_criterion_other)
|
||||||
|
is_choice = num in CRITERION_CHOICE_INDICATORS
|
||||||
|
if is_choice:
|
||||||
|
choice = e_choice.get().strip()
|
||||||
|
if not choice:
|
||||||
|
result.delete('1.0', tk.END)
|
||||||
|
result.insert('1.0', "Выберите вариант значения показателя.")
|
||||||
|
return
|
||||||
|
text, cat = _calc_criterion_other(self.model, isys, num, choice=choice)
|
||||||
|
else:
|
||||||
|
val = _num(e_value.get())
|
||||||
|
if val is None:
|
||||||
|
result.delete('1.0', tk.END)
|
||||||
|
result.insert('1.0', "Введите числовое значение показателя.")
|
||||||
|
return
|
||||||
|
text, cat = _calc_criterion_other(self.model, isys, num, value=val)
|
||||||
|
lines.append(text)
|
||||||
|
if cat:
|
||||||
|
lines.append(f"Категория значимости: {cat} (по порогам ПП-127)")
|
||||||
|
else:
|
||||||
|
lines.append("Категория значимости: — (значение ниже порога III)")
|
||||||
|
elif num == '10' or str(num).startswith('10.'):
|
||||||
from .variables import _criterion_10_cat
|
from .variables import _criterion_10_cat
|
||||||
if str(num) == '10':
|
if str(num) == '10':
|
||||||
ops = _num(e_ops.get())
|
ops = _num(e_ops.get())
|
||||||
@@ -2591,9 +2660,12 @@ class DokoGenApp:
|
|||||||
combo.pack(side=tk.RIGHT, padx=6)
|
combo.pack(side=tk.RIGHT, padx=6)
|
||||||
_combos[num] = combo
|
_combos[num] = combo
|
||||||
|
|
||||||
# Кнопка «🧮» — авторасчёт показателя (п. 8 «ущерб субъекту КИИ»,
|
# Кнопка «🧮» — авторасчёт показателя:
|
||||||
# п. 9 «ущерб бюджетам», п. 10 «прекращение финансовых операций»)
|
# 8/9/10 — экономические формулы; остальные (1–7, 11–14) — по порогам ПП-127
|
||||||
if grp_num in ('8', '9', '10'):
|
from .variables import (CRITERION_QUANT_THRESHOLDS, CRITERION_CHOICE_INDICATORS)
|
||||||
|
if (grp_num in ('8', '9', '10')
|
||||||
|
or num in CRITERION_QUANT_THRESHOLDS
|
||||||
|
or num in CRITERION_CHOICE_INDICATORS):
|
||||||
ttk.Button(row, text='🧮', width=3,
|
ttk.Button(row, text='🧮', width=3,
|
||||||
command=lambda n=num, nm=name, v=var, c=combo:
|
command=lambda n=num, nm=name, v=var, c=combo:
|
||||||
_open_calc_dialog(n, nm, v, c)
|
_open_calc_dialog(n, nm, v, c)
|
||||||
|
|||||||
@@ -601,6 +601,97 @@ def _calc_criterion_10(company, isys, num):
|
|||||||
return text, cat
|
return text, cat
|
||||||
|
|
||||||
|
|
||||||
|
# Пороги количественных показателей 1–7, 11–14 (ПП-127), кроме 8/9/10:
|
||||||
|
# num → [(категория, lo, lo_incl, hi, hi_incl)] — значение соответствует, если
|
||||||
|
# (lo is None или value > lo при not lo_incl / value >= lo при lo_incl) и
|
||||||
|
# (hi is None или value < hi при not hi_incl / value <= hi при hi_incl).
|
||||||
|
CRITERION_QUANT_THRESHOLDS = {
|
||||||
|
'1': [('III', 1, True, 50, True), ('II', 50, False, 500, True), ('I', 500, False, None, False)], # человек
|
||||||
|
'2б': [('III', 2, True, 1000, False), ('II', 1000, True, 5000, False), ('I', 5000, True, None, False)], # тыс. чел.
|
||||||
|
'3б': [('III', 2, True, 1000, False), ('II', 1000, True, 5000, False), ('I', 5000, True, None, False)], # тыс. чел.
|
||||||
|
'4а': [('III', 3, True, 1000, False), ('II', 1000, True, 5000, False), ('I', 5000, True, None, False)], # тыс. абонентов
|
||||||
|
'5а': [('III', 12, False, 24, True), ('II', 6, False, 12, True), ('I', None, False, 6, True)], # часов
|
||||||
|
'5б': [('III', None, False, 30, True), ('II', 30, False, 70, True), ('I', 70, False, None, False)], # %
|
||||||
|
'11б': [('III', 2, True, 1000, False), ('II', 1000, True, 5000, False), ('I', 5000, True, None, False)], # тыс. чел.
|
||||||
|
'13а': [('III', 0, False, 10, True), ('II', 10, False, 15, True), ('I', 15, False, None, False)], # %
|
||||||
|
'13б': [('III', 0, False, 10, True), ('II', 10, False, 40, True), ('I', 40, False, None, False)], # %
|
||||||
|
'14': [('III', 2, False, 4, True), ('II', 1, False, 2, True), ('I', None, False, 1, True)], # часов
|
||||||
|
}
|
||||||
|
|
||||||
|
# Качественные показатели (выбор одного из вариантов III/II/I): 2а, 3а, 3в, 3г, 4б, 6, 7, 11а, 12, 13.1
|
||||||
|
CRITERION_CHOICE_INDICATORS = {'2а', '3а', '3в', '3г', '4б', '6', '7', '11а', '12', '13.1'}
|
||||||
|
|
||||||
|
# Единицы измерения количественных показателей (для подписи в окне авторасчёта)
|
||||||
|
CRITERION_QUANT_UNITS = {
|
||||||
|
'1': 'человек', '2б': 'тыс. человек', '3б': 'тыс. человек', '4а': 'тыс. абонентов',
|
||||||
|
'5а': 'часов', '5б': '%', '11б': 'тыс. человек', '13а': '%', '13б': '%', '14': 'часов',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _criterion_other_cat(num, value):
|
||||||
|
"""Категория количественного показателя (кроме 8/9/10) по значению (ПП-127)."""
|
||||||
|
rules = CRITERION_QUANT_THRESHOLDS.get(str(num))
|
||||||
|
if not rules or value is None:
|
||||||
|
return None
|
||||||
|
for cat, lo, lo_incl, hi, hi_incl in rules:
|
||||||
|
ok = True
|
||||||
|
if lo is not None:
|
||||||
|
ok = ok and ((value >= lo) if lo_incl else (value > lo))
|
||||||
|
if hi is not None:
|
||||||
|
ok = ok and ((value <= hi) if hi_incl else (value < hi))
|
||||||
|
if ok:
|
||||||
|
return cat
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _calc_criterion_other(company, isys, num, value=None, choice=None):
|
||||||
|
"""Расчёт остальных показателей критериев значимости (1–7, 11–14).
|
||||||
|
|
||||||
|
Количественные (1, 2б, 3б, 4а, 5а, 5б, 11б, 13а, 13б, 14): value — число,
|
||||||
|
категория по порогам ПП-127 (CRITERION_QUANT_THRESHOLDS).
|
||||||
|
Качественные (2а, 3а, 3в, 3г, 4б, 6, 7, 11а, 12, 13.1): choice — текст
|
||||||
|
выбранного варианта из criteria_127.json (values), категория — его ключ.
|
||||||
|
Возвращает (текст_обоснования, категория или None).
|
||||||
|
"""
|
||||||
|
num = str(num)
|
||||||
|
if num in CRITERION_CHOICE_INDICATORS:
|
||||||
|
# Качественный показатель: выбираем один из вариантов III/II/I
|
||||||
|
criteria = _load_json('criteria_127.json')
|
||||||
|
for grp in (criteria if isinstance(criteria, list) else []):
|
||||||
|
for ind in grp.get('indicators', []):
|
||||||
|
if str(ind.get('num', '')) == num:
|
||||||
|
vals = ind.get('values', {}) or {}
|
||||||
|
if not choice:
|
||||||
|
return (f"Показатель применим. Для расчёта выберите один из вариантов "
|
||||||
|
f"(III/II/I категория) в окне авторасчёта.", None)
|
||||||
|
cat = None
|
||||||
|
for c, txt in vals.items():
|
||||||
|
if txt and str(txt).strip() == str(choice).strip():
|
||||||
|
cat = c
|
||||||
|
break
|
||||||
|
if not cat:
|
||||||
|
return (f"Показатель применим. Выбранный вариант не найден в перечне значений.", None)
|
||||||
|
text = (f"Показатель {num}: {choice}. Согласно Постановлению № 127 данному значению "
|
||||||
|
f"соответствует {cat}-я категория значимости.")
|
||||||
|
return text, cat
|
||||||
|
return (f"Показатель применим. Варианты значений не найдены в справочнике.", None)
|
||||||
|
|
||||||
|
# Количественный показатель
|
||||||
|
if value is None:
|
||||||
|
unit = CRITERION_QUANT_UNITS.get(num, '')
|
||||||
|
return (f"Показатель применим. Для расчёта введите значение показателя"
|
||||||
|
+ (f" ({unit})" if unit else "") + ".", None)
|
||||||
|
cat = _criterion_other_cat(num, value)
|
||||||
|
unit = CRITERION_QUANT_UNITS.get(num, '')
|
||||||
|
text = (f"Показатель {num}: значение показателя — {value:,.6f} {unit}. "
|
||||||
|
f"Согласно Постановлению № 127 ")
|
||||||
|
if cat:
|
||||||
|
text += (f"данному значению соответствует {cat}-я категория значимости.")
|
||||||
|
else:
|
||||||
|
text += ("значение не достигает порога III категории — категория значимости не присваивается.")
|
||||||
|
return text, cat
|
||||||
|
|
||||||
|
|
||||||
def _build_org_measures_text(company, isys=None):
|
def _build_org_measures_text(company, isys=None):
|
||||||
"""Формирует текст организационных мер из словаря org_measures.json
|
"""Формирует текст организационных мер из словаря org_measures.json
|
||||||
с учётом ответов раздела «Физическая безопасность» опросника.
|
с учётом ответов раздела «Физическая безопасность» опросника.
|
||||||
|
|||||||
Reference in New Issue
Block a user