Skip to content

Jupyter Notebook

Документация разработчика: Архитектура и модули системы аналитики МНПА Воронежской области

Section titled “Документация разработчика: Архитектура и модули системы аналитики МНПА Воронежской области”

Настоящее руководство содержит детальное техническое описание архитектуры, используемых библиотек и ключевых алгоритмов системы сбора, проверки и анализа муниципальных нормативных правовых актов (МНПА) Воронежской области. Документ составлен в соответствии со стандартами технической документации крупных ИТ-платформ (Microsoft Learn / Google Developers) [1, 2].


1. Архитектура системы и информационные потоки

Section titled “1. Архитектура системы и информационные потоки”

Система представляет собой ETL-конвейер (Extract, Transform, Load), реализованный на языке Python и развернутый в среде Jupyter Notebook.

[ Источники данных ]
┌─────────────────────────────────┼─────────────────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Федеральный │ │ Локальная СУБД │ │ Файлы реестров │
│ портал Минюста │ │ MS SQL Server │ │ (Excel .xlsx) │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
│ HTTP POST │ TCP (pyodbc) │ OS Glob
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Ядро обработки (Pandas DataFrame) │
│ - Слияние данных по ID │
│ - Регулярные выражения (парсинг ссылок и дат) │
│ - Логическая кросс-валидация метаданных │
└────────────────────────────────────────┬────────────────────────────────────────────┘
[ Выходные результаты ]
┌────────────────┬───────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ HTML- │ │ Отчеты │ │ Лог-файл │
│ панели │ │ Excel │ │ ошибок │
└───────────┘ └───────────┘ └───────────┘

2. Стек технологий и используемые библиотеки

Section titled “2. Стек технологий и используемые библиотеки”

Для обеспечения стабильности и производительности системы используется стандартный стек библиотек для анализа данных:

Библиотека Назначение в проекте Ключевые используемые модули и методы
pandas Базовые структуры данных, агрегация, фильтрация и слияние датасетов. DataFrame.groupby(), merge(), concat(), DataFrame.style (условное форматирование).
sqlalchemy Абстракция подключения к реляционной базе данных. create_engine()
pyodbc Низкоуровневый драйвер подключения к MS SQL Server. Используется неявно через SQLAlchemy.
requests Сетевое взаимодействие с закрытым API Федерального регистра. requests.Session(), requests.post() с передачей payload и cookie.
openpyxl Создание, разметка и форматирование файлов таблиц Excel. openpyxl.styles, ExcelWriter (автоматическое масштабирование ширины колонок).
re Разбор и верификация строк методами регулярных выражений. re.compile(), проверка шаблонов ссылок.
glob, os Работа с файловой системой (пакетная загрузка отчетов по районам). glob.glob(), os.path.basename().
IPython Отрисовка интерактивных панелей и таблиц с CSS-стилизацией в среде Jupyter. IPython.display.HTML, display().

3. Детальный разбор ключевых модулей (с цитатами кода)

Section titled “3. Детальный разбор ключевых модулей (с цитатами кода)”

Модуль 3.1. Извлечение данных из MS SQL и парсинг неструктурированных дат

Section titled “Модуль 3.1. Извлечение данных из MS SQL и парсинг неструктурированных дат”

Назначение: Подключение к БД MRegister36 и получение документов за 2026 год. Основная сложность заключается в том, что дата принятия документа часто содержится только внутри текстового поля реквизитов. Для ее извлечения используется встроенный в T-SQL синтаксис PATINDEX и SUBSTRING.

Фрагмент SQL-запроса (Extract & Transform):

-- Извлечение даты принятия из текста реквизитов (для расчетов)
CASE
WHEN PATINDEX('%[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]%', [LegalDocument].[AdoptionDescription]) > 0
AND ISDATE(SUBSTRING([LegalDocument].[AdoptionDescription],
PATINDEX('%[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]%', [LegalDocument].[AdoptionDescription]), 10)) = 1
THEN CONVERT(DATE, SUBSTRING([LegalDocument].[AdoptionDescription],
PATINDEX('%[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]%', [LegalDocument].[AdoptionDescription]), 10), 104)
ELSE NULL
END AS [Дата принятия],

Обработка на стороне Pandas: После загрузки данных выполняется принудительное приведение типов и нормализация временных меток во избежание ошибок округления дат при подсчете задержек:

# Нормализация дат до формата YYYY-MM-DD
if 'Дата принятия' in df.columns:
df['Дата принятия'] = pd.to_datetime(df['Дата принятия'], errors='coerce').dt.normalize()
if 'Дата появления в регистре' in df.columns:
df['Дата появления в регистре'] = pd.to_datetime(df['Дата появления в регистре'], errors='coerce').dt.normalize()

Модуль 3.2. Алгоритм кросс-валидации данных (Логический контроль качества)

Section titled “Модуль 3.2. Алгоритм кросс-валидации данных (Логический контроль качества)”

Назначение: Выявление человеческого фактора при ручном вводе данных операторами регистра.

Реализация функции контроля:

def check_errors(row):
rek_low = str(row['Реквизиты']).lower()
kind_low = str(row['Вид']).lower()
name_low = str(row['Название']).lower()
is_changing = row['Изменяющий']
# Кросс-проверка соответствия Вида документа и Текста реквизитов
if kind_low == 'решение' and 'постановление' in rek_low:
return "Вид 'Решение', а в реквизитах указано 'Постановление'"
if kind_low == 'постановление' and 'решение' in rek_low:
return "Вид 'Постановление', а в реквизитах указано 'Решение'"
# Проверка соответствия Органа власти и Вида принимаемого акта
if 'постановление' in rek_low and 'совет ' in rek_low:
return "В реквизитах 'Постановление', но орган указан 'Совет'"
if 'решение' in rek_low and 'администрация' in rek_low:
return "В реквизитах 'Решение', но орган указан 'Администрация'"
# Проверка хронологии дат (контроль логической последовательности)
dt_priyat = row['Дата принятия']
dt_ngr = row['Дата НГР']
if dt_priyat is not None and dt_ngr is not None:
if dt_priyat > dt_ngr:
return "Ошибка в указании даты принятия (позже даты НГР)"
# Контроль флага "Изменяющий"
if 'внесении изменений' in name_low and is_changing == False:
# Исключения, где фраза может встречаться в иных контекстах
if 'Устав' not in name_low and 'признании' not in name_low and 'проекте' not in name_low:
return "Ошибка в типе документа: в названии указано 'изменение', но флаг 'Изменяющий' не стоит"
return None

Модуль 3.3. Валидация внутренних гиперссылок МНПА

Section titled “Модуль 3.3. Валидация внутренних гиперссылок МНПА”

Назначение: Проверка качества связывания документов внутри текстов МНПА. Скрипт сканирует рабочую директорию на наличие Excel-файлов, собирает их, извлекает столбцы по индексам и проверяет ссылки по регулярному выражению.

Пакетный импорт файлов и сопоставление колонок:

# Нахождение всех файлов и извлечение целевых столбцов по индексам (15, 18, 19)
excel_files = glob(os.path.join(folder_path, "*.xlsx"))
for file_path in excel_files:
try:
excel_df = pd.read_excel(file_path, usecols=[15, 18, 19])
excel_df.columns = ['Id', 'Поиск', 'Проставлены_ссылки']
dataframes.append(excel_df)
except Exception as e:
print(f"Ошибка при загрузке {file_path}: {e}")

Регулярное выражение и логика классификации качества ссылок: По стандарту Федерального регистра, корректная ссылка должна содержать внутренний относительный путь к акту.

# Шаблон корректной ссылки во внутренней системе хранения Минюста
pattern = r'/content/act/|\\content\\act\\'
summary = df.groupby('Район').agg(
v_2026=('Id', 'count'),
# Подсчет корректно оформленных ссылок
v_ok=('Проставлены_ссылки', lambda x: x.str.contains(pattern, na=False, regex=True).sum()),
# Подсчет битых/ошибочных ссылок
v_err=('Проставлены_ссылки', lambda x: (x.notna() & (x != "") & ~x.str.contains(pattern, na=False, regex=True)).sum()),
# Подсчет документов без ссылок (непроставленные ссылки)
v_miss=('Проставлены_ссылки', lambda x: (x.isna() | (x == "")).sum())
)

Модуль 3.4. Поиск документов без НГР через API Федерального регистра

Section titled “Модуль 3.4. Поиск документов без НГР через API Федерального регистра”

Назначение: Поиск документов, не получивших государственный регистрационный номер (НГР). Запрос эмулирует поисковый движок портала pravo-search.minjust.ru.

Конфигурация HTTP POST Payload:

request_data = {
"doNotSaveHistory": False,
"request": {
"additionalFields": [
"document_region_parent",
"document_region",
"document_adoption_description",
"document_number",
"document_date_edition",
],
# Ключевой фильтр: поиск документов со статусом "без номера" в поле НГР
"customFilters": [
{
"field": "document_registration_number",
"operator": "EX",
"query": "без номера",
"type": "FACET_FREE_FILTER"
}
],
"filter": [
{
"field": "document_subject_rf_cat",
"query": "Воронежская область"
}
],
"groups": ["Текущие редакции"],
"rows": 100,
"start": start,
"type": "MULTIQUERY",
"uid": cookies['settingsUid']
}
}

4. Конфигурация выходных файлов (XLSX Writer Engine)

Section titled “4. Конфигурация выходных файлов (XLSX Writer Engine)”

Для выгрузки логов ошибок используется продвинутое форматирование openpyxl. Это предотвращает обрезание длинных текстовых строк (например, названий документов) в Excel.

with pd.ExcelWriter(filename, engine='openpyxl') as writer:
errors_df_for_export.to_excel(writer, sheet_name='Ошибки в ссылках', index=False)
# Динамическая подгонка ширины колонок по максимальной длине содержимого
worksheet = writer.sheets['Ошибки в ссылках']
for column in worksheet.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
# Ограничение максимальной ширины колонки в 60 символов во избежание чрезмерного растяжения
adjusted_width = min(max_length + 2, 60)
worksheet.column_dimensions[column_letter].width = adjusted_width

5. Возможные проблемы и пути их решения (Troubleshooting)

Section titled “5. Возможные проблемы и пути их решения (Troubleshooting)”
Симптом проблемы Вероятная причина Метод устранения
Ошибка pyodbc.Error: ... Data source name not found На целевой машине не установлен или имеет другое имя системный ODBC-драйвер MS SQL. Проверить имя драйвера в системе (через “Источники данных ODBC”). Заменить параметр driver='SQL+Server' в строке подключения на точное имя установленного драйвера (например, ODBC Driver 17 for SQL Server).
Запросы к API Минюста возвращают 403 Forbidden или ConnectionTimeout Смена алгоритмов генерации UUID сессии или блокировка по User-Agent со стороны сервера. Обновить заголовки (Headers) в функции get_cookies(). Проверить доступность узла pravo-search.minjust.ru через обычный браузер с той же машины.
При слиянии файлов по районам колонка Id содержит пустые строки (NaN). В локальных файлах result_*.xlsx идентификаторы документов сохранены в другом формате (например, как числа вместо строк-GUID). Выполнить явное преобразование типов перед слиянием: excel_df['Id'] = excel_df['Id'].astype(str).str.lower().

Документ подготовлен для внутреннего использования в качестве руководства по сопровождению аналитического контура СУБД МНПА.