Номер счёта нашёлся на двух страницах: как сохранить происхождение поля Doc-AI

В многостраничном PDF номер счёта встречается дважды: в шапке первой страницы и в приложении со ссылкой на прежний документ. Система извлекла оба значения и оставила вариант с более высокой уверенностью OCR. В учётную систему ушёл номер старого счёта. Символы распознаны хорошо, но поле выбрано из неподходящего места. Уверенность чтения не объясняет роль текста в документе.

Для разбора такого случая рядом с нормализованным значением нужно сохранять происхождение: документ, страницу, область изображения и исходный текст. Тогда выбор можно объяснить, проверить и исправить без повторного распознавания всего файла. Если оставить только строку invoice_number, два разных источника превратятся в один неразличимый ответ.

Отдельное значение и его подтверждения

Сначала соберите кандидатов. Для каждого сохраните raw_text, нормализованное value и список источников. Одинаковые значения на разных страницах могут подтверждать друг друга, но их области нельзя терять при объединении. Разные значения должны остаться конфликтом до применения согласованного правила выбора. Это особенно важно для документов с приложениями, исправлениями и ссылками на предыдущие версии.

В API Document AI предусмотрены текстовые и страничные ссылки сущностей, а области страницы позволяют привязать результат к изображению. В своём формате полезно сохранить тот же смысл, даже если OCR движок другой. Задайте единое соглашение о нумерации страниц и координатах: например, страницы от единицы, прямоугольники в нормализованных координатах. Смешение нулевой и единичной нумерации отправит проверяющего на соседний лист.

Покажем конфликт, сохранив геометрию

Вход ниже уже получен от этапа извлечения. Номер на второй странице отличается от двух совпадающих упоминаний. Код группирует нормализованные значения и сохраняет координаты каждого подтверждения. Высокая OCR confidence остаётся атрибутом источника, но не превращается в автоматическое право записать поле в базу.

from collections import defaultdict
candidates = [
    {"page": 1, "box": [.1, .1, .4, .15], "raw": "Счёт 42-2026", "value": "42-2026", "confidence": .94},
    {"page": 2, "box": [.1, .7, .4, .75], "raw": "Счёт 17-2025", "value": "17-2025", "confidence": .99},
    {"page": 3, "box": [.1, .1, .4, .15], "raw": "42-2026", "value": "42-2026", "confidence": .92},
]
groups = defaultdict(list)
for item in candidates:
    x1, y1, x2, y2 = item["box"]
    assert 0 <= x1 < x2 <= 1 and 0 <= y1 < y2 <= 1
    groups[item["value"]].append({k: item[k] for k in ["page", "box", "raw", "confidence"]})
result = {"status": "conflict" if len(groups) > 1 else "resolved",
          "candidates": dict(groups)}
print(result)
assert result["status"] == "conflict"
assert [x["page"] for x in groups["42-2026"]] == [1, 3]
assert groups["17-2025"][0]["box"] == [.1, .7, .4, .75]

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

Как принять решение и сохранить его след

При автоматическом выборе записывайте правило и версию шаблона. При ручной проверке сохраняйте выбранный источник и причину исправления. Исходные кандидаты нужны для последующего анализа ошибок, но доступ к ним должен учитывать чувствительность документа. В журнал общего назначения достаточно идентификаторов и статуса конфликта; полные фрагменты можно держать в ограниченном хранилище.

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

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

Добавьте документ с повтором правильного номера, конфликтом старого и нового, пустой страницей и перепутанным порядком листов. Для каждого проверьте значение, статус и сохранённые ссылки. Это позволит менять OCR или шаблоны извлечения, сохраняя возможность объяснить итоговый JSON.

В курсе есть Doc-AI, OCR, работа с разметкой и путь от распознанного документа к JSON и базе данных. Следующая задача — собрать проверяемое извлечение полей с отчётом качества и API, в котором происхождение результата сохраняется до потребителя. Откройте программу платного курса «Computer Vision Engineer: от детекции и Doc-AI до продакшна».

Как оценивать нормализованные поля документа.

Продолжить на практике

  • 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.