Автоматизация документооборота в финтехе: от хаоса к порядку

Кейсы и разборы
Pipeline обработки финансовых документов: скан на входе, структурированные поля и аудит-лог на выходе

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, здесь не повторяюсь.

Классификация типа документа

Прежде чем извлекать поля, нужно понять, что перед нами. Рабочая схема — каскад от дешёвого к дорогому:

  1. Правила и ключевые слова. Паспорт содержит «выдан» и код подразделения в формате XXX-XXX, справка 2-НДФЛ (с 2021-го — «Справка о доходах») — характерные заголовки. На структурированных потоках правила закрывают большую часть документов за миллисекунды.
  2. ML-классификатор по тексту для документов, которые правила не различили. Достаточно fine-tuned русской BERT-подобной модели — точность на 10–15 классах высокая, инференс на CPU приемлемый.
  3. Визуальный классификатор как 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

Документы из потока делятся на три корзины:

  1. Автоприём — все проверки пройдены, confidence выше порогов. Данные идут в CRM без участия человека.
  2. Ручная верификация — есть предупреждения: низкий confidence у отдельных полей, сработала кросс-проверка. Оператор видит документ и подсвеченные подозрительные поля — это сокращает его работу с 10 минут перепечатывания до минуты проверки.
  3. Отказ/эскалация — документ не классифицирован, критичные поля не извлечены. Идёт полная ручная обработка и попадает в датасет для дообучения.

Третья корзина важна как источник данных: каждый разобранный человеком кейс — это размеченный пример бесплатно. Система, которая не копит свои ошибки, не улучшается.

Регуляторика: 152-ФЗ, ЦБ, 115-ФЗ

В российском финтехе архитектура определяется не только технологиями, но и регуляторным контуром. Базовые требования:

  • 152-ФЗ «О персональных данных» — паспорта и справки это чистые ПДн: нужны шифрование при хранении и передаче, разграничение доступа, учёт действий с данными, процедуры удаления по запросу субъекта и по истечении сроков хранения. Текст — на официальном портале правовой информации, практика применения — у Роскомнадзора.
  • Требования ЦБ РФ — для организаций под надзором: защита информации (ГОСТ Р 57580), отчётность об инцидентах, требования к операционной надёжности.
  • 115-ФЗ (ПОД/ФТ) — документы идентификации клиентов должны храниться установленные сроки, поэтому «удалим всё через месяц» для анкет клиентов не работает: сроки хранения определяются противоположными требованиями — хранить обязаны.

Практические следствия для архитектуры:

  1. Локальные модели вместо облачных API для документов с ПДн. Это главный аргумент за on-premise контур, и по совокупности он обычно перевешивает разницу в точности.
  2. Аудит-лог как гражданин первого класса: кто и когда создал/изменил/удалил запись, что система распознала, что поправил оператор, какая версия модели обрабатывала документ. При инциденте или проверке вы восстанавливаете картину по логу.
  3. Жизненный цикл данных прописан заранее: где документ хранится, сколько, как удаляется по запросу субъекта (с учётом конфликта со сроками по 115-ФЗ — решается юристами, не инженером).
  4. Минимизация: в логи и очереди не кладём сам документ — только идентификатор и метаданные.

Инфраструктура и метрики

Типовой каркас: 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», а часть способа защиты.


Обрабатываете документы вручную и хотите понять, что из этого реально автоматизировать в вашем регуляторном контуре? Напишите мне — разберём ваш процесс и прикинем архитектуру под ваши типы документов.

Обсудить проект

Есть идея или задача? Давайте обсудим, как можно её реализовать с помощью современных AI-технологий.

Написать мне
Вернуться к блогу