feat: ребрендинг под 187-ФЗ (КИИ)

- Все упоминания 152-ФЗ заменены на 187-ФЗ (заголовок окна, справка,
  импорт, переменные, docstring'и, spec)
- import_152fz -> import_187fz, direction '152fz' -> '187fz'
- autosave_152 -> autosave_187
- Опросный_лист_152_ФЗ.xlsx -> Опросный_лист_187_ФЗ.xlsx
- Промпт ИИ: 'юрист по защите персональных данных (152-ФЗ)' -> 187-ФЗ
This commit is contained in:
2026-08-03 23:25:18 +04:00
parent ac122451b3
commit da4946cb59
11 changed files with 37 additions and 37 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ 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-ФЗ (для «Справка → Пустой опросник») # Пустой опросник 187-ФЗ (для «Справка → Пустой опросник»)
('dokogen/data', 'dokogen/data'), ('dokogen/data', 'dokogen/data'),
# Иконка приложения (для окна программы в exe) # Иконка приложения (для окна программы в exe)
('dokogen/icon.ico', 'dokogen'), ('dokogen/icon.ico', 'dokogen'),
+1 -1
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""DokoGen — Пакет генератора документов 152-ФЗ""" """DokoGen — Пакет генератора документов 187-ФЗ"""
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
+8 -8
View File
@@ -5,7 +5,7 @@
рядом с программой (при сборке .exe — рядом с exe): рядом с программой (при сборке .exe — рядом с exe):
- Инструкция_пользователя.docx - Инструкция_пользователя.docx
- Инструкция_администратора.docx - Инструкция_администратора.docx
- Опросный_лист_152_ФЗ.xlsx (пустой шаблон) - Опросный_лист_187_ФЗ.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(), 'Опросный_лист_187_ФЗ.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': '187-ФЗ (персональные данные)'}
by_group = {'A': [], 'B': []} by_group = {'A': [], 'B': []}
for var, group, desc, _ in VARIABLE_DEFS: for var, group, desc, _ in VARIABLE_DEFS:
by_group.setdefault(group, []).append((var, desc)) by_group.setdefault(group, []).append((var, desc))
@@ -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, 'ДоКоГеНеРаТоР — 187-ФЗ')
_h(doc, 'Инструкция пользователя', 13) _h(doc, 'Инструкция пользователя', 13)
_h(doc, '1. Что это за программа', 12) _h(doc, '1. Что это за программа', 12)
_p(doc, 'Программа автоматически заполняет шаблоны документов по 152-ФЗ ' _p(doc, 'Программа автоматически заполняет шаблоны документов по 187-ФЗ '
'(акты обследования, политики, инструкции, приказы и т.д.) данными ' '(акты обследования, политики, инструкции, приказы и т.д.) данными '
'об организации, информационных системах, комиссии и сотрудниках. ' 'об организации, информационных системах, комиссии и сотрудниках. '
'Данные можно ввести вручную на вкладках или импортировать из ' 'Данные можно ввести вручную на вкладках или импортировать из '
@@ -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, 'ДоКоГеНеРаТоР — 187-ФЗ')
_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', 'Опросный_лист_187_ФЗ.xlsx'),
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_152_ФЗ.xlsx'), os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Опросный_лист_187_ФЗ.xlsx'),
] ]
for c in candidates: for c in candidates:
if os.path.exists(c): if os.path.exists(c):
+9 -9
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""DokoGen — Модуль импорта из Excel (опросный лист 152-ФЗ)""" """DokoGen — Модуль импорта из Excel (опросный лист 187-ФЗ)"""
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_187fz(filepath: str, log_fn: Optional[Callable] = None, settings_path: Optional[str] = None) -> dict:
"""Импорт данных из опросного листа Excel (152-ФЗ). """Импорт данных из опросного листа Excel (187-ФЗ).
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_187_common(wb, data, settings, log_fn)
_import_152_is_list(wb, data, settings, log_fn) _import_187_is_list(wb, data, settings, log_fn)
_import_152_employees(wb, data, settings, log_fn) _import_187_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_187_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_187_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_187_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 — Модели данных (только 187-ФЗ, только нужные поля)"""
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-ФЗ)""" """Организация — основной объект (187-ФЗ)"""
# Полное название # Полное название
full_name: str = "" full_name: str = ""
company_name_1: str = "" # именительный company_name_1: str = "" # именительный
+8 -8
View File
@@ -31,7 +31,7 @@ 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("ДоКоГеНеРаТоР — 187-ФЗ (КИИ)")
root.geometry("1200x800") root.geometry("1200x800")
root.minsize(900, 600) root.minsize(900, 600)
self._set_app_icon() self._set_app_icon()
@@ -44,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_187.json'
) )
# Путь к файлу настроек импорта (словарь «что и откуда брать») # Путь к файлу настроек импорта (словарь «что и откуда брать»)
@@ -1757,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='187fz',
) )
results = [] results = []
@@ -1846,7 +1846,7 @@ class DokoGenApp:
def import_from_survey(self): def import_from_survey(self):
path = filedialog.askopenfilename( path = filedialog.askopenfilename(
title="Выберите опросный лист (152-ФЗ)", title="Выберите опросный лист (187-ФЗ)",
filetypes=[("Excel", "*.xlsx *.xls")] filetypes=[("Excel", "*.xlsx *.xls")]
) )
if not path: if not path:
@@ -1856,8 +1856,8 @@ class DokoGenApp:
# Импорт в отдельном потоке # Импорт в отдельном потоке
def _run(): def _run():
try: try:
from .importer import import_152fz from .importer import import_187fz
data = import_152fz(path, self.log, settings_path=self.import_settings_path) data = import_187fz(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:
@@ -2203,7 +2203,7 @@ class DokoGenApp:
def show_about(self): def show_about(self):
messagebox.showinfo("О программе", messagebox.showinfo("О программе",
"ДоКоГеНеРаТоР — 152-ФЗ\n" "ДоКоГеНеРаТоР — 187-ФЗ\n"
"Версия 1.0\n" "Версия 1.0\n"
"Генератор документов из шаблонов") "Генератор документов из шаблонов")
@@ -2251,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(), "Опросный_лист_187_ФЗ.xlsx", "Сохранить пустой опросник")
def main(): def main():
+4 -4
View File
@@ -19,7 +19,7 @@ def _norm_name(s):
# ОПИСАНИЕ ПЕРЕМЕННЫХ # ОПИСАНИЕ ПЕРЕМЕННЫХ
# ============================================================ # ============================================================
# Формат: (имя_переменной, группа, описание, ключ_резолвера) # Формат: (имя_переменной, группа, описание, ключ_резолвера)
# Группы: A — общие, B — 152-ФЗ # Группы: A — общие, B — 187-ФЗ
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-ФЗ === # === 187-ФЗ ===
('{{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 = "187fz",
) -> 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 == '187fz' 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 ''
+1 -1
View File
@@ -399,7 +399,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-ФЗ). Проверь реквизиты организации. " "Ты — юрист по защите персональных данных (187-ФЗ). Проверь реквизиты организации. "
"Верни ТОЛЬКО 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 — Генератор документов 187-ФЗ
Точка входа: 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='187fz'
) )
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='187fz'
) )
try: try: