282 lines
16 KiB
Python
282 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""DokoGen — справка и инструкции (Word) + пустой опросник (Excel).
|
||
|
||
Файлы генерируются при первом обращении и кладутся в папку docs/
|
||
рядом с программой (при сборке .exe — рядом с exe):
|
||
- Инструкция_пользователя.docx
|
||
- Инструкция_администратора.docx
|
||
- Опросный_лист_PZI.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(), 'Опросный_лист_PZI.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. Переменная подставляется в текст '
|
||
'как есть: {{имя_переменной}}. Циклы: <!-- loop:имя --> ... <!-- loop_end -->.')
|
||
|
||
groups = {'A': 'Общие переменные', 'B': 'PZI (персональные данные)'}
|
||
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, 'ДоКоГеНеРаТоР — PZI')
|
||
_h(doc, 'Инструкция пользователя', 13)
|
||
|
||
_h(doc, '1. Что это за программа', 12)
|
||
_p(doc, 'Программа автоматически заполняет шаблоны документов по PZI '
|
||
'(акты обследования, политики, инструкции, приказы и т.д.) данными '
|
||
'об организации, информационных системах, комиссии и сотрудниках. '
|
||
'Данные можно ввести вручную на вкладках или импортировать из '
|
||
'опросного листа 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, '<!-- 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, '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, 'ДоКоГеНеРаТоР — PZI')
|
||
_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', 'Опросный_лист_PZI.xlsx'),
|
||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_PZI.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
|