Compare commits

...
11 Commits
Author SHA1 Message Date
LocoAgent baa8f095d0 feat: перенос вкладки «Безопасность» из DokoGen 187-ФЗ
- security_tab_187_reference.py — код вкладки (секция построения +
  методы-обработчики + ИИ-проверка) для переноса интерфейса
- словари: attacker_categories, incidents, criteria_indicators,
  org_measures, tech_measures, threats, threats_fstec (+дата актуализации)
- fstec_threats.py — скачивание перечня угроз с bdu.fstec.ru
- verify.py: check_security_ai — ИИ-проверка безопасности по 236 приказу
2026-08-04 17:36:02 +04:00
prog1764 de522360b8 feat: ребрендинг под dokogenMU (основа из dokogen187) 2026-08-03 23:45:16 +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
26 changed files with 1085 additions and 90 deletions
+3 -1
View File
@@ -11,8 +11,10 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules
datas = [ datas = [
# Справочники (defense_tools.json, pd_actions.json, pd_items.json, pd_categories.json) # Справочники (defense_tools.json, pd_actions.json, pd_items.json, pd_categories.json)
('dokogen/dictionaries', 'dokogen/dictionaries'), ('dokogen/dictionaries', 'dokogen/dictionaries'),
# Пустой опросник 152-ФЗ (для «Справка → Пустой опросник») # Пустой опросник MU (для «Справка → Пустой опросник»)
('dokogen/data', 'dokogen/data'), ('dokogen/data', 'dokogen/data'),
# Иконка приложения (для окна программы в exe)
('dokogen/icon.ico', 'dokogen'),
] ]
# ---------- Словари pymorphy3 (обязательно!) ---------- # ---------- Словари pymorphy3 (обязательно!) ----------
+1 -1
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""DokoGen — Пакет генератора документов 152-ФЗ""" """DokoGen — Пакет генератора документов MU"""
from .models import Company, CommissionMember, InformationSystem, PaperDocument from .models import Company, CommissionMember, InformationSystem, PaperDocument
from .declension import inflect_name, decline, company_name_decline, get_short_fio, get_initials from .declension import inflect_name, decline, company_name_decline, get_short_fio, get_initials
+2 -2
View File
@@ -14,8 +14,8 @@ import sys
import json import json
DEFAULT_SETTINGS = { DEFAULT_SETTINGS = {
'dadata_token': '', 'dadata_token': '431aa11706bf83f571cef0319259289aa750bcb1',
'ai_api_key': '', 'ai_api_key': 'sk-3c7e6673f245412697d0e14b6096435f',
'ai_model': 'deepseek-chat', 'ai_model': 'deepseek-chat',
'ai_base_url': 'https://api.deepseek.com', 'ai_base_url': 'https://api.deepseek.com',
} }
@@ -0,0 +1,5 @@
[
"внешний нарушитель",
"внутренний нарушитель",
"внешний и внутренний нарушитель"
]
@@ -0,0 +1,7 @@
[
"Социальная значимость — количество граждан, на которые может повлиять нарушение",
"Политическая значимость — международный или внутриполитический резонанс",
"Экономическая значимость — возможный размер ущерба (в рублях)",
"Экологическая значимость — возможный ущерб окружающей среде",
"Значимость для обороны страны, безопасности государства и правопорядка"
]
+8
View File
@@ -0,0 +1,8 @@
[
"отказ в обслуживании",
"несанкционированный доступ",
"утечка данных (нарушение конфиденциальности)",
"модификация (подмена) данных",
"нарушение функционирования технических средств",
"несанкционированное использование вычислительных ресурсов объекта"
]
+9
View File
@@ -0,0 +1,9 @@
[
"Установление контролируемой зоны",
"Контроль физического доступа к объекту",
"Разработка документов (регламентов, инструкций, руководств) по обеспечению безопасности объекта",
"Назначение ответственных за обеспечение безопасности объекта",
"Обучение и инструктаж персонала",
"Регламентация действий при компьютерных инцидентах",
"Утверждение перечня лиц, имеющих доступ к объекту"
]
+15 -20
View File
@@ -1,22 +1,17 @@
[ [
"Сбор персональных данных", "сбор",
"Запись персональных данных", "систематизация",
"Систематизация персональных данных", "хранение",
"Накопление персональных данных", "извлечение",
"Хранение персональных данных", "передача (предоставление доступа)",
"Уточнение (обновление, изменение) персональных данных", "блокирование",
"Извлечение персональных данных", "уничтожение",
"Использование персональных данных", "запись",
"Передача (распространение, предоставление, доступ) персональных данных", "накопление",
"Обезличивание персональных данных", "уточнение (обновление, изменение)",
"Блокирование персональных данных", "использование",
"Удаление персональных данных", "обезличивание",
"Уничтожение персональных данных", "удаление",
"Трансграничная передача персональных данных", "распространение",
"Автоматизированная обработка персональных данных", "иные действия"
"Неавтоматизированная обработка персональных данных",
"Смешанная обработка персональных данных",
"Формирование и ведение баз данных",
"Передача данных третьим лицам",
"Внутренняя передача данных между подразделениями"
] ]
+14 -6
View File
@@ -1,8 +1,16 @@
[ [
"работники оператора", "Сотрудникам организации",
"клиенты", "Соискателям",
"контрагенты", "Родственникам сотрудников",
"родственники работников", "Уволенным сотрудникам",
"соискатели", "Контрагентам",
"иные лица" "Представителям контрагентов",
"Клиентам",
"Посетителям сайта",
"Выгодоприобретателям по договорам",
"Субъект персональных данных, предоставивший согласие на трансграничную передачу персональных данных",
"Учащимя",
"Студентам",
"Законным представителям",
"Иным категориям субъектов персональных данных, персональные данные которых обрабатываются"
] ]
+38 -22
View File
@@ -1,28 +1,44 @@
[ [
"Фамилия, имя, отчество", "Фамилия, Имя, Отчество",
"Год рождения",
"Месяц рождения",
"Дата рождения", "Дата рождения",
"Место рождения", "Место рождения",
"Паспортные данные",
"Адрес регистрации",
"Адрес проживания",
"ИНН",
"СНИЛС",
"Номер телефона",
"Адрес электронной почты",
"Образование",
"Профессия",
"Сведения о доходах",
"Семейное положение", "Семейное положение",
"Состав семьи", "Социальное положение",
"Состояние здоровья", "Имущественное положение",
"Национальность", "Сведения о доходах",
"Гражданство", "Пол",
"Сведения о судимости", "Адрес электронной почты",
"Фотография", "Адрес места жительства",
"Биометрические данные", "Адрес регистрации",
"Рабочий e-mail", "Номер телефона",
"Страховой номер индивидуального лицевого счета (СНИЛС)",
"Идентификационный номер налогоплательщика (ИНН);",
"Сведения о гражданстве",
"Данные документа, удостоверяющего личность",
"Данные водительского удостоверения",
"Данные документа, удостоверяющего личность за пределами Российской Федерации",
"Данные документа, содержащиеся в свидетельстве о рождении",
"Реквизиты банковских карт",
"Номер расчетного счета",
"Номер лицевого счета",
"Профессия",
"Должность", "Должность",
"Табельный номер", "Сведения о трудовой деятельности (в том числе стаж работы, данные о трудовой занятости на текущее время с указанием наименования и расчетного счета организации)",
"Сведения о воинском учете", "Сведения об отношении к воинской обязанности, о воинском учете",
"Сведения о командировках" "Иные персональные данные",
"Сведения, собираемые посредством метрических программ (например, Яндекс.Метрика)",
"Сведения об образовании",
"Фото-видео изображение лица",
"Данные голоса человека",
"Сведения о состоянии здоровья",
"Национальная принадлежность",
"Политические взгляды",
"Религиозные или философские убеждения",
"Сведения о судимости",
"Данные изображения лица, полученные с помощью фото- видео устройств, на основании которых можно установить его личность и которые используются оператором для установления личности субъекта персональных данных (биометрия)",
"Данные голоса человека, на основании которых можно установить его личность и которые используются оператором для установления личности субъекта персональных данных (биометрия)",
"Изображение папиллярных узоров пальцев рук",
"Иные биометрические персональные данные"
] ]
+14
View File
@@ -0,0 +1,14 @@
[
"Идентификация и аутентификация пользователей",
"Управление доступом",
"Ограничение программной среды",
"Антивирусная защита",
"Защита информации от несанкционированного доступа (СЗИ от НСД)",
"Регистрация и мониторинг событий безопасности",
"Межсетевое экранирование",
"Обнаружение вторжений (СОВ)",
"Криптографическая защита информации (СКЗИ)",
"Защита среды виртуализации",
"Резервное копирование и восстановление",
"Защита технических средств и линий связи"
]
+12
View File
@@ -0,0 +1,12 @@
[
"Угроза несанкционированного доступа к информационным ресурсам объекта",
"Угроза нарушения функционирования объекта (отказ в обслуживании)",
"Угроза утечки (нарушения конфиденциальности) информации",
"Угроза модификации (подмены) данных",
"Угроза внедрения вредоносного кода",
"Угроза несанкционированного использования вычислительных ресурсов",
"Угроза перехвата (прослушивания) сетевого трафика",
"Угроза социальной инженерии",
"Угроза использования уязвимостей программного обеспечения",
"Угроза физического воздействия на технические средства"
]
+229
View File
@@ -0,0 +1,229 @@
[
"Угроза автоматического распространения вредоносного кода в грид-системе",
"Угроза агрегирования данных, передаваемых в грид-системе",
"Угроза использования слабостей криптографических алгоритмов и уязвимостей в программном обеспечении их реализации",
"Угроза аппаратного сброса пароля BIOS",
"Угроза внедрения вредоносного кода в BIOS",
"Угроза внедрения кода или данных",
"Угроза воздействия на программы с высокими привилегиями",
"Угроза восстановления и/или повторного использования аутентификационной информации",
"Угроза восстановления предыдущей уязвимой версии BIOS",
"Угроза выхода процесса за пределы виртуальной машины",
"Угроза деавторизации санкционированного клиента беспроводной сети",
"Угроза деструктивного изменения конфигурации/среды окружения программ",
"Угроза деструктивного использования декларированного функционала BIOS",
"Угроза длительного удержания вычислительных ресурсов пользователями",
"Угроза доступа к защищаемым файлам с использованием обходного пути",
"Угроза доступа к локальным файлам сервера при помощи URL",
"Угроза доступа/перехвата/изменения HTTP cookies",
"Угроза загрузки нештатной операционной системы",
"Угроза заражения DNS-кеша",
"Угроза злоупотребления возможностями, предоставленными потребителям облачных услуг",
"Угроза злоупотребления доверием потребителей облачных услуг",
"Угроза избыточного выделения оперативной памяти",
"Угроза изменения компонентов информационной (автоматизированной) системы",
"Угроза изменения режимов работы аппаратных элементов компьютера",
"Угроза изменения системных и глобальных переменных",
"Угроза искажения XML-схемы",
"Угроза искажения вводимой и выводимой на периферийные устройства информации",
"Угроза использования альтернативных путей доступа к ресурсам",
"Угроза использования вычислительных ресурсов суперкомпьютера «паразитными» процессами",
"Угроза использования информации идентификации/аутентификации, заданной по умолчанию",
"Угроза использования механизмов авторизации для повышения привилегий",
"Угроза использования поддельных цифровых подписей BIOS",
"Угроза использования слабостей кодирования входных данных",
"Угроза использования слабостей протоколов сетевого/локального обмена данными",
"Угроза использования слабых криптографических алгоритмов BIOS",
"Угроза исследования механизмов работы программы",
"Угроза исследования приложения через отчёты об ошибках",
"Угроза исчерпания вычислительных ресурсов хранилища больших данных",
"Угроза исчерпания запаса ключей, необходимых для обновления BIOS",
"Угроза конфликта юрисдикций различных стран",
"Угроза межсайтового скриптинга",
"Угроза межсайтовой подделки запроса",
"Угроза нарушения доступности облачного сервера",
"Угроза нарушения изоляции пользовательских данных внутри виртуальной машины",
"Угроза нарушения изоляции среды исполнения BIOS",
"Угроза нарушения процедуры аутентификации субъектов виртуального информационного взаимодействия",
"Угроза нарушения работоспособности грид-системы при нетипичной сетевой нагрузке",
"Угроза нарушения технологии обработки информации путём несанкционированного внесения изменений в образы виртуальных машин",
"Угроза нарушения целостности данных кеша",
"Угроза неверного определения формата входных данных, поступающих в хранилище больших данных",
"Угроза невозможности восстановления сессии работы на ПЭВМ при выводе из промежуточных состояний питания",
"Угроза невозможности миграции образов виртуальных машин из-за несовместимости аппаратного и программного обеспечения",
"Угроза невозможности управления правами пользователей BIOS",
"Угроза недобросовестного исполнения обязательств поставщиками облачных услуг",
"Угроза незащищённого администрирования облачных услуг",
"Угроза некачественного переноса инфраструктуры в облако",
"Угроза неконтролируемого копирования данных внутри хранилища больших данных",
"Угроза неконтролируемого роста числа виртуальных машин",
"Угроза неконтролируемого роста числа зарезервированных вычислительных ресурсов",
"Угроза неконтролируемого уничтожения информации хранилищем больших данных",
"Угроза некорректного задания структуры данных транзакции",
"Угроза некорректного использования прозрачного прокси-сервера за счёт плагинов браузера",
"Угроза некорректного использования функционала программного и аппаратного обеспечения",
"Угроза некорректной реализации политики лицензирования в облаке",
"Угроза неопределённости в распределении ответственности между ролями в облаке",
"Угроза неопределённости ответственности за обеспечение безопасности облака",
"Угроза неправомерного ознакомления с защищаемой информацией",
"Угроза неправомерного/некорректного использования интерфейса взаимодействия с приложением",
"Угроза неправомерных действий в каналах связи",
"Угроза непрерывной модернизации облачной инфраструктуры",
"Угроза несанкционированного восстановления удалённой защищаемой информации",
"Угроза несанкционированного выключения или обхода механизма защиты от записи в BIOS",
"Угроза несанкционированного доступа к активному и (или) пассивному виртуальному и (или) физическому сетевому оборудованию из физической и (или) виртуальной сети",
"Угроза несанкционированного доступа к аутентификационной информации",
"Угроза несанкционированного доступа к виртуальным каналам передачи",
"Угроза несанкционированного доступа к гипервизору из виртуальной машины и (или) физической сети",
"Угроза несанкционированного доступа к данным за пределами зарезервированного адресного пространства, в том числе выделенного под виртуальное аппаратное обеспечение",
"Угроза несанкционированного доступа к защищаемым виртуальным машинам из виртуальной и (или) физической сети",
"Угроза несанкционированного доступа к защищаемым виртуальным машинам со стороны других виртуальных машин",
"Угроза несанкционированного доступа к защищаемым виртуальным устройствам из виртуальной и (или) физической сети",
"Угроза несанкционированного доступа к локальному компьютеру через клиента грид-системы",
"Угроза несанкционированного доступа к сегментам вычислительного поля",
"Угроза несанкционированного доступа к системе по беспроводным каналам",
"Угроза несанкционированного доступа к системе хранения данных из виртуальной и (или) физической сети",
"Угроза несанкционированного доступа к хранимой в виртуальном пространстве защищаемой информации",
"Угроза несанкционированного изменения аутентификационной информации",
"Угроза несанкционированного использования привилегированных функций BIOS",
"Угроза несанкционированного копирования защищаемой информации",
"Угроза несанкционированного редактирования реестра",
"Угроза несанкционированного создания учётной записи пользователя",
"Угроза несанкционированного удаления защищаемой информации",
"Угроза несанкционированного удалённого внеполосного доступа к аппаратным средствам",
"Угроза несанкционированного управления буфером",
"Угроза несанкционированного управления синхронизацией и состоянием",
"Угроза несанкционированного управления указателями",
"Угроза несогласованности политик безопасности элементов облачной инфраструктуры",
"Угроза несогласованности правил доступа к большим данным",
"Угроза обнаружения открытых портов и идентификации привязанных к ним сетевых служб",
"Угроза обнаружения хостов",
"Угроза обхода некорректно настроенных механизмов аутентификации",
"Угроза общедоступности облачной инфраструктуры",
"Угроза опосредованного управления группой программ через совместно используемые данные",
"Угроза определения типов объектов защиты",
"Угроза определения топологии вычислительной сети",
"Угроза отказа в загрузке входных данных неизвестного формата хранилищем больших данных",
"Угроза отказа в обслуживании системой хранения данных суперкомпьютера",
"Угроза отключения контрольных датчиков",
"Угроза ошибки обновления гипервизора",
"Угроза перебора всех настроек и параметров приложения",
"Угроза перегрузки грид-системы вычислительными заданиями",
"Угроза передачи данных по скрытым каналам",
"Угроза передачи запрещённых команд на оборудование с числовым программным управлением",
"Угроза перезагрузки аппаратных и программно-аппаратных средств вычислительной техники",
"Угроза переполнения целочисленных переменных",
"Угроза перехвата вводимой и выводимой на периферийные устройства информации",
"Угроза перехвата данных, передаваемых по вычислительной сети",
"Угроза перехвата привилегированного потока",
"Угроза перехвата привилегированного процесса",
"Угроза перехвата управления гипервизором",
"Угроза перехвата управления средой виртуализации",
"Угроза повреждения системного реестра",
"Угроза повышения привилегий",
"Угроза подбора пароля BIOS",
"Угроза подделки записей журнала регистрации событий",
"Угроза подключения к беспроводной сети в обход процедуры аутентификации",
"Угроза подмены беспроводного клиента или точки доступа",
"Угроза подмены действия пользователя путём обмана",
"Угроза подмены доверенного пользователя",
"Угроза подмены резервной копии программного обеспечения BIOS",
"Угроза подмены содержимого сетевых ресурсов",
"Угроза подмены субъекта сетевого доступа",
"Угроза получения предварительной информации об объекте защиты",
"Угроза получения сведений о владельце беспроводного устройства",
"Угроза потери доверия к поставщику облачных услуг",
"Угроза потери и утечки данных, обрабатываемых в облаке",
"Угроза потери информации вследствие несогласованности работы узлов хранилища больших данных",
"Угроза потери управления облачными ресурсами",
"Угроза потери управления собственной инфраструктурой при переносе её в облако",
"Угроза преодоления физической защиты",
"Угроза приведения системы в состояние «отказ в обслуживании»",
"Угроза привязки к поставщику облачных услуг",
"Угроза приостановки оказания облачных услуг вследствие технических сбоев",
"Угроза программного выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации",
"Угроза программного сброса пароля BIOS",
"Угроза пропуска проверки целостности программного обеспечения",
"Угроза прямого обращения к памяти вычислительного поля суперкомпьютера",
"Угроза распространения несанкционированно повышенных прав на всю грид-систему",
"Угроза сбоя автоматического управления системой разграничения доступа хранилища больших данных",
"Угроза сбоя обработки специальным образом изменённых файлов",
"Угроза сбоя процесса обновления BIOS",
"Угроза сканирования веб-сервисов, разработанных на основе языка описания WSDL",
"Угроза удаления аутентификационной информации",
"Угроза усиления воздействия на вычислительные ресурсы пользователей при помощи сторонних серверов",
"Угроза установки уязвимых версий обновления программного обеспечения BIOS",
"Угроза утраты вычислительных ресурсов",
"Угроза утраты носителей информации",
"Угроза физического выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации",
"Угроза форматирования носителей информации",
"Угроза «форсированного веб-браузинга»",
"Угроза хищения средств хранения, обработки и (или) ввода/вывода/передачи информации",
"Угроза чрезмерного использования вычислительных ресурсов суперкомпьютера в ходе интенсивного обмена межпроцессорными сообщениями",
"Угроза эксплуатации цифровой подписи программного кода",
"Угроза перехвата исключения/сигнала из привилегированного блока функций",
"Угроза распространения состояния «отказ в обслуживании» в облачной инфраструктуре",
"Угроза включения в проект не достоверно испытанных компонентов",
"Угроза внедрения системной избыточности",
"Угроза заражения компьютера при посещении неблагонадёжных сайтов",
"Угроза «кражи» учётной записи доступа к сетевым сервисам",
"Угроза наличия механизмов разработчика",
"Угроза неправомерного шифрования информации",
"Угроза скрытного включения вычислительного устройства в состав бот-сети",
"Угроза распространения «почтовых червей»",
"Угроза «спама» веб-сервера",
"Угроза «фарминга»",
"Угроза «фишинга»",
"Угроза нарушения технологического/производственного процесса из-за временны́х задержек, вносимых средством защиты",
"Угроза неподтверждённого ввода данных оператором в систему, связанную с безопасностью",
"Угроза несанкционированного использования системных и сетевых утилит",
"Угроза несанкционированной модификации защищаемой информации",
"Угроза отказа подсистемы обеспечения температурного режима",
"Угроза перехвата одноразовых паролей в режиме реального времени",
"Угроза физического устаревания аппаратных компонентов",
"Угроза перехвата управления автоматизированной системой управления технологическими процессами",
"Угроза агрегирования данных, обрабатываемых с помощью мобильного устройства",
"Угроза несанкционированного изменения параметров настройки средств защиты информации",
"Угроза внедрения вредоносного кода через рекламу, сервисы и контент",
"Угроза несанкционированного воздействия на средство защиты информации",
"Угроза подмены программного обеспечения",
"Угроза маскирования действий вредоносного кода",
"Угроза внедрения вредоносного кода за счет посещения зараженных сайтов в сети Интернет",
"Угроза внедрения вредоносного кода в дистрибутив программного обеспечения",
"Угроза использования уязвимых версий программного обеспечения",
"Угроза утечки информации за счет применения вредоносным программным обеспечением алгоритмов шифрования трафика",
"Угроза несанкционированного использования привилегированных функций мобильного устройства",
"Угроза удаленного запуска вредоносного кода в обход механизмов защиты операционной системы",
"Угроза контроля вредоносной программой списка приложений, запущенных на мобильном устройстве",
"Угроза хищения аутентификационной информации из временных файлов cookie",
"Угроза скрытной регистрации вредоносной программой учетных записей администраторов",
"Угроза перехвата управления мобильного устройства при использовании виртуальных голосовых ассистентов",
"Угроза хищения информации с мобильного устройства при использовании виртуальных голосовых ассистентов",
"Угроза утечки пользовательских данных при использовании функций автоматического заполнения аутентификационной информации в браузере",
"Угроза несанкционированной установки приложений на мобильные устройства",
"Угроза утечки информации с неподключенных к сети Интернет компьютеров",
"Угроза несанкционированного изменения вредоносной программой значений параметров программируемых логических контроллеров",
"Угроза нарушения работы компьютера и блокирования доступа к его данным из-за некорректной работы установленных на нем средств защиты",
"Угроза отказа в работе оборудования из-за изменения геолокационной информации о нем",
"Угроза несанкционированного доступа к параметрам настройки оборудования за счет использования «мастер-кодов» (инженерных паролей)",
"Угроза нецелевого использования вычислительных ресурсов средства вычислительной техники",
"Угроза несанкционированного доступа к защищаемой памяти \r\nядра процессора",
"Угроза нарушения работы информационной системы, вызванного обновлением используемого в ней программного обеспечения",
"Угроза использования непроверенных пользовательских данных при формировании конфигурационного файла, используемого программным обеспечением администрирования информационных систем",
"Угроза перехвата управления информационной системой",
"Угроза обхода многофакторной аутентификации",
"Угроза несвоевременного выявления и реагирования компонентами информационной (автоматизированной) системы (в том числе средствами защиты информации) на события безопасности информации",
"Угроза несанкционированного доступа к системе при помощи сторонних сервисов",
"Угроза получения несанкционированного доступа к приложениям, установленным на Smart-картах",
"Угроза использования скомпрометированного доверенного источника обновлений программного обеспечения",
"Угроза раскрытия информации о модели машинного обучения",
"Угроза хищения обучающих данных",
"Угроза нарушения функционирования («обхода») средств, реализующих технологии искусственного интеллекта",
"Угроза модификации модели машинного обучения путем искажения («отравления») обучающих данных",
"Угроза подмены модели машинного обучения",
"Угроза несанкционированного доступа к контейнерам, предоставляющего пользователям расширенные привилегии",
"Угроза нарушения целостности (подмены) контейнеров",
"Угроза нарушения изоляции контейнеров",
"Угроза внедрения вредоносного программного обеспечения в контейнеры",
"Угроза модификации (подмены) образов контейнеров"
]
@@ -0,0 +1 @@
2026-08-04
+149
View File
@@ -0,0 +1,149 @@
# -*- 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 _parse_threats(html):
"""Извлекает названия угроз из HTML-таблицы БДУ.
Строки вида: <tr>...<td>УБИ. 001</td><td>Угроза ...</td>...</tr>
Возвращает список названий (без кода).
"""
names = []
if not html:
return names
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]
# формат: [код, название, ...] — берём ячейку, которая не похожа на код УБИ
for cell in cleaned:
if not cell:
continue
if re.fullmatch(r'УБИ\.?\s*[\d.]+', cell):
continue
if cell in ('', 'Идентификатор', 'Наименование', 'Дата', 'Уровень опасности'):
continue
names.append(cell)
break
return names
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 []
+8 -8
View File
@@ -5,7 +5,7 @@
рядом с программой (при сборке .exe — рядом с exe): рядом с программой (при сборке .exe — рядом с exe):
- Инструкция_пользователя.docx - Инструкция_пользователя.docx
- Инструкция_администратора.docx - Инструкция_администратора.docx
- Опросный_лист_152_ФЗ.xlsx (пустой шаблон) - Опросный_лист_MU.xlsx (пустой шаблон)
""" """
import os import os
@@ -45,7 +45,7 @@ def variables_reference_path():
def survey_path(): def survey_path():
return os.path.join(docs_dir(), 'Опросный_лист_152_ФЗ.xlsx') return os.path.join(docs_dir(), 'Опросный_лист_MU.xlsx')
def _h(doc, text, size=14): def _h(doc, text, size=14):
@@ -83,7 +83,7 @@ def _generate_variables_reference(path):
_p(doc, 'Список переменных для шаблонов Word. Переменная подставляется в текст ' _p(doc, 'Список переменных для шаблонов Word. Переменная подставляется в текст '
'как есть: {{имя_переменной}}. Циклы: <!-- loop:имя --> ... <!-- loop_end -->.') 'как есть: {{имя_переменной}}. Циклы: <!-- loop:имя --> ... <!-- loop_end -->.')
groups = {'A': 'Общие переменные', 'B': '152-ФЗ (персональные данные)'} groups = {'A': 'Общие переменные', 'B': 'MU (персональные данные)'}
by_group = {'A': [], 'B': []} by_group = {'A': [], 'B': []}
for var, group, desc, _ in VARIABLE_DEFS: for var, group, desc, _ in VARIABLE_DEFS:
by_group.setdefault(group, []).append((var, desc)) by_group.setdefault(group, []).append((var, desc))
@@ -108,11 +108,11 @@ def _generate_variables_reference(path):
def _generate_user_manual(path): def _generate_user_manual(path):
"""Инструкция пользователя: программа, циклы, переменные, проверки.""" """Инструкция пользователя: программа, циклы, переменные, проверки."""
doc = docx.Document() doc = docx.Document()
_h(doc, 'ДоКоГеНеРаТоР — 152-ФЗ') _h(doc, 'ДоКоГеНеРаТоР — MU')
_h(doc, 'Инструкция пользователя', 13) _h(doc, 'Инструкция пользователя', 13)
_h(doc, '1. Что это за программа', 12) _h(doc, '1. Что это за программа', 12)
_p(doc, 'Программа автоматически заполняет шаблоны документов по 152-ФЗ ' _p(doc, 'Программа автоматически заполняет шаблоны документов по MU '
'(акты обследования, политики, инструкции, приказы и т.д.) данными ' '(акты обследования, политики, инструкции, приказы и т.д.) данными '
'об организации, информационных системах, комиссии и сотрудниках. ' 'об организации, информационных системах, комиссии и сотрудниках. '
'Данные можно ввести вручную на вкладках или импортировать из ' 'Данные можно ввести вручную на вкладках или импортировать из '
@@ -173,7 +173,7 @@ def _generate_user_manual(path):
def _generate_admin_manual(path): def _generate_admin_manual(path):
"""Инструкция администратора: словари, API-ключи, лимиты.""" """Инструкция администратора: словари, API-ключи, лимиты."""
doc = docx.Document() doc = docx.Document()
_h(doc, 'ДоКоГеНеРаТоР — 152-ФЗ') _h(doc, 'ДоКоГеНеРаТоР — MU')
_h(doc, 'Инструкция администратора', 13) _h(doc, 'Инструкция администратора', 13)
_h(doc, '1. Файлы настроек рядом с программой', 12) _h(doc, '1. Файлы настроек рядом с программой', 12)
@@ -240,8 +240,8 @@ def _ensure_survey():
return target return target
# Ищем шаблон опросника: рядом с программой или встроенный # Ищем шаблон опросника: рядом с программой или встроенный
candidates = [ candidates = [
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'Опросный_лист_152_ФЗ.xlsx'), os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'Опросный_лист_MU.xlsx'),
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_152_ФЗ.xlsx'), os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_MU.xlsx'),
] ]
for c in candidates: for c in candidates:
if os.path.exists(c): if os.path.exists(c):
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

+9 -9
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""DokoGen — Модуль импорта из Excel (опросный лист 152-ФЗ)""" """DokoGen — Модуль импорта из Excel (опросный лист MU)"""
import re import re
import traceback import traceback
@@ -122,8 +122,8 @@ def clean_value(val):
return text return text
def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path: Optional[str] = None) -> dict: def import_mufz(filepath: str, log_fn: Optional[Callable] = None, settings_path: Optional[str] = None) -> dict:
"""Импорт данных из опросного листа Excel (152-ФЗ). """Импорт данных из опросного листа Excel (MU).
settings_path — путь к JSON-файлу настроек импорта (что и откуда брать). settings_path — путь к JSON-файлу настроек импорта (что и откуда брать).
Если None — файл import_settings.json рядом с программой; если его нет — Если None — файл import_settings.json рядом с программой; если его нет —
@@ -154,9 +154,9 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
return data return data
try: try:
_import_152_common(wb, data, settings, log_fn) _import_mu_common(wb, data, settings, log_fn)
_import_152_is_list(wb, data, settings, log_fn) _import_mu_is_list(wb, data, settings, log_fn)
_import_152_employees(wb, data, settings, log_fn) _import_mu_employees(wb, data, settings, log_fn)
except Exception as e: except Exception as e:
log_fn(f"❌ Ошибка при импорте: {e}") log_fn(f"❌ Ошибка при импорте: {e}")
log_fn(traceback.format_exc()) log_fn(traceback.format_exc())
@@ -179,7 +179,7 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
return data return data
def _import_152_common(wb, data, settings, log_fn): def _import_mu_common(wb, data, settings, log_fn):
"""Импорт общих сведений по словарю настроек. """Импорт общих сведений по словарю настроек.
Для каждого поля: сначала пробуем конкретную ячейку (cell), Для каждого поля: сначала пробуем конкретную ячейку (cell),
@@ -247,7 +247,7 @@ def _import_152_common(wb, data, settings, log_fn):
break break
def _import_152_is_list(wb, data, settings, log_fn): def _import_mu_is_list(wb, data, settings, log_fn):
"""Импорт информационных систем из остальных листов.""" """Импорт информационных систем из остальных листов."""
exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники']) exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники'])
is_sheets = [] is_sheets = []
@@ -447,7 +447,7 @@ def _import_152_is_list(wb, data, settings, log_fn):
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_mu_employees(wb, data, settings, log_fn):
"""Импорт комиссии и сотрудников из листа «Сотрудники».""" """Импорт комиссии и сотрудников из листа «Сотрудники»."""
sheet_name = None sheet_name = None
sheet_kw = settings.get('employees_sheet_keyword', 'сотруд') sheet_kw = settings.get('employees_sheet_keyword', 'сотруд')
+2 -2
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""DokoGen — Модели данных (только 152-ФЗ, только нужные поля)""" """DokoGen — Модели данных (только MU, только нужные поля)"""
from dataclasses import dataclass, field, asdict from dataclasses import dataclass, field, asdict
from typing import List, Dict, Any from typing import List, Dict, Any
@@ -49,7 +49,7 @@ class PaperDocument:
@dataclass @dataclass
class Company: class Company:
"""Организация — основной объект (152-ФЗ)""" """Организация — основной объект (MU)"""
# Полное название # Полное название
full_name: str = "" full_name: str = ""
company_name_1: str = "" # именительный company_name_1: str = "" # именительный
+480
View File
@@ -0,0 +1,480 @@
# -*- coding: utf-8 -*-
"""
ВКЛАДКА «БЕЗОПАСНОСТЬ» из DokoGen 187-ФЗ (dokogen187, ветка source)
=====================================================================
Скопировано для переноса в DokoGen MU, чтобы интерфейс был одинаковым.
ЧТО ВХОДИТ:
1. Секция построения вкладки (вставить в метод _build_is_tab перед вкладкой «Средства защиты»)
2. Методы-обработчики (вставить в класс приложения)
3. Словари скопированы в dokogen/dictionaries/:
attacker_categories.json, incidents.json, criteria_indicators.json,
org_measures.json, tech_measures.json, threats.json,
threats_fstec.json, threats_fstec_date.txt
4. Модуль fstec_threats.py (скачивание перечня угроз с bdu.fstec.ru)
5. check_security_ai() ИИ-проверка (см. конец файла, добавить в verify.py)
ЗАВИСИМОСТИ:
- tkinter/ttk (стандарт)
- dokogen/fstec_threats.py (загрузка угроз ФСТЭК)
- dokogen/verify.py: check_security_ai (ИИ-проверка)
- dokogen/api_settings.py: load_api_settings
- Поля модели InformationSystem (добавить в models.py, если нет):
attacker_category, attacker_characteristics, threats, incidents,
criteria_values, criteria_justification, org_measures, tech_measures
- Маппинги _collect_is/_on_is_select (см. комментарии в конце)
ПРИМЕЧАНИЕ: в DokoGen MU (152-ФЗ) модель InformationSystem другая
при переносе адаптировать имена полей или добавить новые.
"""
# ============================================================
# 1. СЕКЦИЯ ПОСТРОЕНИЯ ВКЛАДКИ (вставить в _build_is_tab)
# ============================================================
# ===== Вкладка «Безопасность» =====
sec_tab = ttk.Frame(nb2)
nb2.add(sec_tab, text="Безопасность")
sec_frame = ttk.Frame(sec_tab, padding=8)
sec_frame.pack(fill=tk.BOTH, expand=True)
# --- Категория нарушителя (словарик) ---
ttk.Label(sec_frame, text="Категория нарушителя:").grid(row=0, column=0, sticky=tk.W, pady=2)
self.is_attacker_category_combo = ttk.Combobox(sec_frame, width=68, state='readonly')
self.is_attacker_category_combo.grid(row=0, column=1, padx=8, sticky=tk.EW, pady=1)
try:
import json as _json
_ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json')
with open(_ac, encoding='utf-8') as _f:
self.is_attacker_category_combo['values'] = [''] + _json.load(_f)
except Exception:
pass
# --- Характеристика нарушителя ---
ttk.Label(sec_frame, text="Характеристика нарушителя:").grid(row=1, column=0, sticky=tk.NW, pady=2)
self.is_attacker_characteristics_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
self.is_attacker_characteristics_listbox.grid(row=1, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_char_btn = ttk.Frame(sec_frame)
sec_char_btn.grid(row=1, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_char_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_char_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_char_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1)
# --- Основные угрозы БИ (словарик + загрузка с ФСТЭК) ---
ttk.Label(sec_frame, text="Основные угрозы БИ:").grid(row=2, column=0, sticky=tk.W, pady=2)
self.is_threats_listbox = tk.Listbox(sec_frame, height=5, exportselection=False)
self.is_threats_listbox.grid(row=2, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_thr_btn = ttk.Frame(sec_frame)
sec_thr_btn.grid(row=2, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_thr_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_thr_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_thr_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_thr_btn, text="📚 Из словаря",
command=lambda: self._select_sw_from_dict(self.is_threats_listbox, 'threats_fstec.json')).pack(fill=tk.X, pady=1)
ttk.Button(sec_thr_btn, text="⬇️ Загрузить угрозы ФСТЭК",
command=self._download_fstec_threats).pack(fill=tk.X, pady=1)
self._threats_date_label = ttk.Label(sec_frame, text="", foreground='gray')
self._threats_date_label.grid(row=3, column=1, sticky=tk.W, padx=8, pady=(0, 2))
# --- Типы компьютерных инцидентов (словарик) ---
ttk.Label(sec_frame, text="Типы компьютерных инцидентов:").grid(row=4, column=0, sticky=tk.W, pady=2)
self.is_incidents_listbox = tk.Listbox(sec_frame, height=4, exportselection=False)
self.is_incidents_listbox.grid(row=4, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_inc_btn = ttk.Frame(sec_frame)
sec_inc_btn.grid(row=4, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_inc_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_inc_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_inc_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_inc_btn, text="📚 Из словаря",
command=lambda: self._select_sw_from_dict(self.is_incidents_listbox, 'incidents.json')).pack(fill=tk.X, pady=1)
# --- Показатели критериев значимости (словарик) ---
ttk.Label(sec_frame, text="Показатели критериев значимости:").grid(row=5, column=0, sticky=tk.W, pady=2)
self.is_criteria_values_listbox = tk.Listbox(sec_frame, height=4, exportselection=False)
self.is_criteria_values_listbox.grid(row=5, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_crit_btn = ttk.Frame(sec_frame)
sec_crit_btn.grid(row=5, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_crit_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_crit_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_crit_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_crit_btn, text="📚 Из словаря",
command=lambda: self._select_sw_from_dict(self.is_criteria_values_listbox, 'criteria_indicators.json')).pack(fill=tk.X, pady=1)
# --- Обоснование значений показателей ---
ttk.Label(sec_frame, text="Обоснование значений показателей:").grid(row=6, column=0, sticky=tk.NW, pady=2)
self.is_criteria_justification_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
self.is_criteria_justification_listbox.grid(row=6, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_just_btn = ttk.Frame(sec_frame)
sec_just_btn.grid(row=6, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_just_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_just_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_just_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1)
# --- Организационные меры (словарик) ---
ttk.Label(sec_frame, text="Организационные меры:").grid(row=7, column=0, sticky=tk.W, pady=2)
self.is_org_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
self.is_org_measures_listbox.grid(row=7, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_org_btn = ttk.Frame(sec_frame)
sec_org_btn.grid(row=7, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_org_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_org_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_org_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_org_btn, text="📚 Из словаря",
command=lambda: self._select_sw_from_dict(self.is_org_measures_listbox, 'org_measures.json')).pack(fill=tk.X, pady=1)
# --- Технические меры (словарик) ---
ttk.Label(sec_frame, text="Технические меры:").grid(row=8, column=0, sticky=tk.W, pady=2)
self.is_tech_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
self.is_tech_measures_listbox.grid(row=8, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
sec_tech_btn = ttk.Frame(sec_frame)
sec_tech_btn.grid(row=8, column=2, sticky=tk.N, padx=(4, 0))
ttk.Button(sec_tech_btn, text="+ Добавить",
command=lambda: self._add_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_tech_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_tech_btn, text="✏️ Редактировать",
command=lambda: self._edit_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_tech_btn, text="📚 Из словаря",
command=lambda: self._select_sw_from_dict(self.is_tech_measures_listbox, 'tech_measures.json')).pack(fill=tk.X, pady=1)
# --- Кнопка ИИ-проверки ---
ttk.Button(sec_frame, text="🤖 Проверить безопасность через ИИ",
command=self._check_security_ai).grid(
row=9, column=0, columnspan=3, sticky=tk.W, pady=(10, 0))
sec_frame.columnconfigure(1, weight=1)
# ============================================================
# 2. МЕТОДЫ-ОБРАБОТЧИКИ (вставить в класс приложения)
# ============================================================
def _download_fstec_threats(self):
"""Скачать перечень угроз с сайта ФСТЭК (БДУ) и обновить список."""
try:
from .fstec_threats import download_fstec_threats, get_threats_date
except ImportError as e:
messagebox.showerror("Ошибка", f"Модуль fstec_threats не найден: {e}")
return
self.log("⬇️ Загрузка перечня угроз с bdu.fstec.ru…")
ok, msg, threats = download_fstec_threats()
if not ok:
messagebox.showerror("Загрузка угроз",
f"Не удалось загрузить:\n{msg}\n\n"
"Проверьте доступ к интернету или попробуйте позже.")
self.log(f"❌ Загрузка угроз: {msg}")
return
# Обновляем label даты актуализации
self._update_threats_date_label()
# Если список угроз уже открыт — можно показать диалог выбора
messagebox.showinfo("Загрузка угроз",
f"{msg}\n\nСписок сохранён. Теперь нажмите «📚 Из словаря» "
"в разделе «Основные угрозы БИ», чтобы выбрать нужные угрозы.")
self.log(f"{msg}")
def _update_threats_date_label(self):
"""Обновить подпись даты актуализации списка угроз."""
try:
from .fstec_threats import get_threats_date
d = get_threats_date()
except Exception:
d = None
if hasattr(self, '_threats_date_label'):
if d:
self._threats_date_label.config(
text=f"Актуально на: {d}", foreground='#2e7d32')
else:
self._threats_date_label.config(
text="⚠ Список угроз ещё не скачан — нажмите «⬇️ Загрузить угрозы ФСТЭК»",
foreground='#c62828')
def _check_security_ai(self):
"""Проверка вкладки «Безопасность» через ИИ (DeepSeek/OpenAI-совместимый API)."""
if self._current_is_idx is None:
messagebox.showinfo("Безопасность", "Сначала выберите объект КИИ в списке слева")
return
settings = load_api_settings()
if not settings.get('ai_api_key'):
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
return
# Собираем текущие данные безопасности объекта
self._collect_is()
isys = self.model.information_systems[self._current_is_idx]
security_data = {
'name': isys.name or '',
'description': isys.description or '',
'category': isys.category or '',
'sphere': isys.sphere or '',
'object_type': isys.object_type or '',
'architecture': isys.architecture or '',
'attacker_category': isys.attacker_category or '',
'attacker_characteristics': isys.attacker_characteristics or '',
'threats': isys.threats or '',
'incidents': isys.incidents or '',
'criteria_values': isys.criteria_values or '',
'criteria_justification': isys.criteria_justification or '',
'org_measures': isys.org_measures or '',
'tech_measures': isys.tech_measures or '',
}
self.log(f"🤖 Запрос к ИИ ({settings.get('ai_model') or 'deepseek-chat'}): проверка безопасности")
self._verify_log("Проверка безопасности через ИИ…")
def _run():
try:
from .verify import check_security_ai
result, err = check_security_ai(
security_data,
settings['ai_api_key'],
settings.get('ai_model'),
settings.get('ai_base_url'),
)
self.root.after(0, lambda: self._security_ai_done(result, err))
except Exception as e:
self.root.after(0, lambda: self._security_ai_done(None, str(e)))
threading.Thread(target=_run, daemon=True).start()
def _security_ai_done(self, result, err):
"""Обработка ответа ИИ по безопасности."""
if err:
self._verify_log(f"{err}")
self.log(f"❌ Проверка безопасности: {err}")
return
d = result['data']
lines = [f"Источник: {result['source']}"]
lines.append(f"Оценка: {d.get('rating', 'warning')}")
notes = d.get('notes') or []
if notes:
lines.append("\nЗамечания:")
for n in notes:
lines.append(f"{n}")
props = d.get('proposals') or {}
if any(props.values()):
lines.append("\nПредложения ИИ:")
for key, label in [('attacker_category', 'Нарушитель'),
('threats', 'Угрозы'),
('incidents', 'Инциденты'),
('criteria_values', 'Показатели'),
('criteria_justification', 'Обоснование'),
('org_measures', 'Орг. меры'),
('tech_measures', 'Тех. меры')]:
v = props.get(key)
if v:
lines.append(f" {label}: {v}")
self._verify_log('\n'.join(lines))
self.log(f"🤖 ИИ: оценка {d.get('rating', 'warning')}, замечаний: {len(notes)}")
def _refine_dict_item_dialog(self, base_item):
"""Окно уточнения модели и количества для позиции из словаря.
Возвращает полную строку вида «Windows 10 Pro 5 шт.» или
«Сервер Supermicro SSG-2027R -E1R24N 2 шт.» или None.
"""
import re
# Убираем из базы маркер «(уточнить …)»
base = re.sub(r'\s*\([^)]*уточн[^)]*\)', '', base_item, flags=re.I).strip() or base_item
dialog = tk.Toplevel(self.root)
dialog.title("Уточнить модель и количество")
dialog.geometry("500x180")
dialog.transient(self.root)
dialog.grab_set()
frame = ttk.Frame(dialog, padding=10)
frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(frame, text=f"Позиция: {base}", wraplength=470,
font=('Arial', 9, 'bold')).pack(anchor=tk.W, pady=(0, 8))
ttk.Label(frame, text="Модель / версия / конфигурация (необязательно):").pack(anchor=tk.W)
model_entry = ttk.Entry(frame, width=60)
model_entry.pack(fill=tk.X, pady=(2, 6))
model_entry.focus_set()
ttk.Label(frame, text="Количество:").pack(anchor=tk.W)
count_entry = ttk.Entry(frame, width=20)
count_entry.pack(anchor=tk.W, pady=(2, 6))
result = {}
def _ok():
model = model_entry.get().strip()
count = count_entry.get().strip()
text = base
if model:
text = f"{text} {model}"
if count:
text = f"{text} {count} шт."
result['val'] = text
dialog.destroy()
def _cancel():
result['val'] = None
dialog.destroy()
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X, pady=(6, 0))
ttk.Button(btn_frame, text="✅ ОК", command=_ok).pack(side=tk.RIGHT, padx=2)
ttk.Button(btn_frame, text="Отмена", command=_cancel).pack(side=tk.RIGHT, padx=2)
dialog.bind('<Return>', lambda e: _ok())
dialog.bind('<Escape>', lambda e: _cancel())
dialog.wait_window()
return result.get('val')
def _select_sw_from_dict(self, listbox, dict_name):
"""Выбор позиций из словаря (JSON) — можно несколько."""
import json
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', dict_name)
try:
with open(dict_path, 'r', encoding='utf-8') as f:
items = json.load(f)
title = "Выбор из словаря"
self._select_from_dialog(title, items, listbox)
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
def _sw_dialog(self, title, initial=""):
"""Большое окно для ввода/редактирования позиции ПО."""
dialog = tk.Toplevel(self.root)
dialog.title(title)
dialog.geometry("560x220")
dialog.transient(self.root)
dialog.grab_set()
dialog.resizable(True, True)
frame = ttk.Frame(dialog, padding=10)
frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(frame, text="Текст позиции:").pack(anchor=tk.W)
txt = tk.Text(frame, width=70, height=6, wrap=tk.WORD)
txt.pack(fill=tk.BOTH, expand=True, pady=5)
txt.insert('1.0', initial)
txt.focus_set()
result = {}
def _ok():
result['val'] = txt.get('1.0', 'end-1c').strip()
dialog.destroy()
def _cancel():
result['val'] = None
dialog.destroy()
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X)
ttk.Button(btn_frame, text="✅ ОК", command=_ok).pack(side=tk.RIGHT, padx=2)
ttk.Button(btn_frame, text="Отмена", command=_cancel).pack(side=tk.RIGHT, padx=2)
dialog.bind('<Return>', lambda e: _ok())
dialog.bind('<Escape>', lambda e: _cancel())
dialog.wait_window()
return result.get('val')
def _add_sw_item(self, listbox):
"""Добавить строку в список ПО (большое окно)."""
item = self._sw_dialog("Добавить позицию")
if item:
listbox.insert(tk.END, item)
def _del_sw_item(self, listbox):
"""Удалить выбранную строку из списка ПО."""
sel = listbox.curselection()
if sel:
listbox.delete(sel[0])
def _edit_sw_item(self, listbox):
"""Редактировать выбранную строку списка ПО (большое окно)."""
sel = listbox.curselection()
if not sel:
messagebox.showinfo("Редактировать", "Сначала выберите позицию в списке")
return
current = listbox.get(sel[0])
item = self._sw_dialog("Редактировать позицию", initial=current)
if item is not None:
listbox.delete(sel[0])
listbox.insert(sel[0], item)
# ============================================================
# 3. ИИ-ПРОВЕРКА (добавить в verify.py)
# ============================================================
def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-проверка заполненной вкладки «Безопасность» объекта КИИ (236 приказ).
Возвращает (результат, ошибка): результат dict с 'оценка', 'замечания', 'предложения'.
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
system = (
"Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, "
"приказ ФСТЭК России от 25.12.2017 № 239 «Об утверждении Требований по обеспечению "
"безопасности значимых объектов КИИ» и приказ № 236). Проверь заполненные сведения "
"об объекте КИИ и дай рекомендации. Верни ТОЛЬКО 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
# ============================================================
# 4. МАППИНГИ ПОЛЕЙ
# ============================================================
# В _collect_is() (список виджетов → модель):
# ('is_attacker_category_combo', 'attacker_category'),
# ('is_attacker_characteristics_listbox', 'attacker_characteristics'),
# ('is_threats_listbox', 'threats'),
# ('is_incidents_listbox', 'incidents'),
# ('is_criteria_values_listbox', 'criteria_values'),
# ('is_criteria_justification_listbox', 'criteria_justification'),
# ('is_org_measures_listbox', 'org_measures'),
# ('is_tech_measures_listbox', 'tech_measures'),
# В _on_is_select() — обратный порядок (combo → .set(), listbox → split('\n'))
# После построения вкладок вызвать: self._update_threats_date_label()
+29 -8
View File
@@ -31,9 +31,10 @@ class DokoGenApp:
def __init__(self, root: tk.Tk): def __init__(self, root: tk.Tk):
self.root = root self.root = root
root.title("ДоКоГеНеРаТоР — 152-ФЗ (Персональные данные)") root.title("ДоКоГеНеРаТоР — MU")
root.geometry("1200x800") root.geometry("1200x800")
root.minsize(900, 600) root.minsize(900, 600)
self._set_app_icon()
self.model = Company() self.model = Company()
self.current_file: Optional[str] = None self.current_file: Optional[str] = None
@@ -43,7 +44,7 @@ class DokoGenApp:
self._last_template_dir: str = "" self._last_template_dir: str = ""
self._autosave_path = os.path.join( self._autosave_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), os.path.dirname(os.path.abspath(__file__)),
'.autosave', 'autosave_152.json' '.autosave', 'autosave_mufz.json'
) )
# Путь к файлу настроек импорта (словарь «что и откуда брать») # Путь к файлу настроек импорта (словарь «что и откуда брать»)
@@ -81,6 +82,26 @@ class DokoGenApp:
self._load_autosave() self._load_autosave()
self._start_autosave_timer() self._start_autosave_timer()
def _set_app_icon(self):
"""Устанавливает иконку приложения на окно (заголовок, панель задач).
Работает и из исходников, и из exe (frozen):
- исходники: dokogen/icon.ico рядом с модулем
- exe: dokogen/icon.ico из _MEIPASS (ресурсы PyInstaller)
"""
try:
if getattr(sys, 'frozen', False):
base = getattr(sys, '_MEIPASS', os.path.dirname(sys.executable))
ico = os.path.join(base, 'dokogen', 'icon.ico')
if not os.path.exists(ico):
ico = os.path.join(os.path.dirname(sys.executable), 'icon.ico')
else:
ico = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'icon.ico')
if os.path.exists(ico):
self.root.iconbitmap(ico)
except Exception:
pass
# ==================== МЕНЮ ==================== # ==================== МЕНЮ ====================
def _build_menu(self): def _build_menu(self):
menubar = tk.Menu(self.root) menubar = tk.Menu(self.root)
@@ -1736,7 +1757,7 @@ class DokoGenApp:
is_list=self.model.information_systems, is_list=self.model.information_systems,
doc_number=self.doc_number_var.get(), doc_number=self.doc_number_var.get(),
doc_date=self.doc_date_var.get(), doc_date=self.doc_date_var.get(),
direction='152fz', direction='mufz',
) )
results = [] results = []
@@ -1825,7 +1846,7 @@ class DokoGenApp:
def import_from_survey(self): def import_from_survey(self):
path = filedialog.askopenfilename( path = filedialog.askopenfilename(
title="Выберите опросный лист (152-ФЗ)", title="Выберите опросный лист (MU)",
filetypes=[("Excel", "*.xlsx *.xls")] filetypes=[("Excel", "*.xlsx *.xls")]
) )
if not path: if not path:
@@ -1835,8 +1856,8 @@ class DokoGenApp:
# Импорт в отдельном потоке # Импорт в отдельном потоке
def _run(): def _run():
try: try:
from .importer import import_152fz from .importer import import_mufz
data = import_152fz(path, self.log, settings_path=self.import_settings_path) data = import_mufz(path, self.log, settings_path=self.import_settings_path)
if data: if data:
self._apply_imported_data(data) self._apply_imported_data(data)
except ImportError: except ImportError:
@@ -2182,7 +2203,7 @@ class DokoGenApp:
def show_about(self): def show_about(self):
messagebox.showinfo("О программе", messagebox.showinfo("О программе",
"ДоКоГеНеРаТоР — 152-ФЗ\n" "ДоКоГеНеРаТоР — MU\n"
"Версия 1.0\n" "Версия 1.0\n"
"Генератор документов из шаблонов") "Генератор документов из шаблонов")
@@ -2230,7 +2251,7 @@ class DokoGenApp:
def download_survey(self): def download_survey(self):
from .help_docs import ensure_docs, survey_path from .help_docs import ensure_docs, survey_path
ensure_docs() ensure_docs()
self._save_resource(survey_path(), "Опросный_лист_152_ФЗ.xlsx", "Сохранить пустой опросник") self._save_resource(survey_path(), "Опросный_лист_MU.xlsx", "Сохранить пустой опросник")
def main(): def main():
+4 -4
View File
@@ -19,7 +19,7 @@ def _norm_name(s):
# ОПИСАНИЕ ПЕРЕМЕННЫХ # ОПИСАНИЕ ПЕРЕМЕННЫХ
# ============================================================ # ============================================================
# Формат: (имя_переменной, группа, описание, ключ_резолвера) # Формат: (имя_переменной, группа, описание, ключ_резолвера)
# Группы: A — общие, B — 152-ФЗ # Группы: A — общие, B — MU
VARIABLE_DEFS = [ VARIABLE_DEFS = [
# === Полное название организации (6 падежей) === # === Полное название организации (6 падежей) ===
@@ -68,7 +68,7 @@ VARIABLE_DEFS = [
('{{document_date}}', 'A', 'Дата документа', 'doc_date'), ('{{document_date}}', 'A', 'Дата документа', 'doc_date'),
('{{document_number}}', 'A', 'Номер документа', 'doc_number'), ('{{document_number}}', 'A', 'Номер документа', 'doc_number'),
# === 152-ФЗ === # === MU ===
('{{is_security_requirements}}', 'B', 'Требования уровня защищённости', 'security_reqs'), ('{{is_security_requirements}}', 'B', 'Требования уровня защищённости', 'security_reqs'),
('{{is_list}}', 'B', 'Список всех ИС (нумерованный)', 'is_list'), ('{{is_list}}', 'B', 'Список всех ИС (нумерованный)', 'is_list'),
('{{is_name}}', 'B', 'Наименование первой ИС', 'is:name'), ('{{is_name}}', 'B', 'Наименование первой ИС', 'is:name'),
@@ -138,7 +138,7 @@ def build_replacements(
is_list: List = None, is_list: List = None,
doc_number: str = "", doc_number: str = "",
doc_date: str = "", doc_date: str = "",
direction: str = "152fz", direction: str = "mufz",
) -> Dict[str, str]: ) -> Dict[str, str]:
"""Строит полный словарь замен {{переменная}} → значение.""" """Строит полный словарь замен {{переменная}} → значение."""
if is_list is None: if is_list is None:
@@ -159,7 +159,7 @@ def build_replacements(
replacements = {} replacements = {}
for var_name, group, desc, source in VARIABLE_DEFS: for var_name, group, desc, source in VARIABLE_DEFS:
if direction == '152fz' and group not in ('A', 'B'): if direction == 'mufz' and group not in ('A', 'B'):
continue continue
value = _resolve(source, ctx) value = _resolve(source, ctx)
replacements[var_name] = str(value) if value else '' replacements[var_name] = str(value) if value else ''
+40 -1
View File
@@ -389,6 +389,45 @@ def compare_company_values(old_data, new_data):
return diffs return diffs
def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-проверка заполненной вкладки «Безопасность» объекта КИИ (236 приказ)."""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
system = (
"Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, "
"приказ ФСТЭК России от 25.12.2017 № 239 «Об утверждении Требований по обеспечению "
"безопасности значимых объектов КИИ» и приказ № 236). Проверь заполненные сведения "
"об объекте КИИ и дай рекомендации. Верни ТОЛЬКО 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): def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None):
"""ИИ-проверка реквизитов организации. """ИИ-проверка реквизитов организации.
@@ -399,7 +438,7 @@ def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None
if not api_key: if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)" return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
system = ( system = (
"Ты — юрист по защите персональных данных (152-ФЗ). Проверь реквизиты организации. " "Ты — юрист по защите персональных данных (MU). Проверь реквизиты организации. "
"Верни ТОЛЬКО JSON: " "Верни ТОЛЬКО JSON: "
'{"оценка": "ok|warning|error", ' '{"оценка": "ok|warning|error", '
'"замечания": ["..."], ' '"замечания": ["..."], '
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""DokoGen — Генератор документов 152-ФЗ """DokoGen — Генератор документов MU
Точка входа: python main.py Точка входа: python main.py
""" """
import sys import sys
+2 -2
View File
@@ -87,7 +87,7 @@ def test_zip_loop_expansion():
is_list=company.information_systems, is_list=company.information_systems,
doc_number='001', doc_number='001',
doc_date='31.07.2026', doc_date='31.07.2026',
direction='152fz' direction='mufz'
) )
loops = replacements.get('_loops', {}) loops = replacements.get('_loops', {})
@@ -135,7 +135,7 @@ def test_zip_loop_expansion():
is_list=company.information_systems, is_list=company.information_systems,
doc_number='001', doc_number='001',
doc_date='31.07.2026', doc_date='31.07.2026',
direction='152fz' direction='mufz'
) )
try: try: