ТОРГ-12: какие графы важны и где OCR ошибается чаще всего

Содержание
  1. Самая популярная форма
  2. Что нужно вытащить
  3. Как это работает в ScanFlow
  4. Что важно учесть
  5. FAQ
  6. Что дальше

В очередь на распознавание у нас каждый день падает несколько тысяч накладных. Если посмотреть на распределение по формам — больше половины это ТОРГ-12, унифицированная форма товарной накладной, утверждённая Госкомстатом ещё в 1998 году. Бумажная, разлинованная в 30 колонок, с печатями и подписями поверх итогов. Если OCR-движок умеет распознавать ТОРГ-12 — он закроет половину входящего потока в среднем российском торговом бизнесе. Если не умеет — толку от него мало.

В этой статье разберём, какие графы ТОРГ-12 действительно нужны для импорта в 1С, какие классы OCR-ошибок чаще всего встречаются именно на этой форме и почему простая кросс-валидация qty × price ≈ total с допуском в 1% — один из самых дешёвых и эффективных защитных механизмов в нашем пайплайне.

ТОРГ-12 — самая популярная и самая ломкая форма

ТОРГ-12 — это товарная накладная, в которой поставщик передаёт товар покупателю. С 1998 года она унифицирована Госкомстатом и де-факто стала стандартом: каждая вторая накладная в РФ выглядит именно так. Особенно в продуктах, опте, общепите, FMCG — везде, где сделки происходят по бумаге, а не через ЭДО.

Бухгалтер видит её каждый день. Утром приехала машина с товаром — экспедитор привёз пачку ТОРГ-12, надо принять, сверить, ввести в 1С. К обеду накопилось 15-30 листов на разных бланках от разных поставщиков. И вот тут начинается интересное.

ТОРГ-12 — форма ломкая по своей природе:

  • 30+ колонок в табличной части. Полная форма не помещается на A4 в горизонтальной ориентации — печатают мелким шрифтом, нередко 7-8 pt.
  • Скан/фото плохого качества. Накладную ловят на телефон в полумраке склада, на бумаге пятна, тени, блики.
  • Печати поверх данных. Круглая печать организации часто заходит на ячейку «Сумма с НДС» в подвале — а это критичное поле.
  • Рукописные правки. «Сторнировали последнюю позицию, поправили итог ручкой» — стандартный сценарий в опте.
  • Переносы между листами. Длинная номенклатура на 2-3 страницах, шапка повторяется (или нет), последняя строка обрывается на середине.

То есть OCR-инструмент, который хорошо работает на «чистых» PDF — счёт-фактурах из 1С, актах от крупных контрагентов — на ТОРГ-12 ломается на первом же скане из реального склада. И задача движка не «прочитать каждый пиксель», а «вытащить ровно то, что нужно бухгалтеру для проводки», устойчиво к шуму.

Что нужно вытащить из ТОРГ-12

Хорошая новость: для импорта в 1С нам нужно не всё, что напечатано на форме. Большая часть полей — статика бланка, реквизиты «грузоотправителя» и «грузополучателя» (часто совпадают с поставщиком и покупателем), коды по ОКПО/ОКУД, номера приложений. Это служебная информация, для проводки она не нужна.

Реально критичные поля делятся на три блока.

Шапка накладной:

  • Номер накладной и дата — без них в 1С документ не создать.
  • Поставщик: наименование, ИНН, КПП, юридический адрес. ИНН — главное; по нему можно достать всё остальное из DaData, если у нас уже нет карточки контрагента.
  • Покупатель: ИНН — для контроля, что накладная пришла именно нам.
  • Основание: ссылка на договор или счёт. Не обязательно, но удобно для последующего связывания платёжки с накладной.

Табличная часть (на каждую позицию):

  • Артикул / SKU — код товара у поставщика. Используется для маппинга в наш справочник номенклатуры.
  • Наименование товара — обязательно. Если артикула нет — маппим по названию через нечёткий поиск.
  • Единица измерения: код по ОКЕИ + сокращение (кг, шт, л, упак).
  • Количество — числовое значение.
  • Цена без НДС — за единицу.
  • Ставка НДС — 0/10/20% (исторически 18 и 22 тоже встречаются).
  • Сумма НДС и сумма с НДС — итог по строке.

Подвал:

  • «Всего наименований» и «Всего к оплате» — для сверки с суммой по позициям.
  • Подписи «отпустил» и «принял» — для нас опционально, но удобно сохранять в архив.

В итоге шапка уходит в Документы.ПриходнаяНакладная (поставщик, номер, дата), артикул и наименование позиций — в Справочники.Номенклатура, а количество/цена/НДС — в табличную часть приходной. Всё остальное — нам не нужно.

Как это работает в ScanFlow

Пайплайн распознавания ТОРГ-12 у нас единый с другими формами — Claude Sonnet 4.6 в vision-режиме видит документ целиком и возвращает структурированный JSON. Детали этого подхода разобраны в первой статье про OCR; здесь сосредоточимся на специфике ТОРГ-12: какие поля где брать, какие ошибки ловить, как защититься от ложных срабатываний.

Поля формы: что обязательно, что опционально

Не все поля одинаково критичны. Если в шапке не нашлось ИНН поставщика — импорт в 1С фейлится, нет смысла даже пытаться. А вот если не распознался КПП — нестрашно: по ИНН поставщика мы либо найдём его в нашем справочнике, либо подтянем КПП из DaData по `/findById/party`. То же с банковскими реквизитами — они не нужны для приходной накладной (нужны позже, при создании платёжки).

Минимум для проводки:

  • ИНН поставщика (10 или 12 цифр).
  • Номер накладной + дата.
  • Минимум одна позиция с заполненными количеством, ценой и итогом.

Желательно, но не блокирует:

  • КПП — можно подтянуть из DaData.
  • Артикулы у позиций — если нет, маппим по названию через Fuse.js (нечёткий поиск по справочнику номенклатуры).
  • Адрес поставщика, банковские реквизиты — нужны только для платёжки.

Эта дифференциация важна: если бы мы требовали «всё или ничего», 30-40% реальных накладных не проходили бы через пайплайн. А так — обязательны только три вещи, остальное обогащаем из других источников или восстанавливаем эвристиками.

Типичные OCR-ошибки на ТОРГ-12: SKU↔qty, НДС↔total, перенос между страницами

За год работы на боевом потоке мы накопили статистику по классам ошибок. Вот пятёрка самых частых, которые встречаются именно на ТОРГ-12.

1. SKU распарсен как количество. Артикул товара у поставщика — обычно 5-6 цифр без пробелов: 113393, 004217. Если он стоит в строке рядом с числовыми ячейками, OCR-движок иногда «съезжает» на колонку и записывает артикул в графу «Количество». В итоге получаем строку с qty = 113 393 шт. Очевидно бредовая, но без специальной защиты — попадает в 1С и портит остатки.

2. Свап «без НДС / НДС / с НДС». Три соседние колонки в табличной части: цена без НДС, сумма НДС, сумма с НДС. Цифры в них логически связаны (третье = первое + второе), но визуально похожи. На плохом скане OCR может перепутать порядок: запишет в total то, что было в graph «без НДС», и наоборот. Дальше у позиции получается несоответствие qty × price ≠ total, и это ловится валидацией.

3. Перенос строки между страницами. Позиция №15 начинается на первой странице («Молоко 3.2% Простоквашино…»), а её количество и цена — уже на второй. Если страницы распознаются независимо, на стр. 1 получим «висящее» наименование без числовых полей, на стр. 2 — «висящие» числа без названия товара. Обе строки невалидны. Защита — мерж страниц в один документ ещё до распознавания, плюс эвристика «строка считается завершённой, только если в ней есть и наименование, и числа».

4. Печать поверх итога. Круглая печать организации часто захватывает левый верхний угол подвала, где написано «Всего к оплате: 12 850.00 ₽». Если печать заехала на первую цифру — OCR прочитает 2 850.00 вместо 12 850.00. Несоответствие тут же ловится: сумма по позициям не сходится с итогом из подвала, флаг items_total_mismatch поднимается, накладная не уходит в автоимпорт.

5. Слипшийся unit code с количеством. В ячейке «Количество» напечатано 10, в соседней — кг. Без аккуратной токенизации OCR может склеить их в одну строку 10кг и записать как количество — целиком, со строковым суффиксом. Парсер падает на попытке привести 10кг к Number. Защита — отдельный шаг разбора, который вытаскивает unit-суффикс из чисел.

Кросс-валидация: qty × price ≈ total ± 1%

Самый дешёвый и эффективный защитный механизм в нашем пайплайне. После того как Claude вернул структурированный JSON с позициями, мы для каждой строки делаем тривиальную проверку: умножаем количество на цену и сравниваем с итогом по строке. Допуск — 1%, потому что в накладных регулярно встречаются мелкие расхождения из-за округления НДС (например, 19.00 × 13.50 = 256.50, но в накладной может стоять 256.49 или 256.51).

// src/parser/invoiceParser.ts (упрощённо)
for (const item of items) {
  const expected = item.quantity * item.price;
  const actual = item.total;
  const diff = Math.abs(expected - actual);
  const tolerance = Math.max(expected, actual) * 0.01;

  if (diff > tolerance) {
    item.flagged = true;
    invoice.items_total_mismatch = 1;
  }
}

На наших боевых данных эта проверка ловит около 30% мелких OCR-ошибок ещё до того, как накладная уходит в 1С. Тот же показатель упоминается в первой статье — это не совпадение, а уже четвёртый месяц подряд статистика держится в районе 27-32%. Ошибки бывают такие: цифра потерялась (256.50 → 25.50), две цифры поменялись местами (256.50 → 265.50), запятая съехала (256.50 → 2 565.00).

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

После того как флаг items_total_mismatch установлен, накладная попадает в очередь «требует проверки», а не «готово к импорту». Оператор открывает её в дашборде, видит подсвеченную строку, сверяет с фото и либо правит руками, либо подтверждает (если расхождение реально присутствует в бумажной накладной — да, такое тоже бывает, тогда импортируем как есть и в комментариях помечаем «расхождение в исходнике»).

Что важно учесть

Две специфичные грабли, на которые приходится обращать внимание именно из-за устройства ТОРГ-12. Если их не учесть — парсер портит данные молча, без явных ошибок.

Лимит 4 цифры на количество (артикулы 6-значные)

В нашем парсере зашита жёсткая эвристика: количество в строке ТОРГ-12 не может содержать больше четырёх знаков целой части. То есть 1234 — ОК, 9999 — ОК, а 10 000 и тем более 113 393 — невалидное количество, парсер откажется его подхватывать.

// src/parser/invoiceParser.ts:246
// Standalone quantity: just a number like "60"
// (max 4 digits to avoid article codes)
const qtyMatch = line.match(/^(\d{1,4}(?:[.,]\d+)?)\s+(кг|шт|л|уп|упак|пач|бут)\.?\s*$/i);

Почему 4 цифры? Потому что артикулы товаров у поставщиков почти всегда 5-6 знаков (113393, 004217, 78521), а реальное количество товара в одной строке накладной — почти всегда меньше 10 000 единиц. Случай «отгрузили 50 000 шт болтов» — редкий, и тогда оператор поправит руками. Случай «артикул затесался в графу количество» — частый, и от него важно защититься.

Не «ослабьте лимит», не «уберите ограничение». Эта эвристика — load-bearing в нашем парсере. Каждый раз, когда кто-то предлагал поднять лимит до 5 или 6 знаков «для редких случаев», в течение недели возвращались SKU-как-quantity ошибки в реальных накладных. Если у конкретного клиента действительно бывают отгрузки больше 9999 единиц — это исключение, которое решается на уровне конфигурации, а не правки общего парсера.

Подписи поверх итогов

В подвале ТОРГ-12 есть строка «Всего к оплате» с итоговой суммой — это контрольная сумма, по которой мы сверяем корректность распознавания позиций. И именно эту ячейку чаще всего перекрывают подписи и печати: бухгалтер расписался «отпустил груз», размашисто захватив пару сантиметров вправо — и часть цифры потерялась под завитушками.

Защита тут двухслойная. Первый слой — кросс-валидация: сумма по позициям должна сходиться с итогом из подвала. Если не сходится — поднимаем флаг и не отправляем в 1С автоматически. Второй слой — оператор смотрит фото и принимает решение. Лучше потратить минуту человека на проверку, чем неделю потом разбираться, почему у поставщика разошёлся учёт.

💡

Совет по съёмке ТОРГ-12. Если накладная многостраничная — снимайте страницы поочерёдно с одинакового ракурса и расстояния. Не пытайтесь «упихать» две страницы в один кадр — лучше две отдельные фотографии. Фон обрезается автоматически, перспективные искажения тоже выпрямляются, но только если на снимке видна вся форма целиком и без пересечений с соседней страницей. Освещение — лучше дневной свет от окна, чем верхняя люминесцентная лампа: меньше бликов на печатях.

FAQ

А ТОРГ-12 устарела, же ЭДО?

С 2025 года электронный документооборот стал обязательным только для маркируемых товаров — табак, обувь, парфюмерия, лекарства, молочка, шины и ещё несколько категорий. Всё остальное по-прежнему можно оформлять бумажной ТОРГ-12, и большинство поставщиков в малом и среднем бизнесе именно так и делают. Особенно — сельское хозяйство, опт продуктов, общепит, региональные дистрибьюторы. Полный переход на ЭДО — это горизонт 5-7 лет, не «уже сейчас». До тех пор ТОРГ-12 остаётся основной формой входящей первички.

Что с УПД (универсальным передаточным документом)?

УПД — это «два в одном»: накладная (ТОРГ-12) и счёт-фактура, объединённые в одном бланке. Структура полей очень похожа: те же шапка-таблица-подвал, тот же ОКЕИ, та же графа НДС. Разница в дополнительных реквизитах счёта-фактуры (КПП продавца, страна происхождения товара, номер ГТД) и в признаке функции документа: УПД-1 со статусом «1» — это накладная + счёт-фактура одновременно, УПД-2 со статусом «2» — только накладная. ScanFlow распознаёт оба формата по той же схеме, что и ТОРГ-12 — Claude vision-LLM достаточно гибкий, чтобы не различать их на уровне промпта.

Как ScanFlow понимает, что это именно ТОРГ-12?

В основном режиме (Claude vision) — никак, в смысле «нам не нужно классифицировать». LLM видит документ целиком, понимает, что это товарная накладная (по форме таблицы, по заголовкам колонок, по подписям внизу), и достаёт нужные поля независимо от того, ТОРГ-12 это, УПД, или какая-то фирменная форма поставщика. Для legacy-regex-фолбэка у нас есть несколько якорей (заголовок «ТОРГ-12», наличие подвала «Всего наименований», характерные ширины колонок), но в боевом режиме они почти не используются.

Что дальше

Мы разобрали структуру ТОРГ-12, обязательные поля и пять классов OCR-ошибок, которые ловит наша валидация. Чтобы закрыть тему распознавания первички, осталось два смежных сюжета.

Хотите проверить, как пайплайн справится с вашими накладными — зарегистрируйтесь, сейчас бесплатно во время беты. Загрузите несколько типовых ТОРГ-12 от своих поставщиков и посмотрите, какие поля вытаскиваются и где срабатывает кросс-валидация.