# -*- coding: utf-8 -*- """DokoGen — справка и инструкции (Word) + пустой опросник (Excel). Файлы генерируются при первом обращении и кладутся в папку docs/ рядом с программой (при сборке .exe — рядом с exe): - Инструкция_пользователя.docx - Инструкция_администратора.docx - Опросный_лист_149_FZ.xlsx (пустой шаблон) """ import os import sys import shutil try: import docx from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH DOCX_AVAILABLE = True except ImportError: DOCX_AVAILABLE = False def docs_dir(): """Папка docs рядом с exe (frozen) или рядом с модулем.""" if getattr(sys, 'frozen', False): base = os.path.dirname(sys.executable) else: base = os.path.dirname(os.path.abspath(__file__)) d = os.path.join(base, 'docs') os.makedirs(d, exist_ok=True) return d def user_manual_path(): return os.path.join(docs_dir(), 'Инструкция_пользователя.docx') def admin_manual_path(): return os.path.join(docs_dir(), 'Инструкция_администратора.docx') def variables_reference_path(): return os.path.join(docs_dir(), 'Переменные_шаблонов.docx') def survey_path(): return os.path.join(docs_dir(), 'Опросный_лист_149_FZ.xlsx') def _h(doc, text, size=14): p = doc.add_paragraph() run = p.add_run(text) run.bold = True run.font.size = Pt(size) return p def _p(doc, text, bold=False): p = doc.add_paragraph() run = p.add_run(text) run.bold = bold run.font.size = Pt(11) return p def _bullet(doc, text): p = doc.add_paragraph(style='List Bullet') run = p.add_run(text) run.font.size = Pt(11) return p def _generate_variables_reference(path): """Справочник переменных шаблонов (Word).""" try: from .variables import VARIABLE_DEFS except Exception: VARIABLE_DEFS = [] doc = docx.Document() _h(doc, 'Переменные шаблонов') _p(doc, 'Список переменных для шаблонов Word. Переменная подставляется в текст ' 'как есть: {{имя_переменной}}. Циклы: ... .') groups = {'A': 'Общие переменные', 'B': '149-ФЗ (117 ПР ФСТЭК) (персональные данные)'} by_group = {'A': [], 'B': []} for var, group, desc, _ in VARIABLE_DEFS: by_group.setdefault(group, []).append((var, desc)) for gkey, title in groups.items(): items = by_group.get(gkey, []) if not items: continue _h(doc, title, 12) for var, desc in items: _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, '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) def _generate_user_manual(path): """Инструкция пользователя: программа, циклы, переменные, проверки.""" doc = docx.Document() _h(doc, 'ДоКоГеНеРаТоР — 149-ФЗ (117 ПР ФСТЭК)') _h(doc, 'Инструкция пользователя', 13) _h(doc, '1. Что это за программа', 12) _p(doc, 'Программа автоматически заполняет шаблоны документов по 149-ФЗ (117 ПР ФСТЭК) ' '(акты обследования, политики, инструкции, приказы и т.д.) данными ' 'об организации, информационных системах, комиссии и сотрудниках. ' 'Данные можно ввести вручную на вкладках или импортировать из ' 'опросного листа Excel.') _h(doc, '2. Основные вкладки', 12) _bullet(doc, 'Организация — реквизиты, адреса, руководитель, склонения названий, проверка данных (Морфер / DaData / ИИ).') _bullet(doc, 'Информационные системы — ИС, категории ПДн, действия с ПДн, средства защиты, пользователи.') _bullet(doc, 'Комиссия — состав комиссии по классификации ИСПДн.') _bullet(doc, 'Пользователи — сотрудники с доступом к ПДн.') _bullet(doc, 'Бумажные документы — бумажные носители ПДн и места хранения.') _bullet(doc, 'Генерация — выбор шаблонов и запуск генерации.') _bullet(doc, 'Лог — журнал действий, экспорт в файл.') _h(doc, '3. Переменные в шаблонах', 12) _p(doc, 'В шаблонах Word используются переменные в двойных фигурных скобках, ' 'например {{company_full_name}}, {{inn}}, {{chief_fio_initials}}. ' 'Полный список — в пункте меню «Справка → Переменные шаблонов».') _p(doc, 'Переменные, которые не удалось заменить (нет данных), подсвечиваются ' 'КРАСНЫМ маркером. Незаполненная информация заменяется прочерком «—» ' 'и подсвечивается ЖЁЛТЫМ.') _h(doc, '4. Циклы (повторяющиеся блоки)', 12) _p(doc, 'Если в документе нужно повторить блок для каждой ИС, комиссии, ' 'сотрудника или бумажного документа — используются маркеры цикла:') _p(doc, ' ... {{item.name}} ... ', 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, '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_поле}}.') _h(doc, '5. Импорт из опросника', 12) _p(doc, 'Меню «Генерация → Импорт из опросника» (или кнопка «📥 Импорт»). ' 'Выберите заполненный опросный лист Excel — данные подтянутся ' 'автоматически. Что именно импортировать и откуда — настраивается ' 'в файле import_settings.json (см. инструкцию администратора).') _h(doc, '6. Проверка данных', 12) _bullet(doc, 'Склонение — Морфер 3.0 (бесплатно, без ключа) или ИИ.') _bullet(doc, 'Реквизиты — DaData (по ИНН) или ИИ.') _bullet(doc, 'ИИ-результаты помечаются дисклеймером «сгенерировано ИИ — перепроверьте».') _bullet(doc, 'Ключи API задаются в меню «Файл → Настройки».') _h(doc, '7. Генерация', 12) _p(doc, '1) Укажите папку с шаблонами (кнопка «📁 Шаблоны»).\n' '2) Выберите нужные шаблоны в списке.\n' '3) Укажите папку для результатов.\n' '4) Нажмите «⚡ Генерация».') _p(doc, 'Результаты сохраняются в выбранную папку. Если какой-то документ ' 'не создался — смотрите вкладку «Лог»: там будет причина.') doc.save(path) def _generate_admin_manual(path): """Инструкция администратора: словари, API-ключи, лимиты.""" doc = docx.Document() _h(doc, 'ДоКоГеНеРаТоР — 149-ФЗ (117 ПР ФСТЭК)') _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, 'dokogen.log — журнал работы (пишется автоматически).') _bullet(doc, 'docs/ — инструкции и пустой опросник (создаются автоматически).') _h(doc, '2. Словари (папка dictionaries)', 12) _p(doc, 'JSON-файлы со списками значений для выпадающих списков. ' 'Формат — простой массив строк:') _p(doc, '[\n "Антивирусное ПО",\n "СКЗИ (криптография)",\n "Межсетевой экран"\n]', bold=True) _p(doc, 'Чтобы добавить значение — откройте файл в Блокноте, добавьте строку ' 'через запятую и сохраните. Программа перечитает файл при следующем ' 'открытии окна выбора.') _h(doc, '3. Словарь импорта (import_settings.json)', 12) _p(doc, 'Определяет, какие поля опросника импортируются и откуда. Каждое правило:') _p(doc, '{"name": "Полное наименование организации", "var": "fullName", ' '"cell": "\'Общие сведения\'!B2", "keywords": "полное наименование организации"}', bold=True) _bullet(doc, 'cell — конкретная ячейка (если заполнена — берём оттуда);') _bullet(doc, 'keywords — регулярное выражение для поиска строки-вопроса в колонке A;') _bullet(doc, 'var — ключ в данных программы (fullName, inn, kpp, ogrn и т.д.).') _p(doc, 'Также настраиваются: common_sheet_keyword (лист «Общие сведения»), ' 'employees_sheet_keyword (лист «Сотрудники»), is_sheet_exclude_keywords ' '(какие листы НЕ ИС), value_column (колонка значения, 1 = B).') _h(doc, '4. API-ключи (меню «Файл → Настройки»)', 12) _p(doc, 'DaData — проверка организации по ИНН/ОГРН (наименование, адрес, КПП, руководитель).') _bullet(doc, 'Где взять: https://dadata.ru → регистрация → раздел API. Бесплатный тариф ~10 000 запросов в день.') _bullet(doc, 'Ключ: секция «Статический API-ключ» (формат: буквы и цифры).') _p(doc, 'ИИ (OpenAI-совместимый: DeepSeek, ChatGPT и др.) — проверка склонений и реквизитов.') _bullet(doc, 'DeepSeek: https://platform.deepseek.com → API Keys. Модель deepseek-chat. Дешёвая, ключ пополняется деньгами.') _bullet(doc, 'ChatGPT/OpenAI: https://platform.openai.com → API Keys. Модели gpt-4o-mini, gpt-4o.') _bullet(doc, 'Базовый URL: для DeepSeek можно оставить пустым; для OpenAI — https://api.openai.com.') _p(doc, 'Лимиты запросов: зависят от тарифа. DaData — ~10 000/сутки на бесплатном тарифе. ' 'DeepSeek/OpenAI — оплата за токены, лимиты настраиваются в личном кабинете. ' 'Рекомендуется не превышать несколько сотен проверок в день, чтобы не упереться в лимиты.') _h(doc, '5. Морфер 3.0', 12) _p(doc, 'Бесплатный веб-сервис склонения слов и ФИО. Ключ не требуется — ' 'программа обращается к https://ws3.morpher.ru автоматически.') _h(doc, '6. Логи', 12) _p(doc, 'Все действия (импорт, генерация, обращения к Морфер/DaData/ИИ с ответами) ' 'записываются в файл dokogen.log рядом с программой. Экспорт лога — ' 'кнопка «💾 Экспорт логов» на вкладке «Лог». При проблемах присылайте ' 'этот файл разработчику.') _h(doc, '7. Сборка .exe', 12) _p(doc, 'Программа собирается с PyInstaller: pyinstaller --onefile --windowed main.py. ' 'Файлы настроек (api_settings.json, import_settings.json, dictionaries/, docs/) ' 'создаются автоматически рядом с .exe при первом запуске и остаются ' 'редактируемыми.') doc.save(path) def _ensure_survey(): """Копирует пустой опросник в docs/, если его ещё нет.""" target = survey_path() if os.path.exists(target): return target # Ищем шаблон опросника: рядом с программой или встроенный candidates = [ os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'Опросный_лист_149_FZ.xlsx'), os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_149_FZ.xlsx'), ] for c in candidates: if os.path.exists(c): shutil.copy(c, target) return target return None def ensure_docs(): """Создаёт инструкции и опросник при первом обращении.""" created = [] if DOCX_AVAILABLE: up = user_manual_path() if not os.path.exists(up): try: _generate_user_manual(up) created.append(up) except Exception: pass ap = admin_manual_path() if not os.path.exists(ap): try: _generate_admin_manual(ap) created.append(ap) except Exception: pass vp = variables_reference_path() if not os.path.exists(vp): try: _generate_variables_reference(vp) created.append(vp) except Exception: pass sp = _ensure_survey() if sp and os.path.exists(sp): created.append(sp) return created