fix: аудит ветки source — критические и высокие замечания

- api_settings: убраны реальные API-ключи из кода (чтение из env/файла) — 1.1
- variables: {{is_security_requirements}} больше не подставляет всегда УЗ-4;
  extended_fields/defence_level передаются в контекст — 1.2
- defence.py: эталонная таблица ПП-1119 (п.9-12), 48 комбинаций;
  ui.py: авторасчёт УЗ переведён на неё (public больше не 'всегда 4',
  спец/био/общедоступные/иные разделены) — 2.1
- generator: убран дубль хвоста параграфа в _highlight_unmatched — 2.2
- importer: категории ПДн взаимоисключающие ('иные' только если нет
  спец/био); УЗ пересчитывается при импорте — 2.3
- import_settings(.py/.json): убран перехват 'администратора безопасности'
  правилом ответственного за ПДн — 3.1
- variables: {{category}}/{{is.category}} резолвятся из personal_data_category — 3.2
- verify: ИИ-предложения ИНН/ОГРН/КПП валидируются контрольными суммами
  перед применением — 3.3
- models: defence_level None вместо '' (не маскирует 'не рассчитано')
- .gitignore: api_settings.json/.env не попадают в git
- test_defence.py: юнит-тест всех 48 комбинаций таблицы УЗ
This commit is contained in:
LocoAgent
2026-08-05 08:58:17 +04:00
parent ac122451b3
commit 230a392a15
12 changed files with 277 additions and 57 deletions
+17 -4
View File
@@ -139,8 +139,14 @@ def build_replacements(
doc_number: str = "",
doc_date: str = "",
direction: str = "152fz",
extended_fields: Dict = None,
) -> Dict[str, str]:
"""Строит полный словарь замен {{переменная}} → значение."""
"""Строит полный словарь замен {{переменная}} → значение.
extended_fields — доп. поля для контекста (например, {'defence_level': '2'}).
Если не задано — уровень берётся из первой ИС (is_list[0].defence_level),
чтобы {{is_security_requirements}} не подставлял всегда УЗ-4.
"""
if is_list is None:
is_list = []
@@ -148,6 +154,10 @@ def build_replacements(
commission_data = _build_commission_data(company)
# ---------- Контекст ----------
if not extended_fields and is_list:
dl = getattr(is_list[0], 'defence_level', '') or ''
if dl:
extended_fields = {'defence_level': dl}
ctx = {
'company': company,
'is_list': is_list,
@@ -155,6 +165,7 @@ def build_replacements(
'doc_number': doc_number or '',
'doc_date': doc_date or '',
'direction': direction,
'extended_fields': extended_fields or {},
}
replacements = {}
@@ -447,7 +458,7 @@ def _resolve(source: str, ctx: Dict) -> str:
field_map = {
'name': 'name',
'description': 'description',
'category': 'category',
'category': lambda: ', '.join(first.personal_data_category) or first.category or '',
'defence_level': 'defence_level',
'threat_type': 'threat_type',
'pd_list': lambda: ', '.join(first.personal_data_list or []),
@@ -502,10 +513,12 @@ def _get_fio(company: Company, who: str) -> str:
def _get_security_text(ctx: Dict) -> str:
"""Возвращает текст требований для уровня защищённости."""
defence_level = ctx.get('extended_fields', {}).get('defence_level', '4')
defence_level = ctx.get('extended_fields', {}).get('defence_level') or ''
is_list = ctx.get('is_list', [])
if not defence_level and is_list:
defence_level = str(getattr(is_list[0], 'defence_level', '4') or '4')
defence_level = str(getattr(is_list[0], 'defence_level', '') or '')
if not defence_level:
defence_level = '4'
dl = str(defence_level).strip()[0]
TEXTS = {