TL;DR: Разбираю типовую архитектуру системы автоматической обработки финансовых документов: приём, предобработка, классификация, извлечение полей, валидация по контрольным суммам и эскалация спорных случаев человеку. Сравниваю OCR-движки с реальными trade-off’ами, показываю проверяемый код и объясняю, как уложиться в 152-ФЗ и требования ЦБ РФ, не отдавая документы наружу.
Документооборот в финансовой организации — это поток сканов, фотографий с телефона и PDF-файлов разного качества: паспорта, справки о доходах, банковские выписки, договоры, анкеты. Пока их обрабатывают люди, скорость ограничена руками оператора, а стоимость ошибки пропорциональна сумме сделки. Автоматизация здесь решает две задачи: снимает рутину с сотрудников и оставляет проверяемый след для аудита — что в финтехе не опция, а требование.
Ниже — не рассказ про конкретный проект, а типовая архитектура, которую я собираю по кусочкам на таких задачах. С пояснениями, почему именно так, и где система обычно ломается.
Что нужно автоматизировать на самом деле
Частая ошибка — начинать с выбора OCR-движка. Правильнее начать с карты процесса. Типовой ручной цикл выглядит так:
- документ приходит по почте, из личного кабинета или от менеджера;
- оператор определяет тип документа;
- вбивает поля в CRM или АБС;
- второй сотрудник сверяет введённые данные с оригиналом;
- документ архивируется, действие логируется (или нет).
Автоматизировать нужно не «распознавание текста», а всю цепочку целиком — иначе получится OCR-игрушка, за которой всё равно стоит человек с копипастой. Целевая схема:
Приём → предобработка → классификация типа документа → извлечение полей → валидация → маршрутизация аномалий человеку → аудит-лог.
Последние два пункта критичны и их почему-то часто пропускают. Без маршрутизации аномалий вы получаете незаметную порчу данных, без аудит-лога — проблемы при любой проверке.
Приём и предобработка
Откуда берутся документы
Источников обычно три: email-вложения, загрузка через веб-форму, API от смежных систем. Объединяет их одно: формат на входе непредсказуем. PDF может быть текстовым, сканом или текстовым слоем поверх кривого скана. Поэтому первый шаг — понять, что вообще пришло:
from pypdf import PdfReader
def pdf_has_text_layer(pdf_path: str, min_chars: int = 50) -> bool:
"""Проверяем, есть ли в PDF текстовый слой."""
reader = PdfReader(pdf_path)
for page in reader.pages:
text = page.extract_text() or ""
if len(text.strip()) >= min_chars:
return True
return False
Если текстовый слой есть и он адекватный — OCR не нужен вовсе, экономите время и избегаете ошибок распознавания. Если нет — рендерим страницы в изображения и идём в предобработку.
Нормализация изображений
Реальный вход — это фотографии документа под углом, на тёмном фоне, с тенью от телефона. Минимальный набор на OpenCV:
import cv2
import numpy as np
def preprocess(image_path: str) -> np.ndarray:
image = cv2.imread(image_path)
# Перевод в оттенки серого
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Выравнивание освещения: делим на размытую версию изображения
background = cv2.medianBlur(gray, 51)
normalized = cv2.divide(gray, background, scale=255)
# Адаптивная бинаризация — устойчива к неравномерному свету
binary = cv2.adaptiveThreshold(
normalized, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
blockSize=35, C=15,
)
return binary
Что здесь принципиально: адаптивная бинаризация вместо глобального порога (тень от руки ломает глобальный порог на раз), выравнивание освещения до бинаризации, а не после. Поворот страницы (deskew) можно определить через Hough-преобразование по строкам текста или через minAreaRect по контуру контента — оба способа из OpenCV, работают на структурированных документах. Детали выбора движков и предобработки я разбирал в гайде по OCR, здесь не повторяюсь.
Классификация типа документа
Прежде чем извлекать поля, нужно понять, что перед нами. Рабочая схема — каскад от дешёвого к дорогому:
- Правила и ключевые слова. Паспорт содержит «выдан» и код подразделения в формате
XXX-XXX, справка 2-НДФЛ (с 2021-го — «Справка о доходах») — характерные заголовки. На структурированных потоках правила закрывают большую часть документов за миллисекунды. - ML-классификатор по тексту для документов, которые правила не различили. Достаточно fine-tuned русской BERT-подобной модели — точность на 10–15 классах высокая, инференс на CPU приемлемый.
- Визуальный классификатор как fallback для документов с мусорным OCR-слоем.
Важный нюанс: классификатор должен уметь отвечать «не знаю». Документ с confidence ниже порога не должен попадать в экстрактор «наугад» — он уходит к человеку. Это дешевле, чем потом вычищать данные паспорта, распарсенные по шаблону договора.
Извлечение полей: три слоя
Универсального экстрактора не существует. Рабочая схема — три слоя с разной стоимостью и точностью.
Слой 1: правила и регулярные выражения
Серия и номер паспорта, ИНН, СНИЛС, БИК, код подразделения — всё это формализованные форматы. Регулярки здесь не «грубый хак», а самый точный инструмент: у ИНН есть контрольное число, и его можно проверять. Правила пишутся под конкретный шаблон документа, их много, и это нормально.
Слой 2: ML-модели
Для полей в свободной форме — ФИО, адреса, наименования работодателей — работает NER или layout-aware модели, которые учитывают расположение текста на странице, а не только сам текст. Таблицы в выписках — отдельная история, там помогают детекторы таблиц плюс эвристики по выравниванию колонок.
Слой 3: LLM-экстракция по схеме
Для нестандартных документов, которых в потоке мало, держать отдельный шаблон неэкономично. Здесь подключается LLM: отдаёте распознанный текст, схему полей — получаете структуру. Схему описываю через pydantic — она же служит валидацией ответа:
from pydantic import BaseModel, Field
class PassportData(BaseModel):
last_name: str
first_name: str
middle_name: str | None = None
birth_date: str = Field(pattern=r"^\d{2}\.\d{2}\.\d{4}$")
series: str = Field(pattern=r"^\d{4}$")
number: str = Field(pattern=r"^\d{6}$")
issued_by: str
issue_date: str = Field(pattern=r"^\d{2}\.\d{2}\.\d{4}$")
department_code: str = Field(pattern=r"^\d{3}-\d{3}$")
Если LLM вернула JSON, не проходящий схему, — это повод для повторного запроса или эскалации, а не для «ну почти совпало». Подробнее про паттерны структурированного вывода и связку с retrieval я писал в статье про RAG-пайплайн для корпоративных документов.
Выбор OCR-движка: честные trade-off’ы
- Tesseract 5.x — бесплатный, быстрый на CPU, предсказуемый. На чистых сканах хорош, на фото с телефона и плохом освещении заметно теряет точность. Требует качественной предобработки — без неё результат нестабилен.
- PaddleOCR — лучше справляется с углами, искажениями и нестандартными шрифтами, из коробки даёт координаты блоков. Цена — больший footprint и экосистема, к которой надо привыкнуть.
- Surya — сильный открытый вариант последних лет: хорошо держит многоязычные документы и сложную вёрстку, определяет порядок чтения. Тяжелее Tesseract, хочет GPU для приличной скорости.
- Облачные API (Yandex Vision OCR, Azure Document Intelligence) — высокое качество из коробки, поддержка таблиц и форм, ноль инфраструктуры. Минусы: цена за страницу и, главное для финтеха, документы уходят во внешний контур. Для персональных данных клиентов это почти всегда ограничение.
- ABBYY (FineReader Engine / FlexiCapture) — стабильно высокая точность, зрелые инструменты верификации. Дёшево не бывает: лицензионная модель рассчитана на enterprise.
Практический вывод: базовый контур — локальные модели (Tesseract или PaddleOCR плюс предобработка), облако — только для обезличенных или нечувствительных документов и только если бизнес-договор с регуляторной позицией это позволяет.
Валидация: где ловятся ошибки OCR
Извлечение без валидации — это генератор мусора в базе. У российских реквизитов есть встроенные контрольные числа, и это бесплатный детектор ошибок распознавания. ИНН (10 знаков):
def validate_inn(inn: str) -> bool:
"""Проверка ИНН юрлица (10 знаков) по контрольному числу."""
if len(inn) != 10 or not inn.isdigit():
return False
coefficients = [2, 4, 10, 3, 5, 9, 4, 6, 8]
control = sum(int(d) * c for d, c in zip(inn[:9], coefficients)) % 11 % 10
return control == int(inn[9])
Для физлиц и ИП (12 знаков) проверяются два контрольных разряда — коэффициенты опубликованы в открытых источниках и давно реализованы, например, в ГОСТ-библиотеках. Аналогично проверяются БИК (по справочнику ЦБ РФ), номер счёта в связке с БИК (ключевание по положению ЦБ № 579-П), СНИЛС.
Второй уровень валидации — сверка с шаблоном и кросс-филдовые проверки:
- дата выдачи паспорта не раньше даты рождения + 14 лет (в России первый паспорт выдают в 14);
- код подразделения соответствует формату и дате выдачи;
- сумма в выписке сходится с оборотами по строкам;
- ФИО в справке о доходах совпадает с ФИО в анкете.
Каждое поле получает confidence score, а документ — агрегированный статус. Мой принцип: правила валидации пишутся до модели, а не после. Иначе вы подгоните «валидацию» под то, что модель и так выдаёт.
Маршрутизация аномалий и human-in-the-loop
Документы из потока делятся на три корзины:
- Автоприём — все проверки пройдены, confidence выше порогов. Данные идут в CRM без участия человека.
- Ручная верификация — есть предупреждения: низкий confidence у отдельных полей, сработала кросс-проверка. Оператор видит документ и подсвеченные подозрительные поля — это сокращает его работу с 10 минут перепечатывания до минуты проверки.
- Отказ/эскалация — документ не классифицирован, критичные поля не извлечены. Идёт полная ручная обработка и попадает в датасет для дообучения.
Третья корзина важна как источник данных: каждый разобранный человеком кейс — это размеченный пример бесплатно. Система, которая не копит свои ошибки, не улучшается.
Регуляторика: 152-ФЗ, ЦБ, 115-ФЗ
В российском финтехе архитектура определяется не только технологиями, но и регуляторным контуром. Базовые требования:
- 152-ФЗ «О персональных данных» — паспорта и справки это чистые ПДн: нужны шифрование при хранении и передаче, разграничение доступа, учёт действий с данными, процедуры удаления по запросу субъекта и по истечении сроков хранения. Текст — на официальном портале правовой информации, практика применения — у Роскомнадзора.
- Требования ЦБ РФ — для организаций под надзором: защита информации (ГОСТ Р 57580), отчётность об инцидентах, требования к операционной надёжности.
- 115-ФЗ (ПОД/ФТ) — документы идентификации клиентов должны храниться установленные сроки, поэтому «удалим всё через месяц» для анкет клиентов не работает: сроки хранения определяются противоположными требованиями — хранить обязаны.
Практические следствия для архитектуры:
- Локальные модели вместо облачных API для документов с ПДн. Это главный аргумент за on-premise контур, и по совокупности он обычно перевешивает разницу в точности.
- Аудит-лог как гражданин первого класса: кто и когда создал/изменил/удалил запись, что система распознала, что поправил оператор, какая версия модели обрабатывала документ. При инциденте или проверке вы восстанавливаете картину по логу.
- Жизненный цикл данных прописан заранее: где документ хранится, сколько, как удаляется по запросу субъекта (с учётом конфликта со сроками по 115-ФЗ — решается юристами, не инженером).
- Минимизация: в логи и очереди не кладём сам документ — только идентификатор и метаданные.
Инфраструктура и метрики
Типовой каркас: FastAPI для API и вебхуков, очередь (Redis/RabbitMQ) между приёмом и обработкой, PostgreSQL для метаданных и аудит-лога, S3-совместимое хранилище для файлов, Docker для развёртывания. GPU нужен только если у вас тяжёлые модели и большие объёмы — на умеренном потоке Tesseract и классификатор живут на CPU.
Метрики собираю на двух уровнях:
Технические:
- точность классификации типов документов;
- точность извлечения по каждому полю (считается на данных из ручной верификации);
- распределение confidence — если оно ползёт вниз, изменился входной поток;
- время обработки на документ.
Процессные:
- доля документов в автоприёме vs верификации vs отказе;
- доля исправленных полей при верификации (это фактическая ошибка системы);
- скорость разбора очереди ручной проверки.
Главная метрика, за которой я слежу, — не «accuracy OCR», а доля документов, дошедших до конца без человека при не растущей доле исправлений. Она показывает и качество, и деградацию одновременно.
Типовые грабли
- Вера в средний accuracy. Среднее по потоку бесполезно: режьте метрики по типам документов и источникам. Скан из отделения и фото с телефона клиента — разные вселенные.
- Отсутствие класса «неизвестный документ». Классификатор, обязанный выбрать один из N типов, гарантированно мусорит на посторонних файлах.
- Валидация после продакшена. Контрольные суммы и кросс-проверки пишутся одновременно с экстракторами, иначе первые недели вы чистите базу руками.
- Игнорирование обратной связи верификаторов. Если операторы массово правят одно и то же поле — это баг системы, а не нерадивость операторов.
- Облачный OCR «временно». Временное в финтехе становится постоянным до первой проверки. Решайте вопрос контура обработки ПДн на старте.
FAQ
С чего начинать, если документов пять типов и поток небольшой?
С одного самого структурированного типа — обычно паспорта. Отладьте на нём всю цепочку, включая верификацию и аудит-лог. Остальные типы добавляются на готовый каркас, и каждый следующий стоит заметно дешевле первого.
Когда LLM-экстракция оправданна, а когда нет?
Оправданна для редких нестандартных документов и как fallback за правилами. Не оправданна как единственный механизм: она дороже, медленнее и требует отдельного контура, если документы содержат ПДн (значит, локальная модель). Правила и контрольные суммы дешевле и надёжнее там, где формат формализован.
Сколько стоит взять готовое решение вместо самописного?
ABBYY FlexiCapture и отечественные аналоги реально работают, вопрос в цене лицензий и в том, насколько ваша специфика укладывается в их шаблоны. Собственное решение имеет смысл, когда нужны нестандартные типы документов, глубокая интеграция с внутренними системами или жёсткие требования к контуру ПДн. Про бюджеты на подобную разработку у меня есть отдельный разбор реальных бюджетов на AI-продукты.
Как измерить, что система работает, а не «вроде распознаёт»?
Разметьте вручную выборку из реального потока (несколько сотен документов) и считайте точность по полям на ней. Повторяйте замеры регулярно — входной поток меняется, и точность деградирует молча.
Кто отвечает за ошибку системы в данных клиента?
Юридически — организация, технически ответ в аудит-логе: вы должны показать, что распознала система, что проверил человек и по какой версии модели. Поэтому логирование — не «nice to have», а часть способа защиты.
Обрабатываете документы вручную и хотите понять, что из этого реально автоматизировать в вашем регуляторном контуре? Напишите мне — разберём ваш процесс и прикинем архитектуру под ваши типы документов.