Как было: три источника правды на одну цифру

Подрядчик вёл несколько объектов одновременно. Приход материалов жил в бумаге и в чатах. Прораб снимал ТТН на телефон и кидал фото в Telegram, дальше инженер ПТО или бухгалтер переписывал это в Excel руками.

Из-за ручного переноса факт был оторван от сметы. Перерасход по песку, щебню или асфальту вылезал только после закрытия месяца. К этому моменту деньги уже потрачены, повлиять нечем.

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

Что сделали: один путь документа

Прораб фотографирует накладную прямо на объекте. Фото сжимается в браузере и уходит в приватное хранилище, документ встаёт в очередь. Сервер доворачивает картинку по EXIF, ужимает до 2048 px и отдаёт на распознавание.

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

  • Журнал материалов с двойной приёмкой: объём подтверждает прораб, деньги бухгалтер. Пока нет обеих отметок, документ в журнал не попадает. Дубли по номеру накладной ловятся автоматически.
  • План и факт по объекту: смета грузится из Excel, позиции накладной привязываются к статьям автоматически, лимит расхода показан зонами (зелёная, жёлтая, красная).
  • Раздел «Нераспределено» для позиций, которые не привязались. Они остаются на виду и ждут человека.
  • Выгрузка журнала входного контроля в ГОСТ-форме на девять нумерованных колонок: это то, что подрядчик сдаёт уже своему заказчику.
  • Мобильный ввод под стройку: съёмка через родную камеру телефона, фоновое распознавание, ручное добавление поставки без фото для старых проектов.
!
Объём рейса написан рукой на правой половине листа

Транспортная накладная по Приложению №4 печатается на альбомном листе. Слева разделы с первого по седьмой, справа с восьмого по двенадцатый. Объём за рейс прораб пишет рукой справа, в разделе выдачи груза. Яндекс на общем кадре стабильно возвращал только левую страницу. Первый триггер сделали по краю текста, он не сработал: крупная рукопись справа дотягивала границу. Рабочим оказался признак плотности. Если в правой половине кадра блоков в разы меньше, чем в левой, страница недочитана, её надо вырезать и читать отдельным проходом. Кропа тоже не хватило: фото из мессенджера около 970 px, вырезанная половина около 530 px, модель гадала и читала 25 как 35. Апскейл кропа до 1600 px с лёгкой резкостью сделал чтение стабильным: три прогона из трёх на двух тестовых документах. На одном документе основной проход слепил цифры адреса и объёма и выдал 291 вместо 22, прицельный кроп прочитал верно. Контринтуитивная деталь напоследок: инструкция «игнорируй штамп» сделала только хуже, модель выбрасывала штрихи цифр вместе со штампом.

Почему одного распознавателя не хватило

Бумажную ТТН одна модель целиком не берёт. GPT-4o понимает структуру документа, но путается в печатных цифрах. Yandex Vision точно снимает печатный слой, на рукописи слабее.

Сделали гибрид. Яндекс отдаёт печатный текст с координатами как источник истины, GPT-4o собирает по нему документ и читает рукописные поля. Поверх модели стоят детерминированные проверки: ИНН по контрольной сумме, госномер по строгой структуре российского номера, цена умножается на объём и сверяется со стоимостью строки.

Главное правило системы простое: неверное значение хуже пустого. Всё, в чём распознавание не уверено, показывается человеку подсказкой и подсвечивается до сверки. Гибрид работает за флагом: если Яндекс отвалился, документ читается чистым GPT-4o и сервис остаётся на ногах.

Грабли

Почти каждое решение здесь выросло из провала на реальном фото, а не из идеи в голове. Ниже то, что стоило дней.

  • Модель уверенно врёт. Ловили госномер с несуществующим регионом, даты из будущего, выдуманные позиции материалов. Закрыли бэкенд-валидаторами и правилом в промпте «лучше пусто, чем правдоподобная выдумка».
  • Пример в промпте отравлял результат: модель подставляла номер машины из образца в реальные документы. Пример убрали. Текущую дату перестали передавать целиком, иначе модель копировала её как дату документа.
  • Импорт исторического журнала терял данные молча. Читал не тот лист книги Excel и бил одну накладную на отдельные документы, половина из которых помечалась дублем. Переделали на авто-выбор листа и группировку строк, добавили честный отчёт: сколько накладных, сколько материалов, сколько дублей, сколько ошибок.
  • Сети клиентов режут методы PATCH и DELETE. Правки из браузера молча терялись при живой галочке «сохранено». Всю запись перевели на POST.
  • Съёмка через веб-камеру браузера давала кадр видеопотока без автофокуса, рукопись с него читалась заметно хуже. На телефонах переключились на родную камеру. Фото с айфона упиралось в лимит nginx и падало как «ошибка сети»: добавили сжатие на клиенте.
  • Гонка на двойной приёмке. Если прораб и бухгалтер жмут «проверено» одновременно, каждый видит отметку другого пустой и документ застревает. Отметки стали перечитываться из базы, финальный перевод статуса сделали идемпотентным.
  • В JavaScript граница слова не работает с кириллицей. Из-за этого проверка по слову «ТРАНСПОРТНАЯ» молча не срабатывала на реальных документах, и целый проход распознавания не запускался.

Права и безопасность

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

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

Сроки и что происходит дальше

Первый коммит 19 июня 2026, последний 13 августа. Около восьми недель и 197 коммитов. Каркас с распознаванием и контейнеризацией собрали за первую неделю, роли и план объекта к концу июня, аудит безопасности прошли в начале июля.

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

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

Частые вопросы

Как вести учёт материалов на стройке по фото накладной?

Прораб фотографирует ТТН прямо на объекте. Фото сжимается в браузере и уходит в приватное хранилище, дальше сервер доворачивает картинку по EXIF и отдаёт на распознавание. Документ проходит две приёмки: объём подтверждает прораб, деньги бухгалтер. После этого позиции встают строками в смету объекта, и перерасход виден в тот же день.

Распознаёт ли программа рукописный текст в накладной?

Да, для этого работает связка двух моделей. Yandex Vision снимает печатный слой с координатами и служит источником истины, GPT-4o собирает по нему документ и читает рукописные поля. Поверх стоят детерминированные проверки: ИНН по контрольной сумме, госномер по структуре российского номера, цена умножается на объём и сверяется со стоимостью строки. Правило системы: неверное значение хуже пустого, всё сомнительное подсвечивается человеку.

Как перенести журнал материалов из Excel без потерь?

Импорт сам выбирает нужный лист книги и группирует строки одной накладной в один документ. Отчёт после загрузки честный: сколько накладных, сколько материалов, сколько дублей, сколько ошибок. На реальном файле заказчика перенеслись 341 строка из 341, до переделки система видела только 35.

Как увидеть перерасход по смете на объекте?

Смета грузится из Excel, позиции накладной привязываются к статьям автоматически. Лимит расхода показан зонами: зелёная, жёлтая, красная. Позиции, которые не привязались, лежат в разделе «Нераспределено» и ждут человека, поэтому цифра не расходится молча.

Можно ли выгрузить журнал входного контроля по ГОСТ?

Да, выгрузка идёт в ГОСТ-форме на девять нумерованных колонок. Это тот документ, который подрядчик сдаёт уже своему заказчику. Данные в него попадают из журнала материалов, поэтому переписывать вручную ничего не нужно.