Конвейер пакетной обработки
Описание
Конвейер пакетной обработки — это JSON-конфигурация, которая определяет, как будут обрабатываться документы в пакете. Конвейер описывает последовательность шагов: классификацию страниц, распознавание полей, NLP-извлечение данных, постобработку и валидацию результатов.
Обработка идёт в две фазы.
Основной конвейер (steps) работает постранично. Это дерево шагов: корневой шаг — классификация — определяет тип каждой страницы, а дочерние шаги обрабатывают страницу в зависимости от результата классификации. Шаг основного конвейера всегда видит одну страницу, а не документ целиком.
Конвейер документа (steps внутри определения документа) запускается, когда основной конвейер закончил работу и страницы уже объединены в документы. Такой шаг получает документ целиком: все его страницы сразу, а поля — объединённые со всех страниц. Это нужно там, где данных одной страницы недостаточно: например, чтобы вытащить значение из таблицы, продолжающейся на нескольких страницах.
Фаза 1 — основной конвейер, по странице
Классификация (SmartOCR или NLP)
├── Класс «Счёт-фактура» → Распознавание (SmartOCR) → Постобработка
├── Класс «Паспорт» → Распознавание (SmartOCR)
└── Класс «Договор» → Извлечение NLP
↓ страницы объединяются в документы по documentDefinitions
Фаза 2 — конвейеры документов, по документу целиком
Определение «Счёт-фактура» → Извлечение NLP по всем страницам документа
Определение «Договор» → Постобработка объединённых полейПереход между фазами виден в результате: processing.docsStructureReadyAt отмечает момент, когда структура документов зафиксирована, processing.readyAt — окончание всей обработки. Если ни у одного определения нет своего конвейера, пакет завершается сразу после первой фазы.
Структура конвейера
{
"documentDefinitions": [ ... ],
"notClassifiedDefinition": { ... },
"steps": [ ... ]
}| Поле | Описание |
|---|---|
steps | Корневые шаги основного конвейера (обычно один шаг классификации). Не может быть пустым |
documentDefinitions | Правила группировки страниц в документы и конвейер каждого документа (подробнее) |
notClassifiedDefinition | Настройки и конвейер для нераспознанных страниц |
Идентификаторы шагов уникальны в пределах своего конвейера: шаг основного конвейера и шаг конвейера документа могут называться одинаково, а два шага одного конвейера — нет.
Шаги конвейера
Каждый шаг (PackagePipelineStep) содержит:
| Поле | Описание | Обязательное |
|---|---|---|
id | Идентификатор шага (3–50 символов). Не может повторяться в пределах своего конвейера | Да |
routingKey | Ключ маршрутизации — указывает, какой агент обработает шаг | Кроме шага постобработки |
meta | Тип шага: компонент и его подтип | Да |
classCondition | Точное имя класса — шаг выполнится только для страниц этого класса | Нет |
classConditionRegex | Регулярное выражение для фильтрации по классу | Нет |
complexCondition | Составное условие выполнения шага с булевой алгеброй (подробнее) | Нет |
classify | Параметры классификации (для шагов классификации) | Для классификации |
recognition | Обработка ответа агента: какие поля перезаписывать и с какой уверенностью принимать | Для распознавания и NLP |
rules | Правила обработки уже распознанных полей (подробнее) | Для шага постобработки |
nlp | Параметры NLP (для шагов NLP) | Для NLP |
nextSteps | Дочерние шаги, выполняемые после текущего | Нет |
Правила обработки полей (postProcessing, validations, removeHeaderRows, removeColumns,
cellPostProcessing, cellValidations, jsonFieldExtractors) задаются только у шага
постобработки, в блоке rules. У шага, который обращается к агенту, их указывать нельзя —
конвейер не пройдёт проверку.
Мета-информация (meta)
{
"meta": {
"component": "SmartOCR",
"smartOcrType": "Classification"
}
}| Поле | Значения |
|---|---|
component | "SmartOCR", "NLP" или "PostProcessing" |
smartOcrType | "Classification" или "Recognition" (только для SmartOCR) |
nlpType | "Extraction" или "Classification" (только для NLP) |
Для SmartOCR-шагов указывайте smartOcrType и не указывайте nlpType.
Для NLP-шагов верно обратное. Для шага постобработки не указывайте ни того, ни другого:
агент не вызывается.
Классификация
Классификация — первый шаг конвейера. Определяет тип каждой страницы документа.
Классификация через Умный OCR
{
"id": "classify",
"routingKey": "ocr-clas-a1b2",
"meta": { "component": "SmartOCR", "smartOcrType": "Classification" },
"classify": {
"fallback": "Unknown",
"postProcessing": [],
"classesConfidenceThreshold": {
"Invoice": 0.8,
"Passport": 0.7
}
},
"nextSteps": [ ... ]
}| Поле | Описание |
|---|---|
classify.fallback | Класс, присваиваемый странице, если модель не смогла определить тип |
classify.classesConfidenceThreshold | Минимальная уверенность для каждого класса (0–1). Если уверенность ниже порога, страница получает класс fallback |
classify.postProcessing | Правила постобработки результатов классификации |
Классификация через NLP
Языковая модель используется для классификации вместо Умного OCR. В nlp.schema перечисляются допустимые классы.
{
"id": "classify-nlp",
"routingKey": "nlp-clas-a3k2",
"meta": { "component": "NLP", "nlpType": "Classification" },
"classify": { "fallback": "Unknown" },
"nlp": {
"addImage": true,
"schema": ["Паспорт", "Договор", "Другое"],
"promptBuilder": {
"addUnifiedText": false,
"addAnswer": false,
"addRawAnswer": false
},
"temperature": 0.0,
"minP": 0.1,
"responseLength": 1024
},
"nextSteps": [ ... ]
}NLP-агенты классификации разворачиваются с предварительно настроенными
промптами. В конвейере достаточно указать schema — список допустимых
классов. Не указывайте prependPrompt для классификации. Это вызовет
конфликт со встроенным промптом агента.
Условия выполнения шага
Дочерние шаги обрабатывают только страницы, удовлетворяющие заданным условиям. Есть два способа задать условия: простые поля classCondition / classConditionRegex или составное условие complexCondition.
Маршрутизация по классу (classCondition) #class-routing
Простейший способ фильтрации — по имени класса:
| Поле | Описание | Пример |
|---|---|---|
classCondition | Точное совпадение имени класса (с учётом регистра) | "Invoice" |
classConditionRegex | Регулярное выражение (синтаксис C# Regex) | "^Invoice.*" |
Если оба поля указаны, применяется логика И — оба условия должны совпасть. Если ни одно не указано, шаг выполняется для всех документов.
Пример — распознавание запускается только для страниц, классифицированных как Invoice:
{
"id": "recognize-invoice",
"routingKey": "ocr-rec-struct-c3d4",
"meta": { "component": "SmartOCR", "smartOcrType": "Recognition" },
"classCondition": "Invoice",
"nextSteps": []
}Составное условие (complexCondition) #complex-condition
Для сложных сценариев маршрутизации используйте complexCondition — составное условие с поддержкой булевой алгебры (И / ИЛИ / НЕ). Составное условие позволяет комбинировать несколько критериев: класс документа, уверенность классификации, количество и наличие распознанных полей, условия на значения полей и ячеек.
complexCondition можно использовать вместо или вместе с classCondition / classConditionRegex. Если указаны оба варианта, шаг выполнится только при совпадении всех условий.
Структура complexCondition
{
"complexCondition": {
"classCondition": "Invoice",
"classConditionRegex": null,
"confidence": { "min": 0.8, "max": null },
"fieldCount": { "min": 3, "max": null },
"hasField": { "fieldKey": "INN", "fieldKeyRegex": null, "valueState": "Filled" },
"fieldConditions": [ ... ],
"cellConditions": [ ... ],
"and": [ ... ],
"or": [ ... ],
"negate": false
}
}Все поля внутри complexCondition необязательные. Собственные проверки условия и вложенные условия and объединяются логикой И — все должны быть выполнены. Список or — это альтернатива собственным проверкам: условие выполнено, если выполнены они либо хотя бы одно условие из списка. Поле negate инвертирует итоговый результат.
Поля составного условия
| Поле | Тип | Описание |
|---|---|---|
classCondition | string | Точное совпадение имени класса |
classConditionRegex | string | Регулярное выражение для класса |
confidence | object | Диапазон уверенности классификации |
fieldCount | object | Диапазон количества распознанных полей |
hasField | object | Проверка наличия поля по ключу |
fieldConditions | array | Условия на поля (аналогичны FieldConditions распознавания). Объединяются через ИЛИ |
cellConditions | array | Условия на ячейки таблиц. Объединяются через ИЛИ |
and | array<complexCondition> | Вложенные составные условия, объединённые через И |
or | array<complexCondition> | Альтернативы собственным проверкам условия (подробнее) |
negate | boolean | Инвертировать итоговый результат (true = НЕ) |
Диапазон уверенности (confidence)
Фильтрация по уверенности классификации страницы (значение classConfidence от 0 до 1):
{ "confidence": { "min": 0.8, "max": 0.95 } }| Поле | Тип | Описание |
|---|---|---|
min | decimal | Минимальная уверенность (включительно) |
max | decimal | Максимальная уверенность (включительно) |
Оба поля необязательные — можно задать только нижнюю или только верхнюю границу.
Количество полей (fieldCount)
Фильтрация по количеству распознанных полей на странице:
{ "fieldCount": { "min": 3, "max": 10 } }| Поле | Тип | Описание |
|---|---|---|
min | int | Минимальное количество полей |
max | int | Максимальное количество полей |
Наличие поля (hasField)
Проверяет, что среди распознанных полей есть поле с заданным ключом и что его значение находится в нужном состоянии:
{ "hasField": { "fieldKey": "INN", "valueState": "Filled" } }| Поле | Тип | Описание |
|---|---|---|
fieldKey | string | Точное имя поля |
fieldKeyRegex | string | Регулярное выражение для имени поля (альтернатива fieldKey) |
valueState | string | Каким должно быть значение поля. По умолчанию "Filled" |
Укажите fieldKey или fieldKeyRegex (не оба одновременно).
Состояния значения:
| Значение | Условие выполнено, когда |
|---|---|
"Filled" | у поля есть непустой текст либо непустая таблица |
"Empty" | поле распознано, но значения в нём нет — ни текста, ни ячеек |
"Any" | поле есть, значение любое |
У табличного поля текста не бывает — значение лежит в ячейках, поэтому проверить таблицу
условием на текст нельзя. "Filled" учитывает и текст, и ячейки, так что для таблицы
подходит именно оно.
Если поле с таким именем встречается на странице несколько раз, "Filled" выполняется, когда непусто хотя бы одно значение, а "Empty" — только когда пусты все. Состояние "Empty" требует, чтобы поле существовало: если его нет вовсе, условие не выполнится. Чтобы охватить и этот случай, используйте инверсию: { "negate": true, "hasField": { "fieldKey": "INN", "valueState": "Filled" } }.
Логика ИЛИ через массивы fieldConditions / cellConditions
Массивы fieldConditions и cellConditions используют ту же структуру, что и условия в параметрах распознавания. Внутри массива элементы объединяются через ИЛИ — достаточно совпадения хотя бы одного.
Вложенные условия (and) и инверсия (negate)
Массив and содержит вложенные complexCondition, объединённые через И. Каждый вложенный элемент может содержать собственные условия, включая and, or и negate, что позволяет строить произвольные булевы выражения.
Альтернативы (or) #or-conditions
Массив or содержит вложенные complexCondition и работает как альтернатива собственным проверкам условия: условие выполнено, если выполнены его собственные проверки либо хотя бы одно условие из списка. Если кроме or в условии ничего не задано, решает только список.
{
"complexCondition": {
"or": [
{ "classCondition": "Invoice" },
{ "classCondition": "Receipt" }
]
}
}Шаг выполняется, если класс — Invoice или Receipt.
{
"complexCondition": {
"hasField": { "fieldKey": "Таблица", "valueState": "Filled" },
"or": [{ "classCondition": "Invoice" }]
}
}Здесь достаточно любого из двух: либо таблица непустая, либо класс — Invoice.
Чтобы получить «собственные проверки И (то либо это)», вложите список альтернатив в условие and:
{
"complexCondition": {
"hasField": { "fieldKey": "Таблица", "valueState": "Filled" },
"and": [
{
"or": [
{ "hasField": { "fieldKey": "Основание", "valueState": "Empty" } },
{ "fieldConditions": [{ "fieldKey": "Основание", "textRegex": "(?i)(акт|сч[её]т|заказ)" }] }
]
}
]
}
}Шаг выполнится, если таблица непустая и при этом основание либо не распозналось, либо ссылается на другой документ.
Примеры
Шаг для счетов с высокой уверенностью и наличием ИНН:
{
"id": "process-confident-invoice",
"routingKey": "ocr-rec-struct-c3d4",
"meta": { "component": "SmartOCR", "smartOcrType": "Recognition" },
"complexCondition": {
"classCondition": "Invoice",
"confidence": { "min": 0.85 },
"hasField": { "fieldKey": "INN", "valueState": "Filled" }
},
"nextSteps": []
}Шаг для документов с 5+ полями, но НЕ для класса «Unknown»:
{
"complexCondition": {
"fieldCount": { "min": 5 },
"and": [
{ "classCondition": "Unknown", "negate": true }
]
}
}Распознавание (SmartOCR Recognition)
Извлекает поля из страницы с помощью структурированного OCR.
{
"id": "recognize-invoice",
"routingKey": "ocr-rec-struct-c3d4",
"meta": { "component": "SmartOCR", "smartOcrType": "Recognition" },
"classCondition": "Invoice",
"recognition": {
"overrideClassIfEmpty": null,
"overrideFields": [],
"fieldsConfidenceThreshold": {
"INN": 0.5,
"TotalAmount": 0.4
}
},
"nextSteps": []
}Блок recognition описывает только то, как принять ответ агента:
| Поле | Описание |
|---|---|
fieldsConfidenceThreshold | Минимальная уверенность для каждого поля (0–1). Поля ниже порога отбрасываются |
overrideClassIfEmpty | Присвоить указанный класс, если страница не была классифицирована |
overrideFields | Список полей, которые при повторном распознавании заменяют существующие значения |
Правила обработки самих значений — правка текста, валидация, работа с таблицами — задаются отдельным шагом постобработки, который подвешивается дочерним к шагу распознавания.
Извлечение через NLP #nlp-extraction
Извлекает поля из документа с помощью языковой модели. В schema указываются имена извлекаемых полей.
{
"id": "extract-contract",
"routingKey": "nlp-extr-4bo9",
"meta": { "component": "NLP", "nlpType": "Extraction" },
"classCondition": "Contract",
"nlp": {
"addImage": true,
"schema": ["Номер договора", "Дата", "Контрагент", "Сумма"],
"promptBuilder": {
"addUnifiedText": false,
"addAnswer": false,
"addRawAnswer": false
},
"temperature": 0.0,
"minP": 0.1,
"responseLength": 1024
},
"nextSteps": []
}Параметры NLP
| Поле | Описание | По умолчанию |
|---|---|---|
addImage | Передать изображение страницы модели | - |
schema | Имена полей для извлечения | - |
temperature | Случайность ответа (0–2). Для извлечения рекомендуется 0.0 | 0.0 |
minP | Минимальная вероятность токена (0–1) | 0.1 |
responseLength | Максимальное количество токенов в ответе | 1024 |
Управление промптом (promptBuilder)
promptBuilder управляет данными, которые передаются в промпт языковой модели:
| Поле | Описание |
|---|---|
addUnifiedText | Включить текст, распознанный на предыдущих шагах (OCR) |
addAnswer | Включить ответы предыдущих NLP-шагов |
addRawAnswer | Включить сырые ответы предыдущих NLP-шагов |
prependPrompt | Текст, добавляемый перед данными |
appendPrompt | Текст, добавляемый после данных |
fieldsByConditions | Включить конкретные распознанные поля, отобранные по условиям |
tableColumns | Включить отдельные столбцы распознанных таблиц (подробнее) |
Столбцы таблицы в промпте (tableColumns) #table-columns
Передавать модели таблицу целиком дорого и обычно не нужно: как правило, интересен один столбец. tableColumns кладёт в промпт значения выбранного столбца списком.
{
"promptBuilder": {
"prependPrompt": "Ниже наименования товаров из таблицы документа.",
"tableColumns": [
{
"conditions": { "fieldKey": "Таблица" },
"columnHeaderRegex": "Наименование товара",
"promptKey": "наименования",
"skipEmptyCells": true
}
]
}
}| Поле | Описание |
|---|---|
conditions | Условия отбора табличных полей (fieldKey, fieldKeyRegex и др.) |
columnIndex | Номер столбца, с нуля. Нумерация — после применения правил удаления столбцов |
columnHeader | Точное совпадение текста ячейки в строке 0 (заголовка) |
columnHeaderRegex | Регулярное выражение по тексту заголовка |
promptKey | Ключ, под которым столбец попадёт в промпт. По умолчанию ИмяПоля[НомерСтолбца] |
skipEmptyCells | Пропускать пустые ячейки. По умолчанию true |
Укажите columnIndex, columnHeader или columnHeaderRegex — хотя бы одно. Если задан номер, он имеет приоритет над заголовком.
Заголовок ищется в строке 0 таблицы, поэтому выбор по заголовку работает, только пока шапка
не срезана правилами удаления строк. В конвейере документа шапки обычно уже нет — там
остаётся columnIndex.
В конвейере документа значения столбца собираются по всем страницам документа в порядке страниц — так же, как строки таблицы объединяются в итоговом результате.
NLP-агенты разворачиваются с предварительно настроенными промптами. По
умолчанию достаточно указать schema и источник данных (addImage или
addUnifiedText). Используйте prependPrompt / appendPrompt только когда
нужно дополнить стандартное поведение.
Для NLP-шага обязательно должен быть указан хотя бы один источник данных:
addImage, prependPrompt, addAnswer, addRawAnswer, addUnifiedText,
fieldsByConditions или appendPrompt.
Цепочка OCR → NLP
OCR распознаёт структурированные поля, затем NLP уточняет или извлекает дополнительные данные на основе OCR-результатов:
{
"id": "recognize-invoice",
"routingKey": "ocr-rec-struct-u3v4",
"meta": { "component": "SmartOCR", "smartOcrType": "Recognition" },
"classCondition": "Invoice",
"nextSteps": [
{
"id": "refine-invoice-nlp",
"routingKey": "nlp-extr-w5x6",
"meta": { "component": "NLP", "nlpType": "Extraction" },
"recognition": {
"overrideFields": ["PaymentTerms", "Notes"]
},
"nlp": {
"addImage": false,
"promptBuilder": {
"addUnifiedText": true,
"addAnswer": false,
"addRawAnswer": false
},
"schema": ["PaymentTerms", "Notes", "DeliveryAddress"],
"temperature": 0.0,
"minP": 0.1,
"responseLength": 1024
}
}
]
}В данном примере:
- NLP-шаг не имеет
classCondition— он выполняется для всех документов, дошедших до него через родительский шаг addUnifiedText: trueпередаёт OCR-текст в промпт моделиrecognition.overrideFieldsуказывает, какие существующие поля NLP-шаг имеет право перезаписать
Шаг постобработки #post-processing-step
Шаг постобработки не обращается к агенту — он применяет правила к уже распознанным полям. Поэтому у него нет routingKey, classify, recognition и nlp: только rules.
{
"id": "postprocess-invoice",
"meta": { "component": "PostProcessing" },
"rules": {
"jsonFieldExtractors": [ ... ],
"postProcessing": [ ... ],
"validations": [ ... ],
"removeHeaderRows": [ ... ],
"removeRows": [ ... ],
"removeColumns": [ ... ],
"cellPostProcessing": [ ... ],
"cellValidations": [ ... ]
},
"nextSteps": []
}Должно быть задано хотя бы одно правило. Правила применяются в фиксированном порядке, независимо от порядка в JSON:
jsonFieldExtractors— разбор json-полей в отдельные поляpostProcessing— правка значений полейvalidations— проверка полейremoveHeaderRows— удаление заголовочных строк таблицremoveRows— удаление строк таблицremoveColumns— удаление столбцов таблицcellPostProcessing— правка ячеекcellValidations— проверка ячеек
Порядок важен: например, удаление столбцов меняет их нумерацию, и cellPostProcessing работает уже с новыми номерами.
Где выполняется шаг, зависит от того, в каком конвейере он стоит:
- в основном конвейере — постранично, к полям одной страницы;
- в конвейере документа — один раз на документ, к объединённым полям всех его страниц. Результат складывается на титульную страницу документа.
Условия выполнения (classCondition, classConditionRegex, complexCondition) работают так же, как у остальных шагов.
Шаг постобработки исполняется сервером сразу, без очереди к агенту, поэтому в stepsInWork
он не появляется. Выполненный шаг отмечается в stepIds страницы и документа.
Разбор json-полей (jsonFieldExtractors) #json-field-extractors
Некоторые модели возвращают в одном поле json целиком. Извлекатель достаёт из него значение по JSONPath и кладёт в отдельное поле:
{
"rules": {
"jsonFieldExtractors": [
{
"sourceFieldKey": "Продавец_инн_кпп",
"jsonPath": "$.Продавец_инн",
"targetFieldKey": "поставщик_инн"
}
]
}
}| Поле | Описание |
|---|---|
sourceFieldKey | Имя поля, в котором лежит json |
jsonPath | Путь к значению внутри json |
targetFieldKey | Имя поля, в которое попадёт значение |
Если извлечь значение не удалось — поля нет, в нём не json или по пути пусто, — целевое поле всё равно создаётся пустым. По нему видно, что извлечение не сработало, и это состояние можно поймать условием "valueState": "Empty".
Правка значений полей (postProcessing) #post-processing
Правка меняет значения полей после распознавания. Задаётся в rules.postProcessing шага постобработки. Отдельно существует classify.postProcessing — правка результатов классификации до выбора победившего класса.
{
"rules": {
"postProcessing": [
{
"conditions": { "fieldKey": "INN" },
"modifyRules": {
"text": {
"regexPattern": "[^0-9]",
"replacement": ""
}
}
},
{
"conditions": {
"fieldKeyRegex": "Date.*",
"minConfidence": 0.3
},
"modifyRules": {
"text": {
"regexPattern": "(\\d{2})\\.(\\d{2})\\.(\\d{4})",
"replacement": "$3-$2-$1"
}
}
}
]
}
}Условия отбора полей (conditions)
Все условия опциональны. Если указано несколько, применяется логика И.
| Поле | Описание |
|---|---|
fieldKey | Точное совпадение имени поля |
fieldKeyRegex | Регулярное выражение для имени поля |
text | Точное совпадение текста |
textRegex | Регулярное выражение для текста |
minConfidence | Минимальная уверенность (0–1) |
maxConfidence | Максимальная уверенность (0–1) |
jsonPathCondition | Условие по JSONPath: { "jsonPath": "$.field", "valueRegex": "\\d+" } |
Правила модификации (modifyRules)
Ключ правила — "field" (имя поля) или "text" (текст поля). Значение — объект с regexPattern и replacement.
Примеры:
- Удалить все нецифровые символы из ИНН:
"regexPattern": "[^0-9]","replacement": "" - Переформатировать дату из ДД.ММ.ГГГГ в ГГГГ-ММ-ДД:
"regexPattern": "(\\d{2})\\.(\\d{2})\\.(\\d{4})","replacement": "$3-$2-$1"
Валидация (validations) #validations
Валидация проверяет извлечённые поля и устанавливает флаг isValid. Задаётся в rules.validations шага постобработки.
{
"rules": {
"validations": [
{
"conditions": { "fieldKey": "INN" },
"validations": [{ "textRegex": "^\\d{10}$|^\\d{12}$" }]
},
{
"conditions": { "fieldKey": "TotalAmount" },
"validations": [
{ "textRegex": "^[\\d\\s,.]+$" },
{ "minConfidence": 0.4 }
]
}
]
}
}conditions— отбирает поля для валидации (те же условия, что и в постобработке)validations[]— массив правил. Все правила должны пройти, чтобы поле получилоisValid = true- Доступные правила:
textRegex,text,minConfidence,maxConfidence,fieldKey,fieldKeyRegex,jsonPathCondition
Результат: поле получает isValid = true, если все правила выполнены, false — если хотя бы одно не выполнено. Поля, не попавшие под conditions, сохраняют isValid = null.
Постобработка выполняется до валидации. Валидация проверяет уже модифицированные значения.
Удаление заголовочных строк (removeHeaderRows) #remove-header-rows
Удаляет повторяющиеся заголовочные строки из табличных полей. Типичный сценарий — многостраничная таблица, где на каждой странице повторяется шапка таблицы.
Удаление выполняется каскадно сверху вниз: строки проверяются начиная с первой (row 0). Если строка соответствует одному из правил — она удаляется, и проверяется следующая. Каскад прекращается на первой строке, которая не совпала ни с одним правилом.
{
"rules": {
"removeHeaderRows": [
{
"conditions": { "fieldKey": "invoice_table" },
"rules": [
{ "rowIndex": 0 },
{
"cellMatchers": [
{ "textRegex": "^(Наименование|Название)$", "minCount": 1 },
{ "textRegex": "^Кол-?во$", "exactCount": 1 }
]
}
]
}
]
}
}Конфигурация
Каждый элемент массива removeHeaderRows содержит:
| Поле | Описание |
|---|---|
conditions | Условия отбора табличных полей (fieldKey, fieldKeyRegex и др.) |
rules | Массив правил определения заголовочных строк. Правила связаны через ИЛИ — строка считается заголовочной, если совпало хотя бы одно правило |
Правила (HeaderRowRule)
Правило работает в одном из двух режимов:
| Поле | Описание |
|---|---|
rowIndex | Принудительно удалить строку с указанным индексом (0 — первая строка). Если задан, cellMatchers игнорируются |
cellMatchers | Массив матчеров ячеек. Все матчеры должны совпасть (логика И) |
Матчеры ячеек (CellMatcher)
Проверяют, содержит ли строка ячейки, соответствующие заданным условиям:
| Поле | Описание |
|---|---|
textRegex | Регулярное выражение для текста ячейки |
minCount | Минимальное количество ячеек в строке, соответствующих textRegex |
exactCount | Точное количество ячеек в строке, соответствующих textRegex |
Указывается либо minCount, либо exactCount (не оба). Если не указано ни одного, матчер требует хотя бы одно совпадение.
После удаления строк индексы оставшихся ячеек автоматически пересчитываются, а rawTable перестраивается.
Удаление строк (removeRows) #remove-rows
Удаляет из таблицы любые строки, подходящие под правила. Правила те же, что у removeHeaderRows (rowIndex либо cellMatchers), но каскада сверху вниз нет: проверяется каждая строка таблицы.
{
"rules": {
"removeRows": [
{
"conditions": { "fieldKey": "invoice_table" },
"rules": [
{ "rowIndex": -1 },
{ "cellMatchers": [{ "textRegex": "^Итого", "minCount": 1 }] }
]
}
]
}
}Отрицательный rowIndex отсчитывается от конца таблицы: -1 — последняя строка, -2 — предпоследняя. Это удобно для строки «Итого», номер которой заранее неизвестен.
Удаление столбцов (removeColumns) #remove-columns
Удаляет столбцы из табличных полей по индексу или по тексту заголовка (ячейка в строке 0).
{
"rules": {
"removeColumns": [
{
"conditions": { "fieldKey": "invoice_table" },
"rules": [
{ "columnIndex": 0 },
{ "headerTextRegex": "^(Примечание|Комментарий)$" }
]
}
]
}
}Конфигурация
Каждый элемент массива removeColumns содержит:
| Поле | Описание |
|---|---|
conditions | Условия отбора табличных полей (fieldKey, fieldKeyRegex и др.) |
rules | Массив правил определения столбцов для удаления. Правила связаны через ИЛИ |
Правила (ColumnRule)
| Поле | Описание |
|---|---|
columnIndex | Индекс столбца для удаления (0 — первый столбец) |
headerTextRegex | Регулярное выражение для текста ячейки в строке 0 (заголовка) |
Указывается либо columnIndex, либо headerTextRegex.
После удаления столбцов индексы оставшихся ячеек автоматически пересчитываются, а rawTable перестраивается.
Постобработка ячеек таблиц (cellPostProcessing) #cell-post-processing
Модифицирует текст ячеек в табличных полях. Использует двухуровневую фильтрацию: сначала отбирает поля (conditions), затем ячейки внутри них (cellConditions).
{
"rules": {
"cellPostProcessing": [
{
"conditions": { "fieldKey": "invoice_table" },
"cellConditions": {
"columnHeader": "Сумма",
"textRegex": "^\\s*\\d"
},
"modifyRule": { "regexPattern": "\\s", "replacement": "" }
}
]
}
}Условия отбора ячеек (cellConditions)
| Поле | Описание |
|---|---|
rowIndex | Индекс строки |
columnIndex | Индекс столбца |
columnHeader | Точное совпадение текста ячейки в строке 0 того же столбца |
columnHeaderRegex | Регулярное выражение для заголовка столбца |
textRegex | Регулярное выражение для текста ячейки |
minConfidence / maxConfidence | Диапазон уверенности ячейки (0–1) |
Валидация ячеек таблиц (cellValidations) #cell-validations
Аналогична cellPostProcessing, но вместо модификации устанавливает cell.isValid.
{
"rules": {
"cellValidations": [
{
"conditions": { "fieldKey": "invoice_table" },
"cellConditions": { "columnHeaderRegex": "(Сумма|Итого)" },
"rules": [{ "textRegex": "^\\d+[.,]\\d{2}$", "minConfidence": 0.7 }]
}
]
}
}Порядок обработки табличных полей: удаление заголовочных строк → удаление строк → удаление столбцов → постобработка ячеек → валидация ячеек. Каждый последующий шаг работает с результатами предыдущего.
Определения документов (documentDefinitions) #document-definitions
Определяют, как классифицированные страницы группируются в логические документы.
{
"documentDefinitions": [
{
"id": "invoice",
"title": "Invoice",
"titleClasses": ["Invoice", "Invoice-alt"],
"tailClasses": ["Invoice-page"],
"isPdfGenerated": true,
"steps": []
},
{
"id": "passport",
"title": "Passport",
"titleClasses": ["Passport"],
"isPdfGenerated": false
}
]
}| Поле | Описание |
|---|---|
id | Идентификатор определения. Нужен, если у определения есть свой конвейер; NotClassified зарезервирован |
title | Название документа (обязательное) |
titleClasses | Классы, обозначающие титульную страницу документа (обязательное, не пустое) |
tailClasses | Классы страниц-продолжений. Если не заданы, в документ попадают любые страницы до следующей титульной |
isPdfGenerated | Генерировать PDF из страниц документа после обработки. По умолчанию: false |
steps | Конвейер документа — шаги, которые выполнятся после объединения страниц (подробнее) |
Когда система встречает страницу с классом из titleClasses, она начинает новый документ. Последующие страницы (до следующей титульной) включаются в тот же документ.
Класс, который присвоила классификация, должен встречаться среди titleClasses — иначе
страницу не с чем связать, документ не соберётся, и страница уйдёт в notClassified.
Конвейер документа #document-pipeline
Шаги в steps определения выполняются во второй фазе — когда страницы уже собраны в документы. Устроены они так же, как шаги основного конвейера (условия, nextSteps, те же типы), но работают с документом целиком:
- NLP-шагу передаются все страницы документа сразу, а поля в промпт собираются со всех страниц;
- шаг постобработки применяет правила к объединённым полям документа;
- условия проверяются по документу: класс берётся с титульной страницы, поля — со всех.
{
"id": "upd",
"title": "УПД",
"titleClasses": ["УПД_Первая_Страница"],
"tailClasses": ["УПД_Продолжение"],
"isPdfGenerated": true,
"steps": [
{
"id": "extract-osnovanie",
"routingKey": "nlp-extr-4bo9",
"meta": { "component": "NLP", "nlpType": "Extraction" },
"complexCondition": {
"negate": true,
"hasField": { "fieldKey": "основание", "valueState": "Filled" }
},
"recognition": { "overrideFields": ["основание"] },
"nlp": {
"addImage": false,
"schema": ["основание"],
"promptBuilder": {
"prependPrompt": "Ниже наименования из таблицы документа.",
"appendPrompt": "Верни только JSON вида {\"основание\": \"<значение>\"}.",
"tableColumns": [
{ "conditions": { "fieldKey": "Таблица" }, "columnIndex": 0, "promptKey": "наименования" }
]
},
"temperature": 0.0,
"minP": 0.1,
"responseLength": 512
}
}
]
}Шаг из этого примера ищет основание в таблице документа и только тогда, когда на страницах его найти не удалось.
Шаги конвейера документа отмечаются в результате с префиксом определения: upd::extract-osnovanie. Из-за префикса идентификаторы шагов достаточно держать уникальными внутри своего конвейера.
Если ни у одного определения нет своего конвейера, вторая фаза не выполняется и пакет завершается сразу после основного конвейера.
Неклассифицированные страницы
{
"notClassifiedDefinition": {
"isPdfGenerated": true,
"steps": []
}
}Страницы, не попавшие ни в один класс, группируются в отдельный объект NotClassified. У него тоже может быть свой конвейер — шаги выполнятся для всех нераспознанных страниц разом, а отмечаются с префиксом NotClassified::.
Интерпретация результатов #results
После обработки пакет содержит структуру processing со следующими данными.
Ход обработки отмечают четыре поля:
| Поле | Описание |
|---|---|
startedAt | Обработка запущена |
docsStructureReadyAt | Основной конвейер отработал, страницы объединены в документы — начинается вторая фаза |
readyAt | Обработка завершена полностью |
cancelledAt | Обработка отменена |
У каждого из них есть парный признак: isStarted, isDocsStructureReady, isReady, isCancelled. Состав документов после docsStructureReadyAt уже не меняется, поэтому идентификаторы документов можно запоминать с этого момента.
Документ (PackageDocumentDto)
| Поле | Описание |
|---|---|
id | Идентификатор документа |
title | Название документа (из documentDefinitions) |
definitionId | Идентификатор определения, по которому собран документ |
classConfidence | Уверенность классификации |
pages | Массив страниц документа |
items | Распознанные данные документа (объединение постраничных данных) |
isPdfCreated | PDF готов к скачиванию |
warnings | Предупреждения при обработке |
stepIds | Идентификаторы шагов, обработавших документ. Шаг конвейера документа записывается как <id определения>::<id шага> |
Поле документа (PackageDocumentDtoItem)
| Поле | Описание |
|---|---|
field | Имя поля |
confidence | Уверенность распознавания (0–1) |
coordinates | Координаты области на изображении (minX, minY, maxX, maxY) |
text | Распознанный текст (для скалярных полей) |
cells | Ячейки таблицы (для табличных полей) |
rawTable | Автоматически генерируемое представление: rawTable[столбец][строка] = текст |
isValid | Результат валидации: null — не валидировалось, true — пройдена, false — не пройдена |
Поле является либо скалярным (text заполнен, cells отсутствует), либо табличным (cells заполнен, text отсутствует, rawTable генерируется автоматически).
Ячейка таблицы (PackageDocumentDtoItemCell)
| Поле | Описание |
|---|---|
row | Индекс строки |
column | Индекс столбца |
text | Текст ячейки |
confidence | Уверенность распознавания |
isValid | Результат валидации ячейки |
Страница документа
Каждая страница (PackageDocumentPageDto) содержит:
| Поле | Описание |
|---|---|
class | Класс, присвоенный при классификации |
classConfidence | Уверенность классификации |
indexInPackage | Порядковый номер страницы в пакете |
file | Информация о файле страницы (для скачивания изображения) |
rotationAngle | Угол наклона изображения, определённый агентом (подробнее) |
stepIds | Идентификаторы шагов, обработавших страницу (с префиксом определения — для шагов конвейера документа) |
warnings | Предупреждения по странице |
Постраничные результаты распознавания наружу не отдаются: они нужны серверу, чтобы собрать
items документа, и в ответе остаётся только объединённый результат.
Поля документа (items) — это объединение полей всех его страниц, а не содержимое одной
страницы. Поле, перечисленное в overrideFields, берётся с последней страницы, где оно
непустое, остальные — с первой непустой. Строки таблицы, продолжающейся на нескольких
страницах, дописываются друг за другом.
Угол наклона страницы #rotation-angle
Агент Умного OCR определяет наклон изображения и возвращает его вместе с результатом.
В пакетной обработке это значение только сохраняется в поле rotationAngle страницы —
изображение не поворачивается и координаты полей не пересчитываются.
- Записывается значение последнего шага, который его вернул, включая
0— наклон не обнаружен. - Поле отсутствует, пока ни один шаг не вернул информацию о трансформации изображения: например, если страницу обрабатывал только NLP.
Ошибки обработки
Массив errors содержит информацию об ошибках:
| Поле | Описание |
|---|---|
stepId | Идентификатор шага, на котором произошла ошибка |
fileIds | Идентификаторы файлов, затронутых ошибкой |
errorMsg | Описание ошибки |
Ключи маршрутизации
Ключ маршрутизации (routingKey) связывает шаг конвейера с конкретным агентом. Формат: {тип}-{случайный_суффикс}.
| Тип шага | Префикс | Пример |
|---|---|---|
| OCR-классификация | ocr-clas- | ocr-clas-275i |
| OCR-распознавание | ocr-rec-struct- | ocr-rec-struct-71ui |
| NLP-извлечение | nlp-extr- | nlp-extr-4bo9 |
| NLP-классификация | nlp-clas- | nlp-clas-a3k2 |
Ключ маршрутизации создаётся и копируется в Портале при настройке навыка или проекта.
Полный пример конвейера
Конвейер классифицирует документы через SmartOCR, распознаёт счета-фактуры и приводит их поля в порядок шагом постобработки, извлекает данные из договоров через NLP, а в конвейере документа добирает сумму счёта из таблицы, если на страницах она не нашлась:
{
"documentDefinitions": [
{
"id": "invoice",
"title": "Invoice",
"titleClasses": ["Invoice"],
"isPdfGenerated": true,
"steps": [
{
"id": "extract-total",
"routingKey": "nlp-extr-g7h8",
"meta": { "component": "NLP", "nlpType": "Extraction" },
"complexCondition": {
"negate": true,
"hasField": { "fieldKey": "TotalAmount", "valueState": "Filled" }
},
"recognition": { "overrideFields": ["TotalAmount"] },
"nlp": {
"addImage": false,
"schema": ["TotalAmount"],
"promptBuilder": {
"prependPrompt": "Ниже суммы строк из таблицы счёта.",
"appendPrompt": "Верни только JSON вида {\"TotalAmount\": \"<итоговая сумма>\"}.",
"tableColumns": [
{
"conditions": { "fieldKey": "invoice_table" },
"columnHeaderRegex": "Сумма",
"promptKey": "суммы"
}
]
},
"temperature": 0.0,
"minP": 0.1,
"responseLength": 256
}
}
]
},
{
"id": "contract",
"title": "Contract",
"titleClasses": ["Contract"],
"isPdfGenerated": true
}
],
"notClassifiedDefinition": { "isPdfGenerated": false },
"steps": [
{
"id": "classify",
"routingKey": "ocr-clas-a1b2",
"meta": { "component": "SmartOCR", "smartOcrType": "Classification" },
"classify": {
"fallback": "Unknown",
"classesConfidenceThreshold": { "Invoice": 0.8, "Contract": 0.7 }
},
"nextSteps": [
{
"id": "recognize-invoice",
"routingKey": "ocr-rec-struct-c3d4",
"meta": { "component": "SmartOCR", "smartOcrType": "Recognition" },
"classCondition": "Invoice",
"recognition": {
"fieldsConfidenceThreshold": { "INN": 0.5 }
},
"nextSteps": [
{
"id": "postprocess-invoice",
"meta": { "component": "PostProcessing" },
"rules": {
"postProcessing": [
{
"conditions": { "fieldKey": "INN" },
"modifyRules": {
"text": { "regexPattern": "[^0-9]", "replacement": "" }
}
}
],
"validations": [
{
"conditions": { "fieldKey": "INN" },
"validations": [{ "textRegex": "^\\d{10}$|^\\d{12}$" }]
},
{
"conditions": { "fieldKey": "TotalAmount" },
"validations": [
{ "textRegex": "^[\\d\\s,.]+$" },
{ "minConfidence": 0.4 }
]
}
],
"removeHeaderRows": [
{
"conditions": { "fieldKey": "invoice_table" },
"rules": [{ "rowIndex": 0 }]
}
]
}
}
]
},
{
"id": "extract-contract",
"routingKey": "nlp-extr-e5f6",
"meta": { "component": "NLP", "nlpType": "Extraction" },
"classCondition": "Contract",
"nlp": {
"addImage": true,
"schema": ["Номер договора", "Дата", "Контрагент", "Сумма"],
"promptBuilder": {
"addUnifiedText": false,
"addAnswer": false,
"addRawAnswer": false
},
"temperature": 0.0,
"minP": 0.1,
"responseLength": 1024
}
}
]
}
]
}В результате шаги основного конвейера отметятся на страницах как classify, recognize-invoice, postprocess-invoice, а шаг конвейера документа — как invoice::extract-total.