Compare commits

...
109 Commits
Author SHA1 Message Date
LocoAgent 691b167725 Фикс UnboundLocalError в окне авторасчёта ПП-127
Причина: внутри _calc() в ветках стояли импорты
'from .variables import CRITERION_QUANT_THRESHOLDS...' — Python считает
эти имена локальными для всей функции, а условие
'if (num in CRITERION_QUANT_THRESHOLDS ...)' в начале _calc обращалось
к ним до присваивания → UnboundLocalError при любом расчёте.

Исправление: все импорты из .variables вынесены на уровень
_open_calc_dialog (начало функции); внутренние импорты из _calc()
и дубликаты в теле окна убраны.
2026-08-05 17:03:05 +04:00
LocoAgent a7878804e4 Отраслевые ПП: применимость показателей + формулы ОПК
- criteria_applicability.json: Атомная (ПП-4: без 4, 10-10.7, 14; +6),
  Связь (ПП-402: +6, 8, 9), Оборонная (ПП-796: 1-4, 6-13.1, без 5/10/14),
  Транспорт (+8, 9, 13, 14), Наука (1, 7-9, 11, 13)
- variables.py: _calc_criterion_8_opk (U8=(C+A+X)/Rгод*100%, ПП-796 п.18-20),
  _calc_criterion_13_opk (13а: V=(1-(Vmax-Vинц)/VГОЗ)*100%, 13б: T=(1-tнорм/tненорм)*100%)
- ui.py: ОПК-поля в окне авторасчёта (C/A/X, Vmax/Vинц/VГОЗ, tнорм/tненорм),
  сохранение в criterion_input
2026-08-05 16:57:49 +04:00
LocoAgent 8e97e10714 Опросник: добавлен словарь criterion_fields (20 показателей ПП-127), импортёр распознаёт строки «Показатель N …» в листе объекта и складывает в criterion_input; фильтр по сфере (берутся только применимые показатели) 2026-08-05 15:39:06 +04:00
LocoAgent dbf8c613dd Окно авторасчёта сохраняет введённые значения показателей в criterion_input (модель) — автогенерация шаблона использует их 2026-08-05 15:28:35 +04:00
LocoAgent b2b68258fd Авторасчёт всех показателей ПП-127: поле criterion_input в модели (введённые значения из опросника/окна), автогенерация шаблона считает категории по введённым значениям (1–7, 11–14); фикс остановки расчёта (I-я категория вместо startswith('I')) 2026-08-05 15:27:24 +04:00
LocoAgent 1840f1e2d4 Авторасчёт остальных показателей ПП-127 (1–7, 11–14): количественные — ввод значения → категория по порогам (человек, тыс. чел., часы, %), качественные — выбор варианта III/II/I из справочника; кнопка «🧮» для всех показателей 2026-08-05 15:19:11 +04:00
LocoAgent e15610ba29 Удалить случайно загруженный документ (методичка Минздрава по категорированию КИИ сферы здравоохранения) — файл сохранён локально в dokogen/documents 2026-08-05 15:12:21 +04:00
kneon223557 36d2d5a87b Загрузить файлы в «/» 2026-08-05 15:02:44 +04:00
LocoAgent c90660a0f8 ПП-127 п.6: остановка расчёта по остальным показателям при присвоении I категории (в автогенерации шаблона категорирования — «расчёт не проводится») 2026-08-05 15:01:12 +04:00
LocoAgent 5bb6a2f5e9 Вкладка «Организация»: вместо 5 полей доходов по годам — одна строка «Доход за 5 лет (суммарно)» + «Снижение уровня дохода», жирный заголовок убран; миграция старых файлов (сумма по годам → income_5y), авторасчёт/автозаполнение/DaData/шаблоны обновлены 2026-08-05 14:54:31 +04:00
LocoAgent 829a1a98e3 Отображение после загрузки проекта: доп. ОКВЭД нарезаются по кодам и нормализуются (не показываются «сырыми» склеенными записями), угрозы/инциденты с переносом внутри названия склеиваются в списке 2026-08-05 14:51:35 +04:00
LocoAgent ad54d3e0e2 Импорт: доп. ОКВЭД нарезаются по кодам (переносы внутри названий больше не рвут записи), угрозы с переносом внутри названия склеиваются (УБИ.209 «памяти\r\nядра процессора» → одна строка) 2026-08-05 14:46:39 +04:00
LocoAgent 9f8bf3999d Тех. меры: только при присвоенной категории и по факту СЗИ (без СЗИ нет «Регистрации и мониторинга»/«Антивируса»); промт СЗИ: при «без категории» — базовый минимум + провести категорирование, переданы характеристики объекта (интернет/ПО/архитектура); показатель 10 и 10.1–10.7: авторасчёт по методичке ФСТЭК (операции за год / дни) и порогам ПП-127 2026-08-05 14:39:03 +04:00
LocoAgent 07bf25ebc3 Обоснование ПП-127: висячий отступ — перенесённые строки «Показатель N. …» и текст расчёта с отступом, между показателями пустая строка (продолжение больше не выглядит отдельным пунктом) 2026-08-05 14:26:51 +04:00
LocoAgent 0b3ef66125 Окно авторасчёта ПП-127: результат больше не обрезается — окно растягиваемое (980x820, minsize, resizable), поле результата выше (12 строк) и со скроллбаром (показатели 8 и 9) 2026-08-05 14:17:57 +04:00
LocoAgent 1793182735 Показатель 8: если «Снижение уровня дохода» не заполнено — оценка ущерба «доход за год × 5» (наихудший сценарий), с пометкой в обосновании и окне авторасчёта ПП-127 2026-08-05 14:16:12 +04:00
LocoAgent 52b4ee6f1a Угрозы: список сортируется по возрастанию кода УБИ (УБИ.001 → УБИ.211) во всех местах — подбор программой, ИИ, автозаполнение, импорт, добавление недостающих 2026-08-05 14:14:06 +04:00
LocoAgent c9dffdd4ca ОКВЭД: полный справочник ОКВЭД2 (2770 записей) в едином формате «код название», resolve_okved без дефиса, импорт доп. ОКВЭД без переносов; угрозы: умный подбор — исключение неиспользуемых технологий (виртуализация, облако, мобильные, АСУ ТП, грид, суперкомпьютер, большие данные, беспроводные сети, контейнеры, smart-карты, ИИ), базовые угрозы локальных сетей 2026-08-05 14:08:53 +04:00
LocoAgent 136c58c948 fix: генерация циклов — вложенные циклы (показатели значимости внутри объекта КИИ) брали данные только из item; теперь используют общий словарь loops, документ больше не пустой 2026-08-05 13:52:29 +04:00
LocoAgent 28fa7edaa8 орг. меры: словарик заменён на текст заказчика (15 пунктов), автогенерация с учётом ответов по физической безопасности из опросника (да/нет), импорт ответов в модель 2026-08-05 13:39:24 +04:00
LocoAgent 614044a6ca merge: все правки из fix/audit-2026-08-05 в основную ветку (опросник 187-ФЗ, угрозы, показатели значимости, доп. ОКВЭД) 2026-08-05 13:28:45 +04:00
LocoAgent 9a1058559a показатели значимости: автогенерация таблицы 1-14 в шаблон категорирования (обоснование применимости по сфере + авторасчёт 8/9 из данных организации) 2026-08-05 12:44:03 +04:00
LocoAgent a8fa3f908c fix: угрозы — фильтр по словарю УБИ для всего от ИИ (мусор отбрасывается, УБИ.069 дополняется названием), «Подобрать угрозы» — окно с галочками (синий — программа, красный — ИИ, зелёный — совпало), «Проверить через ИИ» — вердикт + точечное применение по словарю, возвращена кнопка «Загрузить угрозы ФСТЭК» 2026-08-05 11:03:28 +04:00
LocoAgent 033c21fccc опросник: замена на Опросный_лист_187_ФЗ.xlsx (5.25, листы Общие сведения/Сотрудники/ОБЪЕКТ №1), удалён старый 152-ФЗ 2026-08-05 10:46:47 +04:00
LocoAgent bf75d67f5e fix: доп. ОКВЭД из DaData (устойчивый парсинг + лог), сравнение угроз (шире окно, сортировка по УБИ), кнопка «Проверить через ИИ» (check_threats_ai), честные подсказки п.8/п.9, автозаполнение ΔДоходов из балансовой стоимости КИИ, диалог _ask_amount вместо askfloat (формат «121 118 270 руб»), формулировка Бусредн по документу ФСТЭК (трёхлетний период) 2026-08-05 10:41:31 +04:00
LocoAgent 5af23ae525 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
2026-08-05 09:16:16 +04:00
prog1764 868e0065f3 fix: парсер чисел понимает формат опросника «121 118 270 руб» (авторасчёт 8/9 работал с пустыми данными); показатель 8 применим для организаций ОПК по сфере объекта 2026-08-05 02:01:45 +04:00
prog1764 5591dc2c5b docs: инструкции пользователя и администратора переписаны под 187-ФЗ (вкладки, ПП 127, ИИ-функции, словари, СЗИ, циклы) 2026-08-05 01:58:02 +04:00
prog1764 094f6c26e2 fix: подавлено предупреждение openpyxl про Data Validation при импорте (терминал чистый) 2026-08-05 01:40:35 +04:00
prog1764 88ab71dc14 fix: нормализация категории 1/2/3→I/II/III перед ИИ-проверкой (устраняет ложное противоречие с обоснованием); разбивка предложений ИИ по запятым/строкам при применении 2026-08-05 01:36:26 +04:00
prog1764 638e2446d5 feat: применение предложений ИИ по безопасности по галочкам (нарушитель/угрозы/инциденты/показатели/обоснование/меры) 2026-08-05 01:35:30 +04:00
prog1764 b3ae4c22cf feat: ИИ-проверка объекта по частям (временные файлы вход/выход по блокам) 2026-08-05 01:33:43 +04:00
prog1764 d376a28184 fix: окна авторасчёта/сравнения не обрезают текст; СЗИ перенесены в «Безопасность»; ИИ-рекомендации по СЗИ; ИИ-проверка категорий ПП 127; пояснения к полям 8/9; упрощение панели угроз 2026-08-05 01:31:13 +04:00
prog1764 fafeea4fcf fix: баг выбора категории (III выбиралась как I) + «Заполните…» не попадает в обоснование
- проверка категории через regex с границей слова: «Категория значимости: I»
  была подстрокой «…III», из-за чего III выбиралась как I
- если расчёт не удался (в result «Заполните…») — ничего не применяется
  и не пишется в обоснование
2026-08-05 00:54:10 +04:00
prog1764 5544941469 feat: вкладка «Безопасность» — вложенные подвкладки (нормальное отображение)
Вместо тесной двухколоночной раскладки — ряд подвкладок:
1) Основные данные — модель нарушителя (категория, уровень Н1-Н4, виды)
   + кнопки « Автозаполнить» и «🤖 Проверить безопасность через ИИ»
2) Угрозы — панель действий (⬇️ ФСТЭК / ⚙️ программа / 🤖 ИИ / ⚖️ сравнить)
   + большой список угроз на всю ширину
3) Инциденты и критерии — инциденты, показатели критериев,
   кнопка «📋 ПП № 127», обоснование
4) Орг. и тех. меры — оба списка с нормальной высотой

Все виджеты и маппинги сохранены (is_*_listbox/combo на месте)
2026-08-04 23:54:26 +04:00
prog1764 220b923e76 fix: окно расчёта показателей 8/9 — не уходит на задний план, ввод работает, кнопка на месте
Проблемы:
1. Окно уходило на задний план при сдвиге — не было grab_set/lift,
   родитель указывал на главное окно вместо диалога ПП 127
2. В показателе 8 не вводились данные — из-за потери фокуса
3. В показателе 9 кнопка «Рассчитать» не была видна — окно 720x560
   не вмещало все поля (прибыль/налог/ΔДоходов/бюджет/НДПИ/ДИВ/ВСО/НС +
   результат), кнопки уезжали за край

Исправлено:
- родитель окна — dialog (ПП 127), а не root
- w.transient(dialog) + w.lift() + w.grab_set() + w.focus_force()
- тело окна в Canvas + вертикальный Scrollbar (все поля доступны)
- кнопки «🧮 Рассчитать / ✔️ Применить / Отмена» вне скролла — всегда видны
- при закрытии — grab_release()
2026-08-04 23:50:25 +04:00
prog1764 e4e7bb444a feat: папка dictionaries/prompts — словари промтов для каждой ИИ-кнопки
- prompts/suggest_object_params.json: жёсткие правила подбора характеристик —
  выбирать ТОЛЬКО из предоставленных списков (сферы/типы/архитектуры/
  типовые объекты), ничего не выдумывать; назначение строго по форме
  «Объект предназначен для … с целью …»; типовой объект — дословно из 360-р
- prompts/suggest_threats.json, check_security.json, check_company.json,
  declension.json — промты для остальных ИИ-кнопок
- verify.py: _load_prompt() + подключение промтов в suggest_object_params_ai,
  suggest_threats_ai, check_security_ai; ИИ получает полные списки
  (spheres/object_types/architectures) и выбирает из них
- ui.py: окно рекомендаций ИИ — галочки у каждого поля, применяются
  только отмеченные (ИИ может ошибаться в архитектуре — оператор решает)
2026-08-04 23:32:58 +04:00
prog1764 acb7aeaa64 fix: кнопки /🧮 в окне ПП 127 — UnboundLocalError/NameError
Кнопки command=_auto_fill_criteria и lambda _open_calc_dialog создавались
ДО определения этих функций внутри _open_criteria_127_dialog — Python
бросал UnboundLocalError/NameError при нажатии. Определения функций
перенесены выше мест использования (проверено: def < кнопка для всех).
2026-08-04 23:22:35 +04:00
prog1764 c1ad496319 feat: загрузка доходов организации из DaData (показатель 8)
- verify.py: get_company_finance_dadata(inn, token) — findById/party,
  возвращает finance (последний отчётный год) + finance_history (по годам)
- ui.py: кнопка «🌐 Загрузить доходы из DaData (по ИНН)» в блоке
  «Доходы за 5 лет (показатель 8)» — по ИНН организации подтягивает
  доходы за последние 5 лет и заполняет income_year_1..5
- если в тарифе DaData нет финансовой отчётности — понятное сообщение,
  доходы заполняются вручную
2026-08-04 23:20:04 +04:00
prog1764 7742cb1e10 fix: кнопки 🧮 расчёта показателей 8/9 — NameError в _open_calc_dialog
В _open_calc_dialog использовался _num2 (определён только в
_auto_fill_criteria) — tkinter молча глотал исключение, кнопки
«молчали». Заменено на _num (определён внутри _open_calc_dialog).
Дополнительно isys определён в начале диалога ПП 127 для
авторасчёта (доходы/ущерб/балансовая стоимость объекта).
2026-08-04 23:17:48 +04:00
prog1764 4bffd0e08d feat: пометка ★ для ОКВЭД, подходящих под 187-ФЗ (из 360-р)
- dictionaries/okved_187.json: 78 кодов ОКВЭД из Распоряжения ПРФ № 360-р
  (виды деятельности субъектов КИИ по типовым отраслевым объектам)
- okved.json: добавлен недостающий код 09.90 (всего 267)
- ui.py: _load_okved_187_codes/_is_okved_187 — проверка по коду/подкоду
- окно выбора основного ОКВЭД: радио-кнопки с пометкой «★ ...»
- окно выбора доп. ОКВЭД: display_map со звёздочками (в значение
  звёздочка не попадает), легенда «★ — код подходит под 187-ФЗ»
- при применении сохраняется чистый код без ★ (импорт/документы не ломаются)
2026-08-04 23:07:28 +04:00
prog1764 29ba4b0532 feat: поля показателя 8 в GUI (доходы за 5 лет + суммарный ущерб)
- models: income_year_1..5 (доход за 5 лет назад ... за прошлый год),
  income_loss (суммарный экономический ущерб от нарушения критических
  процессов) — понятные названия без ΔДоходов
- вкладка «Организация» → «Финансовые показатели»: 6 новых полей
- авторасчёт п. 8: берёт доходы за 5 лет и ущерб из полей,
  считает №8 = ущерб / средний доход за 5 лет × 100% (проверено
  на примере методички: 33% → I категория)
- импорт из опросника: incomeYear1..5 / incomeLoss
- переменные шаблонов: {{company_income_year_1..5}},
  {{company_income_loss}}
2026-08-04 23:03:55 +04:00
prog1764 5aed863b83 feat: максимальная автоматизация ПП 127
- после автозаполнения категория пересчитывается сразу (по наивысшей
  из выбранных)
- подсказка для ΔДоходов: балансовая стоимость объекта как ориентир
- итоговое сообщение показывает, какие показатели остались незаполненными
  (нет данных для расчёта) — оператор видит, что именно добить вручную
2026-08-04 22:57:26 +04:00
prog1764 0f9b3a1f11 feat: автозаполнение показателей ПП 127 по данным организации
Кнопка « Заполнить автоматически (по данным)» в окне ПП 127:
- п. 8 (ущерб субъекту КИИ): если организация не госкорпорация/ГУП/
  госкомпания/стратегическое АО (Указ № 1009) — автоматически «не
  применим» + обоснование
- п. 9 (ущерб бюджетам): подставляет прибыль и налог из данных
  организации, оператор вводит ΔДоходов → расчёт КНП → ΔНП →
  №9 = ΔБ/Бусредн×100% → рекомендация категории (пороги ПП 127),
  расчёт добавляется в обоснование
- остальные применимые показатели — оператор заполняет вручную
- результаты как рекомендация, всё подтверждается пользователем
2026-08-04 22:55:43 +04:00
prog1764 0098f483d2 feat: подбор типового отраслевого объекта с фильтром по сфере
- detect_sphere(): автоопределение сферы по ключевым словам названия/
  назначения/описания объекта (здравоохранение, наука, транспорт, связь,
  энергетика, банки, ТЭК, атомная, ОПК, ракетно-космическая, горная,
  металлургия, химия)
- suggest_typical_object_program: фильтр по сфере в приоритете —
  1) сфера из комбобокса, 2) сфера объекта (isys.sphere), 3) автодетект,
  4) иначе все сферы. ОПК-объекту НЕ подбираются объекты из
  здравоохранения и других сфер
- если в своей сфере совпадений нет — возвращается пусто, оператор
  выбирает сферу вручную
2026-08-04 22:52:45 +04:00
prog1764 52b3bb8477 feat: авторасчёт показателей 8 и 9 (кнопка 🧮 в окне ПП 127)
- кнопка «🧮» у показателей 8 (ущерб субъекту КИИ) и 9 (ущерб бюджетам)
- п. 8: №8 = ΔДоходов / Дусредн × 100% (доходы за 5 лет), пороги ПП 127:
  1-10% → III, 10-20% → II, >20% → I; применим только для госкорпораций/
  ГУП/госкомпаний/стратегических АО (Указ № 1009)
- п. 9: полный расчёт по «Рекомендациям» ФСТЭК 2021:
  КНП = налог/прибыль (из данных организации), ΔНП = ΔДоходов × КНП,
  ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС, №9 = ΔБ / Бусредн × 100%,
  пороги ПП 127: >0,0003-0,0006 → III, >0,0006-0,001 → II, >0,001 → I
- окно показывает все шаги расчёта, кнопка «Применить» ставит категорию
  в выпадающий список и добавляет расчёт в обоснование
2026-08-04 22:50:50 +04:00
prog1764 4313b0a65d fix: у применимых показателей ПП 127 — и «—» (прочерк) и «не применим» вместе
Выпадающий список применимого показателя: пусто / «—» (применим,
категория не присвоена) / «не применим» / III / II / I.
У неприменимых к сфере — только «не применим» + кнопка 🔓.
2026-08-04 22:46:55 +04:00
prog1764 dadec02665 feat: подбор характеристик объекта КИИ — программа и ИИ
Вкладка «Основное», кнопки под полем типового объекта:
- ⚙️ «Подобрать типовой объект (программа)»: скоринг по совпадению
  значимых слов названия/назначения объекта с перечнем 392 типовых
  отраслевых объектов (Распоряжение 360-р), IDF-веса (редкие слова
  важнее общих) → окно с вариантами, оператор выбирает и применяет
  (заполняет сферу + типовой объект)
- 🤖 «Подобрать характеристики (ИИ)»: DeepSeek предлагает назначение,
  сферу, тип, архитектуру и типовой объект (из перечня 360-р) как
  рекомендацию → окно с полями, оператор подтверждает/правит

Примеры: СКУД → «ИС, АСУ, ИТКС, обеспечивающие контроль и управление
доступом на территорию»; 1С:УПП → «ИС для ведения бухгалтерского учета»
2026-08-04 22:43:04 +04:00
prog1764 b9968449ef fix: прочерк «—» у применимых показателей, «не применим» только у неприменимых
- применимый показатель: пусто / «—» (применим, категория не присвоена)
  / III / II / I категория
- неприменимый к сфере: в списке ТОЛЬКО «не применим», выбран
  автоматически, заблокирован, авто-обоснование из словаря сферы
- кнопка «🔓» разблокирует конкретный показатель и возвращает
  полный список (категории + прочерк)
2026-08-04 21:01:58 +04:00
prog1764 7f70bb0792 feat: в выпадающем списке показателей — «—» (применим без категории) и «не применим»
- options показателя: пусто / «—» (показатель применим, но категория
  не присвоена) / «не применим» / III / II / I
- _selected различает «прочерк» и «не применим» (оба не влияют
  на категорию, но сохраняются в список показателей)
- список показателей: «применим, категория не присвоена (—)» /
  «не применим»
- авто-обоснование: прочерк — «применим, категория не присвоена»,
  не применим — текст из словаря сферы
2026-08-04 20:55:03 +04:00
prog1764 972ff11b49 feat: ОПК показатели 8/9/13 + блокировка неприменимых с разблокировкой
- criteria_applicability.json: ОПК — применимы 8 (ущерб субъекту КИИ),
  9 (ущерб бюджету), 13 (гособоронзаказ) — обязательные для ОПК
- окно ПП 127: неприменимые для сферы показатели автоматически
  выбирают «не применим» и блокируются (серым), НО у каждого есть
  кнопка «🔓» — разблокировать конкретный показатель, если оператор
  считает его применимым (вернётся выбор категории)
- «не применим» сохраняется в список показателей и обоснование,
  не влияет на категорию
2026-08-04 20:53:29 +04:00
prog1764 4fc6fba92a feat: обоснования неприменимости показателей 8-14 для здравоохранения
- criteria_justifications.json: добавлены показатели 8 (ущерб субъекту КИИ),
  9 (ущерб бюджету), 10-10.5 (банковские), 11 (экология), 12 (пункт
  управления), 13 (гособоронзаказ), 14 (оборона/правопорядок) — дословно
  из примера заказчика
- ui.py: _just_text — поиск обоснования по группе показателя (2а→2,
  3б→3, 10.6→10, 13а→13), покрыты все 30 показателей ПП 127
2026-08-04 20:50:43 +04:00
prog1764 6852bc634b feat: обоснования неприменимости показателей критериев по сферам
- dictionaries/criteria_justifications.json: тексты обоснований для сферы
  здравоохранения (показатели 1-7) — дословно из примера заказчика
- окно ПП 127: при выборе «не применим» обоснование из словаря сферы
  автоматически подставляется в поле «Обоснование»
- авто-обоснование при сохранении: категория + выбранные показатели +
  неприменимые показатели с текстами обоснований; если все показатели
  неприменимы — «Категория значимости: не присвоена»
2026-08-04 20:48:55 +04:00
prog1764 3922b8264a feat: вариант «не применим» для показателей критериев значимости
В окне ПП 127 у каждого показателя теперь 5 вариантов: пусто,
«не применим», 3 категория, 2 категория, 1 категория.
«Не применим» не влияет на расчёт категории, но сохраняется
в список показателей как «Показатель N. ... — не применим».
2026-08-04 20:44:53 +04:00
prog1764 8c1d94d881 feat: автозаполнение безопасности автоматически после импорта опросника
После импорта данных (опросник/JSON) программа сама прогоняет логику
по каждому объекту КИИ: нарушитель (по интернету), уровень Н1/Н2,
виды нарушителей, угрозы (скоринг), инциденты (из угроз),
орг./тех. меры (базовые + по СЗИ объекта). Оператор добивает
остальное вручную. Лог: « Автозаполнение безопасности».
2026-08-04 20:37:26 +04:00
prog1764 68caaf9b63 feat: автозаполнение вкладки «Безопасность» по данным объекта
Новая кнопка « Автозаполнить» в панели действий — заполняет пустые поля
логически, без выдумок:
- категория нарушителя: есть интернет → внешний, нет → внутренний
- уровень возможностей: Н1 (внутренний) / Н2 (внешний)
- виды нарушителей: по категории из словаря attacker_types.json
- угрозы: подбор программой (скоринг по характеристикам объекта)
- инциденты: выводятся из подобранных угроз по ключевым словам
- орг. меры: базовый набор (контролируемая зона, доступ, документы,
  ответственные, обучение, инциденты, перечень лиц)
- тех. меры: базовые + доп. по средствам защиты объекта (СЗИ:
  межсетевой экран → МЭ, антивирус, СОВ, СКЗИ, виртуализация)
Незаполненное пользователем не перезаписывает.
2026-08-04 20:35:29 +04:00
prog1764 1ef8485b13 fix: подбор угроз программой + сравнение программа↔ИИ
Проблемы:
1. Подбор программой возвращал 193 из 227 угроз (весь перечень) —
   теперь скоринг по ключевым словам объекта (тип, интернет, нарушитель,
   сеть), отсев нерелевантных: 72-82 угрозы вместо 193
2. Сравнение программа↔ИИ не подсвечивало совпадения: ИИ возвращает
   «1. УБИ.001 …» / с лишними пробелами — сравнение шло по точной строке.
   Теперь ключ сравнения — код УБИ (regex), иначе нормализованный текст;
   добавлена статистика «Совпало / только программа / только ИИ»
2026-08-04 20:31:52 +04:00
prog1764 72f73d4c4f fix: результат ИИ-проверки безопасности показывается отдельным окном
Причина: результат писался только в панель «Проверка данных» на вкладке
«Организация», а пользователь смотрит вкладку «Безопасность» — замечания
были не видны. Теперь после ответа ИИ открывается отдельное окно
«🤖 Проверка безопасности — результат ИИ» с оценкой, замечаниями
и предложениями.
2026-08-04 20:24:42 +04:00
prog1764 07f0800b73 feat: автоподбор применимости показателей критериев значимости по сфере
- dictionaries/criteria_applicability.json: для каждой сферы перечень применимых
  показателей ПП РФ № 127, построен из признаков значимости Распоряжения ПРФ
  № 360-р (например: ОПК → только 13 оборонзаказ, показатель 1 «ущерб жизни
  и здоровью» не применим; банки → только 10; связь → только 4)
- окно ПП 127: неприменимые для сферы показатели блокируются и помечаются
  серым «(не применим для сферы)», исключаются из расчёта категории;
  если применимых показателей для сферы нет — категория «без категории»
2026-08-04 20:22:38 +04:00
prog1764 5bbd9313f5 feat: типовые отраслевые объекты КИИ по сферам (Распоряжение ПРФ № 360-р от 26.02.2026)
- dictionaries/typical_industry_objects.json: 392 типовых отраслевых объекта
  по 14 сферам (здравоохранение 12, наука 9, транспорт 62, связь 12,
  энергетика 12, регистрация прав 1, банки 20, ТЭК 11, атомная 16,
  оборонная 39, ракетно-космическая 72, горнодобывающая 38,
  металлургическая 53, химическая 35), с процессами и ОКВЭД
- ui.py: логика привязки — при выборе сферы объекта КИИ список типовых
  отраслевых объектов заполняется из словаря этой сферы (360-р),
  иначе — общий словарь typical_objects.json
- _on_sphere_changed/_update_typical_objects_by_sphere; вызов при загрузке объекта
2026-08-04 20:04:38 +04:00
prog1764 8438125bd1 feat: нарушители по Методике ФСТЭК от 05.02.2021 (без выдумок)
- attacker_categories.json: 2 категории (внешний/внутренний) с формулировками п. 5.1.6
- attacker_levels.json: 4 уровня возможностей Н1-Н4 (Приложение 8):
  базовые, базовые повышенные, средние, высокие — с полным описанием
  и видами нарушителей по каждому уровню
- attacker_types.json: 13 видов нарушителей (п. 5.1.3) — спецслужбы ИГ,
  террористические группировки, преступные группы, хакеры, конкуренты,
  разработчики ПО, поставщики, персонал, пользователи, сисадмины, бывшие работники
- вкладка «Безопасность»: категория (combobox) + уровень Н1-Н4 (combobox) +
  виды нарушителей (listbox с кнопками 📚 из словаря)
- модель: attacker_level, attacker_types; маппинги collect/select/copy/import;
  генератор: item.attacker_level, item.attacker_types
2026-08-04 19:52:18 +04:00
prog1764 0efd3b4d8b chore: удалить Методику ФСТЭК из репозитория (скачана локально) 2026-08-04 19:50:08 +04:00
kneon223557 49d24db234 Загрузить файлы в «/» 2026-08-04 19:46:13 +04:00
prog1764 c4a8de57d1 feat: словарь управлений ФСТЭК + переменная {{fstek_recipient_address}}
- dictionaries/fstek_offices.json: 7 управлений ФСТЭК по федеральным округам
  с адресами (из официальных реестров) и списками городов/регионов:
  ЦФО (Москва, Севастопольский пр-т 56/40), СЗФО (СПб, Исаакиевская пл. 11),
  ЮФО+СКФО (Ростов-на-Дону, ул. Ярослава Галана 1Е/25), ПФО (Н.Новгород,
  пр-т Гагарина 60 к11), УрФО (Екатеринбург, ул. Гагарина 28Б), СФО
  (Новосибирск, Красный пр-т 41), ДФО (Хабаровск, ул. Ленина 37)
- variables.py: _find_fstek_office(address) — определяет управление по городу/
  региону из адреса организации (с нормализацией сокращений обл./респ./г.о.)
- новые переменные: {{fstek_recipient_address}} (адрес управления для отправки),
  {{fstek_district}} (наименование управления)
- логика: адрес организации → управление ФСТЭК → адрес подставляется в документ
2026-08-04 19:45:33 +04:00
prog1764 730551aeba feat: алиасы item.* в цикле объектов КИИ под шаблон «Сведения» (ФСТЭК)
- item.objective→purpose, item.standard_name→typical_object,
  item.network_category→net_category, item.provider→net_operator,
  item.interaction_purpose→net_purpose, item.interaction_method→net_method,
  item.hardware→hw_sw, item.system_software→os_sw, item.defense_tools→defense_tools_list,
  item.component→subject_element, item.exploiting_org→subject_name,
  item.exploiting_inn→subject_inn, item.domain→domain_name,
  item.threat_actor→attacker_category, item.threat_justification→threats
- логика: item.* — только переменные циклов
2026-08-04 19:38:36 +04:00
prog1764 8e647fc79a feat: окно критериев значимости — дословная таблица ПП РФ № 127
- criteria_127.json переписан точно по Постановлению Правительства РФ
  от 08.02.2019 № 127 (приложение): 5 критериев (социальная, политическая,
  экономическая, экологическая, значимость для обороны страны), 30 показателей
  (1-14 с подпунктами а/б/в/г), значения сразу привязаны к категориям III/II/I
- окно 980x700 со скроллом: у каждого показателя выбор значения из документа
  («III категория: …», «II категория: …», «I категория: …»)
- категория значимости по наивысшей выбранной: I→1-я, II→2-я, III→3-я, иначе без категории
- обоснование: автогенерация текста с перечнем выбранных показателей,
  если поле пустое; кнопки «Рассчитать категорию» и «Сохранить»
- категория сразу подставляется в поле категории значимости объекта
2026-08-04 19:20:52 +04:00
prog1764 879ada5859 fix: убрана кнопка «✏️ Редактировать» у основных угроз БИ
- угрозы выбираются только из словаря (ФСТЭК), редактировать смысла нет
- остаются «- Удалить» и «📚 Из словаря»
2026-08-04 19:18:40 +04:00
prog1764 b044b05eaf feat: финансовые показатели организации + балансовая стоимость объекта
- модели: Company.profit (сумма прибыли), Company.profit_tax (налог с прибыли),
  InformationSystem.balance_value (балансовая стоимость объекта КИИ)
- импорт из опросника: «Сумма прибыли за предыдущий год» и «Сумма налога с прибыли»
  (Общие сведения) → profit/profitTax; «Балансовая стоимость объекта КИИ в рублях»
  (каждый ОБЪЕКТ-лист) → balanceValue, больше не падает в описание
- UI: блок «Финансовые показатели» во вкладке «Организация»; поле
  «Балансовая стоимость (руб.)» в объекте КИИ; маппинги collect/select/copy/import
- генератор: {{company_profit}}, {{company_profit_tax}}, {{item.balance_value}}
2026-08-04 19:17:37 +04:00
prog1764 58b4402d31 fix: кнопка «✏️ Уточнить модель/кол-во» только для ПО/оборудования
- в окнах выбора из словаря инцидентов, угроз, критериев, мер,
  пользователей, СЗИ и ОКВЭД кнопка больше не показывается
- параметр allow_refine: включается только у hw_sw/os_sw/app_sw
  (там «уточнить модель» и количество действительно нужны)
2026-08-04 18:01:27 +04:00
prog1764 6c2e4a0c38 feat: подбор угроз — инвертированная логика (все угрозы минус неактуальные)
- threat_rules: по умолчанию актуальны ВСЕ угрозы БДУ ФСТЭК, исключаются
  заведомо неактуальные: нет интернета → убраны удалённый доступ/веб/почта/
  беспроводные/DDoS/облачные/грид; АСУ → убраны BIOS/грид/виртуализация/браузер
  (теперь 170-200 угроз на объект вместо 15 «самых похожих»)
- _has_internet уточнён: «Отсутствует» у оператора / «изолирован» / «не требуется»
  → внешней сети нет; сеть общего пользования или реальный оператор → есть
- verify.suggest_threats_ai: свежий перечень ФСТЭК (227 угроз) передаётся в промт,
  ИИ выбирает из него, а не из устаревших знаний; + человекочитаемая сводка объекта
- ui: кнопки программа/ИИ без лимита 20; после подбора угрозы сохраняются в модель
  (_collect_is) — при открытии словаря галочки выбранных угроз стоят
- models.from_dict: фильтрация неизвестных полей вложенных объектов — старые файлы
  с удалёнными полями (attacker_characteristics) больше не падают
2026-08-04 17:59:44 +04:00
prog1764 8569b478d4 fix: вернул «+ Добавить» у организационных и технических мер
- _sec_row: новый параметр allow_add — ручное добавление показывается
  даже при наличии словаря
- орг. и тех. меры: allow_add=True (может пригодиться), остальные словарные
  списки (угрозы, инциденты, критерии) — по-прежнему только из словаря
2026-08-04 17:51:17 +04:00
prog1764 6206c867c7 fix: удалено поле «Характеристика нарушителя» (придумано, в опроснике нет)
- убрано из вкладки «Безопасность» (UI), модели InformationSystem,
  маппингов _collect_is/_on_is_select, _copy_is, _apply_imported_data,
  variables.py и данных для ИИ-проверки
- «Категория нарушителя» остаётся (из варианта 3 Кости)
2026-08-04 17:50:22 +04:00
prog1764 5b318eef88 feat: окно «Показатели критериев значимости» по таблице ПП РФ № 127
- кнопка «📋 Заполнить по таблице ПП № 127» в вкладке «Безопасность»
- окно 860x640 со скроллом: 5 критериев (социальная, политическая,
  экономическая, экологическая, оборонная), 12 показателей,
  у каждого выбор значения 1-4 с описанием шкалы
- объединены показатели и обоснование в одном окне (общее поле)
- «🧮 Рассчитать категорию»: по максимальному баллу → 1/2/3/без категории
  (4→1-я, 3→2-я, 2→3-я), результат пишется в категорию значимости объекта
- «💾 Сохранить»: заполняет список показателей и обоснование
- словарь criteria_127.json (формулировки можно править файлом)
2026-08-04 17:48:40 +04:00
prog1764 38eae4b16f fix: вкладка «Безопасность» — убраны кнопки ручного «+ Добавить» у словарных списков
- угрозы, инциденты, критерии, орг./тех. меры — только из словаря (-, ✏️, 📚)
- «+» остался только у «Обоснования значений показателей» (словаря нет)
2026-08-04 17:46:07 +04:00
prog1764 7158a90b76 feat: вкладка «Безопасность» — коды УБИ, автоподбор угроз (программа/ИИ), сравнение, новый UI
- угрозы ФСТЭК теперь с кодами: «УБИ.001 Угроза …» (парсинг кода из БДУ, 227 шт)
- threat_rules.py — логика программы: по характеристикам объекта (тип ИС/АСУ/ИТКС,
  интернет да/нет, нарушитель внутр./внешн.) фильтрует перечень по ключевым словам
- verify.py: suggest_threats_ai — ИИ предлагает актуальные угрозы по описанию объекта
- вкладка перестроена: компактная 2-колоночная раскладка, панель действий сверху:
  «⬇️ Загрузить угрозы ФСТЭК» | «⚙️ Подобрать (программа)» | «🤖 Подобрать (ИИ)» |
  «⚖️ Сравнить» | «🤖 Проверить безопасность через ИИ»
- окно выбора из словаря: 720x680 + поле поиска (фильтр по тексту, важно для 227 угроз)
- сравнение программа↔ИИ: окно с двумя списками, совпавшие подсвечены зелёным
2026-08-04 17:44:54 +04:00
prog1764 b6f595fd32 fix: импорт одного человека в поле ФИО (старые опросники)
- если в ячейке ФИО несколько человек (через запятую/точку с запятой/
  перевод строки или просто подряд) — остаётся первое полное ФИО
- применяется к должностным лицам (chief/ответственный/админ),
  комиссии и сотрудникам при импорте из опросника
- то же при открытии сохранённого файла (from_dict):
  «Коростелев Павел Сергеевич Хрящев Сергей Владимирович …» → «Коростелев Павел Сергеевич»
2026-08-04 17:34:17 +04:00
prog1764 42a243f7b6 feat: уточнение модели/количества для любых позиций словаря (ОС и др.)
- кнопка «✏️ Уточнить модель/кол-во» теперь всегда в окне выбора из словаря
- для позиций без маркера (Windows 10 Pro и т.п.) запрашивает количество:
  вводишь 5 → «Windows 10 Pro 5 шт.»
- для позиций с «(уточнить …)» вычищает маркер из названия и дописывает модель + количество
- формат: «Позиция Модель N шт.» (без запятой)
2026-08-04 17:31:09 +04:00
prog1764 0b3aff4ae9 feat: кнопка «✏️ Уточнить модель/кол-во» в окне выбора из словаря
- если в словаре есть позиции с «(уточнить …)» — в окне выбора появляется кнопка
- отмечаешь галочкой, например «Сервер (уточнить модель)» — жмёшь кнопку,
  программа запрашивает модель/версию/конфигурацию и количество
- в список добавляется полная строка: «Сервер Supermicro SSG-2027R -E1R24N, 2 шт.»
- работает для всех словарей: ПО (hw_sw, os_sw, app_sw), СЗИ и т.д.
2026-08-04 17:30:13 +04:00
prog1764 9d260b2655 chore: удалить опросник из репозитория (скачан локально) 2026-08-04 17:28:45 +04:00
prog1764 3ef1ff41e0 fix: импорт старых данных (обратная совместимость)
- объекты КИИ: добавлены варианты во мн.ч. в словари типов объекта
  (Автоматизированные системы управления, Информационно-телекоммуникационные сети)
  и способов взаимодействия (Проводной, Без проводной) — как в старом опроснике
- employees_access: фильтр плейсхолдеров («Формат: Фамилия Имя Отчество»,
  «Пример: …») и пустых строк при загрузке файла (from_dict) и при импорте опросника
2026-08-04 17:28:20 +04:00
kneon223557 e4d4324005 Загрузить файлы в «/» 2026-08-04 17:23:45 +04:00
prog1764 f04a5bd524 feat: вкладка «Безопасность» — словарики+чекбоксы, загрузка угроз ФСТЭК (БДУ) с датой актуализации, ИИ-проверка
- категория нарушителя — выпадающий список (attacker_categories.json)
- характеристика нарушителя, угрозы, инциденты, показатели критериев,
  обоснование, орг./тех. меры — списки с кнопками (+/-/редактировать/из словаря)
- словари: incidents.json, criteria_indicators.json, org_measures.json, tech_measures.json, threats.json
- кнопка «⬇️ Загрузить угрозы ФСТЭК»: парсит bdu.fstec.ru (227 угроз),
  сохраняет threats_fstec.json + дату актуализации; подпись «Актуально на: …»
  или предупреждение, если ещё не скачивалось
- кнопка «🤖 Проверить безопасность через ИИ»: DeepSeek сверяет заполненное
  с 236 приказом и даёт замечания/предложения (check_security_ai в verify.py)
- новое поле модели attacker_characteristics
2026-08-04 17:10:07 +04:00
prog1764 aae66dcca1 feat: вкладка «ПО» — словари ОС/аппаратных средств/прикладных программ + большое окно редактирования
- словари: hw_sw.json, os_sw.json, app_sw.json
- кнопка «📚 Из словаря» у каждого списка ПО (мультивыбор)
- большое окно добавления/редактирования позиции (560x220, многострочное)
2026-08-04 16:56:51 +04:00
prog1764 ba2a02d1cd feat: вкладка «Субъект» — кнопка «Заполнить из данных организации»
Если объект КИИ находится в той же организации — одним кликом копирует
наименование, адрес, ИНН, КПП из вкладки «Организация» в поля субъекта.
2026-08-04 16:54:38 +04:00
prog1764 28cd764795 feat: вкладка «ПО» — списки с кнопками (как «Средства защиты»)
3 списка: ПО и аппаратные средства, Операционные системы, Прикладные программы.
У каждого кнопки «+ Добавить», «- Удалить», «✏️ Редактировать».
Данные хранятся построчно (\n), импорт и генератор сохраняют переносы.
2026-08-04 16:31:25 +04:00
prog1764 300dfed5b3 Revert "feat: выпадающие списки открываются кликом в любом месте поля (не только по стрелке)"
This reverts commit 463745123c.
2026-08-04 15:59:52 +04:00
prog1764 f5548f2fb8 feat: вкладка «ПО» — многострочные поля (списки построчно, как в опроснике)
- hw_sw/os_sw/app_sw теперь tk.Text с прокруткой вместо однострочного Entry
- импортёр сохраняет переносы строк (_clean_multiline), а не схлопывает в одну строку
- _collect_is/_on_is_select поддержка Text-виджетов
2026-08-04 15:59:06 +04:00
prog1764 463745123c feat: выпадающие списки открываются кликом в любом месте поля (не только по стрелке)
Добавлен обработчик _combo_open для всех комбобоксов: клик по полю
раскрывает список, не нужно тянуть курсор в правый угол к стрелке.
2026-08-04 15:56:52 +04:00
prog1764 4ca596102f fix: убраны галочки ЛВС/Интернет из вкладок объекта КИИ
По 236 приказу эти признаки неактуальны — полный перечень характеристик
объекта уже есть в отдельной вкладке «Сеть».
2026-08-04 15:55:30 +04:00
prog1764 19b481a540 feat: во все выпадающие списки добавлен пустой пункт (сброс выбора)
Комбобоксы сферы, типа, архитектуры, типового объекта, категории/цели/способа сети
теперь имеют пустую строку в начале values — можно сбросить выбор.
2026-08-04 15:54:02 +04:00
prog1764 cf2a80c470 fix: импортёр отбрасывает текст-подсказки из примечаний; выпадающие списки только для выбора
- importer: фильтр HINT_MARKERS — значения вида «Формулировка должна строиться...», «Например...», «Выберите из...» и т.п. не импортируются в поля объекта
- ui: все Combobox (сфера, тип, архитектура, типовой объект, категория/цель/способ сети) — state='readonly', нельзя редактировать вручную
2026-08-04 15:52:56 +04:00
prog1764 9489e6a37d fix: импорт средств защиты — каждая программа отдельным средством
Проблема: clean_value заменял переносы строк на пробелы, из-за чего
список программ в одной ячейке (каждая с новой строки) склеивался
в одну строку. Теперь сырые значения ячеек сохраняются (raw_rows),
разбивка по \n/;/, названия не режутся (KES ≠ KSC).
2026-08-04 15:46:34 +04:00
prog1764 4e7c3463f4 feat: вкладки «Сеть» и «ПО» вместо «Сети и ПО», словарики сети, «Безопасность» вместо «Безопасность (236)»
- вкладка «Сеть»: категория сети (словарик), провайдер, цель взаимодействия (словарик), способ взаимодействия (словарик), ЛВС/Интернет
- вкладка «ПО»: ПО и аппаратные средства, ОС, прикладные программы
- словари: net_categories.json, net_purposes.json, net_methods.json
- вкладка безопасности переименована в «Безопасность»
2026-08-04 15:38:08 +04:00
prog1764 ffbe96d8c0 feat: вкладка «Субъект» — как в опроснике (5 полей)
- Наименование организации, эксплуатирующей объект
- Адрес местонахождения
- Элемент (компонент) объекта, который эксплуатируется лицом
- ИНН лица, эксплуатирующего объект
- КПП его обособленных подразделений

Убраны лишние поля субъекта (руководитель, отв. за безопасность, подразделение, телефон, email), модель/UI/импорт/variables приведены в соответствие
2026-08-04 15:35:53 +04:00
prog1764 ed71343d92 fix: ОКВЭД импорт/DaData/словарь+интернет, доменное имя, вкладка ОКИИ → вложенные вкладки, словарики, СЗИ разбивка, адрес отдельно
- ОКВЭД: resolve_okved (словарь okved.json + DaData suggest okved2), применяется при импорте и DaData; галочки «ОКВЭД осн./доп.» в «что применять»
- DaData: показ и подстановка доп. ОКВЭД
- доменное имя: поле во вкладке «Организация» + импорт
- вкладка «Объекты КИИ»: сверху Наименование/Описание/Категория, остальное вкладками: Основное (назначение, сфера, тип, архитектура, типовой отраслевой объект — со словариками и выпадающими списками), Субъект, Сети и ПО, Безопасность (236), СЗИ, Пользователи ОКИИ
- новые словари: spheres.json, object_types.json, architectures.json, typical_objects.json
- адрес объекта — в отдельное поле (не в описание)
- СЗИ: разбивка слипшихся названий (Kaspersky...)
2026-08-04 15:33:36 +04:00
prog1764 2037925986 feat: ОКИИ — ОКВЭД (импорт+DaData), доменное имя, поля 236 приказа ФСТЭК
- ОКВЭД: починен импорт (split по запятым/точкам с запятой), добавлен okvedExtra и domainName в DEFAULT_SETTINGS + merge полей по var в load_settings (старый import_settings.json теперь дополняется)
- DaData: проверка и подстановка основного/доп. ОКВЭД
- Вкладка «Организация»: поле «Доменное имя и внешний IP» + импорт из опросника
- Вкладка «Объекты КИИ»: поля 236 приказа — сведения (назначение, сфера, тип, архитектура, типовой отраслевой объект), субъект (12 полей), сети и ПО (категория сети, оператор, цель/способ взаимодействия, ПО/аппаратные средства, ОС, прикладные), безопасность (нарушитель, угрозы, инциденты, показатели критериев, обоснование, орг./тех. меры)
- importer: распознавание полей 236 из опросника, variables: новые переменные шаблонов
2026-08-04 14:42:33 +04:00
prog1764 ce1b56066a feat: переделка под ОКИИ (187-ФЗ) — объекты КИИ вместо ИС, категории 1-3, ОКВЭД, убраны ПДн/бумажные носители
- вкладка «Информационные системы» → «Объекты КИИ», убрана «Структура объекта»
- «Тип угроз»/«УЗ» → категория значимости 1/2/3/без категории
- удалены Категории ПДн, кол-во записей, перечень/субъекты/действия с ПДн + словари
- «Пользователи ИС» → «Пользователи ОКИИ», средства защиты оставлены
- вкладка «Бумажные документы» удалена полностью (GUI + логика + модели)
- ОКВЭД: поля во вкладке «Организация», словарь okved.json, выбор галочками, импорт
- нормализация регистра адреса DaData (КАПС → нормальный)
- importer: объекты КИИ (наименование объекта, ПО, СЗИ, пользователи)
2026-08-04 13:58:23 +04:00
prog1764 b115f9e2b4 feat: импортёр 187-ФЗ под опросник ОКИИ (5.25) — словарь полей, распознавание «Наименование объекта» 2026-08-04 13:06:58 +04:00
prog1764 da4946cb59 feat: ребрендинг под 187-ФЗ (КИИ)
- Все упоминания 152-ФЗ заменены на 187-ФЗ (заголовок окна, справка,
  импорт, переменные, docstring'и, spec)
- import_152fz -> import_187fz, direction '152fz' -> '187fz'
- autosave_152 -> autosave_187
- Опросный_лист_152_ФЗ.xlsx -> Опросный_лист_187_ФЗ.xlsx
- Промпт ИИ: 'юрист по защите персональных данных (152-ФЗ)' -> 187-ФЗ
2026-08-03 23:25:18 +04:00
prog1764 ac122451b3 fix: _set_app_icon разрывал __init__ (NameError: root is not defined) 2026-08-03 22:29:54 +04:00
prog1764 eee1842421 feat: иконка на окне программы (заголовок/панель задач) + в ресурсах exe 2026-08-03 22:05:44 +04:00
kneon223557 bb987894b2 Обновить dokogen/api_settings.py 2026-08-03 22:03:38 +04:00
kneon223557 19b74fc72a Обновить dokogen/dictionaries/pd_items.json 2026-08-03 22:02:28 +04:00
kneon223557 5a407f2568 Обновить dokogen/dictionaries/pd_categories.json 2026-08-03 22:01:52 +04:00
kneon223557 faefa786c5 Обновить dokogen/dictionaries/pd_actions.json 2026-08-03 22:01:20 +04:00
prog1764 4893899d5d feat: иконка — 64/128px из нового оригинала (512x512) 2026-08-03 21:59:26 +04:00
prog1764 73edfdf533 feat: иконка — 48px из нового оригинала (512) 2026-08-03 21:58:12 +04:00
prog1764 86836c2c9c feat: иконка — 48px из отдельного оригинала (все размеры из своих файлов) 2026-08-03 21:56:51 +04:00
prog1764 aa6056e686 feat: иконка — каждый размер из отдельного оригинала (16/24/32/48/64/128/256) 2026-08-03 21:56:03 +04:00
61 changed files with 15348 additions and 874 deletions
+5
View File
@@ -3,3 +3,8 @@ __pycache__/
.autosave/
Сгенерированные_документы/
*.result.docx
# Секреты и локальные настройки — НЕ коммитить
api_settings.json
.env
.env.local
+3 -1
View File
@@ -11,8 +11,10 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules
datas = [
# Справочники (defense_tools.json, pd_actions.json, pd_items.json, pd_categories.json)
('dokogen/dictionaries', 'dokogen/dictionaries'),
# Пустой опросник 152-ФЗ (для «Справка → Пустой опросник»)
# Пустой опросник 187-ФЗ (для «Справка → Пустой опросник»)
('dokogen/data', 'dokogen/data'),
# Иконка приложения (для окна программы в exe)
('dokogen/icon.ico', 'dokogen'),
]
# ---------- Словари pymorphy3 (обязательно!) ----------
+2 -2
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""DokoGen — Пакет генератора документов 152-ФЗ"""
"""DokoGen — Пакет генератора документов 187-ФЗ"""
from .models import Company, CommissionMember, InformationSystem, PaperDocument
from .models import Company, CommissionMember, InformationSystem
from .declension import inflect_name, decline, company_name_decline, get_short_fio, get_initials
from .variables import build_replacements, VARIABLE_DEFS
from .generator import process_template
+10 -4
View File
@@ -13,11 +13,17 @@ import os
import sys
import json
# ВНИМАНИЕ: реальные ключи НЕ хранятся в коде (утечка в git-историю — см. аудит).
# Источники ключей (по приоритету):
# 1) переменные окружения DADATA_TOKEN / DEEPSEEK_API_KEY / DEEPSEEK_MODEL / DEEPSEEK_BASE_URL
# 2) файл api_settings.json рядом с программой
# 3) DEFAULT_SETTINGS ниже (пустые значения)
DEFAULT_SETTINGS = {
'dadata_token': '',
'ai_api_key': '',
'ai_model': 'deepseek-chat',
'ai_base_url': 'https://api.deepseek.com',
'dadata_token': os.environ.get('DADATA_TOKEN', ''),
'ai_api_key': os.environ.get('DEEPSEEK_API_KEY', ''),
'ai_model': os.environ.get('DEEPSEEK_MODEL', 'deepseek-chat'),
'ai_base_url': os.environ.get('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
}
# Старые ключи (для совместимости со старым файлом api_settings.json)
Binary file not shown.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
[
"Microsoft Office (уточнить версию)",
"LibreOffice",
"OpenOffice",
"1С:Предприятие 8.3 (уточнить конфигурацию)",
"Контур.Экстерн",
"СБИС",
"Kaspersky Endpoint Security",
"Kaspersky Security Center",
"Почтовый клиент (уточнить)",
"Браузер (уточнить)",
"СЭД (уточнить систему)",
"АРМ оператора (уточнить)",
"СКУД ПО (уточнить)",
"Видеонаблюдение ПО (уточнить)"
]
+9
View File
@@ -0,0 +1,9 @@
[
"одноранговая сеть",
"клиент-серверная система",
"технология \"тонкий клиент\"",
"сеть передачи данных",
"система диспетчерского управления и контроля",
"распределенная система управления",
"иная архитектура"
]
@@ -0,0 +1,10 @@
[
{
"name": "Внешний нарушитель",
"description": "Нарушитель, не имеющий прав доступа в контролируемую (охраняемую) зону (территорию) и (или) полномочий по доступу к информационным ресурсам и компонентам систем и сетей, требующим авторизации"
},
{
"name": "Внутренний нарушитель",
"description": "Нарушитель, имеющий права доступа в контролируемую (охраняемую) зону (территорию) и (или) полномочия по автоматизированному доступу к информационным ресурсам и компонентам систем и сетей. К внутренним нарушителям относятся пользователи, имеющие как непривилегированные (пользовательские), так и привилегированные (административные) права доступа"
}
]
+43
View File
@@ -0,0 +1,43 @@
[
{
"level": "Н1",
"name": "Базовые возможности",
"description": "Нарушитель, обладающий базовыми возможностями. Имеет возможность использовать только известные уязвимости, скрипты и инструменты, свободно распространяемые в сети «Интернет» и разработанные другими лицами. Обладает базовыми компьютерными знаниями и навыками на уровне пользователя. Имеет возможность реализации угроз за счет физических воздействий на технические средства обработки и хранения информации, линии связи и обеспечивающие системы при наличии физического доступа к ним. Может реализовывать только известные угрозы, направленные на известные (документированные) уязвимости, с использованием общедоступных инструментов.",
"types": [
"Отдельные физические лица (хакеры)",
"Лица, обеспечивающие поставку программных, программно-аппаратных средств, обеспечивающих систем",
"Лица, обеспечивающие функционирование систем и сетей или обеспечивающих систем (администрация, охрана, уборщики и т.д.)",
"Авторизованные пользователи систем и сетей",
"Бывшие работники (пользователи)"
]
},
{
"level": "Н2",
"name": "Базовые повышенные возможности",
"description": "Обладает всеми возможностями нарушителей с базовыми возможностями. Хорошо владеет средствами и инструментами, понимает, как они работают, и может вносить изменения в их функционирование для повышения эффективности реализации угроз. Оснащен фреймворками и наборами средств для реализации угроз и использования уязвимостей. Имеет навыки самостоятельного планирования и реализации сценариев угроз. Обладает практическими знаниями о функционировании систем и сетей, операционных систем, а также знаниями защитных механизмов. Может реализовывать угрозы, в том числе направленные на неизвестные (недокументированные) уязвимости, с использованием специально созданных инструментов. Не имеет возможностей реализации угроз на физически изолированные сегменты систем и сетей.",
"types": [
"Преступные группы (два лица и более, действующие по единому плану)",
"Конкурирующие организации",
"Поставщики вычислительных услуг, услуг связи",
"Лица, привлекаемые для установки, настройки, испытаний, пусконаладочных и иных видов работ",
"Системные администраторы и администраторы безопасности"
]
},
{
"level": "Н3",
"name": "Средние возможности",
"description": "Обладает всеми возможностями нарушителей с базовыми повышенными возможностями. Имеет возможность приобретать информацию об уязвимостях, размещаемую на специализированных платных ресурсах (биржах уязвимостей), приобретать дорогостоящие средства и инструменты для реализации угроз, самостоятельно разрабатывать средства (инструменты), необходимые для реализации угроз (атак). Имеет возможность получения доступа к встраиваемому программному обеспечению аппаратных платформ, системному и прикладному программному обеспечению, телекоммуникационному оборудованию для проведения их анализа. Обладает знаниями и практическими навыками проведения анализа программного кода для получения информации об уязвимостях. Имеет возможность реализовывать угрозы в составе группы лиц. Не имеет возможностей реализации угроз на физически изолированные сегменты систем и сетей.",
"types": [
"Террористические, экстремистские группировки",
"Разработчики программных, программно-аппаратных средств"
]
},
{
"level": "Н4",
"name": "Высокие возможности",
"description": "Обладает всеми возможностями нарушителей со средними возможностями. Имеет возможность получения доступа к исходному коду встраиваемого программного обеспечения аппаратных платформ, системного и прикладного программного обеспечения, телекоммуникационного оборудования для получения сведений об уязвимостях «нулевого дня». Имеет возможность внедрения программных (программно-аппаратных) закладок или уязвимостей на различных этапах поставки программного обеспечения или программно-аппаратных средств. Имеет возможность создания методов и средств реализации угроз с привлечением специализированных научных организаций, создания и применения специальных технических средств для добывания информации. Имеет возможность долговременно и незаметно для операторов систем и сетей реализовывать угрозы. Обладает исключительными знаниями и практическими навыками о функционировании систем и сетей. Имеет практически неограниченные возможности реализовывать угрозы, в том числе с использованием недекларированных возможностей, программных, программно-аппаратных закладок, встроенных в компоненты систем и сетей.",
"types": [
"Специальные службы иностранных государств"
]
}
]
+15
View File
@@ -0,0 +1,15 @@
[
"Специальные службы иностранных государств",
"Террористические, экстремистские группировки",
"Преступные группы (криминальные структуры)",
"Отдельные физические лица (хакеры)",
"Конкурирующие организации",
"Разработчики программных, программно-аппаратных средств",
"Лица, обеспечивающие поставку программных, программно-аппаратных средств, обеспечивающих систем",
"Поставщики услуг связи, вычислительных услуг",
"Лица, привлекаемые для установки, настройки, испытаний, пусконаладочных и иных видов работ",
"Лица, обеспечивающие функционирование систем и сетей или обеспечивающих систем оператора (администрация, охрана, уборщики и др.)",
"Авторизованные пользователи систем и сетей",
"Системные администраторы и администраторы безопасности",
"Бывшие (уволенные) работники (пользователи)"
]
+297
View File
@@ -0,0 +1,297 @@
[
{
"criteria": "I. Социальная значимость",
"indicators": [
{
"num": "1",
"name": "Причинение ущерба жизни и здоровью людей (человек)",
"values": {
"III": "более или равно 1, но менее или равно 50",
"II": "более 50, но менее или равно 500",
"I": "более 500"
}
},
{
"num": "2а",
"name": "Прекращение или нарушение функционирования объектов обеспечения жизнедеятельности населения, оцениваемые на территории, на которой возможно нарушение обеспечения жизнедеятельности населения",
"values": {
"III": "в пределах территории одного муниципального образования (численностью от 2 тыс. чел.) или одной внутригородской территории города федерального значения",
"II": "выход за пределы территории одного муниципального образования (численностью от 2 тыс. чел.) или одной внутригородской территории города федерального значения, но не за пределы территории одного субъекта Российской Федерации или территории города федерального значения",
"I": "выход за пределы территории одного субъекта Российской Федерации или территории города федерального значения"
}
},
{
"num": "2б",
"name": "Прекращение или нарушение функционирования объектов обеспечения жизнедеятельности населения, оцениваемые по количеству людей, условия жизнедеятельности которых могут быть нарушены (тыс. человек)",
"values": {
"III": "более или равно 2, но менее 1 000",
"II": "более или равно 1 000, но менее 5 000",
"I": "более или равно 5 000"
}
},
{
"num": "3а",
"name": "Прекращение или нарушение функционирования объектов транспортной инфраструктуры, организаций, осуществляющих деятельность в области грузовых и пассажирских перевозок, транспортных средств, в том числе высокоавтоматизированных транспортных средств, оцениваемые на территории, на которой возможно нарушение транспортного сообщения или предоставления транспортных услуг (пассажирских и грузовых перевозок)",
"values": {
"III": "в пределах территории одного муниципального образования (численностью от 2 тыс. человек) или одной внутригородской территории города федерального значения",
"II": "выход за пределы территории одного муниципального образования (численностью от 2 тыс. человек) или одной внутригородской территории города федерального значения, но не за пределы территории одного субъекта Российской Федерации или территории города федерального значения",
"I": "выход за пределы территории одного субъекта Российской Федерации или территории города федерального значения"
}
},
{
"num": "3б",
"name": "Прекращение или нарушение функционирования объектов транспортной инфраструктуры, оцениваемые по количеству людей, для которых могут быть недоступны транспортные услуги (тыс. человек)",
"values": {
"III": "более или равно 2, но менее 1000",
"II": "более или равно 1000, но менее 5000",
"I": "более или равно 5000"
}
},
{
"num": "3в",
"name": "Прекращение или нарушение функционирования объектов транспортной инфраструктуры, оцениваемые по типам нарушенных перевозок и грузов",
"values": {
"III": "мультимодальные и интермодальные перевозки",
"II": "перевозка, погрузка, разгрузка, хранение опасных грузов, на перевозку которых требуется специальное разрешение, и (или) грузов повышенной опасности в пределах территории одного субъекта Российской Федерации",
"I": "перевозка, погрузка, разгрузка, хранение опасных грузов, на перевозку которых требуется специальное разрешение, и (или) грузов повышенной опасности в пределах территорий 2 и более субъектов Российской Федерации"
}
},
{
"num": "3г",
"name": "Прекращение или нарушение функционирования объектов транспортной инфраструктуры, оцениваемые по категорированию объектов транспортной инфраструктуры",
"values": {
"III": "объект транспортной инфраструктуры 3-й или 4-й категории",
"II": "объект транспортной инфраструктуры 2-й категории",
"I": "объект транспортной инфраструктуры 1-й категории"
}
},
{
"num": "4а",
"name": "Прекращение или нарушение функционирования сети связи, оцениваемые по количеству абонентов, для которых могут быть недоступны услуги связи (тыс. человек)",
"values": {
"III": "более или равно 3, но менее 1 000",
"II": "более или равно 1 000, но менее 5 000",
"I": "более или равно 5 000"
}
},
{
"num": "4б",
"name": "Прекращение или нарушение функционирования сети связи, оцениваемые по уровню органа государственной власти (количеству органов) или организации, для которых могут быть нарушены или недоступны услуги связи",
"values": {
"III": "орган государственной власти субъекта Российской Федерации",
"II": "федеральный орган государственной власти, государственная корпорация, Центральный банк Российской Федерации, 2 и более органа государственной власти субъекта Российской Федерации",
"I": "Администрация Президента Российской Федерации, Правительство Российской Федерации, Федеральное Собрание Российской Федерации, Совет Безопасности Российской Федерации, Верховный Суд Российской Федерации, Конституционный Суд Российской Федерации, 2 и более федеральных органа государственной власти"
}
},
{
"num": "5а",
"name": "Отсутствие доступа к государственной услуге, оцениваемое в максимальном допустимом времени, в течение которого государственная услуга может быть недоступна для получателей такой услуги (часов)",
"values": {
"III": "менее или равно 24, но более 12",
"II": "менее или равно 12, но более 6",
"I": "менее или равно 6"
}
},
{
"num": "5б",
"name": "Отсутствие доступа к государственной услуге, оцениваемое во времени с момента приема запроса о предоставлении государственной услуги органом, предоставляющим государственную услугу, или подведомственной государственному органу организацией, участвующей в предоставлении государственной услуги, в течение которого государственная услуга не может быть оказана (в процентах от времени предоставления услуги, предусмотренного административным регламентом)",
"values": {
"III": "менее или равно 30",
"II": "более 30, но менее или равно 70",
"I": "более 70"
}
}
]
},
{
"criteria": "II. Политическая значимость",
"indicators": [
{
"num": "6",
"name": "Прекращение или нарушение функционирования государственного органа или организации, созданной на основании федерального закона, в части невыполнения возложенной на них функции (полномочия)",
"values": {
"III": "прекращение или нарушение функционирования органа государственной власти субъекта Российской Федерации",
"II": "прекращение или нарушение функционирования федерального органа государственной власти, государственной корпорации, Центрального банка Российской Федерации, 2 и более органов государственной власти субъекта Российской Федерации",
"I": "прекращение или нарушение функционирования Администрации Президента Российской Федерации, Правительства Российской Федерации, Федерального Собрания Российской Федерации, Совета Безопасности Российской Федерации, Верховного Суда Российской Федерации, Конституционного Суда Российской Федерации, 2 и более федеральных органов государственной власти"
}
},
{
"num": "7",
"name": "Нарушение условий международного договора Российской Федерации, срыв переговоров или подписания планируемого к заключению международного договора Российской Федерации, оцениваемые по уровню международного договора Российской Федерации",
"values": {
"III": "нарушение условий договора межведомственного характера (срыв переговоров или подписания)",
"II": "нарушение условий межправительственного договора (срыв переговоров или подписания)",
"I": "нарушение условий межгосударственного договора (срыв переговоров или подписания)"
}
}
]
},
{
"criteria": "III. Экономическая значимость",
"indicators": [
{
"num": "8",
"name": "Возникновение ущерба субъекту критической информационной инфраструктуры, который является государственной корпорацией, государственным унитарным предприятием, государственной компанией, организацией оборонно-промышленного комплекса, стратегическим акционерным обществом, стратегическим предприятием, оцениваемого в снижении уровня дохода (с учетом налога на добавленную стоимость, акцизов и иных обязательных платежей) по всем видам деятельности (процентов от годового объема доходов, усредненного за прошедший пятилетний период)",
"values": {
"III": "более или равно 1, но менее или равно 10",
"II": "более 10, но менее или равно 20",
"I": "более 20"
}
},
{
"num": "9",
"name": "Возникновение ущерба бюджету Российской Федерации, оцениваемого в снижении выплат (отчислений) в бюджет, осуществляемых субъектом критической информационной инфраструктуры (процентов прогнозируемого годового дохода федерального бюджета, усредненного за планируемый 3-летний период)",
"values": {
"III": "более 0,0003, но менее или равно 0,0006",
"II": "более 0,0006, но менее или равно 0,001",
"I": "более 0,001"
}
},
{
"num": "10",
"name": "Прекращение или нарушение проведения клиентами операций по осуществлению перевода денежных средств, осуществляемых субъектом критической информационной инфраструктуры, являющимся в соответствии с законодательством Российской Федерации оператором платформы цифрового рубля, системно значимой кредитной организацией, кредитной организацией, выполняющей функции оператора услуг платежной инфраструктуры системно и социально значимых платежных систем, кредитной организацией, значимой на рынке платежных услуг, оператором услуг платежной инфраструктуры, оказывающим услуги платежной инфраструктуры в рамках системно значимых платежных систем, оцениваемые среднедневным (по отношению к числу календарных дней в году) количеством осуществляемых операций (млн. единиц) (расчет осуществляется по итогам года, а для создаваемых объектов — на основе прогнозных значений)",
"values": {
"III": "менее или равно 70",
"II": "более 70, но менее или равно 120",
"I": "более 120"
}
},
{
"num": "10.1",
"name": "Прекращение или нарушение проведения операций по исполнению обязательств, осуществляемых субъектом критической информационной инфраструктуры, являющимся центральным контрагентом, среднедневной размер обязательств которого по передаче денежных средств в валюте Российской Федерации по итогам клиринга за последние 12 месяцев (трлн. рублей)",
"values": {
"III": "менее 1",
"II": "более или равно 1, но менее 10",
"I": "более или равно 10"
}
},
{
"num": "10.2",
"name": "Прекращение или нарушение проведения учетно-расчетных операций, осуществляемых субъектом критической информационной инфраструктуры, являющимся центральным депозитарием и регистратором финансовых транзакций, среднедневное количество ценных бумаг (ISIN) российских эмитентов, которые учитывались на счетах в центральном депозитарии (оцениваемые за последние 12 месяцев в тыс. штук)",
"values": {
"III": "менее 10",
"II": "более или равно 10, но менее 25",
"I": "более или равно 25"
}
},
{
"num": "10.3",
"name": "Прекращение или нарушение проведения операций по выплатам, передаче и размещению денежных средств, осуществляемых субъектом критической информационной инфраструктуры, являющимся негосударственным пенсионным фондом, которые оцениваются объемом активов (суммой пенсионных накоплений и пенсионных резервов) негосударственного пенсионного фонда (млрд. рублей)",
"values": {
"III": "более или равно 50, но менее 1000",
"II": "более или равно 1000, но менее 2000",
"I": "более или равно 2000"
}
},
{
"num": "10.4",
"name": "Прекращение или нарушение проведения операций по выплатам, перестрахованию, инвестициям, осуществляемых субъектом критической информационной инфраструктуры, являющимся страховой организацией, которые оцениваются объемом активов страховой организации (млрд. рублей)",
"values": {
"III": "более или равно 100, но менее 1500",
"II": "более или равно 1500, но менее 5000",
"I": "более или равно 5000"
}
},
{
"num": "10.5",
"name": "Прекращение или нарушение выполнения функций по переводу денежных средств, осуществляемых субъектом критической информационной инфраструктуры, являющимся оператором услуг информационного обмена (некредитной организацией), который оценивается количеством заключенных договоров с кредитными организациями",
"values": {
"III": "более или равно 25, но менее 100",
"II": "более или равно 100, но менее 150",
"I": "более или равно 150"
}
},
{
"num": "10.6",
"name": "Прекращение или нарушение выполнения деятельности по предоставлению потребительских займов субъектом критической информационной инфраструктуры, являющимся микрофинансовой компанией, которые оцениваются суммой микрозаймов, выданных за годовой отчетный период (млрд. рублей)",
"values": {
"III": "более или равно 100, но менее 1000",
"II": "более или равно 1000, но менее 5000",
"I": "более или равно 5000"
}
},
{
"num": "10.7",
"name": "Прекращение или нарушение предоставления пользователям кредитных историй и субъектам кредитных историй услуг, предусмотренных Федеральным законом «О кредитных историях», субъектом критической информационной инфраструктуры, являющимся бюро кредитных историй, которые оцениваются количеством кредитных историй, хранящихся в бюро кредитных историй (млн. единиц)",
"values": {
"III": "более или равно 30, но менее 200",
"II": "более или равно 200, но менее 500",
"I": "более или равно 500"
}
}
]
},
{
"criteria": "IV. Экологическая значимость",
"indicators": [
{
"num": "11а",
"name": "Вредные воздействия на окружающую среду, оцениваемые на территории, на которой окружающая среда может подвергнуться вредным воздействиям",
"values": {
"III": "в пределах территории одного муниципального образования (численностью от 2 тыс. чел.) или одной внутригородской территории города федерального значения, с выходом вредных воздействий за пределы территории субъекта критической информационной инфраструктуры",
"II": "выход за пределы территории одного муниципального образования (численностью от 2 тыс. чел.) или одной внутригородской территории города федерального значения, но не за пределы территории одного субъекта Российской Федерации или территории города федерального значения, с выходом вредных воздействий за пределы территории субъекта критической информационной инфраструктуры",
"I": "выход за пределы территории одного субъекта Российской Федерации или территории города федерального значения, с выходом вредных воздействий за пределы территории субъекта критической информационной инфраструктуры"
}
},
{
"num": "11б",
"name": "Вредные воздействия на окружающую среду, оцениваемые по количеству людей, которые могут быть подвержены вредным воздействиям (тыс. человек)",
"values": {
"III": "более или равно 2, но менее 1 000",
"II": "более или равно 1 000, но менее 5 000",
"I": "более или равно 5 000"
}
}
]
},
{
"criteria": "V. Значимость для обеспечения обороны страны, безопасности государства и правопорядка",
"indicators": [
{
"num": "12",
"name": "Прекращение или нарушение функционирования (невыполнение установленных показателей) пункта управления (ситуационного центра), оцениваемое в уровне (значимости) пункта управления или ситуационного центра",
"values": {
"III": "прекращение или нарушение функционирования пункта управления или ситуационного центра органа государственной власти субъекта Российской Федерации или города федерального значения",
"II": "прекращение или нарушение функционирования пункта управления или ситуационного центра федерального органа государственной власти или государственной корпорации",
"I": "прекращение или нарушение функционирования пункта управления государством или ситуационного центра Администрации Президента Российской Федерации, Правительства Российской Федерации, Федерального Собрания Российской Федерации, Совета Безопасности Российской Федерации, Верховного Суда Российской Федерации, Конституционного Суда Российской Федерации"
}
},
{
"num": "13а",
"name": "Снижение показателей государственного оборонного заказа, выполняемого (обеспечиваемого) субъектом критической информационной инфраструктуры, оцениваемое в снижении объемов продукции (работ, услуг) в заданный период времени (процентов заданного объема продукции)",
"values": {
"III": "более 0, но менее или равно 10",
"II": "более 10, но менее или равно 15",
"I": "более 15"
}
},
{
"num": "13б",
"name": "Снижение показателей государственного оборонного заказа, выполняемого (обеспечиваемого) субъектом критической информационной инфраструктуры, оцениваемое в увеличении времени изготовления единицы продукции с заданным объемом (процентов установленного времени на изготовление единицы продукции)",
"values": {
"III": "более 0, но менее или равно 10",
"II": "более 10, но менее или равно 40",
"I": "более 40"
}
},
{
"num": "13.1",
"name": "Снижение показателей государственного оборонного заказа, выполняемого (обеспечиваемого) субъектом критической информационной инфраструктуры, оцениваемое по его статусу в кооперации головного исполнителя поставок продукции по государственному оборонному заказу",
"values": {
"III": "соисполнитель, субподрядчик или поставщик части продукции, работ и услуг в рамках контракта по государственному оборонному заказу совместно с исполнителем, заключившим контракт с головным исполнителем государственного оборонного заказа",
"II": "исполнитель контракта по государственному оборонному заказу, заключивший контракт с головным исполнителем государственного оборонного заказа",
"I": "головной исполнитель поставок продукции, работ и услуг по государственному оборонному заказу"
}
},
{
"num": "14",
"name": "Прекращение или нарушение функционирования (невыполнение установленных показателей) информационной системы в области обеспечения обороны страны, безопасности государства и правопорядка, оцениваемое в максимально допустимом времени, в течение которого информационная система может быть недоступна пользователю (часов)",
"values": {
"III": "менее или равно 4, но более 2",
"II": "менее или равно 2, но более 1",
"I": "менее или равно 1"
}
}
]
}
]
@@ -0,0 +1,180 @@
{
"Здравоохранение": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Наука": [
"1",
"7",
"8",
"9",
"11",
"13"
],
"Транспорт": [
"1",
"2",
"3",
"8",
"9",
"11",
"13",
"14"
],
"Связь": [
"4",
"6",
"8",
"9"
],
"Энергетика": [
"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"
],
"Банковская сфера и финансовый рынок": [
"8",
"9",
"10"
],
"Топливно-энергетический комплекс": [
"1",
"2",
"3",
"4",
"5",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14"
],
"Атомная энергетика": [
"1",
"2",
"3",
"5",
"6",
"7",
"8",
"9",
"11",
"12",
"13"
],
"Оборонная промышленность": [
"1",
"2",
"3",
"4",
"6",
"7",
"8",
"9",
"11",
"12",
"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"
]
}
@@ -0,0 +1,7 @@
[
"Социальная значимость — количество граждан, на которые может повлиять нарушение",
"Политическая значимость — международный или внутриполитический резонанс",
"Экономическая значимость — возможный размер ущерба (в рублях)",
"Экологическая значимость — возможный ущерб окружающей среде",
"Значимость для обороны страны, безопасности государства и правопорядка"
]
@@ -0,0 +1,23 @@
{
"Здравоохранение": {
"1": "Рассматриваемый объект КИИ не участвует в обработке информации, необходимой для оказания медицинской помощи, проведения медицинских экспертиз, медицинских осмотров и медицинских освидетельствований, санитарно-противоэпидемических (профилактических) мероприятий и не осуществляет управление, контроль или мониторинг данных бизнес-процессов. Объект является вспомогательной информационной системой, целью которой является автоматизация процессов ведения бухгалтерского учета. Объект не осуществляет мониторинг, контроль или управление другими объектами КИИ, обеспечивающими жизненно важные функции организма. Утрата или иное повреждение объекта КИИ не может повлиять на жизненно важные функции организма.",
"2": "Объект КИИ не относится к объектам обеспечения жизнедеятельности населения, в том числе объектам водоснабжения и канализации, очистки сточных вод, тепло- и электроснабжения, гидротехнических сооружений.",
"3": "Объекты КИИ организации сферы здравоохранения не относятся к объектам транспортной инфраструктуры, и субъект КИИ не имеет в своем составе подразделения транспортной инфраструктуры.",
"4": "Субъект КИИ не является оператором связи, объекты КИИ организации сферы здравоохранения не относятся к объектам транспортной инфраструктуры.",
"5": "Организация сферы здравоохранения является юридическим лицом, обеспечивающим эксплуатацию информационной системы, задействованной в процессе оказания государственной услуги, однако нарушение функционирования информационной системы (рассматриваемого объекта КИИ), не участвующей в обработке информации, необходимой для оказания государственной услуги, не влечет за собой нарушение и (или) прекращение доступа к государственной услуге. Рассматриваемый объект КИИ не участвует в обработке информации, необходимой для оказания государственной услуги.",
"6": "Организация сферы здравоохранения не является органом государственной власти, в связи с этим критерий неприменим.",
"7": "Организация сферы здравоохранения не является субъектом политической сферы деятельности, в связи с этим критерий неприменим.",
"8": "Организация сферы здравоохранения не является государственной корпорацией, государственным унитарным предприятием, государственной компанией, стратегическим акционерным обществом, стратегическим предприятием, в связи с этим критерий неприменим.",
"9": "Объект КИИ задействован в предоставлении медицинских услуг. Показатель 9 применим ко всем субъектам КИИ; при расчёте учитывается, что медицинские услуги освобождены от налогообложения (значение показателя может быть минимальным).",
"10": "Организация сферы здравоохранения не является субъектом банковской сферы деятельности.",
"10.1": "Организация сферы здравоохранения не является центральным контрагентом.",
"10.2": "Организация сферы здравоохранения не является центральным депозитарием.",
"10.3": "Организация сферы здравоохранения не является негосударственным пенсионным фондом.",
"10.4": "Организация сферы здравоохранения не является страховой организацией.",
"10.5": "Организация сферы здравоохранения не является оператором услуг информационного обмена.",
"11": "Объекты КИИ организации сферы здравоохранения не относятся к опасным производственным объектам в соответствии с законодательством Российской Федерации.",
"12": "Организация сферы здравоохранения не выполняет функции пункта управления (ситуационного центра) государственного органа власти или государственной корпорации.",
"13": "Организация сферы здравоохранения не является субъектом обеспечения обороны страны, безопасности государства и правопорядка.",
"14": "Организация сферы здравоохранения не имеет информационных систем, функционирующих в области обеспечения обороны страны, безопасности государства и правопорядка."
}
}
+283
View File
@@ -0,0 +1,283 @@
[
{
"name": "Управление ФСТЭК России по Центральному федеральному округу",
"address": "117342, г. Москва, Севастопольский проспект, д. 56/40, стр. 1",
"cities": [
"москва",
"белгород",
"брянск",
"владимир",
"воронеж",
"иваново",
"калуга",
"кострома",
"курск",
"липецк",
"орёл",
"орел",
"рязань",
"смоленск",
"тамбов",
"тверь",
"тула",
"ярославль",
"подольск",
"балашиха",
"химки",
"мытищи",
"королёв",
"люберцы",
"одинцово",
"домодедово",
"серпухов",
"электросталь",
"сергиев посад",
"коломна"
],
"regions": [
"московская область",
"белгородская область",
"брянская область",
"владимирская область",
"воронежская область",
"ивановская область",
"калужская область",
"костромская область",
"курская область",
"липецкая область",
"орловская область",
"рязанская область",
"смоленская область",
"тамбовская область",
"тверская область",
"тульская область",
"ярославская область"
]
},
{
"name": "Управление ФСТЭК России по Северо-Западному федеральному округу",
"address": "190121, г. Санкт-Петербург, Исаакиевская площадь, д. 11, лит. А",
"cities": [
"санкт-петербург",
"петербург",
"архангельск",
"вологда",
"калининград",
"мурманск",
"великий новгород",
"новгород",
"псков",
"петрозаводск",
"сыктывкар",
"нарьян-мар",
"северодвинск",
"череповец",
"гатчина"
],
"regions": [
"ленинградская область",
"архангельская область",
"вологодская область",
"калининградская область",
"мурманская область",
"новгородская область",
"псковская область",
"республика карелия",
"республика коми",
"ненецкий"
]
},
{
"name": "Управление ФСТЭК России по Южному и Северо-Кавказскому федеральным округам",
"address": "344079, г. Ростов-на-Дону, ул. Ярослава Галана, д. 1Е/25",
"cities": [
"ростов-на-дону",
"ростов",
"астрахань",
"волгоград",
"краснодар",
"майкоп",
"элиста",
"симферополь",
"севастополь",
"сочи",
"новороссийск",
"таганрог",
"шахты",
"волжский",
"камышин",
"махачкала",
"грозный",
"владикавказ",
"нальчик",
"черкесск",
"магас",
"ставрополь",
"пятигорск",
"кисловодск",
"ессентуки",
"минеральные воды",
"назрань"
],
"regions": [
"ростовская область",
"астраханская область",
"волгоградская область",
"краснодарский край",
"республика адыгея",
"республика калмыкия",
"республика крым",
"севастополь",
"республика дагестан",
"чеченская республика",
"республика северная осетия",
"кабардино-балкарская республика",
"карачаево-черкесская республика",
"республика ингушетия",
"ставропольский край"
]
},
{
"name": "Управление ФСТЭК России по Приволжскому федеральному округу",
"address": "603104, г. Нижний Новгород, проспект Гагарина, д. 60, корп. 11",
"cities": [
"нижний новгород",
"казань",
"самара",
"саратов",
"пенза",
"пермь",
"уфа",
"ижевск",
"оренбург",
"ульяновск",
"киров",
"саранск",
"чебоксары",
"йошкар-ола",
"тольятти",
"набережные челны",
"арзамас",
"дзержинск",
"балаково",
"новокуйбышевск",
"сызрань"
],
"regions": [
"нижегородская область",
"республика татарстан",
"самарская область",
"саратовская область",
"пензенская область",
"пермский край",
"республика башкортостан",
"удмуртская республика",
"оренбургская область",
"ульяновская область",
"кировская область",
"республика мордовия",
"чувашская республика",
"республика марий эл"
]
},
{
"name": "Управление ФСТЭК России по Уральскому федеральному округу",
"address": "620078, г. Екатеринбург, ул. Гагарина, д. 28Б",
"cities": [
"екатеринбург",
"челябинск",
"тюмень",
"курган",
"ханты-мансийск",
"салехард",
"сургут",
"нижневартовск",
"магнитогорск",
"нижний тагил",
"каменск-уральский",
"первоуральск",
"миасс",
"зубовой"
],
"regions": [
"свердловская область",
"челябинская область",
"тюменская область",
"курганская область",
"ханты-мансийский",
"ямало-ненецкий"
]
},
{
"name": "Управление ФСТЭК России по Сибирскому федеральному округу",
"address": "630091, г. Новосибирск, Красный проспект, д. 41",
"cities": [
"новосибирск",
"омск",
"томск",
"кемерово",
"барнаул",
"красноярск",
"иркутск",
"новокузнецк",
"абакан",
"горно-алтайск",
"кызыл",
"улан-удэ",
"чита",
"ангарск",
"братск",
"прокопьевск",
"бийск",
"рубцовск",
"бердск",
"северск"
],
"regions": [
"новосибирская область",
"омская область",
"томская область",
"кемеровская область",
"алтайский край",
"красноярский край",
"иркутская область",
"республика алтай",
"республика тыва",
"республика хакасия",
"республика бурятия",
"забайкальский край"
]
},
{
"name": "Управление ФСТЭК России по Дальневосточному федеральному округу",
"address": "680030, г. Хабаровск, ул. Ленина, д. 37",
"cities": [
"хабаровск",
"владивосток",
"благовещенск",
"якутск",
"петропавловск-камчатский",
"южно-сахалинск",
"магадан",
"биробиджан",
"анадырь",
"комсомольск-на-амуре",
"уссурийск",
"находка",
"артём",
"белогорск",
"свободный"
],
"regions": [
"хабаровский край",
"приморский край",
"амурская область",
"республика саха",
"камчатский край",
"сахалинская область",
"магаданская область",
"еврейская автономная область",
"чукотский"
]
}
]
+16
View File
@@ -0,0 +1,16 @@
[
"Сервер (уточнить модель)",
"АРМ пользователя (уточнить конфигурацию)",
"Ноутбук (уточнить модель)",
"Коммутатор (уточнить модель)",
"Маршрутизатор (уточнить модель)",
"Межсетевой экран (уточнить модель)",
"Видеорегистратор (уточнить модель)",
"Видеокамера (уточнить модель)",
"Контроллер доступа СКУД (уточнить модель)",
"Шлагбаум (уточнить модель)",
"Турникет (уточнить модель)",
"Сетевое хранилище (NAS) (уточнить модель)",
"Беспроводная точка доступа (уточнить модель)",
"ИБП (уточнить модель)"
]
+8
View File
@@ -0,0 +1,8 @@
[
"отказ в обслуживании",
"несанкционированный доступ",
"утечка данных (нарушение конфиденциальности)",
"модификация (подмена) данных",
"нарушение функционирования технических средств",
"несанкционированное использование вычислительных ресурсов объекта"
]
+9
View File
@@ -0,0 +1,9 @@
[
"общего пользования",
"выделенная",
"технологическая",
"присоединенная к сети связи общего пользования",
"специального назначения",
"другая сеть связи для передачи информации при помощи электромагнитных систем",
"взаимодействие с сетями электросвязи отсутствует"
]
+6
View File
@@ -0,0 +1,6 @@
[
"проводной",
"беспроводной",
"Проводной",
"Без проводной"
]
+7
View File
@@ -0,0 +1,7 @@
[
"передача (прием) информации",
"оказание услуг",
"управление",
"контроль за технологическим производственным оборудованием (исполнительными устройствами)",
"иная цель"
]
+7
View File
@@ -0,0 +1,7 @@
[
"Информационная система",
"Автоматизированная система управления",
"Информационно-телекоммуникационная сеть",
"Автоматизированные системы управления",
"Информационно-телекоммуникационные сети"
]
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
[
"05.20",
"06.1",
"06.2",
"09.90",
"19.20",
"20.13",
"22.21",
"24.46",
"25.30",
"25.40",
"25.99",
"26.20",
"26.30",
"26.51",
"27.90",
"28.13",
"28.14",
"28.99",
"30.11",
"33.11",
"33.12",
"33.20",
"35.11",
"35.12",
"35.13",
"35.14",
"35.3",
"35.30",
"38.12",
"38.22",
"41.20",
"45.20",
"46.46",
"47.73",
"49",
"49.10",
"49.20",
"49.32",
"49.39",
"49.4",
"49.41",
"49.42",
"49.50",
"50",
"50.20",
"50.30",
"50.40",
"51",
"51.10",
"51.21",
"52",
"52.10",
"52.2",
"52.22",
"52.23",
"52.24",
"61",
"61.1",
"61.20",
"61.3",
"62.01",
"62.09",
"63",
"63.11",
"64",
"64.11",
"71.12",
"71.20",
"72",
"72.19",
"74.90",
"84",
"84.11",
"84.13",
"84.30",
"86.1",
"86.2",
"86.90"
]
+17
View File
@@ -0,0 +1,17 @@
[
"Установлены границы контролируемой зоны.",
"Физическая охрана периметра обеспечена бетонным забором с колючей проволокой, исключающим прямой доступ к зданиям, и турникетом на проходной; въезд и вход осуществляются по пропускам с применением СКУД, ведется учет посетителей.",
"Имеется система видеонаблюдения.",
"Здания не имеют окон, выходящих во вне, входная группа оснащена металлической дверью.",
"Установлены охранно-пожарная сигнализация и тревожная кнопка.",
"Исключена возможность неконтролируемого пребывания посторонних лиц в помещениях, где ведется работа с объектами критической информационной инфраструктуры.",
"Назначены: ответственный за организацию обеспечения безопасности объектов критической информационной инфраструктуры, администратор и администратор безопасности объектов критической информационной инфраструктуры, утверждены инструкции, регламентирующие работу с данными и объектами критической информационной инфраструктуры.",
"Осуществляется внутренний контроль соответствия обеспечения безопасности объектов критической информационной инфраструктуры установленным требованиям.",
"Работники, непосредственно осуществляющие работу с объектами критической информационной инфраструктуры, ознакомлены с положениями законодательства Российской Федерации об объектах критической информационной инфраструктуры, документами, определяющими политику организации в отношении работы с объектами критической информационной инфраструктуры.",
"Обеспечивается учет машинных носителей объектов критической информационной инфраструктуры.",
"Обеспечивается резервирование и восстановление данных, модифицированных или уничтоженных вследствие несанкционированного доступа к ним.",
"Разработаны правила доступа к данным, обрабатываемым в объектах критической информационной инфраструктуры.",
"Определён перечень сотрудников, осуществляющих работу с объектами критической информационной инфраструктуры.",
"Ведётся учёт всех защищаемых носителей информации с помощью их маркировки и занесения учетных данных в журнал учета с отметкой об их выдаче (приеме).",
"Обеспечено раздельное хранение материальных носителей, обработка которых осуществляется в различных целях."
]
+15
View File
@@ -0,0 +1,15 @@
[
"Windows 10 Pro 64bit",
"Windows 11 Pro",
"Windows Server 2019",
"Windows Server 2022",
"Astra Linux Special Edition",
"РЕД ОС",
"Альт Рабочая станция",
"Альт Сервер",
"Ubuntu Server",
"CentOS",
"Debian",
"MS-DOS (для АРМ специального назначения)",
"Android (для планшетов/КПК)"
]
-22
View File
@@ -1,22 +0,0 @@
[
"Сбор персональных данных",
"Запись персональных данных",
"Систематизация персональных данных",
"Накопление персональных данных",
"Хранение персональных данных",
"Уточнение (обновление, изменение) персональных данных",
"Извлечение персональных данных",
"Использование персональных данных",
"Передача (распространение, предоставление, доступ) персональных данных",
"Обезличивание персональных данных",
"Блокирование персональных данных",
"Удаление персональных данных",
"Уничтожение персональных данных",
"Трансграничная передача персональных данных",
"Автоматизированная обработка персональных данных",
"Неавтоматизированная обработка персональных данных",
"Смешанная обработка персональных данных",
"Формирование и ведение баз данных",
"Передача данных третьим лицам",
"Внутренняя передача данных между подразделениями"
]
-8
View File
@@ -1,8 +0,0 @@
[
"работники оператора",
"клиенты",
"контрагенты",
"родственники работников",
"соискатели",
"иные лица"
]
-28
View File
@@ -1,28 +0,0 @@
[
"Фамилия, имя, отчество",
"Дата рождения",
"Место рождения",
"Паспортные данные",
"Адрес регистрации",
"Адрес проживания",
"ИНН",
"СНИЛС",
"Номер телефона",
"Адрес электронной почты",
"Образование",
"Профессия",
"Сведения о доходах",
"Семейное положение",
"Состав семьи",
"Состояние здоровья",
"Национальность",
"Гражданство",
"Сведения о судимости",
"Фотография",
"Биометрические данные",
"Рабочий e-mail",
"Должность",
"Табельный номер",
"Сведения о воинском учете",
"Сведения о командировках"
]
@@ -0,0 +1,8 @@
{
"system": "Ты — юрист по защите персональных данных и критической информационной инфраструктуры (187-ФЗ). Проверь реквизиты организации.",
"rules": [
"Не выдумывай реквизиты — исправляй/дополняй только если уверен.",
"В «полях» для каждого поля укажи статус: ok (заполнено корректно), пусто (не заполнено), ошибка (заполнено с ошибкой)."
],
"response_format": "Верни ТОЛЬКО JSON: {\"оценка\": \"ok|warning|error\", \"замечания\": [\"...\"], \"предложения\": {\"full_name\": \"...\", \"short_name\": \"...\", \"address\": \"...\", \"inn\": \"...\", \"kpp\": \"...\", \"ogrn\": \"...\", \"ogrn_date\": \"...\", \"chief_position\": \"...\", \"chief_fio\": \"...\"}, \"поля\": {\"full_name\": {\"статус\": \"ok|пусто|ошибка\", \"комментарий\": \"...\"}, ...}}"
}
@@ -0,0 +1,4 @@
{
"system": "Ты — эксперт по категорированию объектов критической информационной инфраструктуры (187-ФЗ, Постановление Правительства РФ № 127). Проверь корректность присвоенных категорий значимости по показателям критериев. Категория значимости объекта определяется по максимальной из категорий показателей: I — если хотя бы один показатель I; II — если есть II и нет I; III — если есть III и нет I/II; «—» — если показатели не присвоены. Учитывай сферу деятельности субъекта и применяемость показателей (показатель 8 — госкорпорации/ГУП/госкомпании/стратегические АО; показатель 9 — все субъекты; показатель 10 — финансовый рынок/банки; показатель 13 — оборонно-промышленный комплекс).",
"response_format": "Верни ТОЛЬКО JSON: {\"оценка\": \"ok|warning|error\", \"замечания\": [{\"показатель\": \"номер\", \"категория\": \"текущая\", \"комментарий\": \"что не так и как должно быть\"}], \"предложения\": {\"criteria_values\": \"исправленный список показателей с категориями\", \"criteria_justification\": \"исправленное обоснование\"}, \"итоговая_категория\": \"I|II|III|—\"}. В «предложениях» — только если нужно что-то исправить, иначе пустые строки."
}
@@ -0,0 +1,4 @@
{
"system": "Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, приказ ФСТЭК России № 239 и № 236). Проверь заполненные сведения об объекте КИИ и дай рекомендации.",
"response_format": "Верни ТОЛЬКО JSON: {\"оценка\": \"ok|warning|error\", \"замечания\": [\"...\"], \"предложения\": {\"attacker_category\": \"...\", \"threats\": \"...\", \"incidents\": \"...\", \"criteria_values\": \"...\", \"criteria_justification\": \"...\", \"org_measures\": \"...\", \"tech_measures\": \"...\"}}. В «предложениях» укажи, что добавить/исправить по каждому разделу, если нужно, иначе пустые строки."
}
@@ -0,0 +1,9 @@
{
"system": "Ты — лингвист, специалист по склонению русских наименований организаций (187-ФЗ). Просклоняй название организации по падежам.",
"rules": [
"Склоняй только само название (включая организационно-правовую форму, если она склоняется по правилам русского языка).",
"Сохраняй кавычки, регистр и пунктуацию оригинала.",
"Не выдумывай падежи — используй стандартную парадигму: именительный, родительный, дательный, винительный, творительный, предложный."
],
"response_format": "Верни ТОЛЬКО JSON: {\"nominative\": \"...\", \"genitive\": \"...\", \"dative\": \"...\", \"accusative\": \"...\", \"instrumental\": \"...\", \"prepositional\": \"...\"}"
}
@@ -0,0 +1,4 @@
{
"system": "Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, приказ ФСТЭК России № 239 и № 236). По категории значимости объекта КИИ, характеристикам объекта, актуальным угрозам и уже выбранным средствам защиты информации (СЗИ) определи, каких средств не хватает, и дай рекомендации. Обязательно учитывай следующее:\n1) КАТЕГОРИЯ ЗНАЧИМОСТИ: если категория «без категории», «—», пусто или не присвоена — объект НЕ является значимым объектом КИИ, полный набор СЗИ по приказу № 239 для него НЕ требуется. В этом случае рекомендую только минимальный базовый набор (антивирусная защита, резервное копирование, управление доступом) и главный совет — провести категорирование объекта. НЕ перечисляй средства для I категории (СКЗИ, SIEM, анти-APT, доверенная загрузка и т.п.), если категория не присвоена. Для I категории — максимальный набор, для II — расширенный, для III — базовый набор по приказу № 239.\n2) ХАРАКТЕРИСТИКИ ОБЪЕКТА: внимательно читай переданные характеристики (наличие интернета, архитектура, ПО, тип объекта). НЕ рекомендуй СЗИ для технологий, которых нет у объекта: нет интернета/внешних сетей → не рекомендуй DDoS-защиту, VPN, веб-защиту, защиту от удалённых атак из интернета; нет виртуализации/контейнеров → не рекомендуй защиту виртуальной инфраструктуры и контейнеров; нет мобильных устройств → не рекомендуй защиту мобильных; объект не АСУ ТП → не рекомендуй защиту АСУ ТП; нет гостайны → не рекомендуй средства для гостайны. Отмечай только реально применимые классы СЗИ.\n3) ОБЪЁМ: максимум 5–7 рекомендаций, только самые важные для данной категории и объекта. Если текущий набор СЗИ достаточен — верни пустой список рекомендаций и «достаточно»: true.\nВыбирай СЗИ ТОЛЬКО из реально существующих классов средств защиты (антивирусная защита, СКЗИ, межсетевой экран, SIEM, DLP, IDS/IPS, средства доверенной загрузки, контроля целостности, управления доступом и т.п.).",
"response_format": "Верни ТОЛЬКО JSON: {\"рекомендации\": [{\"сзи\": \"название класса/средства\", \"зачем\": \"для чего нужно\"}], \"общие_советы\": [\"...\"], \"достаточно\": true|false}. Поле «достаточно» — true, если текущий набор СЗИ покрывает требования категории. Не придумывай конкретные торговые марки, если их нет в списке — указывай класс средства."
}
@@ -0,0 +1,13 @@
{
"system": "Ты — эксперт ФСТЭК по категорированию объектов критической информационной инфраструктуры (187-ФЗ, ПП РФ № 127, Распоряжение ПРФ № 360-р, приказ ФСТЭК № 236). Твоя задача — по описанию объекта КИИ ПОДОБРАТЬ его характеристики ТОЛЬКО из предоставленных списков, ничего не выдумывая.",
"rules": [
"Выбирай значения ТОЛЬКО из предоставленных списков. Если подходящего значения в списке нет — верни пустую строку, не придумывай своё.",
"Поле «типовой_объект»: выбери полную строку ТОЛЬКО из предоставленного перечня типовых отраслевых объектов (Распоряжение ПРФ № 360-р), наиболее подходящую. Не перефразируй, не сокращай — копируй строку дословно. Если ничего не подходит — пустая строка.",
"Поле «сфера»: выбери ТОЛЬКО одну из предоставленных сфер. Не придумывай свои.",
"Поле «тип»: выбери ТОЛЬКО один из предоставленных типов объектов.",
"Поле «архитектура»: выбери ТОЛЬКО одну из предоставленных архитектур.",
"Поле «назначение»: заполни строго по форме: «Объект предназначен для <что делает> с целью <зачем>.». Не отклоняйся от этой формы.",
"Поле «обоснование»: 1-2 предложения, почему выбраны такие характеристики."
],
"response_format": "Верни ТОЛЬКО JSON без пояснений: {\"назначение\": \"...\", \"сфера\": \"...\", \"тип\": \"...\", \"архитектура\": \"...\", \"типовой_объект\": \"...\", \"обоснование\": \"...\"}"
}
@@ -0,0 +1,9 @@
{
"system": "Ты — эксперт ФСТЭК по безопасности критической информационной инфраструктуры (187-ФЗ, приказ ФСТЭК № 236). На основе характеристик объекта КИИ выбери из предоставленного перечня АКТУАЛЬНЫЕ угрозы безопасности информации.",
"rules": [
"Выбирай угрозы ТОЛЬКО из предоставленного перечня — не выдумывай и не перефразируй, используй полные строки (код + название) как они есть.",
"Верни от 10 до 40 угроз.",
"Учитывай характеристики объекта: тип (ИС/АСУ/ИТКС), наличие интернета, сферу, категорию нарушителя."
],
"response_format": "Верни ТОЛЬКО JSON: {\"threats\": [\"УБИ.xxx Название угрозы\", ...]} — без пояснений и комментариев."
}
+17
View File
@@ -0,0 +1,17 @@
[
"Здравоохранение",
"Наука",
"Транспорт",
"Связь",
"Энергетика",
"Государственная регистрация прав",
"Банковская сфера и финансовый рынок",
"Топливно-энергетический комплекс",
"Атомная энергетика",
"Оборонная промышленность",
"Ракетно-космическая промышленность",
"Горнодобывающая промышленность",
"Металлургическая промышленность",
"Химическая промышленность",
"Иная сфера деятельности"
]
+14
View File
@@ -0,0 +1,14 @@
[
"Идентификация и аутентификация пользователей",
"Управление доступом",
"Ограничение программной среды",
"Антивирусная защита",
"Защита информации от несанкционированного доступа (СЗИ от НСД)",
"Регистрация и мониторинг событий безопасности",
"Межсетевое экранирование",
"Обнаружение вторжений (СОВ)",
"Криптографическая защита информации (СКЗИ)",
"Защита среды виртуализации",
"Резервное копирование и восстановление",
"Защита технических средств и линий связи"
]
+12
View File
@@ -0,0 +1,12 @@
[
"Угроза несанкционированного доступа к информационным ресурсам объекта",
"Угроза нарушения функционирования объекта (отказ в обслуживании)",
"Угроза утечки (нарушения конфиденциальности) информации",
"Угроза модификации (подмены) данных",
"Угроза внедрения вредоносного кода",
"Угроза несанкционированного использования вычислительных ресурсов",
"Угроза перехвата (прослушивания) сетевого трафика",
"Угроза социальной инженерии",
"Угроза использования уязвимостей программного обеспечения",
"Угроза физического воздействия на технические средства"
]
+229
View File
@@ -0,0 +1,229 @@
[
"УБИ.001 Угроза автоматического распространения вредоносного кода в грид-системе",
"УБИ.002 Угроза агрегирования данных, передаваемых в грид-системе",
"УБИ.003 Угроза использования слабостей криптографических алгоритмов и уязвимостей в программном обеспечении их реализации",
"УБИ.004 Угроза аппаратного сброса пароля BIOS",
"УБИ.005 Угроза внедрения вредоносного кода в BIOS",
"УБИ.006 Угроза внедрения кода или данных",
"УБИ.007 Угроза воздействия на программы с высокими привилегиями",
"УБИ.008 Угроза восстановления и/или повторного использования аутентификационной информации",
"УБИ.009 Угроза восстановления предыдущей уязвимой версии BIOS",
"УБИ.010 Угроза выхода процесса за пределы виртуальной машины",
"УБИ.011 Угроза деавторизации санкционированного клиента беспроводной сети",
"УБИ.012 Угроза деструктивного изменения конфигурации/среды окружения программ",
"УБИ.013 Угроза деструктивного использования декларированного функционала BIOS",
"УБИ.014 Угроза длительного удержания вычислительных ресурсов пользователями",
"УБИ.015 Угроза доступа к защищаемым файлам с использованием обходного пути",
"УБИ.016 Угроза доступа к локальным файлам сервера при помощи URL",
"УБИ.017 Угроза доступа/перехвата/изменения HTTP cookies",
"УБИ.018 Угроза загрузки нештатной операционной системы",
"УБИ.019 Угроза заражения DNS-кеша",
"УБИ.020 Угроза злоупотребления возможностями, предоставленными потребителям облачных услуг",
"УБИ.021 Угроза злоупотребления доверием потребителей облачных услуг",
"УБИ.022 Угроза избыточного выделения оперативной памяти",
"УБИ.023 Угроза изменения компонентов информационной (автоматизированной) системы",
"УБИ.024 Угроза изменения режимов работы аппаратных элементов компьютера",
"УБИ.025 Угроза изменения системных и глобальных переменных",
"УБИ.026 Угроза искажения XML-схемы",
"УБИ.027 Угроза искажения вводимой и выводимой на периферийные устройства информации",
"УБИ.028 Угроза использования альтернативных путей доступа к ресурсам",
"УБИ.029 Угроза использования вычислительных ресурсов суперкомпьютера «паразитными» процессами",
"УБИ.030 Угроза использования информации идентификации/аутентификации, заданной по умолчанию",
"УБИ.031 Угроза использования механизмов авторизации для повышения привилегий",
"УБИ.032 Угроза использования поддельных цифровых подписей BIOS",
"УБИ.033 Угроза использования слабостей кодирования входных данных",
"УБИ.034 Угроза использования слабостей протоколов сетевого/локального обмена данными",
"УБИ.035 Угроза использования слабых криптографических алгоритмов BIOS",
"УБИ.036 Угроза исследования механизмов работы программы",
"УБИ.037 Угроза исследования приложения через отчёты об ошибках",
"УБИ.038 Угроза исчерпания вычислительных ресурсов хранилища больших данных",
"УБИ.039 Угроза исчерпания запаса ключей, необходимых для обновления BIOS",
"УБИ.040 Угроза конфликта юрисдикций различных стран",
"УБИ.041 Угроза межсайтового скриптинга",
"УБИ.042 Угроза межсайтовой подделки запроса",
"УБИ.043 Угроза нарушения доступности облачного сервера",
"УБИ.044 Угроза нарушения изоляции пользовательских данных внутри виртуальной машины",
"УБИ.045 Угроза нарушения изоляции среды исполнения BIOS",
"УБИ.046 Угроза нарушения процедуры аутентификации субъектов виртуального информационного взаимодействия",
"УБИ.047 Угроза нарушения работоспособности грид-системы при нетипичной сетевой нагрузке",
"УБИ.048 Угроза нарушения технологии обработки информации путём несанкционированного внесения изменений в образы виртуальных машин",
"УБИ.049 Угроза нарушения целостности данных кеша",
"УБИ.050 Угроза неверного определения формата входных данных, поступающих в хранилище больших данных",
"УБИ.051 Угроза невозможности восстановления сессии работы на ПЭВМ при выводе из промежуточных состояний питания",
"УБИ.052 Угроза невозможности миграции образов виртуальных машин из-за несовместимости аппаратного и программного обеспечения",
"УБИ.053 Угроза невозможности управления правами пользователей BIOS",
"УБИ.054 Угроза недобросовестного исполнения обязательств поставщиками облачных услуг",
"УБИ.055 Угроза незащищённого администрирования облачных услуг",
"УБИ.056 Угроза некачественного переноса инфраструктуры в облако",
"УБИ.057 Угроза неконтролируемого копирования данных внутри хранилища больших данных",
"УБИ.058 Угроза неконтролируемого роста числа виртуальных машин",
"УБИ.059 Угроза неконтролируемого роста числа зарезервированных вычислительных ресурсов",
"УБИ.060 Угроза неконтролируемого уничтожения информации хранилищем больших данных",
"УБИ.061 Угроза некорректного задания структуры данных транзакции",
"УБИ.062 Угроза некорректного использования прозрачного прокси-сервера за счёт плагинов браузера",
"УБИ.063 Угроза некорректного использования функционала программного и аппаратного обеспечения",
"УБИ.064 Угроза некорректной реализации политики лицензирования в облаке",
"УБИ.065 Угроза неопределённости в распределении ответственности между ролями в облаке",
"УБИ.066 Угроза неопределённости ответственности за обеспечение безопасности облака",
"УБИ.067 Угроза неправомерного ознакомления с защищаемой информацией",
"УБИ.068 Угроза неправомерного/некорректного использования интерфейса взаимодействия с приложением",
"УБИ.069 Угроза неправомерных действий в каналах связи",
"УБИ.070 Угроза непрерывной модернизации облачной инфраструктуры",
"УБИ.071 Угроза несанкционированного восстановления удалённой защищаемой информации",
"УБИ.072 Угроза несанкционированного выключения или обхода механизма защиты от записи в BIOS",
"УБИ.073 Угроза несанкционированного доступа к активному и (или) пассивному виртуальному и (или) физическому сетевому оборудованию из физической и (или) виртуальной сети",
"УБИ.074 Угроза несанкционированного доступа к аутентификационной информации",
"УБИ.075 Угроза несанкционированного доступа к виртуальным каналам передачи",
"УБИ.076 Угроза несанкционированного доступа к гипервизору из виртуальной машины и (или) физической сети",
"УБИ.077 Угроза несанкционированного доступа к данным за пределами зарезервированного адресного пространства, в том числе выделенного под виртуальное аппаратное обеспечение",
"УБИ.078 Угроза несанкционированного доступа к защищаемым виртуальным машинам из виртуальной и (или) физической сети",
"УБИ.079 Угроза несанкционированного доступа к защищаемым виртуальным машинам со стороны других виртуальных машин",
"УБИ.080 Угроза несанкционированного доступа к защищаемым виртуальным устройствам из виртуальной и (или) физической сети",
"УБИ.081 Угроза несанкционированного доступа к локальному компьютеру через клиента грид-системы",
"УБИ.082 Угроза несанкционированного доступа к сегментам вычислительного поля",
"УБИ.083 Угроза несанкционированного доступа к системе по беспроводным каналам",
"УБИ.084 Угроза несанкционированного доступа к системе хранения данных из виртуальной и (или) физической сети",
"УБИ.085 Угроза несанкционированного доступа к хранимой в виртуальном пространстве защищаемой информации",
"УБИ.086 Угроза несанкционированного изменения аутентификационной информации",
"УБИ.087 Угроза несанкционированного использования привилегированных функций BIOS",
"УБИ.088 Угроза несанкционированного копирования защищаемой информации",
"УБИ.089 Угроза несанкционированного редактирования реестра",
"УБИ.090 Угроза несанкционированного создания учётной записи пользователя",
"УБИ.091 Угроза несанкционированного удаления защищаемой информации",
"УБИ.092 Угроза несанкционированного удалённого внеполосного доступа к аппаратным средствам",
"УБИ.093 Угроза несанкционированного управления буфером",
"УБИ.094 Угроза несанкционированного управления синхронизацией и состоянием",
"УБИ.095 Угроза несанкционированного управления указателями",
"УБИ.096 Угроза несогласованности политик безопасности элементов облачной инфраструктуры",
"УБИ.097 Угроза несогласованности правил доступа к большим данным",
"УБИ.098 Угроза обнаружения открытых портов и идентификации привязанных к ним сетевых служб",
"УБИ.099 Угроза обнаружения хостов",
"УБИ.100 Угроза обхода некорректно настроенных механизмов аутентификации",
"УБИ.101 Угроза общедоступности облачной инфраструктуры",
"УБИ.102 Угроза опосредованного управления группой программ через совместно используемые данные",
"УБИ.103 Угроза определения типов объектов защиты",
"УБИ.104 Угроза определения топологии вычислительной сети",
"УБИ.105 Угроза отказа в загрузке входных данных неизвестного формата хранилищем больших данных",
"УБИ.106 Угроза отказа в обслуживании системой хранения данных суперкомпьютера",
"УБИ.107 Угроза отключения контрольных датчиков",
"УБИ.108 Угроза ошибки обновления гипервизора",
"УБИ.109 Угроза перебора всех настроек и параметров приложения",
"УБИ.110 Угроза перегрузки грид-системы вычислительными заданиями",
"УБИ.111 Угроза передачи данных по скрытым каналам",
"УБИ.112 Угроза передачи запрещённых команд на оборудование с числовым программным управлением",
"УБИ.113 Угроза перезагрузки аппаратных и программно-аппаратных средств вычислительной техники",
"УБИ.114 Угроза переполнения целочисленных переменных",
"УБИ.115 Угроза перехвата вводимой и выводимой на периферийные устройства информации",
"УБИ.116 Угроза перехвата данных, передаваемых по вычислительной сети",
"УБИ.117 Угроза перехвата привилегированного потока",
"УБИ.118 Угроза перехвата привилегированного процесса",
"УБИ.119 Угроза перехвата управления гипервизором",
"УБИ.120 Угроза перехвата управления средой виртуализации",
"УБИ.121 Угроза повреждения системного реестра",
"УБИ.122 Угроза повышения привилегий",
"УБИ.123 Угроза подбора пароля BIOS",
"УБИ.124 Угроза подделки записей журнала регистрации событий",
"УБИ.125 Угроза подключения к беспроводной сети в обход процедуры аутентификации",
"УБИ.126 Угроза подмены беспроводного клиента или точки доступа",
"УБИ.127 Угроза подмены действия пользователя путём обмана",
"УБИ.128 Угроза подмены доверенного пользователя",
"УБИ.129 Угроза подмены резервной копии программного обеспечения BIOS",
"УБИ.130 Угроза подмены содержимого сетевых ресурсов",
"УБИ.131 Угроза подмены субъекта сетевого доступа",
"УБИ.132 Угроза получения предварительной информации об объекте защиты",
"УБИ.133 Угроза получения сведений о владельце беспроводного устройства",
"УБИ.134 Угроза потери доверия к поставщику облачных услуг",
"УБИ.135 Угроза потери и утечки данных, обрабатываемых в облаке",
"УБИ.136 Угроза потери информации вследствие несогласованности работы узлов хранилища больших данных",
"УБИ.137 Угроза потери управления облачными ресурсами",
"УБИ.138 Угроза потери управления собственной инфраструктурой при переносе её в облако",
"УБИ.139 Угроза преодоления физической защиты",
"УБИ.140 Угроза приведения системы в состояние «отказ в обслуживании»",
"УБИ.141 Угроза привязки к поставщику облачных услуг",
"УБИ.142 Угроза приостановки оказания облачных услуг вследствие технических сбоев",
"УБИ.143 Угроза программного выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации",
"УБИ.144 Угроза программного сброса пароля BIOS",
"УБИ.145 Угроза пропуска проверки целостности программного обеспечения",
"УБИ.146 Угроза прямого обращения к памяти вычислительного поля суперкомпьютера",
"УБИ.147 Угроза распространения несанкционированно повышенных прав на всю грид-систему",
"УБИ.148 Угроза сбоя автоматического управления системой разграничения доступа хранилища больших данных",
"УБИ.149 Угроза сбоя обработки специальным образом изменённых файлов",
"УБИ.150 Угроза сбоя процесса обновления BIOS",
"УБИ.151 Угроза сканирования веб-сервисов, разработанных на основе языка описания WSDL",
"УБИ.152 Угроза удаления аутентификационной информации",
"УБИ.153 Угроза усиления воздействия на вычислительные ресурсы пользователей при помощи сторонних серверов",
"УБИ.154 Угроза установки уязвимых версий обновления программного обеспечения BIOS",
"УБИ.155 Угроза утраты вычислительных ресурсов",
"УБИ.156 Угроза утраты носителей информации",
"УБИ.157 Угроза физического выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации",
"УБИ.158 Угроза форматирования носителей информации",
"УБИ.159 Угроза «форсированного веб-браузинга»",
"УБИ.160 Угроза хищения средств хранения, обработки и (или) ввода/вывода/передачи информации",
"УБИ.161 Угроза чрезмерного использования вычислительных ресурсов суперкомпьютера в ходе интенсивного обмена межпроцессорными сообщениями",
"УБИ.162 Угроза эксплуатации цифровой подписи программного кода",
"УБИ.163 Угроза перехвата исключения/сигнала из привилегированного блока функций",
"УБИ.164 Угроза распространения состояния «отказ в обслуживании» в облачной инфраструктуре",
"УБИ.165 Угроза включения в проект не достоверно испытанных компонентов",
"УБИ.166 Угроза внедрения системной избыточности",
"УБИ.167 Угроза заражения компьютера при посещении неблагонадёжных сайтов",
"УБИ.168 Угроза «кражи» учётной записи доступа к сетевым сервисам",
"УБИ.169 Угроза наличия механизмов разработчика",
"УБИ.170 Угроза неправомерного шифрования информации",
"УБИ.171 Угроза скрытного включения вычислительного устройства в состав бот-сети",
"УБИ.172 Угроза распространения «почтовых червей»",
"УБИ.173 Угроза «спама» веб-сервера",
"УБИ.174 Угроза «фарминга»",
"УБИ.175 Угроза «фишинга»",
"УБИ.176 Угроза нарушения технологического/производственного процесса из-за временны́х задержек, вносимых средством защиты",
"УБИ.177 Угроза неподтверждённого ввода данных оператором в систему, связанную с безопасностью",
"УБИ.178 Угроза несанкционированного использования системных и сетевых утилит",
"УБИ.179 Угроза несанкционированной модификации защищаемой информации",
"УБИ.180 Угроза отказа подсистемы обеспечения температурного режима",
"УБИ.181 Угроза перехвата одноразовых паролей в режиме реального времени",
"УБИ.182 Угроза физического устаревания аппаратных компонентов",
"УБИ.183 Угроза перехвата управления автоматизированной системой управления технологическими процессами",
"УБИ.184 Угроза агрегирования данных, обрабатываемых с помощью мобильного устройства",
"УБИ.185 Угроза несанкционированного изменения параметров настройки средств защиты информации",
"УБИ.186 Угроза внедрения вредоносного кода через рекламу, сервисы и контент",
"УБИ.187 Угроза несанкционированного воздействия на средство защиты информации",
"УБИ.188 Угроза подмены программного обеспечения",
"УБИ.189 Угроза маскирования действий вредоносного кода",
"УБИ.190 Угроза внедрения вредоносного кода за счет посещения зараженных сайтов в сети Интернет",
"УБИ.191 Угроза внедрения вредоносного кода в дистрибутив программного обеспечения",
"УБИ.192 Угроза использования уязвимых версий программного обеспечения",
"УБИ.193 Угроза утечки информации за счет применения вредоносным программным обеспечением алгоритмов шифрования трафика",
"УБИ.194 Угроза несанкционированного использования привилегированных функций мобильного устройства",
"УБИ.195 Угроза удаленного запуска вредоносного кода в обход механизмов защиты операционной системы",
"УБИ.196 Угроза контроля вредоносной программой списка приложений, запущенных на мобильном устройстве",
"УБИ.197 Угроза хищения аутентификационной информации из временных файлов cookie",
"УБИ.198 Угроза скрытной регистрации вредоносной программой учетных записей администраторов",
"УБИ.199 Угроза перехвата управления мобильного устройства при использовании виртуальных голосовых ассистентов",
"УБИ.200 Угроза хищения информации с мобильного устройства при использовании виртуальных голосовых ассистентов",
"УБИ.201 Угроза утечки пользовательских данных при использовании функций автоматического заполнения аутентификационной информации в браузере",
"УБИ.202 Угроза несанкционированной установки приложений на мобильные устройства",
"УБИ.203 Угроза утечки информации с неподключенных к сети Интернет компьютеров",
"УБИ.204 Угроза несанкционированного изменения вредоносной программой значений параметров программируемых логических контроллеров",
"УБИ.205 Угроза нарушения работы компьютера и блокирования доступа к его данным из-за некорректной работы установленных на нем средств защиты",
"УБИ.206 Угроза отказа в работе оборудования из-за изменения геолокационной информации о нем",
"УБИ.207 Угроза несанкционированного доступа к параметрам настройки оборудования за счет использования «мастер-кодов» (инженерных паролей)",
"УБИ.208 Угроза нецелевого использования вычислительных ресурсов средства вычислительной техники",
"УБИ.209 Угроза несанкционированного доступа к защищаемой памяти \r\nядра процессора",
"УБИ.210 Угроза нарушения работы информационной системы, вызванного обновлением используемого в ней программного обеспечения",
"УБИ.211 Угроза использования непроверенных пользовательских данных при формировании конфигурационного файла, используемого программным обеспечением администрирования информационных систем",
"УБИ.212 Угроза перехвата управления информационной системой",
"УБИ.213 Угроза обхода многофакторной аутентификации",
"УБИ.214 Угроза несвоевременного выявления и реагирования компонентами информационной (автоматизированной) системы (в том числе средствами защиты информации) на события безопасности информации",
"УБИ.215 Угроза несанкционированного доступа к системе при помощи сторонних сервисов",
"УБИ.216 Угроза получения несанкционированного доступа к приложениям, установленным на Smart-картах",
"УБИ.217 Угроза использования скомпрометированного доверенного источника обновлений программного обеспечения",
"УБИ.218 Угроза раскрытия информации о модели машинного обучения",
"УБИ.219 Угроза хищения обучающих данных",
"УБИ.220 Угроза нарушения функционирования («обхода») средств, реализующих технологии искусственного интеллекта",
"УБИ.221 Угроза модификации модели машинного обучения путем искажения («отравления») обучающих данных",
"УБИ.222 Угроза подмены модели машинного обучения",
"УБИ.223 Угроза несанкционированного доступа к контейнерам, предоставляющего пользователям расширенные привилегии",
"УБИ.224 Угроза нарушения целостности (подмены) контейнеров",
"УБИ.225 Угроза нарушения изоляции контейнеров",
"УБИ.226 Угроза внедрения вредоносного программного обеспечения в контейнеры",
"УБИ.227 Угроза модификации (подмены) образов контейнеров"
]
@@ -0,0 +1 @@
2026-08-04
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
[
"Информационная система управления",
"Автоматизированная система управления технологическим процессом",
"Информационно-телекоммуникационная сеть",
"Автоматизированная система управления производством",
"Система диспетчерского управления",
"Система управления базами данных",
"Автоматизированная система научных исследований",
"Система электронного документооборота",
"Информационная система расчетов",
"Система управления финансами",
"Иной типовой отраслевой объект КИИ"
]
Binary file not shown.
Binary file not shown.
+162
View File
@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
"""DokoGen — загрузка перечня угроз безопасности информации с сайта ФСТЭК (БДУ).
Скачивает https://bdu.fstec.ru/threat (таблица угроз, с пагинацией),
парсит названия угроз и сохраняет в dictionaries/threats_fstec.json.
Рядом хранится дата актуализации (threats_fstec_date.txt).
"""
import os
import re
import json
import ssl
import urllib.request
import urllib.parse
from datetime import datetime
BDU_URL = 'https://bdu.fstec.ru/threat'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
def _ssl_ctx():
"""SSL-контекст: сначала обычная проверка, при проблемах — без проверки."""
try:
ctx = ssl.create_default_context()
return ctx
except Exception:
pass
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def _fetch(url, timeout=30):
"""GET с User-Agent, возвращает текст или None."""
req = urllib.request.Request(url, headers={'User-Agent': USER_AGENT})
try:
with urllib.request.urlopen(req, timeout=timeout, context=_ssl_ctx()) as resp:
return resp.read().decode('utf-8', errors='ignore')
except Exception:
# повтор с отключённой проверкой сертификата (некоторые сети/прокси)
try:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
return resp.read().decode('utf-8', errors='ignore')
except Exception:
return None
def _normalize_ubi(code):
"""«УБИ. 001» / «УБИ.001» → «УБИ.001» (без пробела после точки)."""
m = re.search(r'(УБИ\.?)\s*([\d.]+)', code or '', re.I)
if m:
return f"{m.group(1).replace('.', '').upper()}.{m.group(2)}"
return (code or '').strip()
def _parse_threats(html):
"""Извлекает пары (код УБИ, название) из HTML-таблицы БДУ.
Строки вида: <tr>...<td>УБИ. 001</td><td>Угроза ...</td>...</tr>
Возвращает список строк: «УБИ.001 Угроза ...» (код + название).
"""
items = []
if not html:
return items
for row in re.findall(r'<tr[^>]*>(.*?)</tr>', html, re.S):
cells = re.findall(r'<t[dh][^>]*>(.*?)</t[dh]>', row, re.S)
cleaned = [re.sub(r'<[^>]+>', '', c).strip() for c in cells]
if not cleaned:
continue
# первая ячейка — код (УБИ.001), вторая — название
code = _normalize_ubi(cleaned[0]) if cleaned else ''
name = ''
for cell in cleaned[1:]:
if cell and cell not in ('', 'Идентификатор', 'Наименование', 'Дата', 'Уровень опасности'):
name = cell
break
if not name:
continue
if code.startswith('УБИ.'):
items.append(f"{code} {name}")
else:
items.append(name)
return items
def download_fstec_threats(dict_dir=None, max_pages=8, page_size=100):
"""Скачивает перечень угроз с bdu.fstec.ru.
Возвращает (ok, сообщение, список_угроз). При успехе сохраняет:
- dictionaries/threats_fstec.json — список названий угроз
- dictionaries/threats_fstec_date.txt — дата актуализации (ГГГГ-ММ-ДД)
"""
if dict_dir is None:
dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries')
threats = []
for page in range(1, max_pages + 1):
url = f"{BDU_URL}?size={page_size}&page={page}"
html = _fetch(url)
if not html:
break
names = _parse_threats(html)
if not names:
break
for n in names:
if n not in threats:
threats.append(n)
# если на странице меньше page_size — это последняя
if len(names) < page_size:
break
if not threats:
return False, "Не удалось загрузить угрозы с сайта ФСТЭК (bdu.fstec.ru)", []
try:
os.makedirs(dict_dir, exist_ok=True)
with open(os.path.join(dict_dir, 'threats_fstec.json'), 'w', encoding='utf-8') as f:
json.dump(threats, f, ensure_ascii=False, indent=2)
today = datetime.now().strftime('%Y-%m-%d')
with open(os.path.join(dict_dir, 'threats_fstec_date.txt'), 'w', encoding='utf-8') as f:
f.write(today)
except Exception as e:
return False, f"Ошибка сохранения: {e}", threats
return True, f"Загружено угроз: {len(threats)}", threats
def get_threats_date(dict_dir=None):
"""Дата последней актуализации списка угроз (или None)."""
if dict_dir is None:
dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries')
path = os.path.join(dict_dir, 'threats_fstec_date.txt')
if os.path.exists(path):
try:
with open(path, encoding='utf-8') as f:
return f.read().strip()
except Exception:
pass
return None
def load_threats(dict_dir=None):
"""Загружает список угроз: сначала скачанный с ФСТЭК, иначе базовый threats.json."""
if dict_dir is None:
dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries')
fstec = os.path.join(dict_dir, 'threats_fstec.json')
if os.path.exists(fstec):
try:
with open(fstec, encoding='utf-8') as f:
return json.load(f)
except Exception:
pass
base = os.path.join(dict_dir, 'threats.json')
try:
with open(base, encoding='utf-8') as f:
return json.load(f)
except Exception:
return []
+10 -7
View File
@@ -310,10 +310,14 @@ def _subst_var_xml(xml: str, var: str, val: str) -> str:
+ '</w:r><w:r><w:t xml:space="preserve">')
def _expand_nested_loops(template: str, item: Dict) -> str:
def _expand_nested_loops(template: str, item: Dict, loops: Dict = None) -> str:
"""Разворачивает вложенные циклы вида <!-- loop:users_loop --> ... <!-- loop_end -->
данными из item (список словарей). Используется для циклов внутри цикла ИС,
например пользователи ИС. Переменные внутри: {{user_fio}}, {{user_position}}."""
данными из item (список словарей) или из общего словаря циклов loops.
Используется для циклов внутри цикла ИС, например пользователи ИС,
а также для вложенных таблиц (показатели значимости внутри объекта КИИ).
Переменные внутри: {{user_fio}}, {{user_position}}, {{item.*}}."""
if loops is None:
loops = {}
result = template
for _ in range(20):
m_start = LOOP_START.search(result)
@@ -321,6 +325,8 @@ def _expand_nested_loops(template: str, item: Dict) -> str:
break
key = m_start.group(1)
data = item.get(key)
if not isinstance(data, list):
data = loops.get(key)
if not isinstance(data, list):
# Нет данных для вложенного цикла — удаляем блок целиком
depth = 1
@@ -478,7 +484,7 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
clone = template_clean
# Вложенные циклы (users_loop и т.п.) — разворачиваем ДО подстановки
# обычных полей, чтобы {{user_fio}} внутри них не затёрлись алиасами
clone = _expand_nested_loops(clone, item)
clone = _expand_nested_loops(clone, item, loops)
for field, value in item.items():
if isinstance(value, list):
continue # списки — данные вложенных циклов, не подставляем
@@ -692,9 +698,6 @@ def _highlight_unmatched(doc):
pos = m.end()
if pos < len(full):
_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:
_process_para(para)
+18 -18
View File
@@ -5,7 +5,7 @@
рядом с программой (при сборке .exe — рядом с exe):
- Инструкция_пользователя.docx
- Инструкция_администратора.docx
- Опросный_лист_152_ФЗ.xlsx (пустой шаблон)
- Опросный_лист_187_ФЗ.xlsx (пустой шаблон)
"""
import os
@@ -45,7 +45,7 @@ def variables_reference_path():
def survey_path():
return os.path.join(docs_dir(), 'Опросный_лист_152_ФЗ.xlsx')
return os.path.join(docs_dir(), 'Опросный_лист_187_ФЗ.xlsx')
def _h(doc, text, size=14):
@@ -83,7 +83,7 @@ def _generate_variables_reference(path):
_p(doc, 'Список переменных для шаблонов Word. Переменная подставляется в текст '
'как есть: {{имя_переменной}}. Циклы: <!-- loop:имя --> ... <!-- loop_end -->.')
groups = {'A': 'Общие переменные', 'B': '152-ФЗ (персональные данные)'}
groups = {'A': 'Общие переменные', 'B': '187-ФЗ (критическая информационная инфраструктура)'}
by_group = {'A': [], 'B': []}
for var, group, desc, _ in VARIABLE_DEFS:
by_group.setdefault(group, []).append((var, desc))
@@ -97,10 +97,10 @@ def _generate_variables_reference(path):
_bullet(doc, f'{var}{desc}')
_h(doc, 'Циклы', 12)
_bullet(doc, 'information_systems — ИС ({{item.name}}, {{item.description}}, {{item.structure}}, {{item.pd_actions}}, {{item.defense_tools_list}}, {{item.users}}, {{item.category}}, {{item.pd_count}} и др.).')
_bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.purpose}}, {{item.sphere}}, {{item.object_type}}, {{item.architecture}}, {{item.net_category}}, {{item.hw_sw}}, {{item.os_sw}}, {{item.app_sw}}, {{item.defense_tools_list}}, {{item.users}} и др.).')
_bullet(doc, 'commission — комиссия ({{item.role}}, {{item.position}}, {{item.fio}}, {{item.fio_initials}}, {{item.fio_short}}, {{item.signature}}).')
_bullet(doc, 'employees_access — сотрудники с доступом ({{item.position}}, {{item.fio}}).')
_bullet(doc, 'paper_documents — бумажные документы ({{item.document}}, {{item.storage}}).')
doc.save(path)
@@ -108,11 +108,11 @@ def _generate_variables_reference(path):
def _generate_user_manual(path):
"""Инструкция пользователя: программа, циклы, переменные, проверки."""
doc = docx.Document()
_h(doc, 'ДоКоГеНеРаТоР — 152-ФЗ')
_h(doc, 'ДоКоГеНеРаТоР — 187-ФЗ')
_h(doc, 'Инструкция пользователя', 13)
_h(doc, '1. Что это за программа', 12)
_p(doc, 'Программа автоматически заполняет шаблоны документов по 152-ФЗ '
_p(doc, 'Программа автоматически заполняет шаблоны документов по 187-ФЗ '
'(акты обследования, политики, инструкции, приказы и т.д.) данными '
'об организации, информационных системах, комиссии и сотрудниках. '
'Данные можно ввести вручную на вкладках или импортировать из '
@@ -120,10 +120,10 @@ def _generate_user_manual(path):
_h(doc, '2. Основные вкладки', 12)
_bullet(doc, 'Организация — реквизиты, адреса, руководитель, склонения названий, проверка данных (Морфер / DaData / ИИ).')
_bullet(doc, 'Информационные системы — ИС, категории ПДн, действия с ПДн, средства защиты, пользователи.')
_bullet(doc, 'Комиссия — состав комиссии по классификации ИСПДн.')
_bullet(doc, 'Пользователи — сотрудники с доступом к ПДн.')
_bullet(doc, 'Бумажные документы — бумажные носители ПДн и места хранения.')
_bullet(doc, 'Объекты КИИ — наименование, описание, категория значимости, средства защиты, пользователи ОКИИ.')
_bullet(doc, 'Комиссия — состав комиссии по категорированию объектов КИИ.')
_bullet(doc, 'Пользователи — сотрудники с доступом к объектам КИИ.')
_bullet(doc, 'Генерация — выбор шаблонов и запуск генерации.')
_bullet(doc, 'Лог — журнал действий, экспорт в файл.')
@@ -137,13 +137,13 @@ def _generate_user_manual(path):
_h(doc, '4. Циклы (повторяющиеся блоки)', 12)
_p(doc, 'Если в документе нужно повторить блок для каждой ИС, комиссии, '
'сотрудника или бумажного документа — используются маркеры цикла:')
'сотрудника или объекта КИИ — используются маркеры цикла:')
_p(doc, '<!-- loop:information_systems --> ... {{item.name}} ... <!-- loop_end -->', bold=True)
_p(doc, 'Доступные циклы:')
_bullet(doc, 'information_systems — ИС ({{item.name}}, {{item.description}}, {{item.structure}}, {{item.pd_actions}}, {{item.defense_tools_list}}, {{item.users}}, {{item.category}}, {{item.pd_count}} и др.).')
_bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.purpose}}, {{item.sphere}}, {{item.object_type}}, {{item.architecture}}, {{item.net_category}}, {{item.hw_sw}}, {{item.os_sw}}, {{item.app_sw}}, {{item.defense_tools_list}}, {{item.users}} и др.).')
_bullet(doc, 'commission — комиссия ({{item.role}}, {{item.position}}, {{item.fio}}, {{item.fio_initials}}, {{item.fio_short}}, {{item.signature}}).')
_bullet(doc, 'employees_access — сотрудники с доступом ({{item.position}}, {{item.fio}}).')
_bullet(doc, 'paper_documents — бумажные документы ({{item.document}}, {{item.storage}}).')
_p(doc, 'Внутри цикла переменные пишутся через точку: {{item.поле}}. '
'Допускается и синтаксис без точки: {{item_поле}}.')
@@ -173,13 +173,13 @@ def _generate_user_manual(path):
def _generate_admin_manual(path):
"""Инструкция администратора: словари, API-ключи, лимиты."""
doc = docx.Document()
_h(doc, 'ДоКоГеНеРаТоР — 152-ФЗ')
_h(doc, 'ДоКоГеНеРаТоР — 187-ФЗ')
_h(doc, 'Инструкция администратора', 13)
_h(doc, '1. Файлы настроек рядом с программой', 12)
_bullet(doc, 'api_settings.json — API-ключи (DaData, ИИ).')
_bullet(doc, 'import_settings.json — словарь импорта: что и откуда брать из опросника.')
_bullet(doc, 'dictionaries/ — справочники: defense_tools.json, pd_actions.json, pd_items.json, pd_categories.json.')
_bullet(doc, 'dictionaries/ — справочники: defense_tools.json, okved.json (словарь ОКВЭД).')
_bullet(doc, 'dokogen.log — журнал работы (пишется автоматически).')
_bullet(doc, 'docs/ — инструкции и пустой опросник (создаются автоматически).')
@@ -240,8 +240,8 @@ def _ensure_survey():
return target
# Ищем шаблон опросника: рядом с программой или встроенный
candidates = [
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'Опросный_лист_152_ФЗ.xlsx'),
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_152_ФЗ.xlsx'),
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'Опросный_лист_187_ФЗ.xlsx'),
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_187_ФЗ.xlsx'),
]
for c in candidates:
if os.path.exists(c):
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

+309 -73
View File
@@ -31,24 +31,36 @@
"cell": "",
"keywords": "адрес организации \\(фактический\\)"
},
{
"name": "Электронная почта",
"var": "email",
"cell": "",
"keywords": "электронная почта"
},
{
"name": "Телефон",
"var": "phone",
"cell": "",
"keywords": "телефон"
},
{
"name": "ИНН",
"var": "inn",
"cell": "",
"keywords": "инн"
},
{
"name": "Дата ОГРН",
"var": "ogrnDate",
"cell": "",
"keywords": "дата.*огрн"
},
{
"name": "ОГРН",
"var": "ogrn",
"cell": "",
"keywords": "огрн"
},
{
"name": "Дата получения ОГРН",
"var": "ogrnDate",
"cell": "",
"keywords": "дата получения огрн|дата.*огрн"
},
{
"name": "КПП",
"var": "kpp",
@@ -61,6 +73,12 @@
"cell": "",
"keywords": "основной оквэд"
},
{
"name": "Дополнительные ОКВЭД",
"var": "okvedExtra",
"cell": "",
"keywords": "дополнительные оквэд"
},
{
"name": "Должность руководителя",
"var": "chiefPosition",
@@ -74,82 +92,70 @@
"keywords": "фио руководителя"
},
{
"name": "Должность ответственного за ПДн",
"var": "ispdnPosition",
"name": "Должность ответственного лица (безопасность ЗОКИИ)",
"var": "responsiblePosition",
"cell": "",
"keywords": "должность.*защит.*информ|должность.*администрат"
"keywords": "должность ответственного лица"
},
{
"name": "ФИО ответственного за ПДн",
"var": "ispdnFio",
"name": "ФИО ответственного лица (безопасность ЗОКИИ)",
"var": "responsibleFio",
"cell": "",
"keywords": "фио.*защит.*информ|фио.*администрат"
"keywords": "фио ответственного лица"
},
{
"name": "Email ответственного за ПДн",
"var": "ispdnEmail",
"cell": "",
"keywords": "электронная почта.*защит|электронная почта.*ответств|электронная почта.*специалист"
},
{
"name": "Телефон ответственного за ПДн",
"var": "ispdnPhone",
"cell": "",
"keywords": "телефон.*защит|телефон.*ответств|телефон.*специалист"
},
{
"name": "Структурное подразделение по безопасности",
"name": "Подразделение по безопасности ОКИИ",
"var": "ispdnDepartment",
"cell": "",
"keywords": "структур.*подраздел"
},
{
"name": "Номер договора",
"var": "contractNumber",
"name": "Рабочий телефон ответственного",
"var": "ispdnPhone",
"cell": "",
"keywords": "номер.*договора|номер.*контракта"
"keywords": "рабочий телефон ответственного|мобильный телефон ответственного"
},
{
"name": "Дата договора",
"var": "contractDate",
"name": "Электронная почта ответственного",
"var": "ispdnEmail",
"cell": "",
"keywords": "дата.*договора|дата.*контракта"
"keywords": "электронная почта ответственного"
},
{
"name": "Перечень бумажных документов",
"var": "paperDocuments",
"cell": "",
"keywords": "названи.*документ"
},
{
"name": "Место хранения документов",
"var": "paperStorage",
"cell": "",
"keywords": "место.*хранен"
},
{
"name": "Должность ответственного за обработку ПДн",
"var": "responsiblePosition",
"cell": "",
"keywords": "должность.*ответственн"
},
{
"name": "ФИО ответственного за обработку ПДн",
"var": "responsibleFio",
"cell": "",
"keywords": "фио.*ответственн"
},
{
"name": "Должность администратора безопасности",
"name": "Должность специалиста по ЗИ/администратора",
"var": "administratorPosition",
"cell": "",
"keywords": "должность.*администрат.*(?:безопас|защит|пд)"
"keywords": "должность специалиста по защите информации|должность.*администрат"
},
{
"name": "ФИО администратора безопасности",
"name": "ФИО специалиста по ЗИ/администратора",
"var": "administratorFio",
"cell": "",
"keywords": "фио.*администрат.*(?:безопас|защит|пд)"
"keywords": "фио специалиста по защите информации|фио.*администрат"
},
{
"name": "Право владения зданием",
"var": "buildingRight",
"cell": "",
"keywords": "право владения зданием"
},
{
"name": "Сумма прибыли за год",
"var": "profit",
"cell": "",
"keywords": "сумма прибыли"
},
{
"name": "Сумма налога с прибыли",
"var": "profitTax",
"cell": "",
"keywords": "сумма налога с прибыли"
},
{
"name": "Доменное имя и внешний IP",
"var": "domainName",
"cell": "",
"keywords": "доменное имя и внешний сетевой адрес"
},
{
"name": "Сайт организации",
@@ -161,49 +167,279 @@
"name": "Должность ответственного за сайт",
"var": "site_responsible_position",
"cell": "",
"keywords": "должность ответственного за сайт|должность.*отв.*сайт"
"keywords": "должность ответственного за сайт"
},
{
"name": "ФИО ответственного за сайт",
"var": "site_responsible_fio",
"cell": "",
"keywords": "фио.*отв.*сайт|фио.*сайт"
"keywords": "фио ответственного за сайт"
},
{
"name": "Разработчик сайта",
"var": "site_service_provider",
"cell": "",
"keywords": "наименование организации.*разрабат.*сайт|наименование.*размещала.*сайт|разрабат.*сайт.*организац"
"keywords": "организации, которая разрабатывала и размещала корпоративный сайт"
},
{
"name": "ИНН разработчика сайта",
"var": "site_service_provider_inn",
"cell": "",
"keywords": "инн.*разрабат.*сайт|инн.*размещала.*сайт"
"keywords": "инн организации, которая разрабатывала"
},
{
"name": "Размещение сайта (хостинг)",
"name": "Размещение сайта",
"var": "site_hosting",
"cell": "",
"keywords": "размещение сайта|адрес расположения серверов|адрес.*сервер.*сайт"
"keywords": "размещение сайта"
},
{
"name": "Адрес хостинга",
"name": "Адрес расположения серверов",
"var": "site_hosting_address",
"cell": "",
"keywords": "адрес.*хостинг|адрес.*располож.*сервер"
"keywords": "адрес расположения серверов"
},
{
"name": "Электронная почта",
"var": "email",
"name": "Облачная платформа",
"var": "cloud_platform",
"cell": "",
"keywords": "электронная почта"
"keywords": "облачную платформу"
},
{
"name": "Телефон",
"var": "phone",
"name": "Аренда выделенного сервера",
"var": "cloud_dedicated_server",
"cell": "",
"keywords": "телефон"
"keywords": "выделенный сервер"
},
{
"name": "Аренда виртуального ЦОД",
"var": "cloud_vdc",
"cell": "",
"keywords": "виртуальный дата-центр"
},
{
"name": "Серверы в дата-центре",
"var": "cloud_datacenter",
"cell": "",
"keywords": "сервера в дата-центре"
},
{
"name": "Средства контейнеризации",
"var": "cloud_container",
"cell": "",
"keywords": "контейнеризации"
},
{
"name": "Собственная серверная (ЦОД)",
"var": "cloud_own_server",
"cell": "",
"keywords": "собственная серверная"
},
{
"name": "Общее количество компьютеров",
"var": "computers_total",
"cell": "",
"keywords": "общее количество компьютеров"
},
{
"name": "Компьютеры, подключённые к объектам КИИ",
"var": "computers_kii",
"cell": "",
"keywords": "компьютеров подключенных к объектам кии"
},
{
"name": "Центр мониторинга",
"var": "monitoring_center",
"cell": "",
"keywords": "к какому центру подключены"
},
{
"name": "Детали подключения к центру мониторинга",
"var": "monitoring_details",
"cell": "",
"keywords": "детали подключения"
},
{
"name": "Дополнительная информация",
"var": "additionalInfo",
"cell": "",
"keywords": "дополнительная информация"
},
{
"name": "Физическая охрана (ЧОП, сторожа, охранники)",
"var": "physicalGuard",
"cell": "",
"keywords": "физическая охрана"
},
{
"name": "Пожарная сигнализация",
"var": "physicalFireAlarm",
"cell": "",
"keywords": "пожарная сигнализация"
},
{
"name": "Тревожная кнопка",
"var": "physicalPanicButton",
"cell": "",
"keywords": "тревожная кнопка"
},
{
"name": "Решётки на окнах",
"var": "physicalBars",
"cell": "",
"keywords": "решетки на окнах"
},
{
"name": "Металлические двери",
"var": "physicalMetalDoors",
"cell": "",
"keywords": "металлические двери"
},
{
"name": "Система видеонаблюдения",
"var": "physicalVideoSurveillance",
"cell": "",
"keywords": "система видеонаблюдения"
},
{
"name": "Видеодомофон",
"var": "physicalVideoIntercom",
"cell": "",
"keywords": "видеодомофон"
},
{
"name": "Турникет на входе",
"var": "physicalTurnstile",
"cell": "",
"keywords": "турникет на входе"
},
{
"name": "Журнал учёта посетителей",
"var": "physicalVisitorLog",
"cell": "",
"keywords": "журнал учета посетителей|журнал учёта посетителей"
}
],
"criterion_fields": [
{
"num": "1",
"name": "Максимальное число пострадавших людей",
"keywords": "пострадавш",
"unit": "человек"
},
{
"num": "2б",
"name": "Люди с нарушенными условиями жизнедеятельности",
"keywords": "условия жизнедеятельности",
"unit": "тыс. человек"
},
{
"num": "3б",
"name": "Люди, для которых недоступны транспортные услуги",
"keywords": "транспортн",
"unit": "тыс. человек"
},
{
"num": "4а",
"name": "Абоненты, для которых недоступны услуги связи",
"keywords": "абонент",
"unit": "тыс. человек"
},
{
"num": "5а",
"name": "Максимальное время недоступности государственной услуги",
"keywords": "недоступна государственная услуга|недоступности государственной",
"unit": "часов"
},
{
"num": "5б",
"name": "Время, в течение которого госуслуга не может быть оказана",
"keywords": "регламентом|административным регламентом",
"unit": "%"
},
{
"num": "11б",
"name": "Люди, подверженные вредным воздействиям на окружающую среду",
"keywords": "вредным воздействиям",
"unit": "тыс. человек"
},
{
"num": "13а",
"name": "Снижение объёма продукции ГОЗ",
"keywords": "объем продукции|объём продукции",
"unit": "%"
},
{
"num": "13б",
"name": "Увеличение времени изготовления единицы продукции ГОЗ",
"keywords": "время изготовления",
"unit": "%"
},
{
"num": "14",
"name": "Максимальное время недоступности ИС (оборона)",
"keywords": "недоступна пользователю",
"unit": "часов"
},
{
"num": "2а",
"name": "Территория нарушения жизнеобеспечения населения",
"keywords": "территория.*жизнедеятельности|жизнедеятельности населения",
"unit": "вариант"
},
{
"num": "3а",
"name": "Территория нарушения транспортного сообщения",
"keywords": "территория.*транспорт",
"unit": "вариант"
},
{
"num": "3в",
"name": "Типы нарушенных перевозок и грузов",
"keywords": "типам нарушенных перевозок|типы нарушенных перевозок",
"unit": "вариант"
},
{
"num": "3г",
"name": "Категория объекта транспортной инфраструктуры",
"keywords": "категорированию объектов транспортной|категорию объекта транспортной",
"unit": "вариант"
},
{
"num": "4б",
"name": "Уровень органа власти, для которого недоступны услуги связи",
"keywords": "уровню органа государственной власти",
"unit": "вариант"
},
{
"num": "6",
"name": "Уровень государственного органа (невыполнение функции)",
"keywords": "функционирования государственного органа|государственного органа в части",
"unit": "вариант"
},
{
"num": "7",
"name": "Уровень международного договора",
"keywords": "международного договора",
"unit": "вариант"
},
{
"num": "11а",
"name": "Территория вредных воздействий на окружающую среду",
"keywords": "территория.*окружающую среду|окружающая среда может",
"unit": "вариант"
},
{
"num": "12",
"name": "Уровень пункта управления (ситуационного центра)",
"keywords": "пункта управления|ситуационного центра",
"unit": "вариант"
},
{
"num": "13.1",
"name": "Статус в кооперации головного исполнителя ГОЗ",
"keywords": "кооперации головного исполнителя|статусу в кооперации",
"unit": "вариант"
}
]
}
+9 -1
View File
@@ -67,6 +67,8 @@ DEFAULT_SETTINGS = {
_f('ОГРН', 'ogrn', keywords=r'огрн'),
_f('КПП', 'kpp', keywords=r'кпп'),
_f('Основной ОКВЭД', 'okved', keywords=r'основной оквэд'),
_f('Дополнительные ОКВЭД', 'okvedExtra', keywords=r'дополнительные оквэд'),
_f('Доменное имя и внешний сетевой адрес', 'domainName', keywords=r'доменное имя и внешний сетевой адрес'),
_f('Должность руководителя', 'chiefPosition', keywords=r'должность руководителя'),
_f('ФИО руководителя', 'chiefFio', keywords=r'фио руководителя'),
_f('Должность ответственного за ПДн', 'ispdnPosition', keywords=r'должность.*защит.*информ|должность.*администрат'),
@@ -117,7 +119,13 @@ def load_settings(path=None):
merged = dict(DEFAULT_SETTINGS)
for k, v in loaded.items():
if k == 'fields' and isinstance(v, list):
merged['fields'] = v
# Мержим поля по var: поля из файла + недостающие из дефолта
by_var = {f.get('var'): f for f in v}
for df in DEFAULT_SETTINGS.get('fields', []):
dv = df.get('var')
if dv and dv not in by_var:
by_var[dv] = df
merged['fields'] = list(by_var.values())
else:
merged[k] = v
return merged
+336 -168
View File
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
"""DokoGen — Модуль импорта из Excel (опросный лист 152-ФЗ)"""
"""DokoGen — Модуль импорта из Excel (опросный лист 187-ФЗ)"""
import re
import traceback
import warnings
from datetime import datetime, timedelta
from typing import Callable, Optional
@@ -96,13 +97,18 @@ def normalize_address(text: str) -> str:
return ''.join(out)
def clean_value(val):
"""Очистка и нормализация значения ячейки Excel."""
def clean_value(val, as_date=False):
"""Очистка и нормализация значения ячейки Excel.
as_date=True конвертировать серийный номер Excel в дату (только для
полей-дат, напр. ogrnDate). Для финансовых полей НЕ вызывается,
иначе число 50 000 руб. превратится в «дату» (см. аудит importer.py:105-109).
"""
if val is None:
return ''
if isinstance(val, (int, float)):
# Попытка распознать дату (серийный номер Excel)
if 10000 < val < 80000:
# Попытка распознать дату (серийный номер Excel) — только для полей-дат
if as_date and 10000 < val < 80000:
serial = int(val)
if serial > 60:
serial -= 1
@@ -122,8 +128,8 @@ def clean_value(val):
return text
def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path: Optional[str] = None) -> dict:
"""Импорт данных из опросного листа Excel (152-ФЗ).
def import_187fz(filepath: str, log_fn: Optional[Callable] = None, settings_path: Optional[str] = None) -> dict:
"""Импорт данных из опросного листа Excel (187-ФЗ).
settings_path путь к JSON-файлу настроек импорта (что и откуда брать).
Если None файл import_settings.json рядом с программой; если его нет
@@ -141,12 +147,17 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
data = {
'fullName': '', 'shortName': '', 'addressLegal': '', 'addressActual': '',
'email': '', 'phone': '', 'inn': '', 'ogrn': '', 'kpp': '',
'okved': '', 'chiefPosition': '', 'chiefFio': '',
'ispdnFio': '', 'ispdnPosition': '', 'paperDocuments': '', 'paperStorage': '',
'okved': '', 'okvedExtra': '', 'chiefPosition': '', 'chiefFio': '',
'ispdnFio': '', 'ispdnPosition': '',
'ispdnDepartment': '', 'ispdnEmail': '', 'ispdnPhone': '',
'informationSystems': [], 'commission': [], 'employeesAccess': [],
}
# openpyxl не поддерживает расширение Data Validation (выпадающие списки ячеек)
# и предупреждает об этом при каждом импорте — подавляем только это сообщение
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore', message='Data Validation extension is not supported')
try:
wb = openpyxl.load_workbook(filepath, data_only=True, read_only=False)
except Exception as e:
@@ -154,15 +165,30 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
return data
try:
_import_152_common(wb, data, settings, log_fn)
_import_152_is_list(wb, data, settings, log_fn)
_import_152_employees(wb, data, settings, log_fn)
_import_187_common(wb, data, settings, log_fn)
_import_187_is_list(wb, data, settings, log_fn)
_import_187_employees(wb, data, settings, log_fn)
except Exception as e:
log_fn(f"❌ Ошибка при импорте: {e}")
log_fn(traceback.format_exc())
finally:
wb.close()
# Ответы раздела «Физическая безопасность» (да/нет) — собираем в словарь,
# чтобы текст организационных мер генерировался с учётом реальных средств.
ps_keys = [
'physicalGuard', 'physicalFireAlarm', 'physicalPanicButton',
'physicalBars', 'physicalMetalDoors', 'physicalVideoSurveillance',
'physicalVideoIntercom', 'physicalTurnstile', 'physicalVisitorLog',
]
ps = {}
for k in ps_keys:
v = str(data.get(k) or '').strip()
if v:
ps[k] = v
if ps:
data['physicalSecurity'] = ps
# Копирование полей ИСПДн в админа, если админ не заполнен
for src, dst in [('ispdnFio', 'administratorFio'), ('ispdnFio', 'adminFio'),
('ispdnPosition', 'administratorPosition'), ('ispdnPosition', 'adminPosition'),
@@ -179,7 +205,28 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
return data
def _import_152_common(wb, data, settings, log_fn):
def _extract_single_fio(text):
"""Из строки, где может быть несколько ФИО (через запятую, точку с запятой,
перевод строки или просто подряд), выделяет одно первое полное ФИО.
Пример: «Коростелев Павел Сергеевич Хрящев Сергей Владимирович » «Коростелев Павел Сергеевич»
"""
if not text:
return text
text = str(text).strip()
# 1) Явные разделители — берём первую часть
for sep in (',', ';', '\n', '\r'):
if sep in text:
text = text.split(sep)[0].strip()
break
# 2) Если осталось больше 3 слов — это несколько ФИО подряд, оставляем первые 3
words = [w for w in text.split() if w]
if len(words) > 3:
text = ' '.join(words[:3])
return text.strip()
def _import_187_common(wb, data, settings, log_fn):
"""Импорт общих сведений по словарю настроек.
Для каждого поля: сначала пробуем конкретную ячейку (cell),
@@ -226,8 +273,10 @@ def _import_152_common(wb, data, settings, log_fn):
if sn.lower() == sheet_part.lower():
target_ws = wb[sn]
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 key in ('chiefFio', 'responsibleFio', 'ispdnFio', 'adminFio'):
val = _extract_single_fio(val)
data[key] = val
log_fn(f" {name} [ячейка {coord}]: {val}")
continue
@@ -242,13 +291,21 @@ def _import_152_common(wb, data, settings, log_fn):
for field_lower, val in row_cache.items():
if rx.search(field_lower):
if val:
if key in ('chiefFio', 'responsibleFio', 'ispdnFio', 'adminFio'):
val = _extract_single_fio(val)
elif 'дата' in name.lower() or 'date' in key.lower():
val = clean_value(val, as_date=True)
data[key] = val
log_fn(f" {name} [по ключу]: {val}")
break
def _import_152_is_list(wb, data, settings, log_fn):
"""Импорт информационных систем из остальных листов."""
def _import_187_is_list(wb, data, settings, log_fn):
"""Импорт объектов КИИ из остальных листов (ОБЪЕКТ №1, №2, ...).
Заполняет поля 236 приказа ФСТЭК: сфера, назначение, тип, архитектура,
сети электросвязи, ПО, СЗИ, сведения о субъекте.
"""
exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники'])
is_sheets = []
for sn in wb.sheetnames:
@@ -257,134 +314,42 @@ def _import_152_is_list(wb, data, settings, log_fn):
continue
is_sheets.append(sn)
if not is_sheets:
log_fn("⚠ Листы ИС не найдены")
log_fn("⚠ Листы объектов КИИ не найдены")
return
is_list = []
for sheet_name in is_sheets:
ws = wb[sheet_name]
rows = []
raw_rows = []
for row in ws.iter_rows(min_row=1, values_only=True):
raw_vals = list(row)
vals = [clean_value(c) for c in row]
field = (vals[0] or '').strip() if vals else ''
if not field:
continue
rows.append(vals)
raw_rows.append(raw_vals)
if len(rows) < 2:
continue
is_obj = {
'name': '', 'description': '', 'room': '', 'employee': '', 'position': '',
'personalDataList': [], 'pd_subjects_list': [], 'pd_actions': [],
'usersList': [], 'isInternet': False, 'personalDataCount': 'менее 100 000',
'defense_tools': [], 'processing_mode': '',
'name': '', 'description': '', 'address': '', 'usersList': [],
'isInternet': False, 'defense_tools': [], 'software': '',
# 236 приказ
'sphere': '', 'purpose': '', 'object_type': '', 'architecture': '',
'typical_object': '',
'subject_name': '', 'subject_address': '',
'subject_element': '',
'subject_inn': '', 'subject_kpp': '',
'net_category': '', 'net_operator': '', 'net_purpose': '', 'net_method': '',
'hw_sw': '', 'os_sw': '', 'app_sw': '',
'attacker_category': '', 'threats': '', 'incidents': '',
'criteria_values': '', 'criteria_justification': '',
'org_measures': '', 'tech_measures': '',
}
current_section = 'header'
pd_items_raw = []
subjects_raw = []
actions_raw = []
for vals in rows:
field = (vals[0] or '').strip()
field_lower = field.lower()
val_b = clean_value(vals[1]) if len(vals) > 1 else ''
is_checked = val_b.upper() in ('ДА', 'YES', '', '', '1', 'TRUE', 'ЕСТЬ')
if 'цель обработки пд' in field_lower or ('цель обработки' in field_lower and ':' in field):
current_section = 'purpose'
continue
if 'перечисленные пдн принадлежат' in field_lower or 'принадлеж' in field_lower:
current_section = 'subjects'
continue
if 'структура ис' in field_lower or 'структура информационной' in field_lower:
if val_b:
is_obj['structure'] = val_b
elif ':' in field:
# Значение может быть записано в той же ячейке после двоеточия
tail = field.split(':', 1)[1].strip()
if tail and 'выбрать' not in tail.lower():
is_obj['structure'] = tail
continue
if 'названия программ' in field_lower or ('программ' in field_lower and 'ос взаимодейств' in field_lower):
if val_b:
is_obj['software'] = val_b
continue
if 'локальную сеть' in field_lower:
is_obj['is_has_lan'] = is_checked
continue
if 'количество записей' in field_lower or 'количество субъект' in field_lower:
if val_b:
is_obj['personalDataCount'] = val_b
continue
if 'перечень действий с пд' in field_lower or ('действи' in field_lower and 'пд' in field_lower):
current_section = 'actions'
continue
if 'способ обработки пд' in field_lower:
if val_b:
is_obj['processing_mode'] = val_b
continue
if 'интернет' in field_lower:
is_obj['isInternet'] = is_checked
continue
if current_section == 'header':
if 'перечень пд' in field_lower:
current_section = 'pd_items'
elif 'название ис' in field_lower or 'наименование ис' in field_lower or 'наименование информационной' in field_lower:
is_obj['name'] = val_b
log_fn(f" ИС: {val_b}")
elif 'описание ис' in field_lower or 'описание информационной' in field_lower:
if val_b:
is_obj['description'] = val_b
log_fn(f" Описание: {val_b[:60]}")
continue
elif 'сотрудник' in field_lower and 'должност' not in field_lower:
is_obj['employee'] = val_b
if val_b:
parts = [p.strip() for p in val_b.replace('\n', ';').split(';') if p.strip()]
for part in parts:
cleaned = part.strip()
if ':' in cleaned and len(cleaned.split(':')[0]) < 20:
cleaned = cleaned.split(':', 1)[1].strip()
if cleaned and cleaned not in is_obj['usersList']:
is_obj['usersList'].append(cleaned)
elif 'должность сотрудника' in field_lower:
is_obj['position'] = val_b
elif 'наименование отдела' in field_lower:
pass
elif 'номер кабинета' in field_lower:
is_obj['room'] = val_b
elif 'значение' in field_lower or 'примечание' in field_lower:
continue
elif val_b and 'выбрать' not in field_lower:
if not is_obj['description']:
is_obj['description'] = f"{field}: {val_b}"
else:
is_obj['description'] += f"\n{field}: {val_b}"
elif current_section == 'pd_items':
if is_checked and 'выбрать' not in field_lower and field_lower != 'значение':
pd_items_raw.append(field)
if 'цель обработки' in field_lower:
current_section = 'purpose'
elif current_section == 'purpose':
purpose_val = val_b or field or ''
if purpose_val and 'выбрать' not in purpose_val.lower() and ':' not in purpose_val[:40]:
is_obj['purpose'] = purpose_val
log_fn(f" Цель: {purpose_val[:80]}")
current_section = 'header'
elif current_section == 'subjects':
if is_checked and 'выбрать' not in field_lower:
subjects_raw.append(field)
if 'количество записей' in field_lower or 'действи' in field_lower:
continue
elif current_section == 'actions':
if is_checked and 'выбрать' not in field_lower and 'защита' not in field_lower[:20]:
actions_raw.append(field)
if 'защита' in field_lower and 'действи' not in field_lower:
current_section = 'defense'
elif current_section == 'defense':
defense_keywords = {
'антивирус': 'Антивирус',
'крипто': 'СКЗИ',
@@ -395,59 +360,262 @@ def _import_152_is_list(wb, data, settings, log_fn):
'доверен.*загрузк': 'Доверенная загрузка',
'гарантирован': 'Доверенная загрузка',
}
matched_label = None
HINT_MARKERS = [
'формулировка должна', 'например', 'пример:', 'формат:', 'выберите из',
'если не используются', 'при необходимости можно', 'указать', 'вписать',
'если адресов несколько', 'по логике', 'произвольное описание',
]
def _is_hint(text):
"""True, если значение похоже на подсказку/образец из примечания."""
low = (text or '').lower()
for m in HINT_MARKERS:
if m in low:
return True
return False
def _set(field_lower, key, val):
"""Записать значение, если ключевое слово найдено в вопросе
и значение не является подсказкой из примечания."""
if val and 'выбрать' not in field_lower and not _is_hint(val):
is_obj[key] = val
return True
return False
def _clean_multiline(text):
"""Чистит многострочное значение, сохраняя переносы строк."""
if text is None:
return ''
s = str(text).replace('\r\n', '\n').replace('\r', '\n')
lines = [ln.strip() for ln in s.split('\n')]
return '\n'.join(ln for ln in lines if ln)
def _split_entries(text):
"""Разбивает список средств защиты на ОТДЕЛЬНЫЕ программы.
В опроснике каждая программа записана с новой строки (разделитель \n),
также поддерживаются ';' и ','. Название программы при этом НЕ режется
(напр. 'Kaspersky Endpoint Security' остаётся одним средством, KES KSC).
"""
if text is None:
return []
# 1) разбиваем по переносам строк и ';'
parts = re.split(r'[\n\r;]+', str(text))
out = []
for p in parts:
# 2) дополнительно по запятой (если список через запятую)
for sub in re.split(r'\s*,\s*', p):
sub = sub.strip()
if not sub:
continue
# 3) запасной вариант: слипшиеся без разделителя «15.1.0.22239Kaspersky»
sub_parts = re.split(r'(?<=[0-9.])(?=[А-ЯЁA-Z])', sub)
for s in sub_parts:
s = s.strip()
if not s:
continue
cleaned = clean_value(s)
if cleaned and cleaned not in out:
out.append(cleaned)
return out
for idx, vals in enumerate(rows):
field = (vals[0] or '').strip()
field_lower = field.lower()
raw_row = raw_rows[idx] if idx < len(raw_rows) else vals
raw_b = raw_row[1] if len(raw_row) > 1 else None # сырое значение (с переносами строк!)
val_b = clean_value(raw_b) if raw_b is not None else ''
is_checked = val_b.upper() in ('ДА', 'YES', '', '', '1', 'TRUE', 'ЕСТЬ')
# --- Наименование объекта ---
if 'наименование объекта' in field_lower or 'название объекта' in field_lower:
is_obj['name'] = val_b
log_fn(f" Объект КИИ: {val_b}")
continue
# --- Адрес размещения объекта (в отдельное поле, не в описание) ---
if 'адрес размещения' in field_lower or 'адрес расположения' in field_lower or 'местонахождение объекта' in field_lower:
is_obj['address'] = val_b
log_fn(f" Адрес объекта: {val_b[:60]}")
continue
# --- Описание ---
if 'описание окки' in field_lower or 'описание объекта' in field_lower:
if val_b:
is_obj['description'] = val_b
continue
# --- Сфера деятельности ---
if 'сфера' in field_lower and 'деятельност' in field_lower:
if _set(field_lower, 'sphere', val_b):
log_fn(f" Сфера: {val_b[:60]}")
continue
# --- Назначение объекта ---
if 'назначение объекта' in field_lower:
if _set(field_lower, 'purpose', val_b):
log_fn(f" Назначение: {val_b[:60]}")
continue
# --- Тип объекта ---
if 'тип объекта' in field_lower:
if _set(field_lower, 'object_type', val_b):
log_fn(f" Тип объекта: {val_b[:60]}")
continue
# --- Архитектура ---
if 'архитектура' in field_lower:
if _set(field_lower, 'architecture', val_b):
log_fn(f" Архитектура: {val_b[:60]}")
continue
# --- Типовой отраслевой объект ---
if 'типового отраслевого' in field_lower or 'типовой отраслевой' in field_lower:
if _set(field_lower, 'typical_object', val_b):
log_fn(f" Типовой объект: {val_b[:60]}")
continue
# --- Субъект ---
if 'наименование организации эксплуатирующ' in field_lower or ('наименование субъекта' in field_lower):
if _set(field_lower, 'subject_name', val_b):
log_fn(f" Субъект: {val_b[:60]}")
continue
if 'адрес местонахождения' in field_lower:
if _set(field_lower, 'subject_address', val_b):
log_fn(f" Адрес субъекта: {val_b[:60]}")
continue
if 'инн лица, эксплуатирующего' in field_lower or 'инн субъекта' in field_lower:
if _set(field_lower, 'subject_inn', val_b):
log_fn(f" ИНН субъекта: {val_b[:60]}")
continue
if 'кпп его обособленных' in field_lower or 'кпп субъекта' in field_lower:
if _set(field_lower, 'subject_kpp', val_b):
log_fn(f" КПП субъекта: {val_b[:60]}")
continue
if 'элемент (компонент) объекта' in field_lower or 'элемент объекта' in field_lower:
if _set(field_lower, 'subject_element', val_b):
log_fn(f" Элемент объекта: {val_b[:60]}")
continue
# --- Сети электросвязи ---
if 'категория сети электросвязи' in field_lower:
if _set(field_lower, 'net_category', val_b):
log_fn(f" Категория сети: {val_b[:60]}")
continue
if 'наименование провайдера' in field_lower or 'оператор связи' in field_lower:
if _set(field_lower, 'net_operator', val_b):
log_fn(f" Оператор: {val_b[:60]}")
continue
if 'цель взаимодействия' in field_lower:
if _set(field_lower, 'net_purpose', val_b):
log_fn(f" Цель взаимодействия: {val_b[:60]}")
continue
if 'способ взаимодействия' in field_lower:
if _set(field_lower, 'net_method', val_b):
log_fn(f" Способ взаимодействия: {val_b[:60]}")
continue
# --- ПО и аппаратные средства (сохраняем переносы строк) ---
if 'наименования программно-аппаратных средств' in field_lower:
if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b):
is_obj['hw_sw'] = _clean_multiline(raw_b)
log_fn(f" ПО/аппаратные средства: {str(raw_b)[:60]}")
continue
if 'операционная' in field_lower and 'система' in field_lower:
if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b):
is_obj['os_sw'] = _clean_multiline(raw_b)
log_fn(f" ОС: {str(raw_b)[:60]}")
continue
if 'прикладные программы' in field_lower:
if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b):
is_obj['app_sw'] = _clean_multiline(raw_b)
log_fn(f" Прикладные программы: {str(raw_b)[:60]}")
continue
if 'общесистемн' in field_lower:
if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b):
is_obj['os_sw'] = _clean_multiline(raw_b)
log_fn(f" Общесистемное ПО: {str(raw_b)[:60]}")
continue
# --- Безопасность / 236 приказ ---
if 'категория нарушителя' in field_lower:
if _set(field_lower, 'attacker_category', val_b):
log_fn(f" Категория нарушителя: {val_b[:60]}")
continue
if 'основные угрозы' in field_lower or 'угрозы безопасности' in field_lower:
if _set(field_lower, 'threats', val_b):
log_fn(f" Угрозы: {val_b[:60]}")
continue
if 'компьютерных инцидентов' in field_lower or 'инцидент' in field_lower:
if _set(field_lower, 'incidents', val_b):
log_fn(f" Инциденты: {val_b[:60]}")
continue
if 'показателей критериев' in field_lower or 'показатели критериев' in field_lower:
if _set(field_lower, 'criteria_values', val_b):
log_fn(f" Показатели: {val_b[:60]}")
continue
if 'обоснование полученных' in field_lower or 'обоснование значений' in field_lower:
if _set(field_lower, 'criteria_justification', val_b):
log_fn(f" Обоснование: {val_b[:60]}")
continue
if 'организационные меры' in field_lower:
if _set(field_lower, 'org_measures', val_b):
log_fn(f" Орг. меры: {val_b[:60]}")
continue
if 'технические меры' in field_lower:
if _set(field_lower, 'tech_measures', val_b):
log_fn(f" Тех. меры: {val_b[:60]}")
continue
# --- Показатели критериев значимости (ПП-127) → criterion_input ---
# Строки вида «Показатель 1. …» / «Показатель 2а. …» / «Показатель 13.1. …»
m_crit = re.match(r'^показател[ья]?\s*(\d+(?:\.\d+)?)([а-яА-Я]?)', field_lower)
if m_crit:
_num_crit = m_crit.group(1) + m_crit.group(2)
# нормализация: «10.1» → «10.1», «13.1» → «13.1», «2а» → «2а»
_num_crit = _num_crit.replace(' .', '.').strip()
if val_b and 'выбрать' not in field_lower and not _is_hint(val_b):
is_obj.setdefault('criterion_input', {})[_num_crit] = str(val_b).strip()
log_fn(f" Показатель {_num_crit}: {str(val_b)[:60]}")
continue
if 'интернет' in field_lower:
is_obj['isInternet'] = is_checked
continue
if 'сотрудник' in field_lower and 'должност' not in field_lower:
if val_b:
parts = [p.strip() for p in val_b.replace('\n', ';').split(';') if p.strip()]
for part in parts:
cleaned = part.strip()
if ':' in cleaned and len(cleaned.split(':')[0]) < 20:
cleaned = cleaned.split(':', 1)[1].strip()
if cleaned and cleaned not in is_obj['usersList']:
is_obj['usersList'].append(cleaned)
continue
# --- Средства защиты ---
matched = False
for kw, label in defense_keywords.items():
if re.search(kw, field_lower):
matched_label = label
if val_b and 'выбрать' not in field_lower:
for tool in _split_entries(raw_b if raw_b is not None else val_b):
if tool not in is_obj['defense_tools']:
is_obj['defense_tools'].append(tool)
matched = True
break
if matched_label and val_b and 'выбрать' not in field_lower:
# Без префикса категории: просто название средства (Kaspersky, КриптоПро)
is_obj['defense_tools'].append(val_b)
elif matched_label:
pass
elif 'куда' in field_lower or 'передаются' in field_lower:
current_section = 'header'
elif 'способ обработки' in field_lower or 'локальную сеть' in field_lower or 'интернет' in field_lower:
current_section = 'header'
if pd_items_raw:
is_obj['personalDataList'] = pd_items_raw
log_fn(f" ПДн: {len(pd_items_raw)}")
if subjects_raw:
is_obj['pd_subjects_list'] = subjects_raw
log_fn(f" Субъектов: {len(subjects_raw)}")
if actions_raw:
is_obj['pd_actions'] = actions_raw
log_fn(f" Действий: {len(actions_raw)}")
if matched:
continue
if val_b and 'выбрать' not in field_lower and field_lower not in ('значение', 'примечание'):
# Балансовая стоимость объекта КИИ — в отдельное поле
if 'балансовая стоимость' in field_lower or 'стоимость объекта' in field_lower:
is_obj['balanceValue'] = val_b
continue
# Остальные заполненные поля — в описание (адрес и т.д.)
if not is_obj['description']:
is_obj['description'] = f"{field}: {val_b}"
else:
is_obj['description'] += f"\n{field}: {val_b}"
if not is_obj['name']:
is_obj['name'] = sheet_name.strip()
log_fn(f"ИС без названия — использовано имя листа: {is_obj['name']}")
# Автоопределение категорий ПДн
pd_cats = []
all_pd_text = ' '.join(p.lower() for p in pd_items_raw)
special_kw = ['состоян.*здоров', 'национальн', 'политическ', 'религиозн', 'философ', 'судимост', 'интимн']
if any(re.search(kw, all_pd_text) for kw in special_kw):
pd_cats.append('специальные')
bio_kw = ['биометрическ', 'изображен.*лиц', 'голос.*человек', 'папилляр', 'дактилоскоп', 'фото.*изображен']
if any(re.search(kw, all_pd_text) for kw in bio_kw):
pd_cats.append('биометрические')
if pd_items_raw:
pd_cats.append('иные')
if pd_cats:
is_obj['personalDataCategory'] = pd_cats
log_fn(f" Категории ПДн: {', '.join(pd_cats)}")
log_fn(f"Объект без названия — использовано имя листа: {is_obj['name']}")
is_list.append(is_obj)
log_fn(f"{is_obj['name']} — импортирована")
log_fn(f"{is_obj['name']} — импортирован")
if is_list:
data['informationSystems'] = is_list
log_fn(f"\n✅ Всего импортировано ИС: {len(is_list)}")
log_fn(f"\n✅ Всего импортировано объектов КИИ: {len(is_list)}")
def _import_152_employees(wb, data, settings, log_fn):
def _import_187_employees(wb, data, settings, log_fn):
"""Импорт комиссии и сотрудников из листа «Сотрудники»."""
sheet_name = None
sheet_kw = settings.get('employees_sheet_keyword', 'сотруд')
@@ -497,7 +665,7 @@ def _import_152_employees(wb, data, settings, log_fn):
if 'перечень сотрудников' in a_lower:
in_commission = False
in_employees = True
log_fn(" Секция: сотрудники с доступом к ПДн")
log_fn(" Секция: сотрудники с доступом к объектам КИИ")
continue
if a_lower in ('№ п/п', '', 'номер', ''):
continue
@@ -519,7 +687,7 @@ def _import_152_employees(wb, data, settings, log_fn):
commission.append({
'role': role,
'position': col_b,
'fio': col_c.replace('\n', ' '),
'fio': _extract_single_fio(col_c.replace('\n', ' ')),
})
if in_employees:
@@ -528,7 +696,7 @@ def _import_152_employees(wb, data, settings, log_fn):
continue
employee = {
'position': col_b,
'fio': fio.replace('\n', ' '),
'fio': _extract_single_fio(fio.replace('\n', ' ')),
}
if col_e:
employee['is_list'] = [s.strip() for s in col_e.split('\n') if s.strip()]
+143 -35
View File
@@ -1,13 +1,28 @@
# -*- coding: utf-8 -*-
"""DokoGen — Модели данных (только 152-ФЗ, только нужные поля)"""
"""DokoGen — Модели данных (только 187-ФЗ, только нужные поля)"""
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Any
def _clean_single_fio(value):
"""Оставить одно первое ФИО, если в строке их несколько."""
if not value:
return value
value = str(value).strip()
for sep in (',', ';', '\n', '\r'):
if sep in value:
value = value.split(sep)[0].strip()
break
words = [w for w in value.split() if w]
if len(words) > 3:
value = ' '.join(words[:3])
return value.strip()
@dataclass
class CommissionMember:
"""Член комиссии по ПДн"""
"""Член комиссии по категорированию объектов КИИ"""
role: str = "член комиссии"
position: str = ""
fio: str = ""
@@ -15,41 +30,66 @@ class CommissionMember:
@dataclass
class InformationSystem:
"""Информационная система (обработка ПДн)"""
"""Объект КИИ (вместо информационной системы 152-ФЗ)"""
name: str = ""
description: str = ""
is_local_network: bool = False
is_internet: bool = False
defence_level: str = ""
threat_type: str = "3"
category: str = ""
pd_list: str = ""
pd_count: str = "менее 100 000"
users: str = ""
defense_tools_list: List[str] = field(default_factory=list)
processing_modes: str = ""
subject_type: str = "работники организации"
pd_actions_list: List[str] = field(default_factory=list)
pd_subjects_list: List[str] = field(default_factory=list)
personal_data_list: List[str] = field(default_factory=list)
users_list: List[str] = field(default_factory=list)
personal_data_category: List[str] = field(default_factory=list)
purpose: str = ""
room: str = ""
structure: str = "" # Структура информационной системы
category: str = "" # Категория значимости объекта КИИ: 1 / 2 / 3 / без категории
users_list: List[str] = field(default_factory=list) # Пользователи ОКИИ
defense_tools_list: List[str] = field(default_factory=list) # Средства защиты
purpose: str = "" # Назначение объекта (п. 236)
structure: str = "" # Структура объекта КИИ
software: str = "" # Программное обеспечение
# ---- 236 приказ ФСТЭК: сведения об объекте ----
sphere: str = "" # Сфера (область) деятельности
object_type: str = "" # Тип объекта (ИС, АСУ, ИТКС)
architecture: str = "" # Архитектура объекта
typical_object: str = "" # Наименование типового отраслевого объекта КИИ
@dataclass
class PaperDocument:
"""Бумажный документ с ПДн"""
name: str = ""
storage: str = ""
# Субъект (организация, эксплуатирующая объект — как в опроснике)
subject_name: str = "" # Наименование организации, эксплуатирующей объект
subject_address: str = "" # Адрес местонахождения
subject_element: str = "" # Элемент (компонент) объекта, который эксплуатируется лицом
subject_inn: str = "" # ИНН лица, эксплуатирующего объект
subject_kpp: str = "" # КПП его обособленных подразделений
# Сети электросвязи
net_category: str = "" # Категория сети электросвязи
net_operator: str = "" # Оператор связи и (или) провайдер хостинга
net_purpose: str = "" # Цель взаимодействия с сетью электросвязи
net_method: str = "" # Способ взаимодействия с указанием типа доступа
# Программно-аппаратные средства
hw_sw: str = "" # Наименования программно-аппаратных средств и их количество
os_sw: str = "" # Общесистемное ПО (ОС, средства виртуализации)
app_sw: str = "" # Прикладные программы
# Балансовая стоимость объекта КИИ (опросник)
balance_value: str = "" # Балансовая стоимость объекта КИИ в рублях
# Безопасность (236 приказ)
attacker_category: str = "" # Категория нарушителя (внешний/внутренний)
attacker_level: str = "" # Уровень возможностей нарушителя (Н1-Н4, Методика ФСТЭК 05.02.2021)
attacker_types: str = "" # Виды нарушителей (\n — разделитель, п. 5.1.3 Методики)
threats: str = "" # Основные угрозы безопасности информации (\n — разделитель)
incidents: str = "" # Типы компьютерных инцидентов (\n — разделитель)
criteria_values: str = "" # Показатели критериев значимости (\n — разделитель)
criteria_justification: str = "" # Обоснование полученных значений
org_measures: str = "" # Организационные меры (\n — разделитель)
tech_measures: str = "" # Технические меры (\n — разделитель)
# Введённые значения показателей ПП-127 для авторасчёта: {num: значение}
# num — «1», «2а», «2б», …, «14»; значение — число (количественные)
# или выбранный вариант (качественные). Заполняется окном авторасчёта,
# импортом из опросника или вручную; используется в _build_categorization_indicators.
criterion_input: Dict[str, str] = field(default_factory=dict)
@dataclass
class Company:
"""Организация — основной объект (152-ФЗ)"""
"""Организация — основной объект (187-ФЗ)"""
# Полное название
full_name: str = ""
company_name_1: str = "" # именительный
@@ -75,6 +115,9 @@ class Company:
ogrn: str = ""
ogrn_date: str = ""
kpp: str = ""
okved: str = "" # Основной ОКВЭД
okved_extra: List[str] = field(default_factory=list) # Дополнительные ОКВЭД
domain_name: str = "" # Доменное имя и внешний сетевой адрес
# Должностные лица
chief_position: str = ""
@@ -88,15 +131,45 @@ class Company:
contract_number: str = ""
contract_date: str = ""
# Финансовые показатели организации (опросник)
profit: str = "" # Сумма прибыли за предыдущий год
profit_tax: str = "" # Сумма налога с прибыли за предыдущий год
# Показатель 8 (ущерб субъекту КИИ): доходы за 5 лет и суммарный ущерб
# В форме — одна строка «Доход за 5 лет (суммарно)»; по годам разбивает сам оператор.
income_5y: str = "" # Доход за 5 лет суммарно (руб.)
income_year_1: str = "" # Доход за 5 лет назад (руб.) — обратная совместимость
income_year_2: str = "" # Доход за 4 года назад (руб.)
income_year_3: str = "" # Доход за 3 года назад (руб.)
income_year_4: str = "" # Доход за 2 года назад (руб.)
income_year_5: str = "" # Доход за прошлый год (руб.)
income_loss: str = "" # Снижение уровня дохода (суммарный ущерб) от нарушения
# критических процессов (руб.) — ΔДоходов для п. 8
# Показатель 9: компоненты ΔБ (унификация автозаполнения и авторасчёта)
ndpi: str = "" # Снижение выплат от налога на добычу ПИ ΔНДПИ (руб.)
div: str = "" # Снижение выплат от дивидендов ΔДИВ (руб.)
vso: str = "" # Снижение выплат сторонним организациям ΔВСО (руб.)
ns: str = "" # Снижение сборов ΔНС (руб.)
# Показатель 10 (прекращение финансовых операций): операции за год (млн ед.)
ops_count: str = "" # Количество операций за прошедший год (млн ед.)
calendar_days: str = "" # Количество календарных дней в году (по умолчанию 365)
criterion10_value: str = "" # Значение показателя 10.1–10.7 (в своих единицах)
# Списки
commission: List[CommissionMember] = field(default_factory=list)
information_systems: List[InformationSystem] = field(default_factory=list)
paper_documents_list: List[PaperDocument] = field(default_factory=list)
employees_access: List[Dict[str, str]] = field(default_factory=list)
# Наименование структурного подразделения по безопасности
security_department: str = ""
# Ответы раздела «Физическая безопасность» опросника (да/нет):
# охрана, пожарная сигнализация, тревожная кнопка, решётки, металлические двери,
# видеонаблюдение, видеодомофон, турникет, журнал посетителей.
physical_security: Dict[str, str] = field(default_factory=dict)
def update_declensions(self, inflect_func):
"""Обновить склонения полного названия"""
if self.full_name:
@@ -126,24 +199,59 @@ class Company:
def from_dict(cls, data: Dict[str, Any]) -> "Company":
d = dict(data)
if "commission" in d and isinstance(d["commission"], list):
_cvalid = {f.name for f in CommissionMember.__dataclass_fields__.values()}
d["commission"] = [
CommissionMember(**c) if isinstance(c, dict) else c
CommissionMember(**{k: v for k, v in c.items() if k in _cvalid})
if isinstance(c, dict) else c
for c in d["commission"]
]
if "information_systems" in d and isinstance(d["information_systems"], list):
_ivalid = {f.name for f in InformationSystem.__dataclass_fields__.values()}
d["information_systems"] = [
InformationSystem(**isys) if isinstance(isys, dict) else isys
InformationSystem(**{k: v for k, v in isys.items() if k in _ivalid})
if isinstance(isys, dict) else isys
for isys in d["information_systems"]
]
if "paper_documents_list" in d and isinstance(d["paper_documents_list"], list):
d["paper_documents_list"] = [
PaperDocument(**p) if isinstance(p, dict) else p
for p in d["paper_documents_list"]
]
if "employees_access" in d and not isinstance(d["employees_access"], list):
d["employees_access"] = []
# Отбрасываем плейсхолдеры из старых опросников («Формат: Фамилия Имя Отчество») и пустые строки
if "employees_access" in d and isinstance(d["employees_access"], list):
clean = []
for e in d["employees_access"]:
if not isinstance(e, dict):
continue
fio = str(e.get("fio", "")).strip()
pos = str(e.get("position", "")).strip()
if fio.lower().startswith("формат:") or fio.lower().startswith("пример:"):
continue
if not fio and not pos:
continue
clean.append(e)
d["employees_access"] = clean
if "okved_extra" in d and not isinstance(d.get("okved_extra"), list):
d["okved_extra"] = []
valid = {f.name for f in cls.__dataclass_fields__.values()}
return cls(**{k: v for k, v in d.items() if k in valid})
obj = cls(**{k: v for k, v in d.items() if k in valid})
# Миграция: старые файлы с доходами по годам → суммарный доход за 5 лет
if not (getattr(obj, 'income_5y', '') or '').strip():
_sum = 0.0
_has = False
for i in range(1, 6):
v = str(getattr(obj, f'income_year_{i}', '') or '').strip()
if v:
try:
_sum += float(v.replace(' ', '').replace(',', '.').replace('\u00a0', ''))
_has = True
except Exception:
pass
if _has:
obj.income_5y = f"{_sum:,.0f}".replace(',', ' ')
# Если в поле ФИО несколько человек — оставить одного (старые опросники)
for fio_field in ('chief_fio', 'responsible_fio', 'admin_fio'):
val = getattr(obj, fio_field, '')
if val:
setattr(obj, fio_field, _clean_single_fio(val))
return obj
def get_folder_name(self) -> str:
if self.short_name:
+618
View File
@@ -0,0 +1,618 @@
# -*- coding: utf-8 -*-
"""DokoGen — автоматический подбор угроз безопасности информации (236 приказ).
Логика программы: по характеристикам объекта КИИ (тип, сеть, нарушитель)
подбирает актуальные угрозы из перечня БДУ ФСТЭК (threats_fstec.json).
Если перечень не скачан использует базовый threats.json.
Правила эвристические; при наличии методического документа (например,
«Методика оценки угроз безопасности информации» ФСТЭК) адаптируются под него.
"""
import os
import json
import re
def _ubi_num(text):
"""Код УБИ из строки → int: «УБИ.069 Название» → 69. None, если кода нет."""
m = re.search(r'уби\.?\s*(\d+)', str(text), re.IGNORECASE)
return int(m.group(1)) if m else None
def sort_threats_by_ubi(items):
"""Сортировка угроз по возрастанию кода УБИ (УБИ.001, УБИ.002, …).
Строки без кода УБИ уходят в конец (по алфавиту).
"""
def _key(t):
n = _ubi_num(t)
return (0, n) if n is not None else (1, str(t).lower())
return sorted(items, key=_key)
def load_threats(dict_dir=None):
"""Список угроз: сначала скачанный с ФСТЭК (с кодами УБИ), иначе базовый."""
if dict_dir is None:
dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries')
fstec = os.path.join(dict_dir, 'threats_fstec.json')
if os.path.exists(fstec):
try:
with open(fstec, encoding='utf-8') as f:
data = json.load(f)
if data:
return data
except Exception:
pass
base = os.path.join(dict_dir, 'threats.json')
try:
with open(base, encoding='utf-8') as f:
return json.load(f)
except Exception:
return []
# ---------------------------------------------------------------
# Ключевые слова (в названии угрозы) по признакам объекта
# ---------------------------------------------------------------
# Базовый набор — актуален для любого объекта КИИ
BASE_KEYWORDS = [
'вредоносн', 'несанкционированн', 'утечк', 'перехват', 'модификац',
'уничтожен', 'отказ в обслуживании', 'нсд', 'биос', 'пароль',
'криптографическ', 'шифрован', 'аутентификац',
# базовые угрозы локальных сетей / клиент-серверных архитектур
'доступ', 'данн', 'систем', 'конфигурац', 'целостност', 'информац',
'программн', 'пользовател', 'авторизац', 'сеанс', 'журнал', 'реестр',
'файл', 'память', 'буфер', 'ресурс', 'сервер', 'клиент', 'учётн',
'учетн', 'идентификац', 'привилег', 'средств', 'сетев', 'локальн',
]
# Технологии, которые по умолчанию НЕ используются на объекте.
# Если в характеристиках объекта есть явные упоминания — группа возвращается.
TECH_GROUPS = {
'виртуализац': ['виртуал', 'гипервизор', 'контейнер'],
'облако': ['облачн', 'облак'],
'мобильн': ['мобильн', 'смартфон', 'планшет'],
'грид': ['грид', 'суперкомпьютер'],
'большие данные': ['больших данных', 'хранилищ'],
'беспроводн': ['беспроводн', 'wi-fi', 'wifi'],
'smart-карты': ['smart-карт', 'смарт-карт'],
'машинное обучение': ['машинного обучения', 'искусственного интеллекта', 'нейросет'],
}
# Объект имеет подключение к сети Интернет / сеть электросвязи
INTERNET_KEYWORDS = [
'удалённ', 'удаленн', 'сетев', 'фишинг', 'ddos', 'отказ в обслуживании',
'веб', 'электронн', 'почт', 'беспроводн', 'wi-fi', 'wifi', 'vpn',
'маршрутизац', 'протокол', 'интернет',
]
# Объект изолирован (нет подключения к Интернету)
ISOLATED_KEYWORDS = [
'съёмн', 'съемн', 'физическ', 'локальн', 'инсайд', 'носител',
'биос', 'желез', 'аппаратн',
]
# АСУ / технологический процесс
ASU_KEYWORDS = [
'технологическ', 'асутп', 'промышленн', 'scada', 'датчик',
'исполнительн', 'технологическ', 'производств', 'процесс',
]
# ИТКС / сети связи
NETWORK_KEYWORDS = [
'сеть', 'канал', 'протокол', 'маршрутизац', 'связ', 'передач',
'коммутац', 'трафик',
]
# Внутренний нарушитель
INSIDER_KEYWORDS = [
'инсайд', 'сотрудник', 'привилегированн', 'администратор',
'внутренн', 'персонал',
]
# Внешний нарушитель
EXTERNAL_KEYWORDS = [
'внешн', 'удалённ', 'удаленн', 'дистанционн',
]
def _is_asu(isys) -> bool:
t = (getattr(isys, 'object_type', '') or '').lower()
return 'асу' in t or 'автоматизированн' in t or 'технологическ' in t
def _is_network(isys) -> bool:
t = (getattr(isys, 'object_type', '') or '').lower()
return 'телекоммуникац' in t or 'сеть' in t
def _has_internet(isys) -> bool:
"""Есть ли подключение к сети Интернет / внешним сетям."""
if getattr(isys, 'is_internet', False):
return True
desc = ((getattr(isys, 'description', '') or '') + ' ').lower()
operator = (getattr(isys, 'net_operator', '') or '').lower()
net_category = (getattr(isys, 'net_category', '') or '').lower()
# Явные признаки ОТСУТСТВИЯ внешней сети
if 'отсутств' in operator: # оператор «Отсутствует»
return False
if 'отсутств' in desc and ('интернет' in desc or 'подключ' in desc):
return False
if 'не требуется' in desc and ('доменн' in desc or 'интернет' in desc):
return False
if 'изолирован' in desc:
return False
# Сеть общего пользования — это и есть внешняя сеть (Интернет)
if 'общего пользован' in net_category:
return True
# Указан реальный оператор/провайдер — внешняя связь есть
if operator and operator not in ('нет', 'отсутствует', 'не требуется', '-'):
return True
# Консервативно: без явных признаков внешней сети считаем, что её нет
return False
def _is_insider(isys) -> bool:
a = (getattr(isys, 'attacker_category', '') or '').lower()
return 'внутрен' in a
def _score(threat_lower, keywords):
"""Сколько ключевых слов встретилось в названии угрозы."""
return sum(1 for kw in keywords if kw in threat_lower)
def suggest_threats_program(isys, threats=None, limit=None):
"""Подбор угроз по характеристикам объекта КИИ (инвертированная логика).
По умолчанию актуальны ВСЕ угрозы из перечня (БДУ ФСТЭК).
Исключаются только заведомо неактуальные для данного объекта:
- нет сети Интернет убираем угрозы удалённого доступа, веб, почты,
беспроводных сетей, DDoS, облачных и т.п.;
- объект типа АСУ убираем угрозы BIOS, грид-систем, виртуализации
(у технологического оборудования их, как правило, нет).
Такой подход даёт полный список, а не «15 самых похожих».
Возвращает список строк (с кодами УБИ, если они есть в перечне).
"""
if threats is None:
threats = load_threats()
if not threats:
return []
has_internet = _has_internet(isys)
is_asu = _is_asu(isys)
is_network = _is_network(isys)
# --- Технологии, которые явно НЕ используются на объекте ---
# Если в характеристиках объекта (описание, архитектура, ПО, сфера) нет
# упоминаний технологии — угрозы этой группы исключаем.
tech_context = ' '.join([
str(getattr(isys, 'description', '') or ''),
str(getattr(isys, 'architecture', '') or ''),
str(getattr(isys, 'hw_sw', '') or ''),
str(getattr(isys, 'os_sw', '') or ''),
str(getattr(isys, 'app_sw', '') or ''),
str(getattr(isys, 'object_type', '') or ''),
str(getattr(isys, 'sphere', '') or ''),
str(getattr(isys, 'net_category', '') or ''),
str(getattr(isys, 'typical_object', '') or ''),
]).lower()
# --- Слова-исключения (если встретились в названии угрозы — она неактуальна) ---
exclude = []
if not has_internet:
# Объект изолирован: угрозы, реализуемые только через внешние сети
exclude += [
'удалённ', 'удаленн', 'дистанционн', 'веб', 'электронн', 'почт',
'фишинг', 'беспроводн', 'wi-fi', 'wifi', 'интернет', 'ddos', 'vpn',
'облачн', 'грид', 'внешн',
]
if is_asu:
# АСУ: нет BIOS/виртуализации/браузера в привычном понимании
exclude += ['биос', 'грид', 'гипервизор', 'виртуальн', 'браузер']
else:
# Не АСУ — исключаем угрозы АСУ ТП / технологических процессов
exclude += ['технологическ', 'датчик', 'исполнительн', 'асутп', 'scada',
'промышленн', 'контроллер', 'числовым программным управлением',
'программируемых логических']
# Технологии, не упомянутые в характеристиках объекта → исключаем
for tech_name, kws in TECH_GROUPS.items():
if any(kw in tech_context for kw in kws):
continue # технология используется — угрозы оставляем
# Технология не упомянута — исключаем её угрозы
for kw in kws:
exclude.append(kw)
if not is_network and not is_asu:
# Обычная ИС: неактуальны чисто сетевые/телеком-угрозы без указания
# — оставляем всё, кроме специфики каналов связи, если их нет
pass
# --- Положительные ключевые слова: чем больше совпало — тем угроза актуальнее ---
pos = list(BASE_KEYWORDS)
if has_internet:
pos += INTERNET_KEYWORDS
else:
pos += ISOLATED_KEYWORDS
if is_asu:
pos += ASU_KEYWORDS
if is_network:
pos += NETWORK_KEYWORDS
if _is_insider(isys):
pos += INSIDER_KEYWORDS
else:
pos += EXTERNAL_KEYWORDS
# Убираем дубликаты
pos = list(dict.fromkeys(pos))
scored = []
for t in threats:
tl = str(t).lower()
if any(kw in tl for kw in exclude):
continue
sc = sum(1 for kw in pos if kw in tl)
scored.append((sc, t))
# Сортируем по возрастанию кода УБИ (УБИ.001 → УБИ.211…)
scored = [t for sc, t in scored if sc > 0]
result = sort_threats_by_ubi(scored)
if limit and len(result) > limit:
result = result[:limit]
return result
# ---------------------------------------------------------------
# Автозаполнение вкладки «Безопасность» по данным объекта (логическое)
# ---------------------------------------------------------------
# Ключевые слова: угроза → тип инцидента (словарь incidents.json)
INCIDENT_RULES = [
('отказ в обслуживании', 'отказ в обслуживании'),
('несанкционированн', 'несанкционированный доступ'),
('утечк', 'утечка данных (нарушение конфиденциальности)'),
('модификац', 'модификация (подмена) данных'),
('подмен', 'модификация (подмена) данных'),
('функционирован', 'нарушение функционирования технических средств'),
('техническ', 'нарушение функционирования технических средств'),
('использован', 'несанкционированное использование вычислительных ресурсов объекта'),
]
# Базовый набор орг. мер (всегда актуальны для значимого объекта)
BASE_ORG_MEASURES = [
'Установление контролируемой зоны',
'Контроль физического доступа к объекту',
'Разработка документов (регламентов, инструкций, руководств) по обеспечению безопасности объекта',
'Назначение ответственных за обеспечение безопасности объекта',
'Обучение и инструктаж персонала',
'Регламентация действий при компьютерных инцидентах',
'Утверждение перечня лиц, имеющих доступ к объекту',
]
# Базовый набор тех. мер — только универсальные, не требующие конкретных СЗИ.
# Меры, зависящие от средств защиты (антивирус, СЗИ от НСД, регистрация событий
# и т.п.), добавляются отдельно по факту наличия СЗИ (DEFENSE_TOOL_MEASURES).
BASE_TECH_MEASURES = [
'Идентификация и аутентификация пользователей',
'Управление доступом',
'Ограничение программной среды',
'Резервное копирование и восстановление',
'Защита технических средств и линий связи',
]
# Средства защиты → доп. тех. меры (по факту наличия СЗИ у объекта)
DEFENSE_TOOL_MEASURES = [
('межсетев', 'Межсетевое экранирование'),
('firewall', 'Межсетевое экранирование'),
('сетев', 'Межсетевое экранирование'),
('обнаруж', 'Обнаружение вторжений (СОВ)'),
('ids', 'Обнаружение вторжений (СОВ)'),
('ips', 'Обнаружение вторжений (СОВ)'),
('криптограф', 'Криптографическая защита информации (СКЗИ)'),
('vipnet', 'Криптографическая защита информации (СКЗИ)'),
('континент', 'Криптографическая защита информации (СКЗИ)'),
('виртуализ', 'Защита среды виртуализации'),
('vmware', 'Защита среды виртуализации'),
('hyper-v', 'Защита среды виртуализации'),
('антивирус', 'Антивирусная защита'),
('kaspersky', 'Антивирусная защита'),
('eset', 'Антивирусная защита'),
('dr.web', 'Антивирусная защита'),
('аваст', 'Антивирусная защита'),
('avast', 'Антивирусная защита'),
('сзи', 'Защита информации от несанкционированного доступа (СЗИ от НСД)'),
('от нсд', 'Защита информации от несанкционированного доступа (СЗИ от НСД)'),
('dallas', 'Защита информации от несанкционированного доступа (СЗИ от НСД)'),
('secret net', 'Защита информации от несанкционированного доступа (СЗИ от НСД)'),
('аккорд', 'Защита информации от несанкционированного доступа (СЗИ от НСД)'),
('siem', 'Регистрация и мониторинг событий безопасности'),
('dlp', 'Регистрация и мониторинг событий безопасности'),
('мониторинг', 'Регистрация и мониторинг событий безопасности'),
('регистрац', 'Регистрация и мониторинг событий безопасности'),
('доверенная загрузка', 'Контроль (доверенная) загрузки'),
('резервн', 'Резервное копирование и восстановление'),
]
def auto_fill_security(isys, threats=None):
"""Логическое автозаполнение вкладки «Безопасность» по данным объекта КИИ.
Заполняет (только если поле пустое):
attacker_category по наличию интернета (внешний/внутренний нарушитель);
attacker_level Н1/Н2 (внутренний базовые, внешний базовые повышенные);
attacker_types виды нарушителей по категории/интернету;
threats подбор программой (скоринг по характеристикам);
incidents типы инцидентов, выводимые из подобранных угроз;
org_measures / tech_measures базовый набор + по СЗИ объекта.
Возвращает dict {поле: новое_значение} только для пустых полей.
"""
from .models import InformationSystem
res = {}
has_internet = _has_internet(isys)
# 1. Категория нарушителя
if not (getattr(isys, 'attacker_category', '') or '').strip():
res['attacker_category'] = 'Внешний нарушитель' if has_internet else 'Внутренний нарушитель'
# 2. Уровень возможностей
if not (getattr(isys, 'attacker_level', '') or '').strip():
res['attacker_level'] = 'Н2 — Базовые повышенные возможности' if has_internet else 'Н1 — Базовые возможности'
# 3. Виды нарушителей
if not (getattr(isys, 'attacker_types', '') or '').strip():
if has_internet:
types = [
'Отдельные физические лица (хакеры)',
'Преступные группы (криминальные структуры)',
'Конкурирующие организации',
'Авторизованные пользователи систем и сетей',
'Системные администраторы и администраторы безопасности',
'Бывшие (уволенные) работники (пользователи)',
]
else:
types = [
'Авторизованные пользователи систем и сетей',
'Системные администраторы и администраторы безопасности',
'Бывшие (уволенные) работники (пользователи)',
'Лица, обеспечивающие функционирование систем и сетей или обеспечивающих систем оператора (администрация, охрана, уборщики и др.)',
]
res['attacker_types'] = '\n'.join(types)
# 4. Угрозы — подбор программой
if not (getattr(isys, 'threats', '') or '').strip():
prog = suggest_threats_program(isys, threats=threats)
if prog:
res['threats'] = '\n'.join(prog)
# 5. Инциденты — из подобранных угроз
if not (getattr(isys, 'incidents', '') or '').strip():
threats_text = res.get('threats') or (getattr(isys, 'threats', '') or '')
if threats_text:
incidents = []
low = threats_text.lower()
for kw, incident in INCIDENT_RULES:
if kw in low and incident not in incidents:
incidents.append(incident)
if incidents:
res['incidents'] = '\n'.join(incidents)
# 6. Орг. меры — базовый набор
if not (getattr(isys, 'org_measures', '') or '').strip():
res['org_measures'] = '\n'.join(BASE_ORG_MEASURES)
# 7. Тех. меры — базовые + по СЗИ объекта.
# Только если объекту присвоена категория значимости (1/2/3):
# для незначимого объекта технические меры не требуются.
if not (getattr(isys, 'tech_measures', '') or '').strip():
cat = (getattr(isys, 'category', '') or '').strip()
has_category = cat in ('1', '2', '3')
if has_category:
tech = list(BASE_TECH_MEASURES)
defense = ' '.join(getattr(isys, 'defense_tools_list', []) or [])
dlow = defense.lower()
for kw, measure in DEFENSE_TOOL_MEASURES:
if kw in dlow and measure not in tech:
tech.append(measure)
res['tech_measures'] = '\n'.join(tech)
# else: категория не присвоена — технические меры не заполняем
return res
# ---------------------------------------------------------------
# Подбор типового отраслевого объекта КИИ по характеристикам объекта
# ---------------------------------------------------------------
# Стоп-слова: не несут смысла при сопоставлении названия объекта и типового объекта
STOPWORDS = {
'информационные', 'информационно', 'телекоммуникационные', 'автоматизированные',
'системы', 'система', 'сети', 'сеть', 'обеспечивающие', 'обеспечивающих',
'предназначенные', 'предназначенных', 'управление', 'контроль', 'мониторинг',
'объекта', 'объектов', 'объект', 'объекты', 'кии', 'рф', 'российской',
'федерации', 'исполнительной', 'власти', 'органов', 'организаций', 'организации',
'подведомственных', 'функций', 'функции', 'осуществление', 'полномочий',
'обеспечение', 'процессов', 'процесса', 'процессы', 'данных', 'информации',
'сбор', 'хранение', 'обработка', 'представление', 'использование', 'передача',
'автоматизации', 'средств', 'программных', 'программно', 'аппаратных',
'технических', 'деятельность', 'деятельности', 'осуществляющих',
'размещенные', 'размещенных', 'расположенных', 'используемых', 'используемые',
'проведении', 'проведения', 'выполнении', 'выполнения', 'реализация', 'реализации',
'электронных', 'электронного', 'информационных', 'информационного',
# Общие словоформы, встречающиеся почти у всех типовых объектов:
'контроль', 'контроля', 'контролю', 'контролем', 'контроле',
'управление', 'управления', 'управлению', 'управлением', 'управлении',
'безопасность', 'безопасности', 'безопасностью', 'безопасном',
'обеспечение', 'обеспечения', 'обеспечению', 'обеспечением',
'предназначен', 'предназначена', 'предназначено', 'предназначены',
'предоставление', 'предоставления', 'предоставлению',
'взаимодействие', 'взаимодействия', 'взаимодействию',
'функционирование', 'функционирования', 'функционированию',
'деятельность', 'деятельности', 'деятельностью',
'автоматизирован', 'автоматизированной', 'автоматизированных',
'телекоммуникацион', 'телекоммуникационной', 'телекоммуникационных',
'информационн', 'информационной', 'информационных', 'информационное',
'осуществление', 'осуществления', 'осуществлению',
'для', 'систем', 'системы', 'система', 'ресурсов', 'ресурсы',
'целью', 'цели', 'помощи', 'помощь', 'также', 'или', 'иных', 'иное',
'при', 'на', 'по', 'в', 'с', 'и', 'не', 'то', 'числе',
}
def _tokenize(text):
"""Значимые слова текста (без стоп-слов)."""
import re as _re
words = _re.findall(r'[а-яёa-z0-9]+', (text or '').lower())
return [w for w in words if len(w) > 2 and w not in STOPWORDS]
def _load_all_typical(dict_dir=None):
"""Все типовые отраслевые объекты из typical_industry_objects.json (по сферам)."""
import os as _os
if dict_dir is None:
dict_dir = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), 'dictionaries')
path = _os.path.join(dict_dir, 'typical_industry_objects.json')
try:
with open(path, encoding='utf-8') as f:
data = json.load(f)
except Exception:
return []
out = []
for sphere, items in data.items():
if sphere == 'Иная сфера деятельности':
continue
for it in items:
out.append({'sphere': sphere, 'num': it.get('num', ''),
'name': it.get('name', '')})
return out
_IDF_CACHE = None
def _idf_weights(cands):
"""IDF-веса слов: чем реже слово в перечне типовых объектов, тем больше вес.
Общие слова («информационные», «управление», «контроль») встречаются почти
у всех 392 объектов и почти ничего не значат; редкие («доступом», «СКУД»,
«сигнализации») сильно различают объекты.
"""
global _IDF_CACHE
if _IDF_CACHE is not None:
return _IDF_CACHE
import math
df = {}
for c in cands:
for w in set(_tokenize(c['name'])):
df[w] = df.get(w, 0) + 1
n = max(len(cands), 1)
weights = {w: math.log((n + 1) / (df.get(w, 0) + 1)) + 1.0 for w in df}
_IDF_CACHE = weights
return weights
def suggest_typical_object_program(isys, limit=10, sphere=None):
"""Подбор типового отраслевого объекта КИИ по характеристикам объекта.
Логика: значимые слова из названия/назначения/описания объекта КИИ
сопоставляются со значимыми словами названий типовых объектов
(перечень Распоряжения ПРФ 360-р, 392 объекта). Скоринг по IDF:
редкие слова (например, «доступом», «сигнализации») весят больше общих.
Пример: объект «СКУД» / «контроль и управление доступом»
«ИС, АСУ, ИТКС, обеспечивающие контроль и управление доступом на территорию».
Возвращает список dict: {sphere, num, name, score} по убыванию релевантности.
"""
text = ' '.join([
getattr(isys, 'name', '') or '',
getattr(isys, 'purpose', '') or '',
getattr(isys, 'description', '') or '',
])
obj_words = set(_tokenize(text))
if not obj_words:
return []
# Фильтр по сфере (важно: не подбирать ОПК-объекту сферу из здравоохранения):
# 1) сфера, переданная явно (выбрана в комбобоксе);
# 2) сфера, уже заполненная у объекта (isys.sphere);
# 3) автодетект по названию/назначению/описанию;
# 4) иначе — все сферы (подбор без фильтра).
used_sphere = sphere
if not used_sphere:
used_sphere = (getattr(isys, 'sphere', '') or '').strip() or None
if not used_sphere:
used_sphere = detect_sphere(isys)
cands = _load_all_typical()
if used_sphere:
cands = [c for c in cands if c['sphere'] == used_sphere]
if not cands:
# Сфера определена, но объектов нет — не расширяем на другие сферы,
# возвращаем пусто (оператор выберет сферу вручную)
return []
weights = _idf_weights(cands)
scored = []
for c in cands:
tw = set(_tokenize(c['name']))
common = tw & obj_words
if common:
score = sum(weights.get(w, 1.0) for w in common)
scored.append({'sphere': c['sphere'], 'num': c['num'],
'name': c['name'], 'score': round(score, 2),
'matched': sorted(common)})
scored.sort(key=lambda x: (-x['score'], x['name']))
return scored[:limit]
# ---------------------------------------------------------------
# Автоопределение сферы деятельности объекта по ключевым словам
# ---------------------------------------------------------------
SPHERE_KEYWORDS = [
('Здравоохранение', ['медицин', 'здрав', 'поликлиник', 'больниц', 'лечеб', 'клиник', 'фармацевт', 'аптек', 'пациент', 'врач', 'диагностик', 'лабораторн', 'медпомощ']),
('Наука', ['научн', 'исследован', 'разработк', 'лаборатор', 'академи', 'институт', 'ниокр', 'наука']),
('Транспорт', ['транспорт', 'перевоз', 'автомобил', 'железнодорожн', 'авиа', 'морск', 'речн', 'метрополитен', 'вокзал', 'логистик', 'движени']),
('Связь', ['связ', 'телекоммуникац', 'оператор связи', 'интернет-провайдер', 'передач данных', 'сети связи', 'телефон']),
('Энергетика', ['энерг', 'электро', 'теплоснабж', 'электроснабж', 'генерац', 'подстанц', 'котельн', 'энергосистем']),
('Государственная регистрация прав', ['регистрац', 'кадастр', 'недвижим', 'реестр']),
('Банковская сфера и финансовый рынок', ['банк', 'финанс', 'кредит', 'платеж', 'расчет', 'страхов', 'клирин', 'депозитар', 'пенсионн', 'фондов']),
('Топливно-энергетический комплекс', ['нефт', 'газ', 'топлив', 'трубопровод', 'месторожден', 'переработк', 'уголь', 'нефте']),
('Атомная энергетика', ['атомн', 'ядерн', 'радиацион', 'аэс', 'реактор', 'атом']),
('Оборонная промышленность', ['оборон', 'военн', 'вооружен', 'оружи', 'оборонно', 'опк', 'госзаказ', 'госконтракт', 'боеприпас', 'ввт', 'минобороны']),
('Ракетно-космическая промышленность', ['ракет', 'космическ', 'спутник', 'космодром', 'орбит', 'космос', 'пуск']),
('Горнодобывающая промышленность', ['горн', 'добыч', 'шахт', 'рудник', 'карьер', 'месторожден']),
('Металлургическая промышленность', ['металлург', 'прокат', 'стал', 'чугун', 'цветн', 'сплав']),
('Химическая промышленность', ['химическ', 'хим', 'нефтехим', 'взрывчат', 'кислот', 'реактив', 'удобрен']),
]
def detect_sphere(isys):
"""Определяет сферу деятельности объекта КИИ по названию/назначению/описанию.
Возвращает название сферы или None, если определить не удалось.
"""
text = ' '.join([
getattr(isys, 'name', '') or '',
getattr(isys, 'purpose', '') or '',
getattr(isys, 'description', '') or '',
]).lower()
best = None
best_score = 0
for sphere, keywords in SPHERE_KEYWORDS:
score = sum(1 for kw in keywords if kw in text)
if score > best_score:
best = sphere
best_score = score
return best if best_score > 0 else None
+3474 -396
View File
File diff suppressed because it is too large Load Diff
+752 -95
View File
@@ -19,7 +19,7 @@ def _norm_name(s):
# ОПИСАНИЕ ПЕРЕМЕННЫХ
# ============================================================
# Формат: (имя_переменной, группа, описание, ключ_резолвера)
# Группы: A — общие, B — 152-ФЗ
# Группы: A — общие, B — 187-ФЗ
VARIABLE_DEFS = [
# === Полное название организации (6 падежей) ===
@@ -57,10 +57,22 @@ VARIABLE_DEFS = [
('{{company_ogrn_date}}', 'A', 'Дата ОГРН', 'company:ogrn_date'),
('{{company_kpp}}', 'A', 'КПП', 'company:kpp'),
('{{security_department}}', 'A', 'Наименование структурного подразделения по безопасности', 'company:security_department'),
('{{item.pd_actions}}', 'B', 'Действия с ПДн (в цикле ИС)', 'item:pd_actions'),
('{{company_okved}}', 'A', 'Основной ОКВЭД', 'company:okved'),
('{{company_okved_extra}}', 'A', 'Дополнительные ОКВЭД', 'company:okved_extra'),
('{{company_profit}}', 'A', 'Сумма прибыли за предыдущий год (руб.)', 'company:profit'),
('{{company_profit_tax}}', 'A', 'Сумма налога с прибыли (руб.)', 'company:profit_tax'),
('{{company_income_5y}}', 'A', 'Доход за 5 лет суммарно (руб.) — показатель 8', 'company:income_5y'),
('{{company_income_year_1}}', 'A', 'Доход за 5 лет назад (руб.) — показатель 8 (обратная совместимость)', 'company:income_year_1'),
('{{company_income_year_2}}', 'A', 'Доход за 4 года назад (руб.) — показатель 8', 'company:income_year_2'),
('{{company_income_year_3}}', 'A', 'Доход за 3 года назад (руб.) — показатель 8', 'company:income_year_3'),
('{{company_income_year_4}}', 'A', 'Доход за 2 года назад (руб.) — показатель 8', 'company:income_year_4'),
('{{company_income_year_5}}', 'A', 'Доход за прошлый год (руб.) — показатель 8', 'company:income_year_5'),
('{{company_income_loss}}', 'A', 'Суммарный экономический ущерб от нарушения критических процессов (руб.) — показатель 8', 'company:income_loss'),
# === Комиссия ===
('{{commission_list}}', 'A', 'Список комиссии', 'commission_list'),
('{{fstek_district}}', 'A', 'Наименование управления ФСТЭК России (по адресу организации)', 'fstek_district'),
('{{fstek_recipient_address}}', 'A', 'Адрес управления ФСТЭК России для направления сведений', 'fstek_recipient_address'),
# === Специальные переменные ===
('{{date_year}}', 'A', 'Текущий год', 'date:year'),
@@ -68,23 +80,16 @@ VARIABLE_DEFS = [
('{{document_date}}', 'A', 'Дата документа', 'doc_date'),
('{{document_number}}', 'A', 'Номер документа', 'doc_number'),
# === 152-ФЗ ===
('{{is_security_requirements}}', 'B', 'Требования уровня защищённости', 'security_reqs'),
('{{is_list}}', 'B', 'Список всех ИС (нумерованный)', 'is_list'),
('{{is_name}}', 'B', 'Наименование первой ИС', 'is:name'),
('{{name}}', 'B', 'Название ИС (алиас)', 'is:name'),
('{{description}}', 'B', 'Описание ИС (алиас)', 'is:description'),
('{{category}}', 'B', 'Категория ИС (алиас)', 'is:category'),
# === 187-ФЗ ===
('{{is_security_requirements}}', 'B', 'Требования к защите объекта КИИ', 'security_reqs'),
('{{is_list}}', 'B', 'Список всех объектов КИИ (нумерованный)', 'is_list'),
('{{is_name}}', 'B', 'Наименование первого объекта КИИ', 'is:name'),
('{{name}}', 'B', 'Название объекта КИИ (алиас)', 'is:name'),
('{{description}}', 'B', 'Описание объекта КИИ (алиас)', 'is:description'),
('{{category}}', 'B', 'Категория значимости объекта КИИ (алиас)', 'is:category'),
('{{number}}', 'B', 'Номер (алиас)', 'doc_number'),
('{{pd_count}}', 'B', 'Количество записей ПДн (алиас)', 'is:pd_count'),
('{{pd_list}}', 'B', 'Список ПДн (алиас)', 'is:pd_list'),
('{{pd_subjects}}', 'B', 'Субъекты ПДн (алиас)', 'is:pd_subjects'),
('{{threat_type}}', 'B', 'Тип угроз (алиас)', 'is:threat_type'),
('{{users}}', 'B', 'Пользователи ИС (алиас)', 'is:users'),
('{{defence_level}}', 'B', 'Уровень защищённости (алиас)', 'is:defence_level'),
('{{users}}', 'B', 'Пользователи ОКИИ (алиас)', 'is:users'),
('{{full_name_genitive}}', 'A', 'Полное наименование (род.п., алиас)', 'decl:full_name:gent'),
('{{document}}', 'A', 'Название бумажного документа (алиас)', 'company:paper_documents'),
('{{storage}}', 'A', 'Место хранения (алиас)', 'company:paper_storage'),
('{{responsible_fio_accs}}', 'A', 'ФИО ответственного (вин.п.)', 'fio_acl:responsible'),
('{{commission1}}', 'A', 'Должность члена комиссии 1', 'commission_member:0:position'),
('{{commission1_fio}}', 'A', 'ФИО члена комиссии 1', 'commission_member:0:fio'),
@@ -138,7 +143,7 @@ def build_replacements(
is_list: List = None,
doc_number: str = "",
doc_date: str = "",
direction: str = "152fz",
direction: str = "187fz",
) -> Dict[str, str]:
"""Строит полный словарь замен {{переменная}} → значение."""
if is_list is None:
@@ -159,7 +164,7 @@ def build_replacements(
replacements = {}
for var_name, group, desc, source in VARIABLE_DEFS:
if direction == '152fz' and group not in ('A', 'B'):
if direction == '187fz' and group not in ('A', 'B'):
continue
value = _resolve(source, ctx)
replacements[var_name] = str(value) if value else ''
@@ -221,13 +226,22 @@ def _add_is_indexed_vars(replacements: Dict[str, str], isys, idx: int):
'is_address': getattr(isys, 'address', '') or '',
'is_description': isys.description or '',
'is_has_internet': 'да' if isys.is_internet else 'нет',
'is_defence_level': str(isys.defence_level or ''),
'is_category': ', '.join(isys.personal_data_category) or isys.category or '',
'is_pd_list': ', '.join(isys.personal_data_list or []) or '',
'is_category': isys.category or '',
'is_users': ', '.join(isys.users_list or []) or '',
'is_pd_count': str(isys.pd_count or ''),
'is_purpose': isys.purpose or '',
'is_structure': getattr(isys, 'structure', '') or '',
'is_software': getattr(isys, 'software', '') or '',
'is_sphere': getattr(isys, 'sphere', '') or '',
'is_object_type': getattr(isys, 'object_type', '') or '',
'is_architecture': getattr(isys, 'architecture', '') or '',
'is_typical_object': getattr(isys, 'typical_object', '') or '',
'is_net_category': getattr(isys, 'net_category', '') or '',
'is_net_operator': getattr(isys, 'net_operator', '') or '',
'is_net_purpose': getattr(isys, 'net_purpose', '') or '',
'is_net_method': getattr(isys, 'net_method', '') or '',
'is_hw_sw': getattr(isys, 'hw_sw', '') or '',
'is_os_sw': getattr(isys, 'os_sw', '') or '',
'is_app_sw': getattr(isys, 'app_sw', '') or '',
}
for f, v in fields.items():
replacements[f'{{{{{f}_{idx}}}}}'] = str(v)
@@ -295,68 +309,63 @@ def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]:
'address': getattr(isys, 'address', '') or '',
'is_has_lan': 'Да' if isys.is_local_network else 'Нет',
'has_internet': 'да' if isys.is_internet else 'нет',
'defence_level': str(isys.defence_level or ''),
'threat_type': str(isys.threat_type or '3'),
'category': ', '.join(isys.personal_data_category) or isys.category or '',
'pd_list': ', '.join(isys.personal_data_list or []) or '',
'pd_count': str(isys.pd_count or ''),
'category': isys.category or '',
'users': ', '.join(isys.users_list or []) or '',
'users_loop': users_loop,
'defense_tools_list': ', '.join(isys.defense_tools_list or []) or '',
'processing_modes': isys.processing_modes or '',
'pd_subjects': '; '.join(isys.pd_subjects_list or []) or '',
'is_purpose': isys.purpose or '',
'structure': getattr(isys, 'structure', '') or '',
'software': getattr(isys, 'software', '') or '',
'pd_actions': '; '.join(isys.pd_actions_list or []) or '',
# 236 приказ
'purpose': isys.purpose or '',
'sphere': getattr(isys, 'sphere', '') or '',
'object_type': getattr(isys, 'object_type', '') or '',
'architecture': getattr(isys, 'architecture', '') or '',
'typical_object': getattr(isys, 'typical_object', '') or '',
'subject_name': getattr(isys, 'subject_name', '') or '',
'subject_address': getattr(isys, 'subject_address', '') or '',
'subject_element': getattr(isys, 'subject_element', '') or '',
'subject_inn': getattr(isys, 'subject_inn', '') or '',
'subject_kpp': getattr(isys, 'subject_kpp', '') or '',
'net_category': getattr(isys, 'net_category', '') or '',
'net_operator': getattr(isys, 'net_operator', '') or '',
'net_purpose': getattr(isys, 'net_purpose', '') or '',
'net_method': getattr(isys, 'net_method', '') or '',
'hw_sw': getattr(isys, 'hw_sw', '') or '',
'os_sw': getattr(isys, 'os_sw', '') or '',
'app_sw': getattr(isys, 'app_sw', '') or '',
'balance_value': getattr(isys, 'balance_value', '') or '',
'attacker_category': getattr(isys, 'attacker_category', '') or '',
'attacker_level': getattr(isys, 'attacker_level', '') or '',
'attacker_types': getattr(isys, 'attacker_types', '') or '',
'threats': getattr(isys, 'threats', '') or '',
# Алиасы под шаблон «Сведения о результатах категорирования» (ФСТЭК)
'objective': getattr(isys, 'purpose', '') or '',
'standard_name': getattr(isys, 'typical_object', '') or '',
'network_category': getattr(isys, 'net_category', '') or '',
'provider': getattr(isys, 'net_operator', '') or '',
'interaction_purpose': getattr(isys, 'net_purpose', '') or '',
'interaction_method': getattr(isys, 'net_method', '') or '',
'hardware': getattr(isys, 'hw_sw', '') or '',
'system_software': getattr(isys, 'os_sw', '') or '',
'defense_tools': ', '.join(isys.defense_tools_list or []) or '',
'component': getattr(isys, 'subject_element', '') or '',
'exploiting_org': getattr(isys, 'subject_name', '') or '',
'exploiting_inn': getattr(isys, 'subject_inn', '') or '',
'domain': getattr(isys, 'domain_name', '') or '',
'threat_actor': getattr(isys, 'attacker_category', '') or '',
'threat_justification': getattr(isys, 'threats', '') or '',
'incidents': getattr(isys, 'incidents', '') or '',
'criteria_values': getattr(isys, 'criteria_values', '') or '',
'criteria_justification': getattr(isys, 'criteria_justification', '') or '',
# Орг. меры: если не заполнены вручную — автогенерация по опроснику
# (с учётом ответов раздела «Физическая безопасность»).
'org_measures': (getattr(isys, 'org_measures', '') or '')
or _build_org_measures_text(company, isys),
'tech_measures': getattr(isys, 'tech_measures', '') or '',
})
loops['information_systems'] = items
# Бумажные документы
def _storage_with_prep(storage: str) -> str:
"""«сейф» → «в сейфе» (предлог + предложный падеж).
Если уже начинается с предлога оставить как есть."""
if not storage:
return ''
s = storage.strip()
low = s.lower()
if low.startswith(('в ', 'на ', 'под ', 'за ', 'у ', 'при ', 'из ', 'со ', 'во ', 'над ', 'перед ')):
return s
words = s.split()
out = []
# Исключения предложного падежа: «в шкафу», «в углу» (не «в шкафе»)
loct_exceptions = {'шкаф': 'шкафу', 'угол': 'углу', 'край': 'краю', 'рот': 'рту', 'мост': 'мосту'}
for w in words:
core = w.strip('.,;')
if not core or core.isdigit() or core.startswith(''):
out.append(w)
continue
if core.lower() in loct_exceptions:
out.append(loct_exceptions[core.lower()])
continue
try:
p = dc.morph.parse(core)[0]
inf = p.inflect({'loct'})
if inf:
out.append(inf.word)
continue
except Exception:
pass
out.append(w)
return 'в ' + ' '.join(out)
paper_list = getattr(company, 'paper_documents_list', None) or []
if paper_list:
items = []
for p in paper_list:
storage = p.storage or ''
items.append({
'document': p.name or '',
'storage': _storage_with_prep(storage), # «в сейфе»
'storage_raw': storage, # «сейф» (как введено)
})
loops['paper_documents'] = items
# Сотрудники с доступом
employees = getattr(company, 'employees_access', None) or []
if employees:
@@ -368,13 +377,652 @@ 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 лет: основное поле income_5y (суммарно), иначе — обратная совместимость по годам
inc5 = _num(getattr(company, 'income_5y', '') or '')
if inc5:
r_avg = inc5 / 5.0
else:
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':
# Фолбэк: ущерб не заполнен → оценка «доход за год × 5» (наихудший сценарий)
d_estimated = False
if d is None and r_avg:
d = r_avg * 5
d_estimated = True
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} руб. ")
if d_estimated:
text += (f"Снижение уровня дохода не заполнено, поэтому оценка ущерба принята по наихудшему "
f"сценарию: доход за год × 5 лет = {r_avg:,.2f} × 5 = {d:,.2f} руб. ")
else:
text += (f"В случае выхода из строя объекта КИИ потребуется замена, установка и настройка, "
f"стоимость которых составит около {d:,.2f} руб. ")
text += (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 _calc_criterion_8_opk(company, isys):
"""Показатель 8 (ущерб субъекту КИИ) по отраслевым особенностям ОПК
(ПП РФ 796 от 27.06.2026, п. 1820):
U_8 = U_у8 / R_год × 100%,
U_у8 = C + A + X,
где C суммарные затраты на объект КИИ;
A затраты на возмещение ущерба потерпевшим и их имуществу (если инцидент
не влечёт ущерба, A = 0);
X ущерб от ранее произошедших компьютерных инцидентов (если инцидентов
не было или нет данных, X = 0);
R_год усреднённый объём годового дохода за прошедший 5-летний период.
Значения C/A/X берутся из criterion_input (ключи 8_c, 8_a, 8_x);
если C не заполнен используется балансовая стоимость объекта КИИ.
Возвращает (текст_обоснования, категория или None).
"""
crit = dict(getattr(isys, 'criterion_input', None) or {}) if isys else {}
c_val = _num(crit.get('8_c', '') or '') or _num(getattr(isys, 'balance_value', '') or '')
a_val = _num(crit.get('8_a', '') or '') or 0.0
x_val = _num(crit.get('8_x', '') or '') or 0.0
inc5 = _num(getattr(company, 'income_5y', '') or '')
if inc5:
r_avg = inc5 / 5.0
else:
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 c_val is None or not r_avg:
return ("Показатель применим (отраслевые особенности ОПК, ПП РФ № 796). "
"Для расчёта заполните суммарные затраты на объект КИИ (C) и доходы за 5 лет "
"(или балансовую стоимость объекта КИИ).", None)
u_y8 = c_val + a_val + x_val
val_pct = u_y8 / 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"Показатель 8 рассчитывается по отраслевым особенностям ОПК "
f"(ПП РФ № 796 от 27.06.2026): U_8 = U_у8 / R_год × 100%, U_у8 = C + A + X. "
f"Суммарные затраты на объект КИИ: C = {c_val:,.2f} руб. "
f"Затраты на возмещение ущерба потерпевшим: A = {a_val:,.2f} руб. "
f"Ущерб от ранее произошедших компьютерных инцидентов: X = {x_val:,.2f} руб. "
f"Ущерб субъекту КИИ: U_у8 = {c_val:,.2f} + {a_val:,.2f} + {x_val:,.2f} "
f"= {u_y8:,.2f} руб. "
f"Усреднённый годовой доход за 5-летний период: R_год = {r_avg:,.2f} руб. "
f"Показатель ущерба: U_8 = {u_y8:,.2f} / {r_avg:,.2f} × 100% = {val_pct:.6f} %.")
if cat:
text += (f" Так как {val_pct:.6f} % более 1% (пороговое значение для 3-й категории "
f"значимости), по данному показателю объекту присваивается {cat}-я категория значимости.")
else:
text += (f" Так как {val_pct:.6f} % менее 1% (пороговое значение для 3-й категории "
f"значимости), по данному показателю объекту категория значимости не присваивается.")
return text, cat
def _calc_criterion_13_opk(company, isys, num):
"""Показатели 13а/13б по отраслевым особенностям ОПК (ПП РФ № 796, п. 34, 38):
13а: V = (1 (V_max V_инц) / V_ГОЗ) × 100% (снижение объёма продукции ГОЗ)
13б: T = (1 (t_норм / t_ненорм)) × 100% (увеличение времени изготовления)
Значения берутся из criterion_input: 13a_vmax, 13a_vinc, 13a_vgoz;
13b_tnorm, 13b_tnonorm. Категория по порогам ПП-127 (CRITERION_QUANT_THRESHOLDS).
Возвращает (текст_обоснования, категория или None).
"""
crit = dict(getattr(isys, 'criterion_input', None) or {}) if isys else {}
num = str(num)
if num == '13а':
vmax = _num(crit.get('13a_vmax', '') or '')
vinc = _num(crit.get('13a_vinc', '') or '')
vgoz = _num(crit.get('13a_vgoz', '') or '')
if vmax is None or vinc is None or not vgoz:
return ("Показатель 13а рассчитывается по отраслевым особенностям ОПК "
"(ПП РФ № 796): V = (1 (V_max V_инц) / V_ГОЗ) × 100%. "
"Заполните V_max, V_инц, V_ГОЗ (объёмы продукции ГОЗ).", None)
val = (1 - (vmax - vinc) / vgoz) * 100
formula = (f"V = (1 ({vmax:,.2f} {vinc:,.2f}) / {vgoz:,.2f}) × 100% = {val:.6f} %")
else: # 13б
tnorm = _num(crit.get('13b_tnorm', '') or '')
tnon = _num(crit.get('13b_tnonorm', '') or '')
if tnorm is None or tnon is None or tnon <= 0:
return ("Показатель 13б рассчитывается по отраслевым особенностям ОПК "
"(ПП РФ № 796): T = (1 − (t_норм / t_ненорм)) × 100%. "
"Заполните t_норм и t_ненорм (время операций по ГОЗ).", None)
val = (1 - tnorm / tnon) * 100
formula = (f"T = (1 ({tnorm:,.2f} / {tnon:,.2f})) × 100% = {val:.6f} %")
cat = _criterion_other_cat(num, val)
text = (f"Показатель {num} рассчитывается по отраслевым особенностям ОПК "
f"(ПП РФ № 796 от 27.06.2026): {formula}.")
if cat:
text += (f" По порогам ПП-127 это соответствует {cat}-й категории значимости.")
else:
text += " По порогам ПП-127 категория значимости не присваивается."
return text, cat
# Пороги показателя 10 и подпоказателей 10.1–10.7 (ПП-127, «Значение показателя»)
CRITERION_10_THRESHOLDS = {
# num: (категория → (нижняя граница, верхняя граница)), границы «включительно»
# Показатель 10 — среднедневное количество операций (млн ед.):
# III — менее или равно 70, II — более 70 но менее или равно 120, I — более 120.
'10': {'I': (120, None), 'II': (70, 120), 'III': (None, 70)},
'10.1': {'I': (10, None), 'II': (1, 10), 'III': (None, 1)}, # трлн руб.
'10.2': {'I': (25, None), 'II': (10, 25), 'III': (None, 10)}, # тыс. шт.
'10.3': {'I': (2000, None), 'II': (1000, 2000), 'III': (50, 1000)}, # млрд руб.
'10.4': {'I': (5000, None), 'II': (1500, 5000), 'III': (100, 1500)}, # млрд руб.
'10.5': {'I': (150, None), 'II': (100, 150), 'III': (25, 100)}, # договоров
'10.6': {'I': (5000, None), 'II': (1000, 5000), 'III': (100, 1000)}, # млрд руб.
'10.7': {'I': (500, None), 'II': (200, 500), 'III': (30, 200)}, # млн ед.
}
def _criterion_10_cat(num, value):
"""Категория значимости по показателю 10 / 10.x (ПП-127).
value численное значение показателя. Возвращает 'I'/'II'/'III' или None.
"""
thr = CRITERION_10_THRESHOLDS.get(str(num))
if not thr or value is None:
return None
for cat in ('I', 'II', 'III'):
lo, hi = thr[cat]
if lo is not None and value < lo:
continue
if hi is not None and value > hi:
continue
return cat
return None
def _calc_criterion_10(company, isys, num):
"""Расчёт показателя 10 (прекращение финансовых операций) и подпоказателей 10.1–10.7.
Показатель 10: среднедневное количество операций = операции за год (млн ед.) / дни.
Подпоказатели 10.x: значение показателя (в своих единицах) сравнивается с порогами.
Возвращает (текст_обоснования, категория или None).
"""
num = str(num)
if num not in CRITERION_10_THRESHOLDS:
return None, None
if num == '10':
ops = _num(getattr(company, 'ops_count', '') or '')
days = _num(getattr(company, 'calendar_days', '') or '')
if days is None or days <= 0:
days = 365
if ops is None:
return ("Показатель применим. Для расчёта заполните данные организации: "
"«Количество операций за прошедший год (млн ед.)».", None)
val = ops / days
cat = _criterion_10_cat('10', val)
text = (f"Показатель прекращения финансовых операций оценивается среднедневным количеством "
f"операций, проведенных клиентами по банковским счетам и (или) без открытия банковского "
f"счета. Количество операций за прошедший год: {ops:,.2f} млн ед. Количество календарных "
f"дней в году: {days:.0f}. Среднедневное количество операций: {ops:,.2f} / {days:.0f} "
f"= {val:.6f} млн ед./день.")
else:
value = _num(getattr(company, 'criterion10_value', '') or '')
if value is None:
return (f"Показатель применим. Для расчёта заполните значение показателя "
f"«{num}» в данных организации.", None)
val = value
cat = _criterion_10_cat(num, val)
text = (f"Показатель {num} оценивается значением показателя: {val:,.6f} "
f"(в единицах, установленных ПП-127).")
if cat:
text += (f" Согласно Постановлению № 127 полученное значение соответствует "
f"{cat}-й категории значимости, по данному показателю объекту присваивается "
f"{cat}-я категория значимости.")
else:
text += (" Значение не достигает порога III категории — по данному показателю "
"категория значимости не присваивается.")
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г', '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):
"""Формирует текст организационных мер из словаря org_measures.json
с учётом ответов раздела «Физическая безопасность» опросника.
Если в опроснике указано «нет» (охрана, сигнализация, тревожная кнопка,
видеонаблюдение, металлические двери и т.п.) соответствующие пункты
из текста убираются. Пункты, не привязанные к средствам, остаются всегда.
"""
import os as _os
items = _load_json('org_measures.json')
if not isinstance(items, list):
items = []
ps = getattr(company, 'physical_security', None) or {}
def _no(*keys):
"""True, если хотя бы по одному ключу явно указано «нет»/«не имеется»."""
for k in keys:
v = str(ps.get(k) or '').strip().lower()
if v in ('нет', 'не имеется', 'нет,', '0', 'false'):
return True
return False
rules = [
# (подстрока пункта, ключи physical_security, при которых пункт УБИРАЕМ)
('Физическая охрана периметра', ('physicalGuard', 'physicalTurnstile', 'physicalVisitorLog')),
('Имеется система видеонаблюдения', ('physicalVideoSurveillance',)),
('Здания не имеют окон', ('physicalMetalDoors',)),
('охранно-пожарная сигнализация и тревожная кнопка', ('physicalFireAlarm', 'physicalPanicButton')),
('охранно-пожарная сигнализация', ('physicalFireAlarm',)),
('тревожная кнопка', ('physicalPanicButton',)),
('турникетом на проходной', ('physicalTurnstile',)),
('СКУД', ('physicalTurnstile',)),
('учет посетителей', ('physicalVisitorLog',)),
]
out = []
for line in items:
line = str(line).strip()
if not line:
continue
drop = False
for sub, keys in rules:
if sub in line and _no(*keys):
drop = True
break
if not drop:
out.append(line)
return '\n'.join(out) if out else ''
def _build_categorization_indicators(company, is_list):
"""Строки таблицы показателей критериев значимости для шаблона
«Сведения о результатах категорирования» (цикл categorization_indicators).
Для каждого показателя ПП-127 (5 групп, показатели 114 с подпунктами):
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 = []
first_cat_num = None # номер показателя, по которому объект отнесён к I категории (п. 6 ПП-127)
# Введённые значения показателей (из опросника/окна авторасчёта): {num: значение}
crit_in = dict(getattr(isys, 'criterion_input', None) or {}) if isys else {}
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 first_cat_num is not None:
# Остановка расчёта: объект уже отнесён к I категории по одному из показателей
# (п. 6 Правил, утв. ПП РФ № 127: «расчет по остальным показателям не проводится»).
if applicable:
value = ''
justification = (f"Расчёт по данному показателю не проводится: объект отнесён к "
f"I категории значимости по показателю {first_cat_num} "
f"(п. 6 Правил категорирования, утв. ПП РФ № 127).")
category = ''
else:
value = 'Показатель не применим.'
justification = (just.get(num) or just.get(grp_num)
or f"Показатель не применим к объекту КИИ организации сферы «{sphere or ''}».")
elif not applicable:
value = 'Показатель не применим.'
justification = (just.get(num) or just.get(grp_num)
or f"Показатель не применим к объекту КИИ организации сферы «{sphere or ''}».")
elif grp_num in ('8', '9'):
if grp_num == '8' and sphere == 'Оборонная промышленность':
# ОПК: U_8 = (C+A+X)/R_год×100% (ПП РФ № 796, п. 1820)
text, cat = _calc_criterion_8_opk(company, isys)
else:
text, cat = _calc_criterion_8_9(company, isys, grp_num)
if text:
value = text
if cat:
category = f"{cat}-я категория"
else:
category = ''
elif grp_num == '10':
text, cat = _calc_criterion_10(company, isys, num)
if text:
value = text
if cat:
category = f"{cat}-я категория"
else:
category = ''
elif num in ('13а', '13б') and sphere == 'Оборонная промышленность':
# ОПК: V = (1(V_maxV_инц)/V_ГОЗ)×100%, T = (1(t_норм/t_ненорм))×100%
# (ПП РФ № 796, п. 34, 38) — при наличии введённых составляющих
_opk_crit = dict(getattr(isys, 'criterion_input', None) or {}) if isys else {}
_opk_ready = bool(_opk_crit.get('13a_vmax', '') or _opk_crit.get('13a_vinc', '') or _opk_crit.get('13a_vgoz', '') or _opk_crit.get('13b_tnorm', '') or _opk_crit.get('13b_tnonorm', ''))
if _opk_ready:
text, cat = _calc_criterion_13_opk(company, isys, num)
if text:
value = text
if cat:
category = f"{cat}-я категория"
else:
category = ''
else:
value = ''
justification = (just.get(num) or just.get(grp_num)
or 'Показатель применим к объекту КИИ; значение определяется по данным объекта (для ОПК — по формулам ПП РФ № 796: V = (1(V_maxV_инц)/V_ГОЗ)×100%, T = (1(t_норм/t_ненорм))×100%).')
else:
# Применимый показатель без формульного авторасчёта (1–7, 11–14):
# если значение введено (опросник/окно авторасчёта) — считаем категорию по порогам
if num in crit_in and str(crit_in.get(num, '')).strip():
raw = str(crit_in.get(num, '')).strip()
# Число или выбранный вариант (качественный)
try:
_v = float(raw.replace(' ', '').replace(',', '.').replace('\u00a0', ''))
text, cat = _calc_criterion_other(company, isys, num, value=_v)
except ValueError:
text, cat = _calc_criterion_other(company, isys, num, choice=raw)
if text:
value = text
if cat:
category = f"{cat}-я категория"
else:
category = ''
else:
value = ''
justification = (just.get(num) or just.get(grp_num)
or 'Показатель применим к объекту КИИ; значение определяется по данным объекта.')
# Если по показателю присвоена I категория — фиксируем для остановки расчёта
# (точное совпадение «I-я категория», а не startswith('I'), иначе ловится II/III)
if first_cat_num is None and category.startswith('I-я'):
first_cat_num = num
rows.append({
'section': section,
'name': name,
'number': num,
'value': value,
'justification': justification,
'category': category,
})
return rows
# ============================================================
# РЕЗОЛВЕР ЗНАЧЕНИЙ
# ============================================================
def _find_fstek_office(address_text: str):
"""Определяет управление ФСТЭК России по адресу организации.
Ищет город/регион из адреса в словаре fstek_offices.json.
Возвращает dict {'name': ..., 'address': ...} или None.
"""
import json as _json
import os as _os
if not address_text:
return None
dict_path = _os.path.join(_os.path.dirname(__file__), 'dictionaries', 'fstek_offices.json')
try:
with open(dict_path, encoding='utf-8') as _f:
offices = _json.load(_f)
except Exception:
return None
hay = address_text.lower()
# Нормализация сокращений: обл.→область, респ.→республика, г.о.→городской округ
hay = hay.replace('обл.', 'область').replace('обл,', 'область')
hay = hay.replace('респ.', 'республика').replace('респ,', 'республика')
hay = hay.replace('г.о.', 'городской округ').replace('г.о,', 'городской округ')
hay = hay.replace('ао', 'автономный округ')
best = None
best_len = 0
for off in offices:
# Ищем по городам
for city in off.get('cities', []):
if city in hay and len(city) > best_len:
best = off
best_len = len(city)
# Ищем по регионам (область/край/республика)
for region in off.get('regions', []):
if region in hay and len(region) > best_len:
best = off
best_len = len(region)
return best
def _resolve(source: str, ctx: Dict) -> str:
"""Преобразует ключ-источник в значение, подставляя из контекста."""
company = ctx.get('company')
@@ -422,6 +1070,16 @@ def _resolve(source: str, ctx: Dict) -> str:
if source in commission_data:
return str(commission_data[source])
# Управление ФСТЭК (адресат отправки сведений о категорировании)
if source in ('fstek_recipient_address', 'fstek_district', 'fstek_office_name'):
addr = getattr(company, 'address', '') or ''
off = _find_fstek_office(addr)
if off:
if source == 'fstek_recipient_address':
return off.get('address', '') or ''
return off.get('name', '') or ''
return ''
# Даты
if source.startswith('date:'):
fmt = source.split(':', 1)[1]
@@ -448,11 +1106,6 @@ def _resolve(source: str, ctx: Dict) -> str:
'name': 'name',
'description': 'description',
'category': 'category',
'defence_level': 'defence_level',
'threat_type': 'threat_type',
'pd_list': lambda: ', '.join(first.personal_data_list or []),
'pd_count': 'pd_count',
'pd_subjects': lambda: '; '.join(first.pd_subjects_list or []),
'users': lambda: ', '.join(first.users_list or []),
}
if field in field_map:
@@ -501,21 +1154,25 @@ def _get_fio(company: Company, who: str) -> str:
def _get_security_text(ctx: Dict) -> str:
"""Возвращает текст требований для уровня защищённости."""
defence_level = ctx.get('extended_fields', {}).get('defence_level', '4')
"""Возвращает текст требований к защите объекта КИИ по категории значимости."""
is_list = ctx.get('is_list', [])
if not defence_level and is_list:
defence_level = str(getattr(is_list[0], 'defence_level', '4') or '4')
dl = str(defence_level).strip()[0]
category = ''
if is_list:
category = str(getattr(is_list[0], 'category', '') or '').strip()
TEXTS = {
'1': """Для обеспечения 1-го уровня защищённости...""",
'2': """Для обеспечения 2-го уровня защищённости...""",
'3': """Для обеспечения 3-го уровня защищённости...""",
'4': """Для обеспечения 4-го уровня защищённости персональных данных необходимо:
режим безопасности помещений;
сохранность носителей ПДн;
утверждение перечня лиц с доступом к ПДн;
использование сертифицированных СЗИ.""",
'1': """Для значимого объекта КИИ 1-й категории значимости необходимо:
создание системы безопасности значимого объекта КИИ;
обеспечение защиты информации от компьютерных атак;
категорирование объектов КИИ и контроль его результатов.""",
'2': """Для значимого объекта КИИ 2-й категории значимости необходимо:
создание системы безопасности значимого объекта КИИ;
обеспечение защиты информации от компьютерных атак.""",
'3': """Для значимого объекта КИИ 3-й категории значимости необходимо:
обеспечение защиты информации от компьютерных атак.""",
}
return TEXTS.get(dl, TEXTS['4'])
if category in TEXTS:
return TEXTS[category]
return """Для объекта КИИ, не являющегося значимым, необходимо:
принятие мер по защите информации, предусмотренных законодательством РФ о безопасности КИИ."""
+587 -3
View File
@@ -14,6 +14,18 @@
"""
import json
def _load_prompt(name):
"""Загружает промт из dictionaries/prompts/<name>.json (system/rules/response_format)."""
import os as _os
p = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)),
'dictionaries', 'prompts', name + '.json')
try:
with open(p, encoding='utf-8') as f:
return json.load(f)
except Exception:
return {}
import re
from datetime import datetime, timedelta
import urllib.parse
@@ -65,6 +77,17 @@ _ADDRESS_ABBR = {
'СТ': 'ст.', 'Ш': 'ш.', 'Р-Н': 'р-н.', 'Б-Р': 'б-р.', 'МКР': 'мкр.',
'НАБ': 'наб.', 'ТУП': 'туп.', 'ПР-Д': 'пр-д.', 'КРП': 'крп.',
'Г.': 'г.', 'УЛ.': 'ул.', 'Д.': 'д.', 'Р-Н.': 'р-н.',
'Г.О': 'г.о.', 'Г.О.': 'г.о.', 'ГО': 'г.о.', 'М.О': 'м.о.', 'М.О.': 'м.о.',
}
# Слова, которые в адресах пишутся со строчной буквы (кроме случаев,
# когда стоят первым словом после точки — «г. Электроугли» и т.п.)
_ADDRESS_LOWERCASE = {
'область', 'край', 'республика', 'округ', 'район', 'город', 'поселок',
'посёлок', 'деревня', 'село', 'станица', 'хутор', 'аул', 'улица',
'проспект', 'переулок', 'бульвар', 'шоссе', 'набережная', 'площадь',
'проезд', 'тупик', 'аллея', 'линия', 'дом', 'строение', 'корпус',
'квартира', 'офис', 'помещение', 'этаж', 'комната', 'участок',
}
@@ -105,7 +128,12 @@ def _normalize_text(text):
def normalize_address(text):
"""«Г Крымск» → «г. Крымск», «Р-Н» → «р-н.» и т.п."""
"""«Г Крымск» → «г. Крымск», «Р-Н» → «р-н.», «МОСКОВСКАЯ ОБЛАСТЬ» → «Московская область».
Приводит адрес к нормальному регистру: каждое слово с заглавной буквы,
адресные сокращения (г., ул., д., обл., г.о.) и административные термины
(область, район, город, дом и т.п.) со строчной.
"""
if not text:
return ''
parts = re.split(r'(\s+|[,\;])', text)
@@ -115,7 +143,70 @@ def normalize_address(text):
out.append(_ADDRESS_ABBR[p.strip().upper()])
else:
out.append(p)
return ''.join(out)
res = ''.join(out)
# КАПС → нормальный регистр по словам
res = _normalize_text(res)
# Адресные термины — со строчной буквы (сохраняя пунктуацию)
words = res.split(' ')
fixed = []
for w in words:
m = re.match(r'^([^\w]*)([\w\-]+)([^\w]*)$', w, re.UNICODE)
if m:
pre, core, post = m.group(1), m.group(2), m.group(3)
if core.lower() in _ADDRESS_LOWERCASE:
fixed.append(pre + core.lower() + post)
continue
fixed.append(w)
return ' '.join(fixed)
def resolve_okved(code, dadata_token=None):
"""Код ОКВЭД → «код название» (без дефиса), например «27.32.2 Производство силовых кабелей».
Принимает и голый код («27.32.2»), и строку с названием
(«27.32.2 - Производство силовых кабелей» / «27.32.2 Производство силовых кабелей»).
1) Ищем в словаре dictionaries/okved.json (строки вида «27.32.2 Название»).
2) Если не нашли запрашиваем DaData suggest okved2 (если есть токен).
3) Иначе возвращаем нормализованную строку «код название» как есть.
"""
code = (code or '').strip()
if not code:
return ''
# Извлекаем код из входной строки: «27.32.2 - Производство силовых кабелей» → «27.32.2»
m = re.match(r'^\s*(\d+(?:\.\d+)*)', code)
code_only = m.group(1) if m else code.split()[0].rstrip('.')
# 1) Словарь
try:
import os, json
dict_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries', 'okved.json')
with open(dict_path, encoding='utf-8') as f:
items = json.load(f)
for item in items:
item = str(item).strip()
# совпадение по коду: «27.32.2» или «27.32.2 Название»
if item.split()[0].rstrip('.') == code_only.rstrip('.'):
return item
except Exception:
pass
# 2) DaData suggest okved2
if dadata_token:
try:
js, err = _http_json('https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/okved2',
data={'query': code_only, 'count': 1},
headers={'Content-Type': 'application/json',
'Authorization': f'Token {dadata_token}'})
if not err and js:
sugg = (js.get('suggestions') or [])
if sugg and sugg[0].get('value'):
return sugg[0]['value']
except Exception:
pass
# 3) Нормализуем вход: «27.32.2 - Название» → «27.32.2 Название» (без дефиса)
rest = code[len(m.group(1)):].strip() if m else ''
rest = rest.lstrip('- ').strip()
if rest:
return f"{code_only} {rest}"
return code_only
def _fmt_date(value):
@@ -178,6 +269,38 @@ def check_declension_morpher(text):
# ============================================================
# DADATA — проверка организации по ИНН (нужен API-ключ)
# ============================================================
def get_company_finance_dadata(inn, token):
"""Доходы организации по годам через DaData (findById/party → finance/finance_history).
Возвращает (dict, ошибка): dict вида
{'finance': {...}, 'history': [{'year': 2023, 'income': 123456, ...}, ...]}
finance показатели за последний отчётный год;
history история по годам (если доступна в тарифе DaData).
"""
if not token:
return None, "Не задан API-ключ DaData (Файл → Настройки → API)"
inn = (inn or '').replace(' ', '')
if not inn or not inn.isdigit():
return None, "Введите ИНН для загрузки доходов"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Token {token}',
}
js, err = _http_json(DADATA_URL, data={'query': inn}, headers=headers)
if err:
return None, err
suggestions = (js or {}).get('suggestions') or []
if not suggestions:
return None, f"DaData: организация с ИНН {inn} не найдена"
d = suggestions[0].get('data') or {}
return {
'finance': d.get('finance') or {},
'history': d.get('finance_history') or [],
'inn': d.get('inn', inn),
'name': (d.get('name') or {}).get('short_with_opf') or suggestions[0].get('value', ''),
}, None
def check_company_dadata(inn, token):
"""Проверка организации по ИНН/ОГРН через DaData.
@@ -220,6 +343,21 @@ def check_company_dadata(inn, token):
chief_fio = _normalize_text(management.get('name', ''))
ogrn_date = _fmt_date(d.get('ogrn_date') or '')
# ОКВЭД: основной (okved) и дополнительные (okveds — список словарей с 'code')
okved = d.get('okved', '') or ''
okved_extra = []
raw_okveds = d.get('okveds') or []
if isinstance(raw_okveds, str):
# DaData иногда отдаёт доп. ОКВЭД одной строкой через запятую
raw_okveds = [x.strip() for x in raw_okveds.split(',') if x.strip()]
for ok in raw_okveds:
if isinstance(ok, dict):
code = (ok.get('code') or '').strip()
else:
code = str(ok or '').strip()
if code:
okved_extra.append(code)
return {
'source': 'DaData',
'source_url': 'https://dadata.ru',
@@ -236,6 +374,8 @@ def check_company_dadata(inn, token):
'chief_position': chief_position,
'chief_fio': chief_fio,
'inn': d.get('inn', inn),
'okved': okved,
'okved_extra': okved_extra,
},
'apply': {'company': {
'full_name': full_name,
@@ -246,6 +386,8 @@ def check_company_dadata(inn, token):
'ogrn_date': ogrn_date,
'chief_position': chief_position,
'chief_fio': chief_fio,
'okved': okved,
'okved_extra': okved_extra,
}},
}, None
@@ -389,6 +531,320 @@ def compare_company_values(old_data, new_data):
return diffs
def _ubi_code(text):
"""Извлекает код УБИ из строки: «УБИ.069 Название» → '069'."""
m = re.search(r'уби\.?\s*(\d+)', str(text), re.IGNORECASE)
return m.group(1) if m else None
def _norm_threat_text(text):
"""Нормализация названия угрозы для сравнения: без ведущего номера, лишних пробелов."""
s = re.sub(r'^\d+[.)]?\s*', '', str(text or '').strip())
s = re.sub(r'\s+', ' ', s).strip()
return s.lower()
def normalize_threats(text):
"""Склеить переносы внутри названий угроз.
В опросниках встречается «УБИ.209 Угроза памяти\r\nядра процессора»
перенос строки внутри одного названия. Строка, не начинающаяся с «УБИ.»,
считается продолжением предыдущей угрозы.
"""
if not text:
return ''
lines = [ln.strip() for ln in str(text).split('\n') if ln.strip()]
out = []
for ln in lines:
if re.match(r'^уби\.?\s*\d+', ln, re.IGNORECASE):
out.append(ln)
elif out:
out[-1] = out[-1] + ' ' + ln
else:
out.append(ln)
return '\n'.join(out)
def split_okved_extra(value):
"""Разбить okvedExtra (строка или список строк с переносами) на записи «код название».
В опросниках доп. ОКВЭД часто идут склеенными:
«25.93 Производство изделий из проволоки\nцепей и пружин 32.12.1 »
Здесь «цепей и пружин» продолжение названия 25.93, а 32.12.1 новый код.
Нарезаем по кодам ОКВЭД (двузначный код с точками в начале записи).
"""
if isinstance(value, (list, tuple)):
text = ' '.join(str(x) for x in value)
else:
text = str(value or '')
text = text.replace('\r', ' ').replace('\n', ' ')
# Нарезаем по началу кода ОКВЭД: «25.93», «32.12.1», «43.2» …
# Перед кодом не должно быть цифры или точки (чтобы не разрезать «25.93» на «25» и «93»)
parts = re.split(r'(?<!\d)(?<!\.)(?=\d{2}(?:\.\d+)*\b)', text)
out = []
for p in parts:
p = p.strip().strip('-—– ').strip()
if p and re.match(r'^\d{2}(?:\.\d+)*', p):
out.append(p)
return out
def filter_threats_by_dict(lines, threats_full=None):
"""Приводит строки угроз (от ИИ или оператора) к каноническому виду по словарю УБИ.
Для каждой строки:
- есть код УБИ берём полную запись «УБИ.xxx Название» из словаря;
- кода нет ищем по названию (точное совпадение или вхождение);
- не нашлось в словаре отбрасываем (мусор от ИИ).
Возвращает уникальные канонические строки, отсортированные по коду УБИ.
"""
if threats_full is None:
from .threat_rules import load_threats
threats_full = load_threats()
by_code = {}
by_norm = {}
for t in threats_full:
t = str(t).strip()
code = _ubi_code(t)
if code:
by_code.setdefault(code, t)
by_norm.setdefault(_norm_threat_text(t), t)
out = []
seen = set()
for line in lines or []:
line = str(line or '').strip()
if not line:
continue
code = _ubi_code(line)
canon = None
if code and code in by_code:
canon = by_code[code]
else:
n = _norm_threat_text(line)
if n in by_norm:
canon = by_norm[n]
else:
for k, v in by_norm.items():
if n and (n in k or k in n):
canon = v
break
if canon and canon not in seen:
seen.add(canon)
out.append(canon)
out.sort(key=lambda t: (int(_ubi_code(t)) if _ubi_code(t) else 10 ** 9, t))
return out
def suggest_threats_ai(security_data, api_key, model='deepseek-chat', base_url=None,
threats=None):
"""ИИ предлагает актуальные угрозы БИ для объекта КИИ по его характеристикам.
security_data dict: name, description, object_type, sphere, architecture,
attacker_category, has_internet (True/False), etc.
threats актуальный перечень угроз БДУ ФСТЭК (список строк «УБИ.xxx »).
Передаётся в промт целиком, чтобы ИИ выбирал из свежего списка,
а не из своего устаревшего.
Возвращает (список_угроз, ошибка).
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
if threats:
threat_list_text = "\n".join(f"{i+1}. {t}" for i, t in enumerate(threats))
else:
threat_list_text = "(перечень не предоставлен — используй знания об актуальном перечне БДУ ФСТЭК)"
pr = _load_prompt('suggest_threats')
system = pr.get('system', (
"Ты — эксперт ФСТЭК по безопасности критической информационной инфраструктуры "
"(187-ФЗ, приказ ФСТЭК № 236). На основе характеристик объекта КИИ выбери из "
"предоставленного перечня АКТУАЛЬНЫЕ угрозы безопасности информации."
))
rules = pr.get('rules', [])
resp = pr.get('response_format',
'Верни ТОЛЬКО JSON: {"threats": ["УБИ.xxx Название угрозы", ...]} '
'— от 10 до 40 угроз, без пояснений.')
user = (
"Характеристики объекта КИИ:\n"
f"{json.dumps(security_data, ensure_ascii=False)}\n\n"
"Актуальный перечень угроз БДУ ФСТЭК (выбирай ТОЛЬКО из него, "
"используй полные строки как они есть):\n"
f"{threat_list_text}\n\n"
+ ("Правила:\n" + "\n".join(f"{i+1}. {r}" for i, r in enumerate(rules)) + "\n\n" if rules else "")
+ resp
)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
threats_out = js.get('threats') or []
if isinstance(threats_out, str):
threats_out = [threats_out]
threats_out = [str(t).strip() for t in threats_out if str(t).strip()]
# Жёсткий фильтр по словарю: мусор отбрасываем, «УБИ.069» дополняем
# полным названием из актуального перечня (если он передан).
threats_out = filter_threats_by_dict(threats_out, threats)
return threats_out, None
def check_threats_ai(security_data, current_threats, api_key, model='deepseek-chat', base_url=None):
"""ИИ проверяет уже подобранный список угроз БИ для объекта КИИ.
В отличие от suggest_threats_ai (подбор заново), эта функция проверяет
текущий список: какие угрозы лишние, каких не хватает, и даёт комментарий.
Возвращает (dict, ошибка):
{"лишние": [...], "недостающие": [...], "комментарий": "..."}
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
if not current_threats:
return None, "Список угроз пуст — сначала подберите угрозы (программой или ИИ)."
system = (
"Ты — эксперт ФСТЭК по безопасности критической информационной инфраструктуры "
"(187-ФЗ, приказ ФСТЭК № 236). Проверь, корректно ли подобран перечень угроз "
"безопасности информации для объекта КИИ."
)
user = (
"Характеристики объекта КИИ:\n"
f"{json.dumps(security_data, ensure_ascii=False)}\n\n"
"Текущий перечень угроз (подобран программой или оператором):\n"
+ "\n".join(f"{i+1}. {t}" for i, t in enumerate(current_threats))
+ "\n\nВерни ТОЛЬКО JSON: "
'{"лишние": ["угрозы, которые НЕ актуальны для этого объекта"], '
'"недостающие": ["актуальные угрозы, которых в списке не хватает"], '
'"комментарий": "краткий вывод по перечню"}'
)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
# «Недостающие»/«лишние» — тоже через словарь: мусор отбрасываем,
# неполные строки («УБИ.069») дополняем полными названиями.
missing = filter_threats_by_dict(js.get('недостающие') or [])
extra = filter_threats_by_dict(js.get('лишние') or [])
comment = js.get('комментарий') or ''
return {'лишние': extra, 'недостающие': missing, 'комментарий': comment}, None
def suggest_object_params_ai(object_data, api_key, model='deepseek-chat', base_url=None,
typical_objects=None, spheres=None, object_types=None,
architectures=None):
"""ИИ предлагает характеристики объекта КИИ: назначение, сферу, тип, архитектуру,
типовой отраслевой объект как рекомендацию оператору.
object_data dict: name, description, purpose (что известно об объекте).
typical_objects перечень типовых отраслевых объектов (из 360-р) для выбора;
spheres / object_types / architectures полные списки допустимых значений.
Промт берётся из dictionaries/prompts/suggest_object_params.json.
Возвращает (dict, ошибка): dict с полями
назначение / сфера / тип / архитектура / типовой_объект / обоснование.
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
# Загрузка промта из папки dictionaries/prompts
import os as _os
prompt_path = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)),
'dictionaries', 'prompts', 'suggest_object_params.json')
try:
with open(prompt_path, encoding='utf-8') as _f:
prompt = json.load(_f)
except Exception:
prompt = {}
system = prompt.get('system', 'Ты — эксперт ФСТЭК по категорированию объектов КИИ.')
rules = prompt.get('rules', [])
response_format = prompt.get('response_format',
'Верни ТОЛЬКО JSON: {"назначение": "...", "сфера": "...", '
'"тип": "...", "архитектура": "...", "типовой_объект": "...", '
'"обоснование": "..."}')
# Полные списки допустимых значений
def _fmt_list(name, items):
if not items:
return f"{name}: (список не предоставлен)"
return f"{name}:\n" + "\n".join(f"- {i}" for i in items)
lists_text = "\n\n".join([
_fmt_list("Сферы деятельности (выбирай ТОЛЬКО из них)", spheres),
_fmt_list("Типы объектов (выбирай ТОЛЬКО из них)", object_types),
_fmt_list("Архитектуры (выбирай ТОЛЬКО из них)", architectures),
])
if typical_objects:
to_text = "\n".join(f"{i+1}. {t}" for i, t in enumerate(typical_objects[:400]))
else:
to_text = "(перечень не предоставлен)"
rules_text = "\n".join(f"{i+1}. {r}" for i, r in enumerate(rules))
user = (
"Информация об объекте КИИ:\n"
f"{json.dumps(object_data, ensure_ascii=False)}\n\n"
f"{lists_text}\n\n"
"Перечень типовых отраслевых объектов КИИ (Распоряжение ПРФ № 360-р, "
"выбирай ТОЛЬКО из него, копируй строку дословно):\n"
f"{to_text}\n\n"
"Правила:\n"
f"{rules_text}\n\n"
f"{response_format}"
)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
return {
'назначение': str(js.get('назначение', '') or '').strip(),
'сфера': str(js.get('сфера', '') or '').strip(),
'тип': str(js.get('тип', '') or '').strip(),
'архитектура': str(js.get('архитектура', '') or '').strip(),
'типовой_объект': str(js.get('типовой_объект', '') or '').strip(),
'обоснование': str(js.get('обоснование', '') or '').strip(),
}, None
def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-проверка заполненной вкладки «Безопасность» объекта КИИ (236 приказ).
Возвращает (результат, ошибка): результат dict с 'оценка', 'замечания', 'предложения'.
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
pr = _load_prompt('check_security')
system = pr.get('system', (
"Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, "
"приказ ФСТЭК России от 25.12.2017 № 239 и приказ № 236). Проверь заполненные "
"сведения об объекте КИИ и дай рекомендации."
))
system = system + " " + pr.get('response_format',
'Верни ТОЛЬКО JSON: {"оценка": "ok|warning|error", "замечания": ["..."], '
'"предложения": {"attacker_category": "...", "threats": "...", "incidents": "...", '
'"criteria_values": "...", "criteria_justification": "...", '
'"org_measures": "...", "tech_measures": "..."}} '
'В «предложениях» укажи, что добавить/исправить по каждому разделу, иначе пустые строки.')
user = json.dumps(security_data, ensure_ascii=False)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
notes = js.get('замечания') or []
if isinstance(notes, str):
notes = [notes]
proposals = js.get('предложения') or {}
if not isinstance(proposals, dict):
proposals = {}
return {
'source': 'ИИ',
'source_url': 'openai-совместимый API',
'disclaimer': AI_DISCLAIMER,
'kind': 'security',
'data': {
'rating': js.get('оценка', 'warning'),
'notes': notes,
'proposals': proposals,
},
}, None
def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-проверка реквизитов организации.
@@ -399,7 +855,8 @@ def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
system = (
"Ты — юрист по защите персональных данных (152-ФЗ). Проверь реквизиты организации. "
"Ты — специалист по проверке реквизитов организации (ИНН/КПП/ОГРН). "
"Проверь реквизиты организации. "
"Верни ТОЛЬКО JSON: "
'{"оценка": "ok|warning|error", '
'"замечания": ["..."], '
@@ -438,3 +895,130 @@ def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None
},
'apply': {'company': {k: v for k, v in proposals.items() if v}},
}, None
def suggest_defense_ai(defense_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-рекомендации по средствам защиты информации (СЗИ).
По категории значимости, угрозам и текущему списку СЗИ ИИ подсказывает,
чего не хватает (например, антивирус, СКЗИ, SIEM и т.п.).
Возвращает (результат, ошибка).
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
pr = _load_prompt('suggest_defense')
system = pr.get('system', (
"Ты — эксперт по безопасности КИИ (187-ФЗ, приказ ФСТЭК № 239). "
"По категории значимости объекта и актуальным угрозам определи, каких "
"средств защиты информации (СЗИ) не хватает, и дай рекомендации."
))
system = system + " " + pr.get('response_format',
'Верни ТОЛЬКО JSON: {"рекомендации": [{"сзи": "...", "зачем": "..."}], '
'"общие_советы": ["..."]}')
user = json.dumps(defense_data, ensure_ascii=False)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
recs = js.get('рекомендации') or []
if not isinstance(recs, list):
recs = []
tips = js.get('общие_советы') or []
if isinstance(tips, str):
tips = [tips]
return {
'source': 'ИИ',
'source_url': 'openai-совместимый API',
'disclaimer': AI_DISCLAIMER,
'kind': 'defense',
'data': {
'рекомендации': recs,
'общие_советы': tips,
},
}, None
def check_criteria_ai(criteria_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-проверка категорий значимости по ПП РФ № 127.
По данным объекта КИИ (назначение, сфера, категории по показателям,
обоснования) ИИ проверяет корректность присвоенных категорий.
Возвращает (результат, ошибка).
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
pr = _load_prompt('check_criteria')
system = pr.get('system', (
"Ты — эксперт по категорированию объектов КИИ (187-ФЗ, ПП РФ № 127). "
"Проверь присвоенные категории значимости по показателям критериев."
))
system = system + " " + pr.get('response_format',
'Верни ТОЛЬКО JSON: {"оценка": "ok|warning|error", '
'"замечания": [{"показатель": "...", "категория": "...", "комментарий": "..."}], '
'"предложения": {"criteria_values": "...", "criteria_justification": "..."}, '
'"итоговая_категория": "I|II|III|—"}')
user = json.dumps(criteria_data, ensure_ascii=False)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
notes = js.get('замечания') or []
if isinstance(notes, str):
notes = [notes]
proposals = js.get('предложения') or {}
if not isinstance(proposals, dict):
proposals = {}
return {
'source': 'ИИ',
'source_url': 'openai-совместимый API',
'disclaimer': AI_DISCLAIMER,
'kind': 'criteria',
'data': {
'rating': js.get('оценка', 'warning'),
'notes': notes,
'proposals': proposals,
'final_category': js.get('итоговая_категория', ''),
},
}, None
def check_object_partial(blocks, api_key, model='deepseek-chat', base_url=None, workdir=None):
"""ИИ-проверка объекта КИИ по частям.
blocks: список [{'name': 'Реквизиты организации', 'prompt': 'check_company', 'data': {...}}, ...]
Для каждого блока: данные пишутся во временный файл (вход), вызывается ИИ,
ответ пишется во временный файл (выход). Возвращает (results, errors):
results [{name, ok, text, files: {in: путь, out: путь}}].
"""
import tempfile
import os
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
if workdir is None:
workdir = tempfile.mkdtemp(prefix='dokogen_ai_')
results = []
for i, blk in enumerate(blocks, 1):
name = blk.get('name', f'Блок {i}')
prompt_name = blk.get('prompt', 'check_security')
data = blk.get('data', {})
f_in = os.path.join(workdir, f'block_{i}_input.json')
f_out = os.path.join(workdir, f'block_{i}_output.json')
try:
with open(f_in, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception:
pass
pr = _load_prompt(prompt_name)
system = pr.get('system', 'Ты — эксперт по КИИ (187-ФЗ). Проверь данные и дай рекомендации.')
system = system + ' ' + pr.get('response_format', 'Верни ТОЛЬКО JSON с результатом проверки.')
user = json.dumps(data, ensure_ascii=False)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
results.append({'name': name, 'ok': False, 'text': f'Ошибка: {err}', 'files': {'in': f_in, 'out': f_out}})
continue
try:
with open(f_out, 'w', encoding='utf-8') as f:
json.dump(js, f, ensure_ascii=False, indent=2)
except Exception:
pass
text = json.dumps(js, ensure_ascii=False, indent=2)
results.append({'name': name, 'ok': True, 'text': text, 'files': {'in': f_in, 'out': f_out}})
return results, None
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""DokoGen — Генератор документов 152-ФЗ
"""DokoGen — Генератор документов 187-ФЗ
Точка входа: python main.py
"""
import sys
+10 -10
View File
@@ -30,8 +30,8 @@ def create_test_template():
doc.add_paragraph('<!-- loop:information_systems -->')
doc.add_paragraph('=== IS: {{item.name}} ===')
doc.add_paragraph('Category: {{item.category}}')
doc.add_paragraph('Threat type: {{item.threat_type}}')
doc.add_paragraph('Defence level: {{item.defence_level}}')
doc.add_paragraph('Sphere: {{item.sphere}}')
doc.add_paragraph('Object type: {{item.object_type}}')
doc.add_paragraph('')
# Inner loop: commission
@@ -72,12 +72,12 @@ def test_zip_loop_expansion():
CommissionMember(role='Член комиссии', position='Бухгалтер', fio='Сидорова А.А.'),
],
information_systems=[
InformationSystem(name='ИС-1 "Бухгалтерия"', category='специальные',
pd_count='менее 100 000', threat_type='1', defence_level='2'),
InformationSystem(name='ИС-2 "Кадры"', category='иные',
pd_count='более 100 000', threat_type='3', defence_level='4'),
InformationSystem(name='ИС-3 "Склад"', category='общедоступные',
pd_count='менее 100 000', threat_type='2', defence_level='3'),
InformationSystem(name='ОКИИ-1 "Платёжный шлюз"', category='1',
sphere='Банковская сфера и финансовый рынок', object_type='АСУ'),
InformationSystem(name='ОКИИ-2 "Кадры"', category='2',
sphere='Наука', object_type='ИС'),
InformationSystem(name='ОКИИ-3 "Склад"', category='3',
sphere='Здравоохранение', object_type='ИС'),
]
)
@@ -87,7 +87,7 @@ def test_zip_loop_expansion():
is_list=company.information_systems,
doc_number='001',
doc_date='31.07.2026',
direction='152fz'
direction='187fz'
)
loops = replacements.get('_loops', {})
@@ -135,7 +135,7 @@ def test_zip_loop_expansion():
is_list=company.information_systems,
doc_number='001',
doc_date='31.07.2026',
direction='152fz'
direction='187fz'
)
try: