feat: переделка под ОКИИ (187-ФЗ) — объекты КИИ вместо ИС, категории 1-3, ОКВЭД, убраны ПДн/бумажные носители
- вкладка «Информационные системы» → «Объекты КИИ», убрана «Структура объекта» - «Тип угроз»/«УЗ» → категория значимости 1/2/3/без категории - удалены Категории ПДн, кол-во записей, перечень/субъекты/действия с ПДн + словари - «Пользователи ИС» → «Пользователи ОКИИ», средства защиты оставлены - вкладка «Бумажные документы» удалена полностью (GUI + логика + модели) - ОКВЭД: поля во вкладке «Организация», словарь okved.json, выбор галочками, импорт - нормализация регистра адреса DaData (КАПС → нормальный) - importer: объекты КИИ (наименование объекта, ПО, СЗИ, пользователи)
This commit is contained in:
+53
-150
@@ -141,8 +141,8 @@ def import_187fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
|
||||
data = {
|
||||
'fullName': '', 'shortName': '', 'addressLegal': '', 'addressActual': '',
|
||||
'email': '', 'phone': '', 'inn': '', 'ogrn': '', 'kpp': '',
|
||||
'okved': '', 'chiefPosition': '', 'chiefFio': '',
|
||||
'ispdnFio': '', 'ispdnPosition': '', 'paperDocuments': '', 'paperStorage': '',
|
||||
'okved': '', 'okvedExtra': '', 'chiefPosition': '', 'chiefFio': '',
|
||||
'ispdnFio': '', 'ispdnPosition': '',
|
||||
'ispdnDepartment': '', 'ispdnEmail': '', 'ispdnPhone': '',
|
||||
'informationSystems': [], 'commission': [], 'employeesAccess': [],
|
||||
}
|
||||
@@ -248,7 +248,7 @@ def _import_187_common(wb, data, settings, log_fn):
|
||||
|
||||
|
||||
def _import_187_is_list(wb, data, settings, log_fn):
|
||||
"""Импорт информационных систем из остальных листов."""
|
||||
"""Импорт объектов КИИ из остальных листов (ОБЪЕКТ №1, №2, ...)."""
|
||||
exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники'])
|
||||
is_sheets = []
|
||||
for sn in wb.sheetnames:
|
||||
@@ -257,7 +257,7 @@ def _import_187_is_list(wb, data, settings, log_fn):
|
||||
continue
|
||||
is_sheets.append(sn)
|
||||
if not is_sheets:
|
||||
log_fn("⚠ Листы ИС не найдены")
|
||||
log_fn("⚠ Листы объектов КИИ не найдены")
|
||||
return
|
||||
|
||||
is_list = []
|
||||
@@ -275,16 +275,20 @@ def _import_187_is_list(wb, data, settings, log_fn):
|
||||
continue
|
||||
|
||||
is_obj = {
|
||||
'name': '', 'description': '', 'room': '', 'employee': '', 'position': '',
|
||||
'personalDataList': [], 'pd_subjects_list': [], 'pd_actions': [],
|
||||
'usersList': [], 'isInternet': False, 'personalDataCount': 'менее 100 000',
|
||||
'defense_tools': [], 'processing_mode': '',
|
||||
'name': '', 'description': '', 'usersList': [],
|
||||
'isInternet': False, 'defense_tools': [], 'software': '',
|
||||
}
|
||||
|
||||
current_section = 'header'
|
||||
pd_items_raw = []
|
||||
subjects_raw = []
|
||||
actions_raw = []
|
||||
defense_keywords = {
|
||||
'антивирус': 'Антивирус',
|
||||
'крипто': 'СКЗИ',
|
||||
'межсетев': 'Межсетевой экран',
|
||||
'сзи.*от несанкционирован': 'СЗИ от НСД',
|
||||
'обнаружен.*вторжен': 'СОВ',
|
||||
'программные.*средств.*модул': 'Доверенная загрузка',
|
||||
'доверен.*загрузк': 'Доверенная загрузка',
|
||||
'гарантирован': 'Доверенная загрузка',
|
||||
}
|
||||
|
||||
for vals in rows:
|
||||
field = (vals[0] or '').strip()
|
||||
@@ -292,159 +296,58 @@ def _import_187_is_list(wb, data, settings, log_fn):
|
||||
val_b = clean_value(vals[1]) if len(vals) > 1 else ''
|
||||
is_checked = val_b.upper() in ('ДА', 'YES', '✓', '✔', '1', 'TRUE', 'ЕСТЬ')
|
||||
|
||||
if 'цель обработки пд' in field_lower or ('цель обработки' in field_lower and ':' in field):
|
||||
current_section = 'purpose'
|
||||
if 'наименование объекта' in field_lower or 'название объекта' in field_lower:
|
||||
is_obj['name'] = val_b
|
||||
log_fn(f" Объект КИИ: {val_b}")
|
||||
continue
|
||||
if 'перечисленные пдн принадлежат' in field_lower or 'принадлеж' in field_lower:
|
||||
current_section = 'subjects'
|
||||
continue
|
||||
if 'структура ис' in field_lower or 'структура информационной' in field_lower:
|
||||
if 'описание объекта' in field_lower or 'описание окки' in field_lower:
|
||||
if val_b:
|
||||
is_obj['structure'] = val_b
|
||||
elif ':' in field:
|
||||
# Значение может быть записано в той же ячейке после двоеточия
|
||||
tail = field.split(':', 1)[1].strip()
|
||||
if tail and 'выбрать' not in tail.lower():
|
||||
is_obj['structure'] = tail
|
||||
is_obj['description'] = val_b
|
||||
continue
|
||||
if 'названия программ' in field_lower or ('программ' in field_lower and 'ос взаимодейств' in field_lower):
|
||||
if 'операционная' in field_lower or 'прикладные программы' in field_lower or 'наименования программно' in field_lower:
|
||||
if val_b:
|
||||
is_obj['software'] = val_b
|
||||
continue
|
||||
if 'локальную сеть' in field_lower:
|
||||
is_obj['is_has_lan'] = is_checked
|
||||
continue
|
||||
if 'количество записей' in field_lower or 'количество субъект' in field_lower:
|
||||
if val_b:
|
||||
is_obj['personalDataCount'] = val_b
|
||||
continue
|
||||
if 'перечень действий с пд' in field_lower or ('действи' in field_lower and 'пд' in field_lower):
|
||||
current_section = 'actions'
|
||||
continue
|
||||
if 'способ обработки пд' in field_lower:
|
||||
if val_b:
|
||||
is_obj['processing_mode'] = val_b
|
||||
is_obj['software'] = (is_obj['software'] + '; ' + val_b) if is_obj['software'] else val_b
|
||||
continue
|
||||
if 'интернет' in field_lower:
|
||||
is_obj['isInternet'] = is_checked
|
||||
continue
|
||||
|
||||
if current_section == 'header':
|
||||
if 'перечень пд' in field_lower:
|
||||
current_section = 'pd_items'
|
||||
elif 'название ис' in field_lower or 'наименование ис' in field_lower or 'наименование информационной' in field_lower or 'наименование объекта' in field_lower or 'название объекта' in field_lower:
|
||||
is_obj['name'] = val_b
|
||||
log_fn(f" ИС: {val_b}")
|
||||
elif 'описание ис' in field_lower or 'описание информационной' in field_lower:
|
||||
if val_b:
|
||||
is_obj['description'] = val_b
|
||||
log_fn(f" Описание: {val_b[:60]}")
|
||||
continue
|
||||
elif 'сотрудник' in field_lower and 'должност' not in field_lower:
|
||||
is_obj['employee'] = val_b
|
||||
if val_b:
|
||||
parts = [p.strip() for p in val_b.replace('\n', ';').split(';') if p.strip()]
|
||||
for part in parts:
|
||||
cleaned = part.strip()
|
||||
if ':' in cleaned and len(cleaned.split(':')[0]) < 20:
|
||||
cleaned = cleaned.split(':', 1)[1].strip()
|
||||
if cleaned and cleaned not in is_obj['usersList']:
|
||||
is_obj['usersList'].append(cleaned)
|
||||
elif 'должность сотрудника' in field_lower:
|
||||
is_obj['position'] = val_b
|
||||
elif 'наименование отдела' in field_lower:
|
||||
pass
|
||||
elif 'номер кабинета' in field_lower:
|
||||
is_obj['room'] = val_b
|
||||
elif 'значение' in field_lower or 'примечание' in field_lower:
|
||||
continue
|
||||
elif val_b and 'выбрать' not in field_lower:
|
||||
if not is_obj['description']:
|
||||
is_obj['description'] = f"{field}: {val_b}"
|
||||
else:
|
||||
is_obj['description'] += f"\n{field}: {val_b}"
|
||||
elif current_section == 'pd_items':
|
||||
if is_checked and 'выбрать' not in field_lower and field_lower != 'значение':
|
||||
pd_items_raw.append(field)
|
||||
if 'цель обработки' in field_lower:
|
||||
current_section = 'purpose'
|
||||
elif current_section == 'purpose':
|
||||
purpose_val = val_b or field or ''
|
||||
if purpose_val and 'выбрать' not in purpose_val.lower() and ':' not in purpose_val[:40]:
|
||||
is_obj['purpose'] = purpose_val
|
||||
log_fn(f" Цель: {purpose_val[:80]}")
|
||||
current_section = 'header'
|
||||
elif current_section == 'subjects':
|
||||
if is_checked and 'выбрать' not in field_lower:
|
||||
subjects_raw.append(field)
|
||||
if 'количество записей' in field_lower or 'действи' in field_lower:
|
||||
continue
|
||||
elif current_section == 'actions':
|
||||
if is_checked and 'выбрать' not in field_lower and 'защита' not in field_lower[:20]:
|
||||
actions_raw.append(field)
|
||||
if 'защита' in field_lower and 'действи' not in field_lower:
|
||||
current_section = 'defense'
|
||||
elif current_section == 'defense':
|
||||
defense_keywords = {
|
||||
'антивирус': 'Антивирус',
|
||||
'крипто': 'СКЗИ',
|
||||
'межсетев': 'Межсетевой экран',
|
||||
'сзи.*от несанкционирован': 'СЗИ от НСД',
|
||||
'обнаружен.*вторжен': 'СОВ',
|
||||
'программные.*средств.*модул': 'Доверенная загрузка',
|
||||
'доверен.*загрузк': 'Доверенная загрузка',
|
||||
'гарантирован': 'Доверенная загрузка',
|
||||
}
|
||||
matched_label = None
|
||||
for kw, label in defense_keywords.items():
|
||||
if re.search(kw, field_lower):
|
||||
matched_label = label
|
||||
break
|
||||
if matched_label and val_b and 'выбрать' not in field_lower:
|
||||
# Без префикса категории: просто название средства (Kaspersky, КриптоПро)
|
||||
is_obj['defense_tools'].append(val_b)
|
||||
elif matched_label:
|
||||
pass
|
||||
elif 'куда' in field_lower or 'передаются' in field_lower:
|
||||
current_section = 'header'
|
||||
elif 'способ обработки' in field_lower or 'локальную сеть' in field_lower or 'интернет' in field_lower:
|
||||
current_section = 'header'
|
||||
|
||||
if pd_items_raw:
|
||||
is_obj['personalDataList'] = pd_items_raw
|
||||
log_fn(f" ПДн: {len(pd_items_raw)}")
|
||||
if subjects_raw:
|
||||
is_obj['pd_subjects_list'] = subjects_raw
|
||||
log_fn(f" Субъектов: {len(subjects_raw)}")
|
||||
if actions_raw:
|
||||
is_obj['pd_actions'] = actions_raw
|
||||
log_fn(f" Действий: {len(actions_raw)}")
|
||||
if 'сотрудник' in field_lower and 'должност' not in field_lower:
|
||||
if val_b:
|
||||
parts = [p.strip() for p in val_b.replace('\n', ';').split(';') if p.strip()]
|
||||
for part in parts:
|
||||
cleaned = part.strip()
|
||||
if ':' in cleaned and len(cleaned.split(':')[0]) < 20:
|
||||
cleaned = cleaned.split(':', 1)[1].strip()
|
||||
if cleaned and cleaned not in is_obj['usersList']:
|
||||
is_obj['usersList'].append(cleaned)
|
||||
continue
|
||||
# Средства защиты
|
||||
matched = False
|
||||
for kw, label in defense_keywords.items():
|
||||
if re.search(kw, field_lower):
|
||||
if val_b and 'выбрать' not in field_lower:
|
||||
is_obj['defense_tools'].append(val_b)
|
||||
matched = True
|
||||
break
|
||||
if matched:
|
||||
continue
|
||||
if val_b and 'выбрать' not in field_lower and field_lower not in ('значение', 'примечание'):
|
||||
# Остальные заполненные поля — в описание (адрес, сфера, тип и т.д.)
|
||||
if not is_obj['description']:
|
||||
is_obj['description'] = f"{field}: {val_b}"
|
||||
else:
|
||||
is_obj['description'] += f"\n{field}: {val_b}"
|
||||
|
||||
if not is_obj['name']:
|
||||
is_obj['name'] = sheet_name.strip()
|
||||
log_fn(f" ⚠ ИС без названия — использовано имя листа: {is_obj['name']}")
|
||||
|
||||
# Автоопределение категорий ПДн
|
||||
pd_cats = []
|
||||
all_pd_text = ' '.join(p.lower() for p in pd_items_raw)
|
||||
special_kw = ['состоян.*здоров', 'национальн', 'политическ', 'религиозн', 'философ', 'судимост', 'интимн']
|
||||
if any(re.search(kw, all_pd_text) for kw in special_kw):
|
||||
pd_cats.append('специальные')
|
||||
bio_kw = ['биометрическ', 'изображен.*лиц', 'голос.*человек', 'папилляр', 'дактилоскоп', 'фото.*изображен']
|
||||
if any(re.search(kw, all_pd_text) for kw in bio_kw):
|
||||
pd_cats.append('биометрические')
|
||||
if pd_items_raw:
|
||||
pd_cats.append('иные')
|
||||
if pd_cats:
|
||||
is_obj['personalDataCategory'] = pd_cats
|
||||
log_fn(f" Категории ПДн: {', '.join(pd_cats)}")
|
||||
log_fn(f" ⚠ Объект без названия — использовано имя листа: {is_obj['name']}")
|
||||
|
||||
is_list.append(is_obj)
|
||||
log_fn(f" ✅ {is_obj['name']} — импортирована")
|
||||
log_fn(f" ✅ {is_obj['name']} — импортирован")
|
||||
|
||||
if is_list:
|
||||
data['informationSystems'] = is_list
|
||||
log_fn(f"\n✅ Всего импортировано ИС: {len(is_list)}")
|
||||
log_fn(f"\n✅ Всего импортировано объектов КИИ: {len(is_list)}")
|
||||
|
||||
|
||||
def _import_187_employees(wb, data, settings, log_fn):
|
||||
@@ -497,7 +400,7 @@ def _import_187_employees(wb, data, settings, log_fn):
|
||||
if 'перечень сотрудников' in a_lower:
|
||||
in_commission = False
|
||||
in_employees = True
|
||||
log_fn(" Секция: сотрудники с доступом к ПДн")
|
||||
log_fn(" Секция: сотрудники с доступом к объектам КИИ")
|
||||
continue
|
||||
if a_lower in ('№ п/п', '№', 'номер', ''):
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user