показатели значимости: автогенерация таблицы 1-14 в шаблон категорирования (обоснование применимости по сфере + авторасчёт 8/9 из данных организации)

This commit is contained in:
LocoAgent
2026-08-05 12:44:03 +04:00
parent a8fa3f908c
commit 9a1058559a
+195
View File
@@ -373,9 +373,204 @@ def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]:
})
loops['employees_access'] = items
# --- Шаблон «Сведения о результатах категорирования» (ФСТЭК) ---
# Цикл объектов КИИ — тот же список, что information_systems.
if is_list:
loops['kii_objects'] = loops.get('information_systems', [])
# Цикл показателей критериев значимости (ПП-127): автоматически
# формируем строки таблицы с обоснованием «применим/не применим» по сфере
# и расчётом для показателей 8 и 9 (как в примере заказчика).
loops['categorization_indicators'] = _build_categorization_indicators(company, is_list)
return loops
# ============================================================
# ПОКАЗАТЕЛИ КРИТЕРИЕВ ЗНАЧИМОСТИ (ПП-127)
# ============================================================
def _num(s):
"""«121 118 270 руб», «1 000», «2 212.5» → число (float) или None."""
s = str(s or '').strip()
if not s:
return None
s = s.replace('\u00a0', ' ').replace(' ', '')\
.replace('руб', '').replace('', '').replace('р.', '').replace(',', '.')
try:
return float(s)
except Exception:
return None
def _load_json(name):
import json as _json
import os as _os
p = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), 'dictionaries', name)
try:
with open(p, encoding='utf-8') as f:
return _json.load(f)
except Exception:
return {}
def _calc_criterion_8_9(company, isys, num):
"""Расчёт показателя 8 (ущерб субъекту КИИ) или 9 (ущерб бюджету РФ).
Возвращает (текст_обоснования, категория или None).
Логика — как в авторасчёте программы (окно ПП-127), текст — по образцу
заполнения заказчика для ОПК.
"""
# ΔДоходов: ущерб из вкладки «Организация» или балансовая стоимость объекта
d = _num(getattr(company, 'income_loss', '') or '')
if d is None:
d = _num(getattr(isys, 'balance_value', '') or '')
# Доходы за 5 лет
incs = [_num(getattr(company, f'income_year_{i}', '') or '') for i in range(1, 6)]
incs = [x for x in incs if x is not None]
r_avg = (sum(incs) / len(incs)) if incs else None
if num == '8':
if d is None or not r_avg:
return ("Показатель применим. Для расчёта заполните данные организации: "
"«Снижение уровня дохода» и доходы за 5 лет, или балансовую стоимость объекта КИИ.", None)
val_pct = d / r_avg * 100
if val_pct > 20:
cat = 'I'
elif val_pct > 10:
cat = 'II'
elif val_pct >= 1:
cat = 'III'
else:
cat = None
text = (f"Реализация компьютерных атак на объект не приведет к снижению доходов субъекта КИИ. "
f"Объем среднегодового дохода субъекта КИИ (с учетом НДС), усредненный за прошедший "
f"5-летний период, составляет {r_avg:,.2f} руб. В случае выхода из строя объекта КИИ "
f"потребуется замена, установка и настройка, стоимость которых составит около {d:,.2f} руб. "
f"Абсолютный ущерб субъекта КИИ: U={d:,.2f} руб. "
f"Показатель ущерба субъекту КИИ: U_8 = U / R_год = {d:,.2f} / {r_avg:,.2f} "
f"= {val_pct:.6f} %.")
if cat:
text += (f" Так как {val_pct:.6f} % более 1% (пороговое значение для 3-й категории "
f"значимости согласно Постановлению № 127), по данному показателю объекту "
f"присваивается {cat}-я категория значимости.")
else:
text += (f" Так как {val_pct:.6f} % менее 1% (пороговое значение для 3-й категории "
f"значимости согласно Постановлению № 127), по данному показателю объекту "
f"категория значимости не присваивается.")
return text, cat
# num == '9'
if d is None:
return ("Показатель применим. Для расчёта заполните данные организации: "
"«Снижение уровня дохода» или балансовую стоимость объекта КИИ.", None)
profit = _num(getattr(company, 'profit', '') or '')
tax = _num(getattr(company, 'profit_tax', '') or '')
if profit and tax is not None and profit > 0:
knp = tax / profit
dnp = d * knp
if d > profit:
dnp = tax
else:
knp = 0.0
dnp = 0.0
DEFAULT_BUDGET_AVG = 21228053357700.0 # Бусредн — прогноз годового дохода федбюджета, усреднённый за 3 года
b = DEFAULT_BUDGET_AVG
val_pct = dnp / b * 100
if val_pct > 0.001:
cat = 'I'
elif val_pct > 0.0006:
cat = 'II'
elif val_pct > 0.0003:
cat = 'III'
else:
cat = None
text = (f"Реализация компьютерных атак на объект не приведет к снижению доходов федерального бюджета. "
f"Объем прогнозируемого годового дохода федерального бюджета Российской Федерации, "
f"усредненный за планируемый трехлетний период, составляет {b:,.2f} руб. "
f"Коэффициент выплат (отчислений) от налога на прибыль субъекта КИИ = {knp:.4f}. "
f"В случае выхода из строя объекта КИИ потребуется замена, установка и настройка, "
f"стоимость которых составит около {d:,.2f} руб. "
f"Снижение выплат (отчислений) от налога на прибыль субъекта КИИ в бюджеты Российской Федерации "
f"Уабс = {d:,.2f} × {knp:.4f} = {dnp:,.2f} руб. (приближённо). "
f"Показатель ущерба бюджетам = {val_pct:.6f} %.")
if cat:
text += (f" Так как {val_pct:.6f} % более 0,0003% (пороговое значение для 3-й категории "
f"значимости согласно Постановлению № 127), по данному показателю объекту "
f"присваивается {cat}-я категория значимости.")
else:
text += (f" Так как {val_pct:.6f} % менее 0,0003% (пороговое значение для 3-й категории "
f"значимости согласно Постановлению № 127), по данному показателю объекту "
f"категория значимости не присваивается.")
return text, cat
def _build_categorization_indicators(company, is_list):
"""Строки таблицы показателей критериев значимости для шаблона
«Сведения о результатах категорирования» (цикл categorization_indicators).
Для каждого показателя ПП-127 (5 групп, показатели 1–14 с подпунктами):
section, name, number, value, justification, category.
- Если показатель не применим для сферы объекта — value = «Показатель не применим.»,
justification — текст из словаря обоснований по сфере (или заготовка).
- Если применим (8/9) — автоматический расчёт из данных организации.
"""
import re as _re
criteria = _load_json('criteria_127.json')
applicability = _load_json('criteria_applicability.json')
justifications = _load_json('criteria_justifications.json')
if not isinstance(criteria, list):
criteria = []
sphere = getattr(is_list[0], 'sphere', '') if is_list else ''
sphere = (sphere or '').strip()
appl = applicability.get(sphere) if isinstance(applicability, dict) else None
just = (justifications.get(sphere) or {}) if isinstance(justifications, dict) else {}
isys = is_list[0] if is_list else None
rows = []
for grp in criteria:
section = grp.get('criteria', '')
for ind in grp.get('indicators', []):
num = str(ind.get('num', ''))
name = ind.get('name', '')
m = _re.match(r'(\d+)', num)
grp_num = m.group(1) if m else num
applicable = appl is None or grp_num in appl
value = ''
justification = ''
category = ''
if not applicable:
value = 'Показатель не применим.'
justification = (just.get(num) or just.get(grp_num)
or f"Показатель не применим к объекту КИИ организации сферы «{sphere or ''}».")
elif grp_num in ('8', '9'):
text, cat = _calc_criterion_8_9(company, isys, grp_num)
if text:
value = text
if cat:
category = f"{cat}-я категория"
else:
category = ''
else:
# Применимый показатель без авторасчёта — оператор заполняет значение
value = ''
justification = (just.get(num) or just.get(grp_num)
or 'Показатель применим к объекту КИИ; значение определяется по данным объекта.')
rows.append({
'section': section,
'name': name,
'number': num,
'value': value,
'justification': justification,
'category': category,
})
return rows
# ============================================================
# РЕЗОЛВЕР ЗНАЧЕНИЙ
# ============================================================