Перейти к содержанию

Импорт и поиск по реестру

Модули registry_import.py и registry_search.py отвечают за две ключевые операции с реестрами: загрузку строк из файла Excel в базу данных и поиск соответствий между строками реестра и документами архива.

Импорт строк реестра

Общая последовательность

Класс RegistryImporter (модуль registry_import.py) выполняет импорт строк из файла Excel в коллекцию registry_rows базы данных MongoDB. Импорт запускается после того, как пользователь завершил настройку реестра в мастере (выбрал лист, указал заголовочную строку, привязал колонки к полям).

Последовательность действий:

  1. Из коллекции registries загружается запись реестра с настройками: имя листа (sheet_name), параметры парсинга (parsing), список полей (fields).
  2. Из хранилища MinIO скачивается исходный файл Excel.
  3. Файл открывается библиотекой openpyxl в режиме только для чтения.
  4. Из заданного листа считываются все строки начиная со строки данных (data_start_row). Ячейки с датами преобразуются в текст формата ДД.ММ.ГГГГ, остальные значения приводятся к строкам.
  5. Если в настройках указаны колонки для заполнения пропусков (fill_down_columns), применяется процедура заполнения сверху вниз.
  6. Старые строки реестра удаляются из коллекции registry_rows.
  7. Для каждой непустой строки формируется документ с тремя представлениями данных (см. ниже) и вставляется в базу порциями по 500 штук.
  8. Обновляется статистика реестра: количество импортированных строк, итоговая сумма (если указана колонка суммы).

Заполнение пропусков сверху вниз

Функция apply_fill_down решает типичную для реестров проблему: в Excel ячейки первой колонки (например, контрагент) часто заполняются только в первой строке группы, а следующие строки той же группы оставляются пустыми. Функция проходит по указанным колонкам сверху вниз и заменяет пустые ячейки значением последней непустой ячейки выше:

Строка 1: "ООО Компания"  ->  "ООО Компания"
Строка 2: [пусто]         ->  "ООО Компания"
Строка 3: [пусто]         ->  "ООО Компания"
Строка 4: "ООО Другая"    ->  "ООО Другая"

Колонки для заполнения выбирает пользователь на шаге мастера настройки реестра.

Три представления строки

Каждая импортированная строка сохраняется в коллекции registry_rows с тремя словарями:

Поле Назначение Пример
raw_data Исходные значения всех ячеек строки, ключ --- номер колонки {"0": "08V0801", "1": "01.07.2008", "2": "Компания"}
mapped_data Значения привязанных полей, ключ --- идентификатор поля из настроек {"contract_number": "08V0801", "contract_date": "01.07.2008"}
normalized Приведённые значения для поиска {"contract_number": "08V0801", "contract_date": "2008-07-01"}

Кроме этих словарей, каждая строка содержит:

  • row_number --- номер строки в исходном файле Excel;
  • matches --- пустой массив (заполняется позднее при поиске);
  • best_score --- лучший балл совпадения (изначально 0);
  • status --- статус обработки (pending).

Нормализация значений

Нормализация определяется по типу привязанного поля (информация о типе берётся из глобальных настроек поиска --- коллекция settings, документ search_fields):

Тип поля Способ нормализации Пример
Номер (number) Из строки извлекаются только цифры "08V-0801" -> "080801"
Дата (date) Приведение к формату ГГГГ-ММ-ДД через date_utils "01.07.2008" -> "2008-07-01"
Сумма (amount) Приведение к числу с плавающей точкой "1 234,56" -> 1234.56
Текст Приведение к нижнему регистру, удаление знаков препинания, сжатие пробелов "ООО \"Компания\" " -> "ооо компания"

Поиск документов по реестру

Общая схема

Класс RegistrySearcher (модуль registry_search.py) выполняет поиск документов из коллекции documents, соответствующих каждой строке реестра. Поиск основан на правилах сопоставления (matching_rules), которые пользователь настраивает в мастере реестра.

Последовательность действий:

  1. Из реестра загружаются активные правила сопоставления. Если правил нет, но есть поля старого формата (fields), выполняется миграция на лету: поля оборачиваются в одно правило с именем «Основное правило».
  2. Из всех правил собираются пути к полям документов (document_fields). Пути могут приходить из глобальных настроек поиска (режим preset) или задаваться вручную (режим custom).
  3. Из коллекции documents загружаются все документы, у которых хотя бы одно из нужных полей заполнено. Документы с признаком is_contract_file (созданные контрактным импортом) исключаются.
  4. По загруженным документам строятся два указателя: прямой и обратный.
  5. Строки реестра обрабатываются порциями по 100 штук. Для каждой строки выполняется поиск по каждому активному правилу.
  6. Результаты всех правил объединяются: если один документ найден по нескольким правилам, его баллы по полям берутся как лучшие из всех правил, а список правил сохраняется.
  7. Итоги записываются в коллекцию registry_rows (поле matches), обновляется статистика реестра.

Прямой указатель документов

Метод _build_document_index строит словарь {идентификатор документа -> {путь к полю -> (исходное значение, приведённое значение, тип поля)}}. Тип поля определяется по имени пути:

  • путь содержит number, act_number или buh_number --- тип number;
  • путь содержит date --- тип date;
  • путь содержит amount --- тип amount;
  • все остальные --- тип text.

Приведение значений выполняется теми же функциями, что и при импорте строк реестра.

Обратный указатель для быстрого поиска кандидатов

Метод _build_reverse_index строит словарь {путь к полю -> {приведённое значение -> множество идентификаторов документов}}. Для дат в указатель заносятся все возможные толкования неоднозначной даты (см. раздел Работа с неоднозначными датами).

Обратный указатель позволяет находить документы-кандидаты за время O(1) вместо полного перебора. Это критически важно при большом количестве документов и строк реестра.

Двухфазный поиск

Для каждой строки реестра поиск выполняется в две фазы:

Фаза 1 --- отбор кандидатов. Метод _find_candidates использует обратный указатель для быстрого отбора документов-кандидатов. Для каждого поля правила:

  • Числа: ищется точное совпадение приведённого значения. Если не найдено --- ищутся номера, содержащие искомый номер или содержащиеся в нём (частичное совпадение).
  • Даты: ищутся все толкования даты из строки реестра. При диапазонном способе сравнения дополнительно проверяются даты в пределах допуска (+-N дней).
  • Суммы: ищется точное совпадение округлённой суммы. При диапазонном способе --- суммы в пределах указанного процента.
  • Текст (точное): ищется точное совпадение приведённого текста.
  • Текст (вхождение): ищутся значения, содержащие искомую строку или содержащиеся в ней.
  • Текст (приблизительное): обратный указатель не используется, требуется полный перебор.

Если есть хотя бы одно индексируемое поле, но кандидатов не найдено --- строка считается без совпадений. Если индексируемых полей нет (все поля приблизительные) --- проверяются все документы.

Фаза 2 --- подсчёт баллов. Для каждого документа-кандидата вычисляется балл по каждому полю правила, затем рассчитывается взвешенная сумма. Если сумма достигает минимального порога правила, документ считается совпавшим.

Способы сравнения полей

Метод _compare_values поддерживает четыре типа полей и три способа сравнения (match_method):

Числа:

  • Точное (exact): извлекаются только цифры из обоих значений и сравниваются. Для десятизначных системных номеров применяется особая логика: совпадение по первым двум и последним четырём цифрам считается полным совпадением. Для остальных номеров используется расстояние Левенштейна с порогом 60 % для учёта ошибок распознавания.

Даты:

  • Точное (exact): сравнение через compare_dates с учётом неоднозначности формата (подробнее --- Работа с неоднозначными датами).
  • Диапазонное (range): сравнение через compare_dates_with_tolerance с допуском +-N дней.

Суммы:

  • Точное (exact): разница менее 0,01 --- балл 1,0; разница менее 1 % --- балл 0,95; разница менее 5 % --- балл 0,8.
  • Диапазонное (range): если относительная разница в пределах допуска, балл от 0,8 до 1,0 пропорционально отклонению.

Текст:

  • Точное (exact): сравнение приведённых строк.
  • Вхождение (contains): одна строка должна содержать другую.
  • Приблизительное (fuzzy): сначала проверяется отношение длин (отсечение при разнице более чем в 3 раза). Затем вычисляются fuzzy_ratio, partial_ratio (по подстроке) и token_sort_ratio (сравнение отсортированных слов); берётся лучший результат.

Подсчёт взвешенного балла

Веса полей нормализуются так, чтобы их сумма равнялась 1. Итоговый балл документа вычисляется как взвешенная сумма баллов по отдельным полям:

итоговый балл = сумма(балл по полю * нормализованный вес поля)

Например, если номер договора имеет вес 40, а дата --- вес 20, то нормализованные веса будут 0,667 и 0,333. При полном совпадении номера (балл 1,0) и отсутствии совпадения даты (балл 0,0) итоговый балл составит 0,667.

Мультиправила и подправила

Поиск поддерживает несколько независимых правил сопоставления. Каждое правило имеет свой набор полей, весов и минимальный порог. Правила применяются последовательно.

Документ, найденный по нескольким правилам, получает объединённые баллы: для каждого поля берётся лучший балл из всех правил, список правил сохраняется в результате (rule_ids, rule_names).

Каждое правило может содержать подправила (sub_rules). Подправило --- дополнительное условие: если основное правило нашло документ, проверяются подправила, и хотя бы одно из них должно тоже сработать (пройти свой минимальный порог). Если ни одно подправило не сработало --- документ отклоняется.

Режим применения правил (rule_mode):

  • all_matches --- применяются все правила, результаты объединяются;
  • first_match --- как только хотя бы одно правило нашло совпадение, остальные не применяются.

Предупреждения по суммам

Если в настройках реестра указана колонка суммы (sum_column_index), поиск генерирует предупреждения для найденных совпадений. Если сумма документа меньше суммы строки реестра более чем на 1 %, к совпадению добавляется предупреждение типа amount_less с указанием обеих сумм. Это помогает пользователю обнаружить ситуации, когда найденный документ покрывает не всю сумму строки реестра.

Сохранение результатов

Результаты записываются в каждую строку реестра (коллекция registry_rows):

  • matches --- массив найденных документов (до 10), отсортированный по убыванию балла;
  • best_score --- лучший балл среди всех совпадений;
  • status --- matched если есть хотя бы одно совпадение, no_match если нет;
  • has_warnings --- признак наличия предупреждений.

Каждый элемент массива matches содержит:

Поле Описание
document_id Идентификатор документа
score Итоговый балл совпадения (от 0 до 1)
field_scores Баллы по отдельным полям
matched_values Исходные значения полей документа, по которым найдено совпадение
confirmed Подтверждено ли совпадение пользователем вручную
rule_ids Список идентификаторов правил, по которым найден документ
rule_names Список названий правил
warnings Массив предупреждений
source Источник документа

После завершения поиска обновляется статистика реестра: количество найденных и ненайденных строк, средний балл совпадения, найденная сумма (сумма колонки суммы по найденным строкам).