feat: Add data models for disciplinary actions, organizations, and training centers

- Implemented DisciplinaryAction model for tracking disciplinary measures.
- Created Organization model to represent auditing organizations with relevant details.
- Developed TrainingCenter model for educational and methodological centers.
- Added methods to convert models to dictionaries for easy export.

feat: Implement parsers for auditors and organizations

- Developed AuditorsParser to scrape and parse auditor registry data.
- Created OrganizationsParser for scraping auditing organizations' registry.
- Implemented GenericRegistryParser for handling generic tabular data.

chore: Set up base parser functionality

- Established BaseParser class with common methods for making requests and parsing HTML.
- Added pagination handling and detailed page parsing capabilities.

feat: Implement Excel export functionality

- Created ExcelExporter class for exporting data to Excel files with formatting options.
- Added support for exporting multiple sheets and organization data specifically.

chore: Add logging utilities

- Implemented setup_logger function for consistent logging across the application.
- Configured logging to output to both console and file.

chore: Update requirements and scripts

- Added necessary dependencies for parsing, Excel handling, and logging.
- Created run.sh and run.fish scripts for easy execution of the parser.
This commit is contained in:
ada
2025-10-25 20:31:45 +03:00
commit 12d5c6eaff
23 changed files with 2210 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
"""
Модели данных
"""
from .organization import Organization
from .auditor import Auditor
from .certificate import Certificate
from .training_center import TrainingCenter
from .disciplinary_action import DisciplinaryAction
from .audit_network import AuditNetwork
__all__ = [
"Organization",
"Auditor",
"Certificate",
"TrainingCenter",
"DisciplinaryAction",
"AuditNetwork",
]
+55
View File
@@ -0,0 +1,55 @@
"""
Модель данных для сети аудиторских организаций
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from datetime import datetime
@dataclass
class AuditNetwork:
"""Модель сети аудиторских организаций"""
# Основная информация
name: str
network_type: str # "Российская" или "Международная"
registration_number: Optional[str] = None
# Участники
member_organizations: List[str] = field(default_factory=list)
member_count: Optional[int] = None
# Дополнительная информация
country: Optional[str] = None
headquarters: Optional[str] = None
website: Optional[str] = None
description: Optional[str] = None
# Контакты
contact_person: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
# Метаинформация
source_url: Optional[str] = None
parsed_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Преобразование в словарь для экспорта"""
return {
"Название сети": self.name,
"Тип": self.network_type,
"Регистрационный номер": self.registration_number or "",
"Количество участников": self.member_count or "",
"Участники": ", ".join(self.member_organizations[:10]), # Первые 10
"Страна": self.country or "",
"Штаб-квартира": self.headquarters or "",
"Сайт": self.website or "",
"Описание": self.description or "",
"Контактное лицо": self.contact_person or "",
"Телефон": self.phone or "",
"Email": self.email or "",
"URL источника": self.source_url or "",
"Дата сбора данных": self.parsed_at.strftime("%d.%m.%Y %H:%M:%S"),
}
+77
View File
@@ -0,0 +1,77 @@
"""
Модель данных для аудитора
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from datetime import datetime
@dataclass
class Auditor:
"""Модель аудитора или индивидуального аудитора"""
# Основная информация
full_name: str
ornz: str # Основной регистрационный номер записи
certificate_number: str
region: str
status: Optional[str] = None
# Дополнительная информация
inn: Optional[str] = None
snils: Optional[str] = None
qualification: Optional[str] = None
# Организация (для аудиторов)
organization_name: Optional[str] = None
organization_inn: Optional[str] = None
# Даты
certificate_issue_date: Optional[datetime] = None
membership_start_date: Optional[datetime] = None
membership_end_date: Optional[datetime] = None
# Дополнительные данные
education: Optional[str] = None
experience_years: Optional[int] = None
specializations: List[str] = field(default_factory=list)
# Метаинформация
source_url: Optional[str] = None
parsed_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Преобразование в словарь для экспорта"""
return {
"ФИО": self.full_name,
"ОРНЗ": self.ornz,
"Номер аттестата": self.certificate_number,
"Регион": self.region,
"Статус": self.status or "",
"ИНН": self.inn or "",
"СНИЛС": self.snils or "",
"Квалификация": self.qualification or "",
"Организация": self.organization_name or "",
"ИНН организации": self.organization_inn or "",
"Дата выдачи аттестата": (
self.certificate_issue_date.strftime("%d.%m.%Y")
if self.certificate_issue_date
else ""
),
"Дата начала членства": (
self.membership_start_date.strftime("%d.%m.%Y")
if self.membership_start_date
else ""
),
"Дата окончания членства": (
self.membership_end_date.strftime("%d.%m.%Y")
if self.membership_end_date
else ""
),
"Образование": self.education or "",
"Стаж (лет)": self.experience_years or "",
"Специализации": ", ".join(self.specializations),
"URL источника": self.source_url or "",
"Дата сбора данных": self.parsed_at.strftime("%d.%m.%Y %H:%M:%S"),
}
+59
View File
@@ -0,0 +1,59 @@
"""
Модель данных для квалификационного аттестата
"""
from dataclasses import dataclass, field
from typing import Optional, Dict
from datetime import datetime
@dataclass
class Certificate:
"""Модель квалификационного аттестата аудитора"""
# Основная информация
certificate_number: str
auditor_full_name: str
issue_date: datetime
status: str
# Дополнительная информация
qualification_type: Optional[str] = None
issuer: Optional[str] = None
validity_period: Optional[str] = None
# Данные аудитора
auditor_inn: Optional[str] = None
auditor_snils: Optional[str] = None
# Причина аннулирования (для аннулированных)
cancellation_reason: Optional[str] = None
cancellation_date: Optional[datetime] = None
# Метаинформация
source_url: Optional[str] = None
parsed_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Преобразование в словарь для экспорта"""
return {
"Номер аттестата": self.certificate_number,
"ФИО аудитора": self.auditor_full_name,
"Дата выдачи": (
self.issue_date.strftime("%d.%m.%Y") if self.issue_date else ""
),
"Статус": self.status,
"Тип квалификации": self.qualification_type or "",
"Выдан": self.issuer or "",
"Срок действия": self.validity_period or "",
"ИНН аудитора": self.auditor_inn or "",
"СНИЛС аудитора": self.auditor_snils or "",
"Причина аннулирования": self.cancellation_reason or "",
"Дата аннулирования": (
self.cancellation_date.strftime("%d.%m.%Y")
if self.cancellation_date
else ""
),
"URL источника": self.source_url or "",
"Дата сбора данных": self.parsed_at.strftime("%d.%m.%Y %H:%M:%S"),
}
+65
View File
@@ -0,0 +1,65 @@
"""
Модель данных для дисциплинарной меры
"""
from dataclasses import dataclass, field
from typing import Optional, Dict
from datetime import datetime
@dataclass
class DisciplinaryAction:
"""Модель меры дисциплинарного воздействия"""
# Основная информация
subject_name: str # ФИО аудитора или название организации
subject_type: str # "auditor" или "organization"
ornz: str
action_type: str # Тип меры воздействия
# Детали
violation_description: str
decision_date: datetime
decision_number: Optional[str] = None
# Дополнительная информация
inn: Optional[str] = None
region: Optional[str] = None
effective_date: Optional[datetime] = None
expiry_date: Optional[datetime] = None
# Решение
decision_body: Optional[str] = None
appeal_status: Optional[str] = None
# Метаинформация
source_url: Optional[str] = None
parsed_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Преобразование в словарь для экспорта"""
return {
"Субъект": self.subject_name,
"Тип субъекта": (
"Аудитор" if self.subject_type == "auditor" else "Организация"
),
"ОРНЗ": self.ornz,
"Мера воздействия": self.action_type,
"Описание нарушения": self.violation_description,
"Дата решения": (
self.decision_date.strftime("%d.%m.%Y") if self.decision_date else ""
),
"Номер решения": self.decision_number or "",
"ИНН": self.inn or "",
"Регион": self.region or "",
"Дата вступления в силу": (
self.effective_date.strftime("%d.%m.%Y") if self.effective_date else ""
),
"Дата окончания": (
self.expiry_date.strftime("%d.%m.%Y") if self.expiry_date else ""
),
"Орган принявший решение": self.decision_body or "",
"Статус обжалования": self.appeal_status or "",
"URL источника": self.source_url or "",
"Дата сбора данных": self.parsed_at.strftime("%d.%m.%Y %H:%M:%S"),
}
+81
View File
@@ -0,0 +1,81 @@
"""
Модель данных для аудиторской организации
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from datetime import datetime
@dataclass
class Organization:
"""Модель аудиторской организации"""
# Основная информация
name: str
ornz: str # Основной регистрационный номер записи
inn: str
region: str
status: Optional[str] = None
# Дополнительная информация (заполняется при детальном парсе)
full_name: Optional[str] = None
ogrn: Optional[str] = None
kpp: Optional[str] = None
address: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
website: Optional[str] = None
director: Optional[str] = None
# Даты
registration_date: Optional[datetime] = None
membership_start_date: Optional[datetime] = None
membership_end_date: Optional[datetime] = None
# Дополнительные данные
auditors_count: Optional[int] = None
certificates: List[str] = field(default_factory=list)
networks: List[str] = field(default_factory=list)
# Метаинформация
source_url: Optional[str] = None
parsed_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Преобразование в словарь для экспорта"""
return {
"Наименование": self.name,
"Полное наименование": self.full_name or "",
"ОРНЗ": self.ornz,
"ИНН": self.inn,
"КПП": self.kpp or "",
"ОГРН": self.ogrn or "",
"Регион": self.region,
"Статус": self.status or "",
"Адрес": self.address or "",
"Телефон": self.phone or "",
"Email": self.email or "",
"Сайт": self.website or "",
"Руководитель": self.director or "",
"Дата регистрации": (
self.registration_date.strftime("%d.%m.%Y")
if self.registration_date
else ""
),
"Дата начала членства": (
self.membership_start_date.strftime("%d.%m.%Y")
if self.membership_start_date
else ""
),
"Дата окончания членства": (
self.membership_end_date.strftime("%d.%m.%Y")
if self.membership_end_date
else ""
),
"Количество аудиторов": self.auditors_count or "",
"Сертификаты": ", ".join(self.certificates),
"Сети": ", ".join(self.networks),
"URL источника": self.source_url or "",
"Дата сбора данных": self.parsed_at.strftime("%d.%m.%Y %H:%M:%S"),
}
+79
View File
@@ -0,0 +1,79 @@
"""
Модель данных для учебно-методического центра
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from datetime import datetime
@dataclass
class TrainingCenter:
"""Модель учебно-методического центра (УМЦ)"""
# Основная информация
name: str
registration_number: str
inn: str
region: str
status: Optional[str] = None
# Дополнительная информация
full_name: Optional[str] = None
ogrn: Optional[str] = None
kpp: Optional[str] = None
address: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
website: Optional[str] = None
director: Optional[str] = None
# Аккредитация
accreditation_date: Optional[datetime] = None
accreditation_number: Optional[str] = None
programs: List[str] = field(default_factory=list)
# Даты
registration_date: Optional[datetime] = None
exclusion_date: Optional[datetime] = None
exclusion_reason: Optional[str] = None
# Метаинформация
source_url: Optional[str] = None
parsed_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Преобразование в словарь для экспорта"""
return {
"Наименование": self.name,
"Полное наименование": self.full_name or "",
"Регистрационный номер": self.registration_number,
"ИНН": self.inn,
"КПП": self.kpp or "",
"ОГРН": self.ogrn or "",
"Регион": self.region,
"Статус": self.status or "",
"Адрес": self.address or "",
"Телефон": self.phone or "",
"Email": self.email or "",
"Сайт": self.website or "",
"Руководитель": self.director or "",
"Дата аккредитации": (
self.accreditation_date.strftime("%d.%m.%Y")
if self.accreditation_date
else ""
),
"Номер аккредитации": self.accreditation_number or "",
"Программы": ", ".join(self.programs),
"Дата регистрации": (
self.registration_date.strftime("%d.%m.%Y")
if self.registration_date
else ""
),
"Дата исключения": (
self.exclusion_date.strftime("%d.%m.%Y") if self.exclusion_date else ""
),
"Причина исключения": self.exclusion_reason or "",
"URL источника": self.source_url or "",
"Дата сбора данных": self.parsed_at.strftime("%d.%m.%Y %H:%M:%S"),
}