Как поймать ошибки распознавания первички: контрольные суммы ИНН, КПП, БИК и арифметика НДС
После распознавания реквизиты проверяют формулами, а не второй моделью: контрольное число ИНН, ключ расчётного счёта с БИК, формат КПП, равенства «количество × цена = сумма» и НДС по ставке 22 %. Ниже я даю алгоритмы с рабочим кодом, разбор замечаний в карточке и честный список того, что такие проверки не ловят.
Почему после распознавания я сначала запускаю формулы, а не вторую модель
Распознавание читает скан, а не понимает документ: оно видит «6» там, где напечатана «8», и с одинаковой уверенностью отдаёт правильную и неправильную цифру. У бухгалтера на экране потом оказывается аккуратная карточка, в которой всё выглядит правдоподобно. Я больше пятнадцати лет работаю рядом с 1С и вижу одно и то же: ошибку в ИНН или сумме находят не там, где её сделали, а на этапе оплаты, сверки или запроса из налоговой. Мы в АйТи-Фреш занимаемся ИТ-аутсорсингом для компаний до 50 рабочих мест, и такие разборы у нас регулярные.
Для наглядности возьму условный пример: логистическая компания «Складская линия», 23 рабочих места, один главный бухгалтер и один помощник. Это модельный расчёт, а не отчёт о чьём-то внедрении: объёмы и доли ошибок ниже — мои допущения, вы подставите свои. Допустим, 260 входящих документов в месяц приходят сканами и фото, остальное идёт по ЭДО. Как устроен весь конвейер от скана до документа в 1С, я подробно разбирал в статье про конвейер OCR для бухгалтерии. Здесь я про один этап: что делать с уже прочитанными полями.
Мой принцип простой: всё, что можно проверить арифметикой, проверяется арифметикой. Контрольная сумма ИНН считается за микросекунды, стоит ноль рублей и не «галлюцинирует». В нашем модуле, то есть в распознавании первички в 1С, это седьмая стадия из десяти: после извлечения полей идёт проверка реквизитов (ИНН, КПП, БИК, счёт, НДС, суммы) и только за ней перепроверка, где модель подключается лишь при сомнении. Такой порядок важнее, чем выбор самого движка, о котором я писал отдельно в сравнении Tesseract, PaddleOCR и языковых моделей.
Дальше по порядку: как проверяется ИНН, что можно и чего нельзя проверить у КПП, БИК и расчётного счёта, как проверять НДС и суммы, как искать дубли, как замечания выглядят для бухгалтера и, главное, чего эти проверки не ловят. Код в статье рабочий, я гонял его на публичных реквизитах и на своих тестовых данных.
Как проверить контрольное число ИНН: алгоритм для 10 и 12 знаков
У ИНН организации 10 знаков, у физического лица и предпринимателя 12. Последний знак у организации и два последних у физлица — контрольные числа. По документу ФНС об ИНН они «рассчитываются по алгоритму, определённому ФНС»; сам алгоритм в тексте приказа не расписан, он давно опубликован в открытых описаниях и реализован в 1С. Я сверил его по нескольким независимым описаниям и по тому, как он ведёт себя на публичных ИНН банков и крупных компаний. Для десятизнакового: первые девять цифр умножаются на коэффициенты 2, 4, 10, 3, 5, 9, 4, 6, 8, сумма делится на 11, остаток (если он равен 10, берётся 0) должен совпасть с десятой цифрой. Для двенадцатизнакового считаются два числа: одиннадцатая цифра по коэффициентам 7, 2, 4, 10, 3, 5, 9, 4, 6, 8 и двенадцатая по 3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8, оба остатка от деления на 11 сводятся к одной цифре тем же правилом.
На Python это выглядит так. Хранить ИНН надо строкой, иначе теряются ведущие нули, а первым делом проверять длину и то, что в строке только цифры:
import re
W10 = (2, 4, 10, 3, 5, 9, 4, 6, 8)
W11 = (7, 2, 4, 10, 3, 5, 9, 4, 6, 8)
W12 = (3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8)
def ctrl(digits, weights):
return sum(int(d) * w for d, w in zip(digits, weights)) % 11 % 10
def inn_ok(inn: str) -> bool:
if not re.fullmatch(r"\d{10}|\d{12}", inn):
return False
if len(inn) == 10:
return ctrl(inn[:9], W10) == int(inn[9])
return (ctrl(inn[:10], W11) == int(inn[10])
and ctrl(inn[:11], W12) == int(inn[11]))На условном ИНН 7804123453 функция вернёт True, на 7604123453 (восьмёрка прочитана шестёркой) — False. Это вымышленный пример, но публичные ИНН банков и крупных компаний проходят эту функцию так же.
Что здесь ловится, я проверил симуляцией на 20 000 случайных ИНН с одной искажённой цифрой. Для двенадцатизнакового пропусков не было вовсе, для десятизнакового формула пропускает около 1,7 % одиночных ошибок: причина в том, что остаток 0 и остаток 10 после сведения к одной цифре дают одинаковый результат. Перестановку соседних цифр десятизнаковый ИНН пропускает примерно в 1,9 % случаев, двенадцатизнаковый в сотых долях процента. И обратная сторона: случайная строка из десяти цифр проходит проверку примерно в каждом десятом случае, а из двенадцати в каждом сотом. Поэтому «проверка пройдена» означает «вряд ли ошибка чтения», но не «такая компания существует» — для этого нужен реестр, а не формула.
Есть свежая деталь, которую я бы не пропускал. С 1 января 2026 года действует новый порядок присвоения ИНН (приказ ФНС от 26.06.2025 № ЕД-7-14/559@): у вновь присваиваемых номеров первые два знака — код управления ФНС по субъекту, вторые два — индекс, определяемый налоговой службой, а не «код инспекции», как раньше. Длина номера не меняется, ранее присвоенные ИНН остаются прежними. Если в вашем валидаторе или в самописной обработке где-то зашита логика «первые четыре знака — код инспекции», её стоит убрать, а проверку контрольного числа менять не нужно: ФНС отдельно разъяснила, что методика расчёта контрольного числа ИНН и структура КПП не меняются, поменялся смысл третьего и четвёртого знаков.
Что можно и что нельзя проверить у КПП, БИК и расчётного счёта
Здесь самая частая путаница, и её я слышу даже от опытных коллег: считается, что у КПП и БИК есть контрольные суммы. У КПП её нет. КПП состоит из девяти знаков: четыре знака кода налогового органа, два знака причины постановки на учёт и три знака порядкового номера; на местах знаков причины могут стоять и заглавные латинские буквы. Поэтому формула для КПП — это проверка формата, и я так её и называю в коде. КПП со строчными буквами или с кириллицей такой формат не пройдёт, и это правильно: типичный результат OCR — русская «С» вместо латинской «C», которые на скане выглядят одинаково.
Кроме формата, у КПП есть смысловые проверки против справочника. Если у контрагента 12 знаков в ИНН (предприниматель), КПП быть не должно, если 10 — должен быть. Найденный по ИНН контрагент в вашей базе имеет известный КПП, а у одного юрлица их может быть несколько (обособленные подразделения), поэтому расхождение с базой — это предупреждение оператору, а не ошибка. Это тот самый шаг, который потом переходит в сопоставление контрагентов и номенклатуры с 1С: точное совпадение по паре ИНН и КПП, а при нечитаемом ИНН нечёткий поиск по наименованию с подтверждением оператора.
БИК тоже не имеет контрольной суммы, это девять цифр, и проверять его можно только форматом и по справочнику БИК Банка России. Зато расчётный счёт проверяется по-настоящему, и делается это вместе с БИК. Алгоритм лежит в письме Банка России от 08.09.1997 № 515 и до сих пор используется: к 20 разрядам счёта слева приписывается три цифры из БИК (для счёта клиента разряды 7–9, для корсчёта банка в подразделении Банка России нолик и разряды 5–6), получается 23 разряда. Их умножают на коэффициенты 7, 1, 3, 7, 1, 3 и так по кругу, у каждого произведения берут младший разряд, складывают, и сумма должна быть кратна 10. Ключ — девятый разряд самого счёта.
KPP_RE = re.compile(r"\d{4}[0-9A-Z]{2}\d{3}") # NNNN PP XXX
def kpp_format_ok(kpp: str) -> bool:
return bool(KPP_RE.fullmatch(kpp))
W_ACC = (7, 1, 3) * 7 + (7, 1) # 23 разряда
def account_key_ok(account: str, bik: str) -> bool:
if not (re.fullmatch(r"\d{20}", account) and re.fullmatch(r"\d{9}", bik)):
return False
# счёт в подразделении Банка России (корсчёт 30101, ЕКС 40102):
# "0" + разряды 5-6 БИК; счёт клиента в банке: разряды 7-9 БИК
in_cbr = account[:5] in ("30101", "40102")
prefix = "0" + bik[4:6] if in_cbr else bik[6:9]
s = prefix + account
return sum(int(d) * w % 10 for d, w in zip(s, W_ACC)) % 10 == 0Я прогнал эту функцию на публичных реквизитах: корсчета нескольких крупных банков, единый казначейский счёт 40102 и расчётные счета благотворительных фондов, опубликованные ими для пожертвований, проходят, а номер с одной изменённой цифрой — нет. Казначейские счета 03… у меня эту проверку не прошли ни с одним вариантом префикса, поэтому для них я замечание не выставляю, а сверяю реквизиты с уведомлением получателя. Ограничение: в некоторых номерах счетов в шестом разряде встречаются буквы, и такие счета мой упрощённый код не проверяет, а функция в БСП (по её описанию) их обрабатывает.
Если вы разработчик 1С, писать эти функции с нуля не нужно. В библиотеке стандартных подсистем в общем модуле РегламентированныеДанныеКлиентСервер есть функции ИННСоответствуетТребованиям и КонтрольныйКлючЛицевогоСчетаСоответствуетТребованиям (сигнатуры я сверял по описанию БСП: вторая функция принимает номер счёта, БИК и признак «счёт в кредитной организации»; проверьте их в своей версии библиотеки):
ТекстОшибки = "";
ИННВерный = РегламентированныеДанныеКлиентСервер.ИННСоответствуетТребованиям(
ИНН, Истина, ТекстОшибки);Моя позиция такая: в 1С эти проверки хороши на ручном вводе, но для распознанных документов они должны работать до записи в базу, на стороне конвейера, чтобы замечание попало в карточку с указанием, какое поле проверить, а не в диалог ошибки при записи.
Арифметика НДС и сумм: как проверить «в том числе», «сверху» и итог по строкам
Сумма — самое «шумное» поле на скане: цифр много, шрифт мелкий, печать поверх. Зато в документе одни и те же числа связаны между собой, и эти связи я использую как самопроверку. Количество на цену даёт стоимость без налога. Ставка на стоимость даёт сумму налога. Сумма без налога плюс налог даёт стоимость с налогом. Сумма по строкам должна сойтись с итогом. Одна неверно прочитанная цифра почти всегда ломает хотя бы одно из этих равенств.
Ставки важно держать в справочнике, а не «вообще любое число». С 1 января 2026 года основная ставка НДС 22 % (Федеральный закон от 28.11.2025 № 425-ФЗ), расчётные ставки для обратного расчёта 22/122 и 10/110, а у плательщиков НДС на УСН есть специальные 5 % и 7 % с расчётными 5/105 и 7/107 (методические рекомендации ФНС по НДС для УСН 2026). Допустимый набор в моей проверке: 22, 10, 0, 5, 7 и отдельное значение «без НДС». Ставка 20 % в документе за 2026 год — не автоматическая ошибка: ставка привязана к отгрузке, и декабрьские отгрузки или авансовые случаи бывают. Я делаю из этого предупреждение «проверьте дату отгрузки», а не блокировку. Как меняется формат самого УПД, я разбирал отдельно в материале про переход на УПД 5.03.
Различие «в том числе» и «сверху» нужно проверять по-разному. Когда цена включает НДС, налог выделяется обратным счётом: итог × 22 / 122. Когда налог добавляется сверху, он считается от базы: база × 22 / 100. Если перепутать формулы, получится расхождение примерно в пятую часть налога, и это сразу видно. Копейки надо считать через Decimal, не через float, иначе 0,1 + 0,2 не равно 0,3 и вы получите фантомные ошибки. Вот мой рабочий каркас:
from decimal import Decimal as D, ROUND_HALF_UP
RATES = {D("22"), D("10"), D("0"), D("5"), D("7")}
def money(x):
return D(x).quantize(D("0.01"), ROUND_HALF_UP)
def vat_incl(total, rate): # «в том числе»: итог × r / (100 + r)
return money(total * rate / (100 + rate))
def vat_on_top(base, rate): # «сверху»: база × r / 100
return money(base * rate / 100)
def check_lines(lines, header_total, eps=D("0.01")):
# lines: (кол-во, цена, база, ставка, НДС, с налогом) - как прочитано со скана
problems, sum_total = [], D(0)
for i, (qty, price, base, rate, vat, total) in enumerate(lines, 1):
if rate not in RATES:
problems.append(f"Строка {i}: ставка {rate}% не из допустимых")
if abs(qty * price - base) > eps:
problems.append(f"Строка {i}: {qty} x {price} != {base}")
if abs(vat_on_top(base, rate) - vat) > eps:
problems.append(f"Строка {i}: НДС {vat}, ожидали {vat_on_top(base, rate)}")
if abs(base + vat - total) > eps:
problems.append(f"Строка {i}: {base} + {vat} != {total}")
sum_total += total
if abs(sum_total - header_total) > eps * len(lines):
problems.append(f"Сумма строк {sum_total} против итога {header_total}")
return problemsНа модельном документе из трёх строк (20 рулонов плёнки по 612,50, 40 паллет по 1 190,00 и 12 позиций по 184,30 при ставке 22 %, итог 75 715,15) функция ничего не находит. Теперь имитирую три типичные ошибки чтения: 40 паллет прочитано как 48, «486,55» как «466,55» и в итоге «75 715,15» как «75 775,15». В каждом случае функция указывает на конкретную строку или итог:
Строка 2: 48 x 1190.00 != 47600.00
Строка 3: НДС 466.55, ожидали 486.55
Строка 3: 2211.60 + 466.55 != 2698.15
Сумма строк 75715.15 против итога 75775.15В каркасе НДС строки сверяется «сверху»; если документ составлен с налогом «в том числе», сравнивайте прочитанный НДС с vat_incl(total, rate) — на копейку результаты могут разойтись, это нормально. Про допуск. Я ставлю по строке 1 копейку, а на итог 1 копейку на строку: это мой инженерный выбор, а не норма закона. По разъяснениям Минфина, собранным в подборке КонсультантПлюс, правило округления налога до рублей к суммам НДС в счёте-фактуре не применяется, а копеечные расхождения у поставщиков, как правило, возникают из-за разного порядка округления. Из этого следует честное ограничение: если распознавание перепутало последнюю цифру копеек, а поставщик сам «округлил» не так, проверка её не отличит от нормального округления. Такое замечание нужно выносить как информационное, а всё, что больше допуска, как ошибку.
Как искать дубли документов: поставщик, номер, дата, сумма
Дубль — это не ошибка чтения, а ошибка процесса: один и тот же УПД пришёл по почте, потом курьером, потом бухгалтер ещё раз выложила его в общую папку. Формальная связка для поиска простая: ИНН поставщика, номер документа, дата и сумма. В нашем модуле дубли ищутся и внутри загружаемой пачки, и против уже заведённого в 1С, потому что дубль часто прилетает не рядом, а через недели.
Ключ нужно нормализовать, иначе он ничего не найдёт. Номер приходит как «№ 0000124», «124» и «N124», а дата бывает в трёх форматах. Я сначала привожу номер к верхнему регистру (иначе строчная «n124» не совпадёт с «N124»), затем убираю пробелы, знак номера и ведущие нули, дату превращаю в ISO, сумму округляю до копеек:
def dup_key(inn, number, date, total):
num = re.sub(r"^[№N#\s]+|\s+", "", number.upper()).lstrip("0")
return (inn, num, date.isoformat(), money(total))Обратная сторона — ложные срабатывания. Корректировочный документ может повторять номер и сумму оригинала, а один и тот же акт и счёт-фактура на ту же сумму — разные документы. Поэтому в ключ разумно добавить вид документа, а дубль показывать оператору как замечание «возможно, уже заведён», а не удалять автоматически. Заодно такая проверка страхует и от повторной загрузки того же файла.
Как замечания видит бухгалтер и как его ответ снимает замечание
Проверки бесполезны, если результат нельзя прочитать за пять секунд. В нашем модуле замечания живут в карточке документа, рядом с распознанными полями, и написаны по-человечески: «Строка 2: 1.0 × 5 839,48 ≠ 7 124,26 (расхождение 1 284,78)» или «Сумма строк 2 192,61 против итога 2 159,57: расхождение 33,04 ₽». По тексту сразу видно, куда смотреть: не «ошибка валидации», а конкретная строка и величина расхождения. Бухгалтер открывает скан рядом и за минуту понимает, что не так.
Дальше самое важное — реакция. Бухгалтер может ответить на замечание комментарием: например, что причина указана неверно и в графе «Всего к оплате» написано 36 620,00. Система снимает замечание и подставляет названное значение. Правки оператора «было → стало» при этом попадают в базу знаний системы. Обучение при этом не идёт на документах с неустранёнными ошибками, чтобы система не запоминала чужой мусор.
Граница безопасности прежняя: в 1С уходят только непроведённые черновики, команда проведения из конвейера запрещена всегда, а запись выполняется после явного «Применить» оператора, и каждое решение подписано его учётной записью. Почему я так настаиваю на черновиках, объяснил в статье про черновики вместо проводок.
Пока запись не включена, работает режим dry-run: система показывает, что было бы создано. Если хотите увидеть, как формулы и замечания ведут себя на вашей первичке, можете прогнать ваши сканы в dry-run: 10–20 типичных документов, карточки, замечания и то, что создалось бы в вашей 1С.
Какие ошибки проверки ловят, а какие пропускают: таблица и модельный расчёт
Сведу всё в одну таблицу. Столбец «ловит» относится к моему коду выше и к описанным в статье алгоритмам; цифры для ИНН и счёта — из моей симуляции на случайных данных: | Ошибка | Ловится? | Чем | |---|---|---| | Одна цифра ИНН искажена | почти всегда (12 знаков — всегда, 10 знаков — около 98 %) | контрольное число | | Одна цифра расчётного счёта искажена | всегда | ключ счёта с БИК | | Соседние цифры счёта переставлены | примерно в 89 % случаев | ключ счёта с БИК | | Цифра в количестве, цене или сумме | обычно | арифметика строки и итога | | Ставка 22 вместо 10 | да | ставка × база против налога | | Тот же документ загружен повторно | да | ключ поставщик+номер+дата+сумма | | ИНН прочитан верно, но это чужая компания | нет | нужен реестр, не формула | | Покупатель и продавец перепутаны местами | нет, оба ИНН валидны | сверка со своими юрлицами | | Ошибка в последней цифре копеек | нет, неотличима от округления | допуск в 1 копейку | | Неверное наименование или количество при согласованной сумме | нет | сопоставление с номенклатурой |
Из таблицы вытекает главное: формулы отсекают ошибки чтения цифр, но не ошибки смысла. В нашем каскаде при чистых проверках и высокой уверенности модель вообще не вызывается, но это экономия вызовов, а не гарантия истины. Документ всё равно попадает оператору на «Применить», просто с меньшим числом красных строк. Ошибку смысла ловит человек и справочники 1С: сопоставление контрагента по ИНН и КПП, номенклатуры по артикулу, дубль.
Теперь модельный расчёт для «Складской линии» (напомню: условные цифры). Допущения: 260 сканов в месяц; в 4 % документов, то есть в 10, после распознавания остаётся хотя бы одна ошибка чтения; из этих десяти шесть — искажённые цифры в реквизитах и суммах, которые ловят формулы, четыре — ошибки смысла. Допустим, что ошибка, найденная на карточке, стоит 5 минут, а найденная после оплаты или при сверке — 40. Тогда шесть пойманных документов стоят 30 минут вместо 240, разница около 3,5 часа в месяц. Это немного, и я не буду делать вид, что проверки окупаются часами: они окупаются тем, что платёж не уходит на неверный счёт, а неверный ИНН не остаётся в счёте-фактуре. Здесь уместно помнить п. 2 ст. 169 НК РФ: ошибки, которые не мешают налоговому органу идентифицировать продавца и покупателя, товар, стоимость, ставку и сумму налога, не являются основанием для отказа в вычете, но полагаться на эту норму, а не исправлять реквизиты, я не советую; проверьте свою ситуацию.
Итог, к которому я прихожу в проектах: сначала детерминированные проверки и справочники, потом человек, и только затем, при сомнении, модель. Хотите проверить это на своей стопке — напишите нам, разберём на вашей первичке. Подставьте в расчёт свои значения: число сканов, реальную долю ошибок (её вы замерите на пилоте в dry-run) и стоимость разбора ошибки у вас.
Частые вопросы
Есть ли контрольная сумма у КПП и БИК?
Нет. У КПП проверяют формат из девяти знаков (четыре знака кода налогового органа, два знака причины постановки, три знака порядкового номера; на местах причины допускаются заглавные латинские буквы). У БИК проверяют длину, цифры и наличие в справочнике БИК. Контрольная сумма есть у ИНН и у ключа расчётного счёта.
Как проверить ИНН по контрольному числу?
Для 10 знаков: первые девять цифр умножьте на 2, 4, 10, 3, 5, 9, 4, 6, 8, сумму разделите на 11, и остаток (10 заменяется на 0) должен равняться десятой цифре. Для 12 знаков считаются два числа с коэффициентами 7, 2, 4, 10, 3, 5, 9, 4, 6, 8 и 3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8.
Значит ли, что ИНН прошёл проверку, что компания существует?
Нет. Случайная строка из десяти цифр проходит проверку примерно в 10 % случаев. Формула ловит ошибки чтения, а существование и статус контрагента проверяют по реестру или сервису проверки контрагентов.
Какие ставки НДС допускать при проверке в 2026 году?
Основная ставка 22 %, пониженная 10 %, ноль, а у плательщиков НДС на УСН ещё специальные 5 % и 7 %. Ставку 20 % в документе за 2026 год лучше показывать как предупреждение: ставка зависит от даты отгрузки, и переходные случаи бывают.
Что делать с копеечным расхождением НДС?
Ставьте допуск в одну копейку на строку и показывайте такое расхождение как информационное. НДС в счёте-фактуре не округляют по правилам уплаты налога, поэтому копеечные разницы у поставщиков встречаются. Всё, что больше допуска, стоит выносить как ошибку.
Проверки заменяют бухгалтера?
Нет. Они отсекают ошибки чтения цифр. Ошибки смысла ловят справочники 1С и человек, а в нашем модуле запись в 1С идёт только черновиками после явного «Применить» оператора, проведение из конвейера запрещено.
Источники
- ФНС России: приказ от 26.06.2025 № ЕД-7-14/559@ (порядок присвоения ИНН) — Проверено: действует с 01.01.2026, структура ИНН, длина 10/12 знаков, контрольное число «по алгоритму ФНС». https://www.nalog.gov.ru/rn77/about_fts/docs/16581721/
- ФНС России (КонсультантПлюс): как отображается структура ИНН для организаций — Проверено: методика расчёта контрольного числа ИНН и структура и условия присвоения КПП с 2026 года не меняются, изменился смысл 3–4 знаков ИНН. https://www.consultant.ru/document/cons_doc_LAW_518274/
- ГАРАНТ: Письмо Банка России от 08.09.1997 № 515 — Проверено: порядок расчёта контрольного ключа в номере лицевого счёта, коэффициенты 7, 1, 3, условный номер по БИК. https://base.garant.ru/576000/
- ФНС России: методические рекомендации по НДС для УСН 2026 — Проверено: расчётные ставки 22/122, 10/110, 5/105, 7/107 (п. 4 ст. 164 НК РФ). https://data.nalog.ru/html/sites/www.new.nalog.ru/doc/metod_usn_nds_2026.pdf
- 1С:ИТС: ставка НДС 22 % с 1 января 2026 года — Проверено: основная ставка 22 %, Федеральный закон от 28.11.2025 № 425-ФЗ, расчётные ставки 22/122 и 10/110. https://its.1c.ru/db/content/newscomm/src/497590.htm
- КонсультантПлюс: статья 169 НК РФ «Счёт-фактура» — Проверено: п. 2 — ошибки, не препятствующие идентификации продавца, покупателя, товара, стоимости, ставки и суммы налога, не основание для отказа в вычете. https://www.consultant.ru/document/cons_doc_LAW_28165/66291cb6e9896ddcd8aa96ba17cbcba10085683d/
