# -*- coding: utf-8 -*- """DokoGen — Модели данных (только 187-ФЗ, только нужные поля)""" from dataclasses import dataclass, field, asdict from typing import List, Dict, Any def _clean_single_fio(value): """Оставить одно первое ФИО, если в строке их несколько.""" if not value: return value value = str(value).strip() for sep in (',', ';', '\n', '\r'): if sep in value: value = value.split(sep)[0].strip() break words = [w for w in value.split() if w] if len(words) > 3: value = ' '.join(words[:3]) return value.strip() @dataclass class CommissionMember: """Член комиссии по категорированию объектов КИИ""" role: str = "член комиссии" position: str = "" fio: str = "" @dataclass class InformationSystem: """Объект КИИ (вместо информационной системы 152-ФЗ)""" name: str = "" description: str = "" is_local_network: bool = False is_internet: bool = False category: str = "" # Категория значимости объекта КИИ: 1 / 2 / 3 / без категории users_list: List[str] = field(default_factory=list) # Пользователи ОКИИ defense_tools_list: List[str] = field(default_factory=list) # Средства защиты purpose: str = "" # Назначение объекта (п. 236) structure: str = "" # Структура объекта КИИ software: str = "" # Программное обеспечение # ---- 236 приказ ФСТЭК: сведения об объекте ---- sphere: str = "" # Сфера (область) деятельности object_type: str = "" # Тип объекта (ИС, АСУ, ИТКС) architecture: str = "" # Архитектура объекта typical_object: str = "" # Наименование типового отраслевого объекта КИИ # Субъект (организация, эксплуатирующая объект — как в опроснике) subject_name: str = "" # Наименование организации, эксплуатирующей объект subject_address: str = "" # Адрес местонахождения subject_element: str = "" # Элемент (компонент) объекта, который эксплуатируется лицом subject_inn: str = "" # ИНН лица, эксплуатирующего объект subject_kpp: str = "" # КПП его обособленных подразделений # Сети электросвязи net_category: str = "" # Категория сети электросвязи net_operator: str = "" # Оператор связи и (или) провайдер хостинга net_purpose: str = "" # Цель взаимодействия с сетью электросвязи net_method: str = "" # Способ взаимодействия с указанием типа доступа # Программно-аппаратные средства hw_sw: str = "" # Наименования программно-аппаратных средств и их количество os_sw: str = "" # Общесистемное ПО (ОС, средства виртуализации) app_sw: str = "" # Прикладные программы # Балансовая стоимость объекта КИИ (опросник) balance_value: str = "" # Балансовая стоимость объекта КИИ в рублях # Безопасность (236 приказ) attacker_category: str = "" # Категория нарушителя (внешний/внутренний) threats: str = "" # Основные угрозы безопасности информации (\n — разделитель) incidents: str = "" # Типы компьютерных инцидентов (\n — разделитель) criteria_values: str = "" # Показатели критериев значимости (\n — разделитель) criteria_justification: str = "" # Обоснование полученных значений org_measures: str = "" # Организационные меры (\n — разделитель) tech_measures: str = "" # Технические меры (\n — разделитель) @dataclass class Company: """Организация — основной объект (187-ФЗ)""" # Полное название full_name: str = "" company_name_1: str = "" # именительный company_name_2: str = "" # родительный company_name_3: str = "" # дательный company_name_4: str = "" # винительный company_name_5: str = "" # творительный company_name_6: str = "" # предложный # Краткое название short_name: str = "" short_name_1: str = "" short_name_2: str = "" short_name_3: str = "" short_name_4: str = "" short_name_5: str = "" short_name_6: str = "" # Адрес и реквизиты address: str = "" city_name: str = "" inn: str = "" ogrn: str = "" ogrn_date: str = "" kpp: str = "" okved: str = "" # Основной ОКВЭД okved_extra: List[str] = field(default_factory=list) # Дополнительные ОКВЭД domain_name: str = "" # Доменное имя и внешний сетевой адрес # Должностные лица chief_position: str = "" chief_fio: str = "" responsible_position: str = "" responsible_fio: str = "" admin_position: str = "" admin_fio: str = "" # Документ contract_number: str = "" contract_date: str = "" # Финансовые показатели организации (опросник) profit: str = "" # Сумма прибыли за предыдущий год profit_tax: str = "" # Сумма налога с прибыли за предыдущий год # Списки commission: List[CommissionMember] = field(default_factory=list) information_systems: List[InformationSystem] = field(default_factory=list) employees_access: List[Dict[str, str]] = field(default_factory=list) # Наименование структурного подразделения по безопасности security_department: str = "" def update_declensions(self, inflect_func): """Обновить склонения полного названия""" if self.full_name: inflected = inflect_func(self.full_name) self.company_name_1 = self.full_name self.company_name_2 = inflected.get("gent", "") self.company_name_3 = inflected.get("datv", "") self.company_name_4 = inflected.get("accs", "") self.company_name_5 = inflected.get("ablt", "") self.company_name_6 = inflected.get("loct", "") def update_short_declensions(self, inflect_func): """Обновить склонения сокращённого названия""" if self.short_name: inflected = inflect_func(self.short_name) self.short_name_1 = self.short_name self.short_name_2 = inflected.get("gent", "") self.short_name_3 = inflected.get("datv", "") self.short_name_4 = inflected.get("accs", "") self.short_name_5 = inflected.get("ablt", "") self.short_name_6 = inflected.get("loct", "") def to_dict(self) -> Dict[str, Any]: return asdict(self) @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Company": d = dict(data) if "commission" in d and isinstance(d["commission"], list): _cvalid = {f.name for f in CommissionMember.__dataclass_fields__.values()} d["commission"] = [ CommissionMember(**{k: v for k, v in c.items() if k in _cvalid}) if isinstance(c, dict) else c for c in d["commission"] ] if "information_systems" in d and isinstance(d["information_systems"], list): _ivalid = {f.name for f in InformationSystem.__dataclass_fields__.values()} d["information_systems"] = [ InformationSystem(**{k: v for k, v in isys.items() if k in _ivalid}) if isinstance(isys, dict) else isys for isys in d["information_systems"] ] if "employees_access" in d and not isinstance(d["employees_access"], list): d["employees_access"] = [] # Отбрасываем плейсхолдеры из старых опросников («Формат: Фамилия Имя Отчество») и пустые строки if "employees_access" in d and isinstance(d["employees_access"], list): clean = [] for e in d["employees_access"]: if not isinstance(e, dict): continue fio = str(e.get("fio", "")).strip() pos = str(e.get("position", "")).strip() if fio.lower().startswith("формат:") or fio.lower().startswith("пример:"): continue if not fio and not pos: continue clean.append(e) d["employees_access"] = clean if "okved_extra" in d and not isinstance(d.get("okved_extra"), list): d["okved_extra"] = [] valid = {f.name for f in cls.__dataclass_fields__.values()} obj = cls(**{k: v for k, v in d.items() if k in valid}) # Если в поле ФИО несколько человек — оставить одного (старые опросники) for fio_field in ('chief_fio', 'responsible_fio', 'admin_fio'): val = getattr(obj, fio_field, '') if val: setattr(obj, fio_field, _clean_single_fio(val)) return obj def get_folder_name(self) -> str: if self.short_name: return self.short_name if self.full_name: return self.full_name[:30] return "Новая_организация"