fix: аудит dokogen187 — критические и высокие замечания

- api_settings: убраны реальные API-ключи из кода (env/файл) — 1.1
- verify: промт ИИ для проверки реквизитов больше не ссылается на 187-ФЗ
  как на закон о ПДн («юрист по ПДн (187-ФЗ)» → специалист по реквизитам)
- ui: дата ПП РФ №127 исправлена 08.02.2019 → 08.02.2018 (3 места)
- help_docs: группа B переименована «187-ФЗ (персональные данные)» →
  «187-ФЗ (критическая информационная инфраструктура)»
- generator: убран дубль хвоста параграфа в _highlight_unmatched
- importer: «магия дат» (числа 10001-79999 → дата Excel) теперь только
  для полей-дат (as_date), финансовые поля не ломаются
- ui (_collect_is): убраны дубли пар маппинга attacker_level/attacker_types
- ui (импорт): убрано наследие 152-ФЗ ispdnPosition/ispdnFio из маппинга
- test_generator: переписан под модель 187-ФЗ (category/sphere/object_type
  вместо pd_count/threat_type/defence_level)
- criteria_justifications: п.9 для здравоохранения согласован с кодом
  (убран «критерий неприменим» — противоречие с «применим ко всем»)
- criteria_applicability: Транспорт (добавлены п.1, п.2; убран п.10),
  Банковская сфера (добавлены п.8, п.9) — требуется экспертная сверка
- ui: Бусредн вынесен в константу DEFAULT_BUDGET_AVG (данные Минфина,
  обновлять ежегодно); автозаполнение п.9 унифицировано с авторасчётом
  (ΔБ = ΔНП+ΔНДПИ+ΔДИВ+ΔВСО+ΔНС)
- ui: п.8 — предупреждение при неполных данных (менее 5 лет доходов)
- models: поля ndpi/div/vso/ns для п.9; комментарий income_loss уточнён
- .gitignore: api_settings.json/.env не попадают в git
This commit is contained in:
LocoAgent
2026-08-05 09:16:16 +04:00
parent 868e0065f3
commit 5af23ae525
11 changed files with 107 additions and 68 deletions
+5
View File
@@ -3,3 +3,8 @@ __pycache__/
.autosave/ .autosave/
Сгенерированные_документы/ Сгенерированные_документы/
*.result.docx *.result.docx
# Секреты и локальные настройки — НЕ коммитить
api_settings.json
.env
.env.local
+10 -4
View File
@@ -13,11 +13,17 @@ import os
import sys import sys
import json import json
# ВНИМАНИЕ: реальные ключи НЕ хранятся в коде (утечка в git-историю — см. аудит).
# Источники ключей (по приоритету):
# 1) переменные окружения DADATA_TOKEN / DEEPSEEK_API_KEY / DEEPSEEK_MODEL / DEEPSEEK_BASE_URL
# 2) файл api_settings.json рядом с программой
# 3) DEFAULT_SETTINGS ниже (пустые значения)
DEFAULT_SETTINGS = { DEFAULT_SETTINGS = {
'dadata_token': '431aa11706bf83f571cef0319259289aa750bcb1', 'dadata_token': os.environ.get('DADATA_TOKEN', ''),
'ai_api_key': 'sk-3c7e6673f245412697d0e14b6096435f', 'ai_api_key': os.environ.get('DEEPSEEK_API_KEY', ''),
'ai_model': 'deepseek-chat', 'ai_model': os.environ.get('DEEPSEEK_MODEL', 'deepseek-chat'),
'ai_base_url': 'https://api.deepseek.com', 'ai_base_url': os.environ.get('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
} }
# Старые ключи (для совместимости со старым файлом api_settings.json) # Старые ключи (для совместимости со старым файлом api_settings.json)
@@ -23,9 +23,10 @@
"9" "9"
], ],
"Транспорт": [ "Транспорт": [
"10", "1",
"11", "2",
"3" "3",
"11"
], ],
"Связь": [ "Связь": [
"4" "4"
@@ -61,6 +62,8 @@
"14" "14"
], ],
"Банковская сфера и финансовый рынок": [ "Банковская сфера и финансовый рынок": [
"8",
"9",
"10" "10"
], ],
"Топливно-энергетический комплекс": [ "Топливно-энергетический комплекс": [
@@ -1,23 +1,23 @@
{ {
"Здравоохранение": { "Здравоохранение": {
"1": "Рассматриваемый объект КИИ не участвует в обработке информации, необходимой для оказания медицинской помощи, проведения медицинских экспертиз, медицинских осмотров и медицинских освидетельствований, санитарно-противоэпидемических (профилактических) мероприятий и не осуществляет управление, контроль или мониторинг данных бизнес-процессов. Объект является вспомогательной информационной системой, целью которой является автоматизация процессов ведения бухгалтерского учета. Объект не осуществляет мониторинг, контроль или управление другими объектами КИИ, обеспечивающими жизненно важные функции организма. Утрата или иное повреждение объекта КИИ не может повлиять на жизненно важные функции организма.", "1": "Рассматриваемый объект КИИ не участвует в обработке информации, необходимой для оказания медицинской помощи, проведения медицинских экспертиз, медицинских осмотров и медицинских освидетельствований, санитарно-противоэпидемических (профилактических) мероприятий и не осуществляет управление, контроль или мониторинг данных бизнес-процессов. Объект является вспомогательной информационной системой, целью которой является автоматизация процессов ведения бухгалтерского учета. Объект не осуществляет мониторинг, контроль или управление другими объектами КИИ, обеспечивающими жизненно важные функции организма. Утрата или иное повреждение объекта КИИ не может повлиять на жизненно важные функции организма.",
"2": "Объект КИИ не относится к объектам обеспечения жизнедеятельности населения, в том числе объектам водоснабжения и канализации, очистки сточных вод, тепло- и электроснабжения, гидротехнических сооружений.", "2": "Объект КИИ не относится к объектам обеспечения жизнедеятельности населения, в том числе объектам водоснабжения и канализации, очистки сточных вод, тепло- и электроснабжения, гидротехнических сооружений.",
"3": "Объекты КИИ организации сферы здравоохранения не относятся к объектам транспортной инфраструктуры, и субъект КИИ не имеет в своем составе подразделения транспортной инфраструктуры.", "3": "Объекты КИИ организации сферы здравоохранения не относятся к объектам транспортной инфраструктуры, и субъект КИИ не имеет в своем составе подразделения транспортной инфраструктуры.",
"4": "Субъект КИИ не является оператором связи, объекты КИИ организации сферы здравоохранения не относятся к объектам транспортной инфраструктуры.", "4": "Субъект КИИ не является оператором связи, объекты КИИ организации сферы здравоохранения не относятся к объектам транспортной инфраструктуры.",
"5": "Организация сферы здравоохранения является юридическим лицом, обеспечивающим эксплуатацию информационной системы, задействованной в процессе оказания государственной услуги, однако нарушение функционирования информационной системы (рассматриваемого объекта КИИ), не участвующей в обработке информации, необходимой для оказания государственной услуги, не влечет за собой нарушение и (или) прекращение доступа к государственной услуге. Рассматриваемый объект КИИ не участвует в обработке информации, необходимой для оказания государственной услуги.", "5": "Организация сферы здравоохранения является юридическим лицом, обеспечивающим эксплуатацию информационной системы, задействованной в процессе оказания государственной услуги, однако нарушение функционирования информационной системы (рассматриваемого объекта КИИ), не участвующей в обработке информации, необходимой для оказания государственной услуги, не влечет за собой нарушение и (или) прекращение доступа к государственной услуге. Рассматриваемый объект КИИ не участвует в обработке информации, необходимой для оказания государственной услуги.",
"6": "Организация сферы здравоохранения не является органом государственной власти, в связи с этим критерий неприменим.", "6": "Организация сферы здравоохранения не является органом государственной власти, в связи с этим критерий неприменим.",
"7": "Организация сферы здравоохранения не является субъектом политической сферы деятельности, в связи с этим критерий неприменим.", "7": "Организация сферы здравоохранения не является субъектом политической сферы деятельности, в связи с этим критерий неприменим.",
"8": "Организация сферы здравоохранения не является государственной корпорацией, государственным унитарным предприятием, государственной компанией, стратегическим акционерным обществом, стратегическим предприятием, в связи с этим критерий неприменим.", "8": "Организация сферы здравоохранения не является государственной корпорацией, государственным унитарным предприятием, государственной компанией, стратегическим акционерным обществом, стратегическим предприятием, в связи с этим критерий неприменим.",
"9": "Объект КИИ задействован только в предоставлении медицинских услуг, освобожденных от налогообложения, в связи с этим критерий неприменим.", "9": "Объект КИИ задействован в предоставлении медицинских услуг. Показатель 9 применим ко всем субъектам КИИ; при расчёте учитывается, что медицинские услуги освобождены от налогообложения (значение показателя может быть минимальным).",
"10": "Организация сферы здравоохранения не является субъектом банковской сферы деятельности.", "10": "Организация сферы здравоохранения не является субъектом банковской сферы деятельности.",
"10.1": "Организация сферы здравоохранения не является центральным контрагентом.", "10.1": "Организация сферы здравоохранения не является центральным контрагентом.",
"10.2": "Организация сферы здравоохранения не является центральным депозитарием.", "10.2": "Организация сферы здравоохранения не является центральным депозитарием.",
"10.3": "Организация сферы здравоохранения не является негосударственным пенсионным фондом.", "10.3": "Организация сферы здравоохранения не является негосударственным пенсионным фондом.",
"10.4": "Организация сферы здравоохранения не является страховой организацией.", "10.4": "Организация сферы здравоохранения не является страховой организацией.",
"10.5": "Организация сферы здравоохранения не является оператором услуг информационного обмена.", "10.5": "Организация сферы здравоохранения не является оператором услуг информационного обмена.",
"11": "Объекты КИИ организации сферы здравоохранения не относятся к опасным производственным объектам в соответствии с законодательством Российской Федерации.", "11": "Объекты КИИ организации сферы здравоохранения не относятся к опасным производственным объектам в соответствии с законодательством Российской Федерации.",
"12": "Организация сферы здравоохранения не выполняет функции пункта управления (ситуационного центра) государственного органа власти или государственной корпорации.", "12": "Организация сферы здравоохранения не выполняет функции пункта управления (ситуационного центра) государственного органа власти или государственной корпорации.",
"13": "Организация сферы здравоохранения не является субъектом обеспечения обороны страны, безопасности государства и правопорядка.", "13": "Организация сферы здравоохранения не является субъектом обеспечения обороны страны, безопасности государства и правопорядка.",
"14": "Организация сферы здравоохранения не имеет информационных систем, функционирующих в области обеспечения обороны страны, безопасности государства и правопорядка." "14": "Организация сферы здравоохранения не имеет информационных систем, функционирующих в области обеспечения обороны страны, безопасности государства и правопорядка."
} }
} }
-3
View File
@@ -692,9 +692,6 @@ def _highlight_unmatched(doc):
pos = m.end() pos = m.end()
if pos < len(full): if pos < len(full):
_add_run_with_tabs(para, full[pos:], first) _add_run_with_tabs(para, full[pos:], first)
run = para.add_run(full[pos:])
if first is not None:
_copy_run_style(first, run)
for para in doc.paragraphs: for para in doc.paragraphs:
_process_para(para) _process_para(para)
+1 -1
View File
@@ -83,7 +83,7 @@ def _generate_variables_reference(path):
_p(doc, 'Список переменных для шаблонов Word. Переменная подставляется в текст ' _p(doc, 'Список переменных для шаблонов Word. Переменная подставляется в текст '
'как есть: {{имя_переменной}}. Циклы: <!-- loop:имя --> ... <!-- loop_end -->.') 'как есть: {{имя_переменной}}. Циклы: <!-- loop:имя --> ... <!-- loop_end -->.')
groups = {'A': 'Общие переменные', 'B': '187-ФЗ (персональные данные)'} groups = {'A': 'Общие переменные', 'B': '187-ФЗ (критическая информационная инфраструктура)'}
by_group = {'A': [], 'B': []} by_group = {'A': [], 'B': []}
for var, group, desc, _ in VARIABLE_DEFS: for var, group, desc, _ in VARIABLE_DEFS:
by_group.setdefault(group, []).append((var, desc)) by_group.setdefault(group, []).append((var, desc))
+12 -5
View File
@@ -97,13 +97,18 @@ def normalize_address(text: str) -> str:
return ''.join(out) return ''.join(out)
def clean_value(val): def clean_value(val, as_date=False):
"""Очистка и нормализация значения ячейки Excel.""" """Очистка и нормализация значения ячейки Excel.
as_date=True конвертировать серийный номер Excel в дату (только для
полей-дат, напр. ogrnDate). Для финансовых полей НЕ вызывается,
иначе число 50 000 руб. превратится в «дату» (см. аудит importer.py:105-109).
"""
if val is None: if val is None:
return '' return ''
if isinstance(val, (int, float)): if isinstance(val, (int, float)):
# Попытка распознать дату (серийный номер Excel) # Попытка распознать дату (серийный номер Excel) — только для полей-дат
if 10000 < val < 80000: if as_date and 10000 < val < 80000:
serial = int(val) serial = int(val)
if serial > 60: if serial > 60:
serial -= 1 serial -= 1
@@ -253,7 +258,7 @@ def _import_187_common(wb, data, settings, log_fn):
if sn.lower() == sheet_part.lower(): if sn.lower() == sheet_part.lower():
target_ws = wb[sn] target_ws = wb[sn]
break break
val = clean_value(cell_value(target_ws, coord)) val = clean_value(cell_value(target_ws, coord), as_date=('дата' in name.lower() or 'date' in key.lower()))
if val: if val:
if key in ('chiefFio', 'responsibleFio', 'ispdnFio', 'adminFio'): if key in ('chiefFio', 'responsibleFio', 'ispdnFio', 'adminFio'):
val = _extract_single_fio(val) val = _extract_single_fio(val)
@@ -273,6 +278,8 @@ def _import_187_common(wb, data, settings, log_fn):
if val: if val:
if key in ('chiefFio', 'responsibleFio', 'ispdnFio', 'adminFio'): if key in ('chiefFio', 'responsibleFio', 'ispdnFio', 'adminFio'):
val = _extract_single_fio(val) val = _extract_single_fio(val)
elif 'дата' in name.lower() or 'date' in key.lower():
val = clean_value(val, as_date=True)
data[key] = val data[key] = val
log_fn(f" {name} [по ключу]: {val}") log_fn(f" {name} [по ключу]: {val}")
break break
+7 -1
View File
@@ -135,9 +135,15 @@ class Company:
income_year_3: str = "" # Доход за 3 года назад (руб.) income_year_3: str = "" # Доход за 3 года назад (руб.)
income_year_4: str = "" # Доход за 2 года назад (руб.) income_year_4: str = "" # Доход за 2 года назад (руб.)
income_year_5: str = "" # Доход за прошлый год (руб.) income_year_5: str = "" # Доход за прошлый год (руб.)
income_loss: str = "" # Суммарный экономический ущерб от нарушения income_loss: str = "" # Снижение уровня дохода (суммарный ущерб) от нарушения
# критических процессов (руб.) — ΔДоходов для п. 8 # критических процессов (руб.) — ΔДоходов для п. 8
# Показатель 9: компоненты ΔБ (унификация автозаполнения и авторасчёта)
ndpi: str = "" # Снижение выплат от налога на добычу ПИ ΔНДПИ (руб.)
div: str = "" # Снижение выплат от дивидендов ΔДИВ (руб.)
vso: str = "" # Снижение выплат сторонним организациям ΔВСО (руб.)
ns: str = "" # Снижение сборов ΔНС (руб.)
# Списки # Списки
commission: List[CommissionMember] = field(default_factory=list) commission: List[CommissionMember] = field(default_factory=list)
information_systems: List[InformationSystem] = field(default_factory=list) information_systems: List[InformationSystem] = field(default_factory=list)
+34 -20
View File
@@ -26,6 +26,11 @@ from .verify import (
) )
from .api_settings import load_api_settings, save_api_settings from .api_settings import load_api_settings, save_api_settings
# Бусредн — средний доход федерального бюджета за 3 года (данные Минфина).
# ВАЖНО: значение устаревает — обновлять ежегодно по данным Минфина или
# запрашивать у оператора в диалоге авторасчёта (поле редактируется).
DEFAULT_BUDGET_AVG = 21228053357700.0
class DokoGenApp: class DokoGenApp:
"""Главное окно приложения.""" """Главное окно приложения."""
@@ -353,7 +358,7 @@ class DokoGenApp:
("Доход за 3 года назад (руб.):", "income_year_3"), ("Доход за 3 года назад (руб.):", "income_year_3"),
("Доход за 2 года назад (руб.):", "income_year_4"), ("Доход за 2 года назад (руб.):", "income_year_4"),
("Доход за прошлый год (руб.):", "income_year_5"), ("Доход за прошлый год (руб.):", "income_year_5"),
("Суммарный экономический ущерб от нарушения критических процессов (руб.):", "income_loss"), ("Снижение уровня дохода от нарушения критических процессов (руб.):", "income_loss"),
] ]
for j, (label, key) in enumerate(income_fields): for j, (label, key) in enumerate(income_fields):
ttk.Label(f_fin, text=label).grid(row=len(fin_fields) + 1 + j, column=0, sticky=tk.W, pady=2) ttk.Label(f_fin, text=label).grid(row=len(fin_fields) + 1 + j, column=0, sticky=tk.W, pady=2)
@@ -1418,8 +1423,6 @@ class DokoGenApp:
('is_os_sw_listbox', 'os_sw'), ('is_os_sw_listbox', 'os_sw'),
('is_app_sw_listbox', 'app_sw'), ('is_app_sw_listbox', 'app_sw'),
('is_attacker_category_combo', 'attacker_category'), ('is_attacker_category_combo', 'attacker_category'),
('is_attacker_level_combo', 'attacker_level'),
('is_attacker_types_listbox', 'attacker_types'),
('is_attacker_level_combo', 'attacker_level'), ('is_attacker_level_combo', 'attacker_level'),
('is_attacker_types_listbox', 'attacker_types'), ('is_attacker_types_listbox', 'attacker_types'),
('is_threats_listbox', 'threats'), ('is_threats_listbox', 'threats'),
@@ -2079,7 +2082,7 @@ class DokoGenApp:
def _open_criteria_127_dialog(self): def _open_criteria_127_dialog(self):
"""Окно заполнения показателей критериев значимости по ПП РФ № 127. """Окно заполнения показателей критериев значимости по ПП РФ № 127.
Таблица перенесена дословно из Постановления Правительства РФ от 08.02.2019 127: Таблица перенесена дословно из Постановления Правительства РФ от 08.02.2018 127:
5 критериев (социальная, политическая, экономическая, экологическая, 5 критериев (социальная, политическая, экономическая, экологическая,
значимость для обороны страны), показатели 1-14 с подпунктами, значимость для обороны страны), показатели 1-14 с подпунктами,
значения сразу привязаны к категориям III / II / I. значения сразу привязаны к категориям III / II / I.
@@ -2169,7 +2172,7 @@ class DokoGenApp:
ttk.Label(body, text="Показатели критериев значимости объекта КИИ", ttk.Label(body, text="Показатели критериев значимости объекта КИИ",
font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0, 2)) font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0, 2))
ttk.Label(body, text="Постановление Правительства РФ от 08.02.2019 № 127 «Об утверждении Правил категорирования " ttk.Label(body, text="Постановление Правительства РФ от 08.02.2018 № 127 «Об утверждении Правил категорирования "
"объектов критической информационной инфраструктуры РФ» (приложение). " "объектов критической информационной инфраструктуры РФ» (приложение). "
"По каждому показателю выберите значение, которому соответствует объект. " "По каждому показателю выберите значение, которому соответствует объект. "
"Категория определяется по наивысшей из выбранных.", "Категория определяется по наивысшей из выбранных.",
@@ -2247,7 +2250,7 @@ class DokoGenApp:
"нарушения (в опроснике её нет, вводится вручную; можно приравнять к ущербу из показателя 8).", "нарушения (в опроснике её нет, вводится вручную; можно приравнять к ущербу из показателя 8).",
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W) foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
e_budget = _row("Доход федерального бюджета, усреднённый за 3 года Бусредн (руб.):", 'budget', e_budget = _row("Доход федерального бюджета, усреднённый за 3 года Бусредн (руб.):", 'budget',
'21228053357700') f'{DEFAULT_BUDGET_AVG:.0f}')
ttk.Label(frm, text="⚠️ Бусредн — средний доход федерального бюджета за 3 года (данные Минфина, " ttk.Label(frm, text="⚠️ Бусредн — средний доход федерального бюджета за 3 года (данные Минфина, "
"подставлено значение по умолчанию; при необходимости уточните).", "подставлено значение по умолчанию; при необходимости уточните).",
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W) foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
@@ -2275,19 +2278,22 @@ class DokoGenApp:
e_profit = e_tax = None e_profit = e_tax = None
# Доходы за 5 лет — из данных организации (поля «Доходы за 5 лет») # Доходы за 5 лет — из данных организации (поля «Доходы за 5 лет»)
_incs = [_num(getattr(self.model, f'income_year_{i}', '') or '') for i in range(1, 6)] _incs_raw = [getattr(self.model, f'income_year_{i}', '') or '' for i in range(1, 6)]
_incs = [x for x in _incs if x is not None] _incs = [_num(x) for x in _incs_raw]
_d_avg = (sum(_incs) / len(_incs)) if _incs else None _incs_filled = [x for x in _incs if x is not None]
_d_avg = (sum(_incs_filled) / len(_incs_filled)) if _incs_filled else None
_d_loss = _num(getattr(self.model, 'income_loss', '') or '') _d_loss = _num(getattr(self.model, 'income_loss', '') or '')
e_dincome = _row("Суммарный экономический ущерб от нарушения критических процессов (руб.):", e_dincome = _row("Снижение уровня дохода от нарушения критических процессов (руб.):",
'dincome', f"{_d_loss:,.0f}" if _d_loss else '') 'dincome', f"{_d_loss:,.0f}" if _d_loss else '')
ttk.Label(frm, text="⚠️ Ущерб — поле «Суммарный ущерб (показатель 8)» из вкладки «Организация» " ttk.Label(frm, text="⚠️ ΔДоходов — поле «Снижение уровня дохода (показатель 8)» из вкладки «Организация» "
"(подставлено автоматически, можно поправить).", "(подставлено автоматически, можно поправить).",
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W) foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
e_budget = _row("Доходы субъекта КИИ, усреднённые за 5 лет (руб.):", e_budget = _row("Доходы субъекта КИИ, усреднённые за 5 лет (руб.):",
'budget', f"{_d_avg:,.0f}" if _d_avg else '') 'budget', f"{_d_avg:,.0f}" if _d_avg else '')
ttk.Label(frm, text="⚠️ Доходы за 5 лет — среднее по полям «Доход за …» из вкладки «Организация» " _n_years = len(_incs_filled)
"(подставлено автоматически, можно поправить).", ttk.Label(frm, text=f"⚠️ Доходы за 5 лет — среднее по заполненным годам ({_n_years} из 5). "
"ПП-127 требует усреднение за прошедший пятилетний период: "
"при неполных данных результат методологически некорректен — заполните все 5 лет.",
foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W) foreground='#8a6d00', wraplength=900, justify=tk.LEFT).pack(anchor=tk.W)
result = tk.Text(frm, height=8, wrap=tk.WORD, state=tk.NORMAL) result = tk.Text(frm, height=8, wrap=tk.WORD, state=tk.NORMAL)
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0)) result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
@@ -2302,7 +2308,7 @@ class DokoGenApp:
result.insert('1.0', "Заполните оба поля (ущерб и доходы за 5 лет).") result.insert('1.0', "Заполните оба поля (ущерб и доходы за 5 лет).")
return return
val = d / b * 100 val = d / b * 100
lines.append(f"Суммарный экономический ущерб от нарушения критических процессов: {d:,.0f} руб.") lines.append(f"Снижение уровня дохода от нарушения критических процессов: {d:,.0f} руб.")
lines.append(f"Доходы субъекта КИИ, усреднённые за 5 лет: {b:,.0f} руб.") lines.append(f"Доходы субъекта КИИ, усреднённые за 5 лет: {b:,.0f} руб.")
lines.append(f"№8 = ущерб / доходы за 5 лет × 100% = {d:,.0f} / {b:,.0f} × 100 = {val:.2f} %") lines.append(f"№8 = ущерб / доходы за 5 лет × 100% = {d:,.0f} / {b:,.0f} × 100 = {val:.2f} %")
if val > 20: if val > 20:
@@ -2585,7 +2591,7 @@ class DokoGenApp:
if chosen: if chosen:
auto_parts.append( auto_parts.append(
"Категория значимости объекта КИИ определена в соответствии с Правилами категорирования " "Категория значимости объекта КИИ определена в соответствии с Правилами категорирования "
"объектов критической информационной инфраструктуры РФ (ПП РФ от 08.02.2019 № 127). " "объектов критической информационной инфраструктуры РФ (ПП РФ от 08.02.2018 № 127). "
f"Объект отнесён к {cat} категории значимости. Основания: " f"Объект отнесён к {cat} категории значимости. Основания: "
+ "; ".join(f"показатель {num} ({c} категория)" for num, _, c, _ in chosen) + "." + "; ".join(f"показатель {num} ({c} категория)" for num, _, c, _ in chosen) + "."
) )
@@ -2685,12 +2691,18 @@ class DokoGenApp:
parent=dialog, minvalue=0) parent=dialog, minvalue=0)
if dincome is None: if dincome is None:
continue continue
budget = 21228053357700.0 # Бусредн (доход федерального бюджета за 3 года) budget = DEFAULT_BUDGET_AVG # Бусредн (доход федерального бюджета за 3 года)
knp = tax / profit knp = tax / profit
dnp = dincome * knp dnp = dincome * knp
if dincome > profit: if dincome > profit:
dnp = tax dnp = tax
val = dnp / budget * 100 # Унификация с авторасчётом: ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС
ndpi = _num2(getattr(self.model, 'ndpi', '') or '') or 0
div = _num2(getattr(self.model, 'div', '') or '') or 0
vso = _num2(getattr(self.model, 'vso', '') or '') or 0
ns = _num2(getattr(self.model, 'ns', '') or '') or 0
db = dnp + ndpi + div + vso + ns
val = db / budget * 100
if val > 0.001: if val > 0.001:
cat = 'I' cat = 'I'
elif val > 0.0006: elif val > 0.0006:
@@ -2708,8 +2720,10 @@ class DokoGenApp:
calc_txt = ( calc_txt = (
f"Коэффициент КНП = налог/прибыль = {tax:,.0f} / {profit:,.0f} = {knp:.4f}\n" f"Коэффициент КНП = налог/прибыль = {tax:,.0f} / {profit:,.0f} = {knp:.4f}\n"
f"ΔНП = ΔДоходов × КНП = {dincome:,.0f} × {knp:.4f} = {dnp:,.1f} руб.\n" f"ΔНП = ΔДоходов × КНП = {dincome:,.0f} × {knp:.4f} = {dnp:,.1f} руб.\n"
f"ΔНДПИ = {ndpi:,.0f}, ΔДИВ = {div:,.0f}, ΔВСО = {vso:,.0f}, ΔНС = {ns:,.0f} руб.\n"
f"ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС = {db:,.1f} руб.\n"
f"Бусредн (доход федерального бюджета за 3 года) = {budget:,.0f} руб.\n" f"Бусредн (доход федерального бюджета за 3 года) = {budget:,.0f} руб.\n"
f"№9 = ΔБ / Бусредн × 100% = {dnp:,.1f} / {budget:,.0f} × 100 = {val:.6f} %\n" f"№9 = ΔБ / Бусредн × 100% = {db:,.1f} / {budget:,.0f} × 100 = {val:.6f} %\n"
f"Категория значимости: {cat or '— (не присваивается)'}" f"Категория значимости: {cat or '— (не присваивается)'}"
) )
prefix = f"Показатель {num}. {name} — рекомендация по расчёту:" prefix = f"Показатель {num}. {name} — рекомендация по расчёту:"
@@ -4136,8 +4150,8 @@ class DokoGenApp:
'chief_fio': 'chiefFio', 'chief_fio': 'chiefFio',
'responsible_position': 'responsiblePosition', 'responsible_position': 'responsiblePosition',
'responsible_fio': 'responsibleFio', 'responsible_fio': 'responsibleFio',
'admin_position': ('administratorPosition', 'ispdnPosition'), 'admin_position': 'administratorPosition',
'admin_fio': ('administratorFio', 'ispdnFio'), 'admin_fio': 'administratorFio',
} }
for entry_key, data_keys in officials_map.items(): for entry_key, data_keys in officials_map.items():
entry = self._official_entries.get(entry_key) entry = self._official_entries.get(entry_key)
+2 -1
View File
@@ -694,7 +694,8 @@ def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None
if not api_key: if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)" return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
system = ( system = (
"Ты — юрист по защите персональных данных (187-ФЗ). Проверь реквизиты организации. " "Ты — специалист по проверке реквизитов организации (ИНН/КПП/ОГРН). "
"Проверь реквизиты организации. "
"Верни ТОЛЬКО JSON: " "Верни ТОЛЬКО JSON: "
'{"оценка": "ok|warning|error", ' '{"оценка": "ok|warning|error", '
'"замечания": ["..."], ' '"замечания": ["..."], '
+8 -8
View File
@@ -30,8 +30,8 @@ def create_test_template():
doc.add_paragraph('<!-- loop:information_systems -->') doc.add_paragraph('<!-- loop:information_systems -->')
doc.add_paragraph('=== IS: {{item.name}} ===') doc.add_paragraph('=== IS: {{item.name}} ===')
doc.add_paragraph('Category: {{item.category}}') doc.add_paragraph('Category: {{item.category}}')
doc.add_paragraph('Threat type: {{item.threat_type}}') doc.add_paragraph('Sphere: {{item.sphere}}')
doc.add_paragraph('Defence level: {{item.defence_level}}') doc.add_paragraph('Object type: {{item.object_type}}')
doc.add_paragraph('') doc.add_paragraph('')
# Inner loop: commission # Inner loop: commission
@@ -72,12 +72,12 @@ def test_zip_loop_expansion():
CommissionMember(role='Член комиссии', position='Бухгалтер', fio='Сидорова А.А.'), CommissionMember(role='Член комиссии', position='Бухгалтер', fio='Сидорова А.А.'),
], ],
information_systems=[ information_systems=[
InformationSystem(name='ИС-1 "Бухгалтерия"', category='специальные', InformationSystem(name='ОКИИ-1 "Платёжный шлюз"', category='1',
pd_count='менее 100 000', threat_type='1', defence_level='2'), sphere='Банковская сфера и финансовый рынок', object_type='АСУ'),
InformationSystem(name='ИС-2 "Кадры"', category='иные', InformationSystem(name='ОКИИ-2 "Кадры"', category='2',
pd_count='более 100 000', threat_type='3', defence_level='4'), sphere='Наука', object_type='ИС'),
InformationSystem(name='ИС-3 "Склад"', category='общедоступные', InformationSystem(name='ОКИИ-3 "Склад"', category='3',
pd_count='менее 100 000', threat_type='2', defence_level='3'), sphere='Здравоохранение', object_type='ИС'),
] ]
) )