К содержимому
MoranaLabs.
Инженерные гайды14 мин чтения0 просмотров

Автоматическая сверка документов с контрагентами: что ломается на 10 000 строк 

Что ломается, когда строк становится десять тысяч: квадратичный перебор, блокирующие ключи, каскад от одной неверной пары и недетерминизм между прогонами. Плюс формула, по которой видно, окупается ли проект на часах бухгалтера, и регламент вместо квартального аврала.

0xReality

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

Дальше — что ломается на объеме, чем чинится и на каких числах проект перестает окупаться. Коммерческая рамка на странице автоматической сверки документов, здесь инженерия.

Почему это не решается формулой в Excel

ВПР — правильный инструмент, пока строк двести и ключ у обеих сторон одинаковый. Дальше он ломается по четырем причинам, и каждая встречается в первом же реальном файле.

  • Ключа нет. Номер приходит как «б/н», как «0000-000123» против «123», с префиксом филиала, с пробелом в конце ячейки.
  • Ключ не уникален. Два документа с номером 47 за год — обычное дело, и формула молча возьмет первое совпадение.
  • Связь многие ко многим. Одна оплата закрывает три накладные, одна ваша строка отвечает двум чужим.
  • Итоги сходятся, причина не видна. СУММЕСЛИМН покажет разницу в 27 550 ₽ по контрагенту и не подскажет, какая из шестисот строк ее дала.

Задача, которая тут решается на самом деле, называется сопоставлением: сначала понять, какая строка какой соответствует, и только потом искать разницу. На паре документов ее решают глазами; дальше речь только про массовый прогон, где глаз кончается на второй сотне строк.

Классы расхождений и что с каждым делать

Правые колонки важнее левой: класс определяет, кто разбирает строку и в какой срок.

КлассКак виден в данныхДействиеЧей вопрос
Разошлась суммаКоличество и цена совпали, дельта меньше процентаПроверить ставку НДС, построчное округление, скидку и ретробонусНаш при округлении, общий при скидке
Разошлось количествоДокумент сопоставлен, штуки разныеСверить с приемкой: недовоз, пересорт, упаковки против штукСклад
Нет у насПары нет ни по одному ключу, ИНН и период совпадаютИскать непроведенный или потерянный документ у себяНаш
Нет у контрагентаТо же зеркальноПисьмо с копией документа и номером строкиКонтрагент
ДубльДве наши строки на одну чужую, сумма и дата совпалиСнять задвоение, найти источник: ручной ввод поверх обменаНаш
Разъехался периодПара нашлась, суммы совпали, даты разошлись на дниРешить, чьим месяцем закрывать, до подписания актаОбе бухгалтерии

Седьмой класс на объеме выстреливает первым: сопоставление не состоялось из-за нехватки данных. У контрагента выгрузка без артикулов и номеров документов, только наименование и сумма. Дыру в источнике закрывает тот, кто формирует выгрузку.

Как выглядит нормальный отчет о сверке

Отчет, с которым можно работать, — таблица решений. Одна строка на одно расхождение, и в ней хватает всего, чтобы решить без исходников.

  1. Идентификаторы обеих сторон: документ и номер строки у вас, документ и номер строки у контрагента.
  2. Обе версии значений рядом: наименование, количество, цена, сумма — вашей стороной и чужой, в соседних колонках.
  3. Дельта в рублях и в процентах. Процент отвечает на вопрос «это округление или потерянная позиция».
  4. Класс расхождения из таблицы выше.
  5. Счет сопоставления и запас до второго кандидата. Пара со счетом 0,91 при втором кандидате 0,90 остается спорной, даже если порог пройден.
  6. Действие, ответственная сторона, статус разбора. По отчету неделю работают несколько человек сразу.

Шапка прогона важнее, чем кажется: идентификатор, дата, имена и хеши обоих файлов-источников, период, число строк с каждой стороны, число пар и спорных, итоговые суммы. Без нее через месяц никто не скажет, какой из трех отчетов актуален.

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

Сортировка по умолчанию — по сумме расхождения вниз: разбор начинается с денег.

Десять тысяч строк — это сто миллионов пар

Арифметика неумолимая. Десять тысяч ваших строк против десяти тысяч чужих дают 10 000 × 10 000 = 100 000 000 пар-кандидатов. Сравнение пары — это нормализация двух наименований, нечеткое сравнение текста, разбор чисел и взвешивание признаков.

Замерьте стоимость пары у себя: прогоните тысячу пар и засеките время. Обычно выходит от десятков до сотен микросекунд, смотря какое сравнение вы себе позволили. Дальше умножайте.

  • 20 мкс на пару → 100 000 000 × 0,00002 с = 2000 секунд, чуть больше получаса.
  • 200 мкс на пару → 20 000 секунд, пять с половиной часов.

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

Вторая ловушка объема — повторная работа. Нормализация наименования регулярными выражениями стоит порядка тридцати микросекунд. В предобработке это 10 000 × 30 мкс, три десятых секунды. Внутри цикла сравнения та же нормализация идет дважды на пару: 200 000 000 × 30 мкс дают 6000 секунд, сто минут процессорного времени на уже сделанную работу. Нормализация, токенизация, приведение единиц и разбор чисел считаются один раз на строку.

Блокирующие ключи: как срезать перебор в сто раз

Полный перебор не нужен. Накладная от 12 марта на 84 000 ₽ не может соответствовать акту от 30 сентября на 1 200 ₽ от другого контрагента. Блокирующий ключ — признак, по которому строки заранее раскладываются на группы; сравниваются только строки внутри одной группы.

Правило, которое стоит запомнить: разбиение на K примерно равных блоков делит перебор на K. Сорок контрагентов на три месяца квартала дают 120 блоков. 100 000 000 ÷ 120 ≈ 830 000 пар. Из получаса стало семнадцать секунд.

Блокирующий ключЧто даетЧем ломается
ИНН контрагентаСамый сильный и дешевый: контрольная сумма проверяется арифметическиДубли карточек, филиалы с разными КПП, смена реквизитов при реорганизации
Месяц документа с окном ±1Режет период, работает на любых данныхОтгрузка 31-го и приемка 1-го, документ проведен следующим кварталом
Порядок суммыБакеты по величине: до тысячи, до десяти тысяч, до ста тысячСумма с НДС против суммы без НДС, частичная оплата, строка разбита надвое — бакеты обязаны перекрываться
Артикул или код поставщикаТочное совпадение, нулевая цена сравненияЕсть далеко не у всех, у контрагента бывает своя кодировка
Первые токены наименованияЛовит позиции без артикуловОпечатка или лишнее слово в начале разводит пару по блокам
Номер договораХорош для длинных отношений с фиксированной номенклатуройУ половины контрагентов поле пустое или заполнено произвольно

Главная опасность блокировки: пара, которую ключ развел по разным блокам, потеряна навсегда, ее уже не спасет никакой порог. Лечится проходами: первый по сильному ключу, второй по ослабленному, третий по независимому признаку, кандидаты объединяются. Строка, не нашедшая пару нигде, уходит в класс «нет у второй стороны» и попадает человеку на глаза.

Порог: почему минус пять пунктов ломают отчет

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

Десять тысяч строк, доля ложных сопоставлений один процент — сто неверно связанных пар. Каждая портит минимум две строки: ту, которую связали неправильно, и настоящую, оставшуюся без партнера и уехавшую в класс «нет у второй стороны». Двести строк мусора и суммы, которые не сойдутся ни при каком разборе. На двухстах строках тот же процент дает две ложные пары — четыре строки и десять минут разбора. Потому на прототипе порог и кажется безобидным.

Сама механика двух порогов и асимметрия цены ошибки разобраны в статье про сопоставление двух документов. На объеме к ним добавляется замер: прежде чем двигать порог, стройте гистограмму счетов на своих данных. Видно, сколько пар лежит выше 0,95, сколько попадает в коридор спорных и сколько часов ручного разбора вы покупаете каждым шагом вниз. Пять пунктов порога на десяти тысячах строк — это сотни строк в очереди у живого человека.

Каскад: одна неверная пара тянет за собой соседние

Жадное сопоставление работает так: пары сортируются по счету, лучшая забирается, обе строки помечаются занятыми, идем дальше. Дешево и почти всегда правильно.

Две одинаковые позиции от одного поставщика в одном месяце с суммами 84 200 ₽ и 84 250 ₽. Жадный проход связывает первую вашу строку со второй чужой: счет оказался выше на случайном совпадении в написании наименования. Настоящий партнер первой чужой строки уже занят, она берет второго кандидата, тот отбирает партнера у следующей. В отчете три расхождения там, где была одна ошибка, и на квартальном файле такие цепочки тянутся на десятки строк.

Три приема, которые это лечат.

  1. Взаимно лучший кандидат. Пара принимается, только если ваша строка — лучший кандидат для чужой и одновременно наоборот. Односторонние совпадения идут в спорные; это снимает большую часть каскадов.
  2. Запас до второго кандидата. Если разница счетов между первым и вторым меньше выбранной величины, пара спорная независимо от абсолютного значения. Именно этот признак ловит близнецов по сумме.
  3. Глобальное назначение внутри блока. Венгерский алгоритм решает задачу о назначениях за куб от размера блока: на ста строках это миллион операций, доли секунды. На десяти тысячах без блокировки — триллион, то есть никогда. Блоки нужны и ради этого, а не только ради скорости.

Каскад бывает и на уровне сумм: при сверке по нарастающему сальдо одна пропущенная январская накладная делает несходящимися февраль, март и весь квартал. Сверяют обороты периода и документы, сальдо держат контрольной суммой.

Память: где сто миллионов пар превращаются в десять гигабайт

Считаем так же прямолинейно. Пара с признаками и счетом занимает около сотни байт даже при аккуратной упаковке. Сто миллионов пар — десять гигабайт, и процесс умирает на середине прогона: OOM-килл и никакого отчета.

  • Пары не материализуются списком, они текут генератором: в память попадает только прошедшее нижний порог.
  • Блоки обрабатываются по одному, пиковая память определяется самым крупным. Отсюда требование следить за перекосом: один контрагент с восемью тысячами строк из десяти — обычная картина, такой блок дробят вторым ключом.
  • Векторы наименований считаются один раз на строку: десять тысяч векторов вместо ста миллионов сравнений текста. Матрица сходства 10 000 × 10 000 во float32 занимает 400 мегабайт, поэтому и ее считают блоками.

Приблизительный поиск ближайших соседей на таком объеме не нужен: точное умножение матриц по блокам укладывается в секунды и не тащит за собой недетерминизм.

Воспроизводимость: два прогона на одних данных дают один отчет

Момент, который отделяет инструмент от игрушки. Бухгалтер отправляет отчет, контрагент через день просит перепроверить, второй прогон выдает другой набор расхождений. Доверие кончилось, дальше все руками.

Откуда берется недетерминизм:

  • Порядок обхода. Множества и словари не гарантируют одинаковый порядок между запусками процесса. Кандидаты с равным счетом принимаются в разной последовательности.
  • Параллельная обработка блоков. Два потока претендуют на одну строку, побеждает тот, кто успел первым.
  • Плавающая точка. Сумма ста слагаемых в разном порядке дает разный последний знак. Деньги хранятся целыми копейками, никаких float.
  • Приблизительные индексы ближайших соседей выдают разный результат при разном порядке вставки.
  • Языковая модель с ненулевой температурой. Если LLM участвует в разборе наименований, температура ноль и кэш ответов по хешу запроса обязательны.
  • Текущая дата в правилах. Условие «документ прошлого месяца» первого числа значит совсем другое, чем тридцатого.

Лечится дисциплиной: детерминированная сортировка кандидатов с разрешением равенства по идентификатору, фиксированный seed, снапшот и хеш входных файлов, версия правил и справочника в шапке отчета, отчетная дата параметром. Плюс регрессионный прогон на эталонных данных с построчным различием между версиями: изменили правило — видно, какие строки переехали.

Как считать экономию, чтобы потом не спорить

Арифметика открытая, подставляйте свои числа. Оклад 80 000 ₽ плюс тридцать процентов взносов дают 104 000 ₽ в месяц, при 165 рабочих часах час стоит 630 ₽. Квартальная сверка вдвоем за рабочую неделю — 2 × 5 × 8 = 80 часов, 320 часов в год, или 201 600 ₽.

После автоматизации остается разбор спорных. Заложите двадцать процентов от исходного времени: 64 часа в год, экономия 256 часов, или 161 000 ₽. Пилот стоит от 350 000 ₽ и на одних часах окупается за два с лишним года. Долго.

Формула порога: часов ручной сверки в год = цена проекта ÷ (0,8 × стоимость часа). При 350 000 ₽ и ставке 630 ₽ это 694 часа в год, около 174 часов в квартал — четыре человеко-недели на каждый аврал.

Часов на сверку в кварталЧасов в годЭкономия 80% в деньгахОкупаемость 350 000 ₽
4016080 640 ₽Больше четырех лет
80320161 280 ₽~2,2 года
174694349 776 ₽12 месяцев
3201280645 120 ₽~6,5 месяца
64025601 290 240 ₽~3 месяца

Сопровождение в таблице не учтено намеренно: разовая квартальная сверка на выгрузках его не требует, регулярный режим требует и окупается только на двух нижних строках.

Часы — половина счета, и обычно меньшая. Вторая берется из прошлого года по четырем позициям: суммы, списанные на прочие расходы, потому что расхождение так и не нашли; дебиторка без подписанного акта, пока идет срок исковой давности; пени за несвоевременное закрытие расчетов по договорам; дни задержки закрытия квартала, умноженные на стоимость дня работы финансовой службы.

Оцените, какая доля этого ловилась бы вовремя, и складывайте с экономией часов. Методика расчета ставки разобрана в цене распознавания документов, прикинуть вилку под свой объем можно на калькуляторе.

Регламент: регулярная сверка вместо квартального аврала

Здесь квадратичная сложность впервые работает на вас. Тот же поток помесячно дает в прогоне около 3300 строк вместо десяти тысяч, то есть 3300 × 3300 ≈ 11 миллионов пар: в девять раз меньше на прогон и в три раза меньше суммарно за квартал. Еженедельно в прогоне около 770 строк, 593 000 пар — в 169 раз меньше на прогон.

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

Организационная сторона важнее арифметической. Расхождение недельной давности чинится звонком: люди помнят отгрузку, период не закрыт, исправление проводится текущей датой. То же расхождение через квартал означает переписку, корректировочный документ и объяснение аудитору.

  1. Еженедельно: что пришло по ЭДО против проведенного в базе. Прогон автоматический, разбор до тридцати минут. Где именно тут расходятся суммы, разобрано в отдельной статье.
  2. Ежемесячно: обороты по каждому активному контрагенту с классификацией расхождений и очередью спорных.
  3. Квартально: акт сверки. К этому моменту остается хвост, не закрывшийся за три месяца.
  4. Владелец очереди спорных — человек с именем и сроком разбора. Отчет без владельца не открывает никто; это главная причина, по которой такие проекты умирают через месяц после приемки.
  5. Порог разбора по сумме. Пятнадцать минут работы стоят 630 × 0,25 = 157 ₽, и расхождение на пятьдесят рублей дороже разобрать, чем списать. Порог, лимит списания и ответственный утверждаются до запуска, иначе бухгалтерия из осторожности разбирает все подряд.

Когда автоматическая сверка не нужна

  • Меньше семисот часов ручной сверки в год и без денег в расхождениях. Формула выше, числа ваши. Ниже планки честный ответ — навести порядок в выгрузках и оставить как есть.
  • Контрагентов пятеро, сверка укладывается в день. ВПР по номеру документа тут работает.
  • Надо сравнить два конкретных файла или две редакции договора. Другая задача и другой инструмент, она в соседней статье про сверку двух документов.
  • Источники существуют только на бумаге и в сканах. Сначала распознавание первички, сверка вторым шагом.
  • Справочник проще пересобрать заново. Дубли контрагентов и позиции без артикулов ломают главные блокирующие ключи, и никакая модель это не компенсирует.
  • Некому разбирать спорные. Система отдаст двести строк, требующих решения человека. Без владельца это мертвый отчет.

Чек-лист перед первым прогоном

  1. Выгрузите квартал с обеих сторон и посчитайте строки. Их число — вход для всей арифметики выше.
  2. Проверьте поля контрагента: артикул, номер документа, дата, количество, цена без НДС. Отсутствие артикула меняет проект сильнее, чем объем.
  3. Посчитайте дубли контрагентов: один поставщик, заведенный трижды за десять лет, разваливает главный блокирующий ключ.
  4. Замерьте секундомером разбор пяти спорных строк руками — это ваша стоимость строки.
  5. Возьмите из прошлого года сумму расхождений, которые так и не нашли.
  6. Назовите владельца очереди спорных и срок разбора.
  7. Решите вопрос контура: выгрузки или подключение к базе. Как устроена связка без правок конфигурации — ИИ в 1С.

Первый прогон идет на выгрузках, доступ в базу для старта не нужен, и это снимает главный стопор со стороны безопасности: пилот от 350 000 ₽ доходит до первого отчета за четыре недели. Порядок работ и ступени — на странице автоматической сверки документов с контрагентами. Если у вас уже лежат две выгрузки за квартал, по ним видно, какая доля строк закрывается блокирующими ключами без всякой интеллектуальности. Честнее оценки до договора не бывает.

  • #
  • #алгоритмы
  • #бухгалтерия
  • #контрагенты
  • #сверка документов
  • #сопоставление данных
ПоделитьсяTelegramX
рассылка

Новые статьи — на почту

Лонгриды про ML в проде, edge и компьютерное зрение — сразу после выхода.

Канал в Telegram: morana.log

Без спама. Нажимая «Подписаться», соглашаетесь с обработкой персональных данных

бесплатный pdf-гайд

Edge AI или облако: когда тащить нейросеть на железо

Признаки, фреймворк выбора и прикидка экономии — короткий PDF-гайд на почту.

PDF · 5 страниц · без спама. Нажимая «Получить», соглашаетесь с обработкой персональных данных

Читать дальше
— заявка

Опишите задачу  ответим как инженеры. 

Оставьте имя и Telegram — остальное обсудим. Без брифов на 40 слайдов и звонков по три раза.

Отвечаем за пару часовотвечает инженер, а не отдел продажNDA по запросу

Сюда напишем — это быстрее всего

Или сразу написать в Telegram

Без спама и звонков-роботов. Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности и обработкой персональных данных.