Импорт и поиск по реестру¶
Модули registry_import.py и registry_search.py отвечают за две ключевые операции с реестрами: загрузку строк из файла Excel в базу данных и поиск соответствий между строками реестра и документами архива.
Импорт строк реестра¶
Общая последовательность¶
Класс RegistryImporter (модуль registry_import.py) выполняет импорт строк из файла Excel в коллекцию registry_rows базы данных MongoDB. Импорт запускается после того, как пользователь завершил настройку реестра в мастере (выбрал лист, указал заголовочную строку, привязал колонки к полям).
Последовательность действий:
- Из коллекции
registriesзагружается запись реестра с настройками: имя листа (sheet_name), параметры парсинга (parsing), список полей (fields). - Из хранилища MinIO скачивается исходный файл Excel.
- Файл открывается библиотекой
openpyxlв режиме только для чтения. - Из заданного листа считываются все строки начиная со строки данных (
data_start_row). Ячейки с датами преобразуются в текст форматаДД.ММ.ГГГГ, остальные значения приводятся к строкам. - Если в настройках указаны колонки для заполнения пропусков (
fill_down_columns), применяется процедура заполнения сверху вниз. - Старые строки реестра удаляются из коллекции
registry_rows. - Для каждой непустой строки формируется документ с тремя представлениями данных (см. ниже) и вставляется в базу порциями по 500 штук.
- Обновляется статистика реестра: количество импортированных строк, итоговая сумма (если указана колонка суммы).
Заполнение пропусков сверху вниз¶
Функция 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), которые пользователь настраивает в мастере реестра.
Последовательность действий:
- Из реестра загружаются активные правила сопоставления. Если правил нет, но есть поля старого формата (
fields), выполняется миграция на лету: поля оборачиваются в одно правило с именем «Основное правило». - Из всех правил собираются пути к полям документов (
document_fields). Пути могут приходить из глобальных настроек поиска (режимpreset) или задаваться вручную (режимcustom). - Из коллекции
documentsзагружаются все документы, у которых хотя бы одно из нужных полей заполнено. Документы с признакомis_contract_file(созданные контрактным импортом) исключаются. - По загруженным документам строятся два указателя: прямой и обратный.
- Строки реестра обрабатываются порциями по 100 штук. Для каждой строки выполняется поиск по каждому активному правилу.
- Результаты всех правил объединяются: если один документ найден по нескольким правилам, его баллы по полям берутся как лучшие из всех правил, а список правил сохраняется.
- Итоги записываются в коллекцию
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 |
Источник документа |
После завершения поиска обновляется статистика реестра: количество найденных и ненайденных строк, средний балл совпадения, найденная сумма (сумма колонки суммы по найденным строкам).