OCR прочитал сумму: как проверить денежные поля перед загрузкой в учёт
Сервис распознал счёт и вернул JSON. В поле итога стоит число, формат корректный, уверенность OCR высокая. Но загрузка документа в учёт всё ещё может создать ошибочную сумму. Полезная дополнительная проверка — связать денежные поля теми же арифметическими правилами, по которым составлен документ, и отправлять расхождения на ручную проверку.
Сначала зададим смысл каждой суммы
Для примера возьмём один тип документа: строки содержат суммы без налога, subtotal равен сумме строк, tax добавляется сверху, total равен subtotal плюс tax. Это явный контракт конкретного шаблона. В документе с ценами, уже включающими налог, или со скидкой после итогов формулы будут другими. Поэтому первый шаг при загрузке — определить тип формы и применимое правило.
Денежные значения передаются строками с точкой и ровно двумя десятичными знаками. Такое ограничение упрощает договор между извлечением и проверкой. Если OCR вернул запятую, валютный символ или пробелы разрядов, их нормализуют в отдельном этапе с сохранением исходного текста. Нельзя молча выкидывать все нецифровые символы: минус и десятичный разделитель меняют смысл числа.
Проверка использует Decimal из строк. Это позволяет точно представить десятичные суммы и сравнивать копейки без погрешности двоичного float. Мы не будем округлять произвольное число до двух знаков на этапе приёмки: лишняя точность считается нарушением входного контракта. Так ошибка извлечения не превратится в незаметно исправленную сумму.
Отделим ошибку поля от расхождения итогов
В примере три результата извлечения. У первого все связи сходятся, у второго OCR потерял разряд в total, у третьего tax имеет неверный формат. Код возвращает причины отклонения и id документа. Это позволяет сформировать очередь, в которой сотрудник видит, что именно нужно сверить с изображением.
import re
from decimal import Decimal
def money(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9]+\.[0-9]{2}", value):
raise ValueError("money_format")
return Decimal(value)
def check(doc):
try:
lines = [money(v) for v in doc["lines"]]
subtotal, tax, total = (money(doc[k]) for k in ("subtotal", "tax", "total"))
except (ValueError, KeyError, TypeError):
return ["invalid_fields"]
reasons = []
if not lines:
reasons.append("empty_lines")
if sum(lines, Decimal("0.00")) != subtotal:
reasons.append("lines_vs_subtotal")
if subtotal + tax != total:
reasons.append("subtotal_tax_vs_total")
return reasons
base = {"lines": ["100.10", "200.20"], "subtotal": "300.30", "tax": "60.06", "total": "360.36"}
docs = [("ok", base), ("bad_total", {**base, "total": "36.36"}),
("bad_format", {**base, "tax": "60,06"})]
results = {id_: check(doc) for id_, doc in docs}
for id_, reasons in results.items():
print(id_, "accept" if not reasons else ",".join(reasons))
assert results == {"ok": [], "bad_total": ["subtotal_tax_vs_total"], "bad_format": ["invalid_fields"]}
assert check({**base, "lines": ["100.10", "200.21"]}) == ["lines_vs_subtotal"]
Документ ok проходит проверку. bad_total получает причину subtotal_tax_vs_total, а bad_format — invalid_fields. Неверный десятичный разделитель не смешивается с арифметическим расхождением. Сумма строк вычисляется из входных значений; ожидаемый итог не зашит в функцию. Дополнительный assert проверяет изменение копейки в отдельной строке.
На такой проверке нельзя автоматически «исправить» total суммой других полей. Если subtotal тоже распознан неправильно, система просто заменит одну ошибку другой. Покажите человеку исходное изображение, распознанные значения и конкретное равенство, которое не сошлось. Если у OCR есть координаты фрагментов, подсветите все участвующие поля, включая строки, а не только итог.
Согласованность полезна, но не доказывает правильность
Несколько неверных чисел могут согласованно удовлетворять формулам. Например, распознавание пропустило одну строку и одновременно уменьшило subtotal и total. Арифметическая проверка этого не обнаружит. На размеченной выборке нужно отдельно считать точность денежных полей и долю ошибочных документов, прошедших автоматическую приёмку. Для проверки полноты строк пригодится сравнение с ручной разметкой таблицы.
Для разных шаблонов заведите версионируемые правила. Если документ содержит построчный налог с округлением, считайте налог по строкам и только затем суммируйте. Расчёт от общей суммы может дать другую копейку. Если присутствуют скидки, авансы или удержания, включайте их как отдельные поля формулы. Допуск на расхождение задавайте из правил документа; произвольный широкий допуск скрывает потерянные значения.
Перед записью в БД сохраняйте статус проверки, причины, версию правил и ссылку на исходный файл. При повторной загрузке различайте новое распознавание и новый документ, чтобы исправление OCR не создало дубликат хозяйственной операции. Статистику причин отказа удобно использовать для выбора следующей работы: корректировать извлечение таблиц, нормализацию валюты или распознавание конкретного шаблона.
Поля валюты и знака документа проверяйте до арифметики. Возврат, корректировочный документ и обычный счёт могут по-разному задавать отрицательные суммы. Пример принимает только неотрицательные значения, поэтому для таких форм нужен отдельный контракт с явным знаком. Если разные строки имеют разные валюты, сложение вообще требует согласованного курса и даты пересчёта. Корректное равенство чисел без этой информации не подтверждает пригодность записи для учёта.
При ручной проверке записывайте исправление структурированно: какое поле изменили, какое значение было в извлечении и что видно в документе. Это позволяет оценить, какие ошибки переживают автоматические правила. Например, частые исправления реквизитов при сходящихся суммах означают, что расширять денежные равенства недостаточно. Для следующей версии потребуется проверка контрагента и номера документа по собственным правилам, а тестовый набор должен сохранять эти случаи отдельно от арифметических расхождений.
В платном курсе «Computer Vision Engineer» есть прикладная линия Doc-AI: чеки и акты проходят путь от распознавания до JSON и БД. Вы также работаете с сервисным слоем и метриками. Это помогает собрать весь процесс вокруг проверки: получить поля, сохранить результат и разобрать ошибку документа. Откройте программу курса.
Продолжить на практике
- Computer Vision Engineer: от детекции и Doc-AI до продакшна ↗
Практический курс по Computer Vision. Соберёте продакшн-сервис: датасет → обучение (YOLO/RT-DETR, SAM), трекинг, OCR/Doc-AI, веб-приложение (UI+API) и мониторинг. Экспорт в ONNX/TensorRT/OpenVINO, батчинг/квантование под CPU/GPU. Плюс профессиональные блоки: mAP/IoU/Dice и калибровка доверия, QA разметки и аугментации, p95/стоимость, дрейф данных и алерты, guardrails (NMS/правила), A/B-тесты, Triton/gRPC/FastAPI.
