Дата 03/04/2026 в документе: как не принять догадку парсера за правильное поле

В счёте напечатано 03/04/2026. OCR прочитал все символы правильно. Парсер успешно превратил строку в дату, и документ отправился в оплату. Только поставщик имел в виду 3 апреля, а система выбрала 4 марта. Проверка на успешный разбор не обнаружила проблему: обе даты существуют в календаре. Ошибка возникла при выборе соглашения о порядке дня и месяца.

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

Разберём строку по разрешённым форматам

Python datetime.strptime проверяет строку по указанному формату и отвергает невозможную дату. Используем два заранее разрешённых варианта: день/месяц/год и месяц/день/год. Группируем одинаковые даты, чтобы 04/04/2026 не считалась неоднозначной только из-за двух подходящих форматов. Если ни один не подошёл, статус будет invalid.

from datetime import datetime
formats = {"DMY": "%d/%m/%Y", "MDY": "%m/%d/%Y"}
def parse_candidates(raw):
    values = {}
    for name, fmt in formats.items():
        try:
            value = datetime.strptime(raw, fmt).date().isoformat()
        except ValueError:
            continue
        values.setdefault(value, []).append(name)
    status = "invalid" if not values else "ambiguous" if len(values) > 1 else "resolved"
    return {"raw": raw, "status": status, "candidates": values}
for text in ["03/04/2026", "13/04/2026", "04/04/2026", "31/02/2026"]:
    print(parse_candidates(text))
assert parse_candidates("03/04/2026")["status"] == "ambiguous"
assert set(parse_candidates("03/04/2026")["candidates"]) == {"2026-04-03", "2026-03-04"}
assert parse_candidates("13/04/2026")["status"] == "resolved"
assert parse_candidates("04/04/2026")["status"] == "resolved"
assert parse_candidates("31/02/2026")["status"] == "invalid"

У первой строки остаются два разных значения. У 13/04 подходит только порядок DMY, у 04/04 оба формата дают один день. Невозможное 31 февраля отвергается. Это локальная проверка интерпретации уже распознанного текста. Если OCR перепутал цифру, календарно допустимый результат всё ещё может быть неверным, поэтому исходный фрагмент страницы нужно сохранить.

Откуда взять правило выбора

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

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

Нормализованное значение должно иметь объяснение

В итоговом JSON храните raw, value в ISO-формате, выбранное правило и источник на странице. Для ambiguous value может отсутствовать, а candidates остаются доступными проверяющему. После ручного решения запишите, какой кандидат принят. Это позволяет повторно обработать архив при исправлении шаблона и понять, какие документы были затронуты неправильным правилом.

Не смешивайте дату счёта, срок оплаты и дату поставки. У каждой сущности свой контекст, хотя формат одинаков. Дополнительные бизнес-проверки полезны: срок оплаты не должен случайно оказаться раньше счёта без предусмотренного объяснения. Но такая проверка обнаруживает противоречие, а не доказывает единственно верный вариант. Автоматическая перестановка дня и месяца ради прохождения условия способна скрыть ошибку извлечения соседнего поля.

В проверочный набор включите двусмысленную дату, однозначный день больше двенадцати, совпадающие компоненты, високосный февраль и невозможное число. Отдельно проверяйте пробелы, разделители и правила нормализации OCR. Разрешённые форматы должны быть конечным списком, который можно объяснить владельцу документа, а изменение списка — версионируемым событием.

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

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

В курсе есть OCR, Doc-AI, оценка качества и сборка сервиса, который передаёт извлечённые данные в JSON и базу. Следующая задача — соединить распознавание с явными правилами полей и маршрутом проверки, чтобы готовая дата сохраняла проверяемый смысл. Откройте программу платного курса «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.