Recognition State and Progress
While the recognition pipeline runs, the finders and the processors record the outcome of every lookup in a shared recognition state. The state becomes the mapping data of the draft order, drives its progress indicator, and tells the draft order edit page which fields to highlight. This page describes the state mapper, the state writers, the field weights, and the progress calculation, and shows how to record the outcome of a custom mapping step and how to change the weights. All examples use the Acme\Bundle\SmartOrderBundle namespace.
State Mapper and State Writers
Every lookup records its outcome in a shared state array managed by Oro\Bundle\EntityRecognitionBundle\State\StateMapperInterface:
set(\BackedEnum $state, string|array $path, array $indexes = [])— Writes$state->valueat one or several property paths.get(string $path)— Reads a value.unset(string|array $path)— Sets one or several paths tonull.getState()andsetState(array $data)— Read or replace the whole array.
The implementation, StateMapper, uses the Symfony property accessor on a plain array. Each $index placeholder in a path is replaced by the next value from $indexes, so '[lineItems][$index][productSku]' with [2] becomes [lineItems][2][productSku]. The values come from Oro\Bundle\EntityRecognitionBundle\State\StateEnum: valid, invalid, missing, and ambiguous. The pipeline itself writes only valid and invalid.
The mapper is registered once, under the service id Oro\Bundle\EntityRecognitionBundle\State\StateMapperInterface, and stores the state for the whole recognition run. RecognitionDataProcessor (priority 30) copies getState() into OrderRecognition::mappingData and the raw payload into OrderRecognition::rawData.
Important
StateMapperInterface is a single stateful service shared by all finders and processors of one run. Record custom outcomes through a dedicated StateInterface implementation and never call setState() or unset() from a custom processor; doing so discards the results the built-in finders already recorded.
Individual outcomes are written through Oro\Bundle\OrderRecognitionBundle\State\StateInterface, which declares update(\BackedEnum $state, array $indexes = []): void. Each implementation knows its own paths and is registered in Resources/config/states.yml with the state mapper as its only argument:
State writer |
Paths written |
Written by |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
every top-level key of the mapping data |
|
Because the address state writers ignore $indexes and always write both address keys, a failed country or region lookup for either address marks both addresses.
Add a State Writer
To record the outcome of a custom mapping step, add a state writer with its own path. The following writer covers a notes value per line item, which a custom processor can then mark valid or invalid for each line item index:
namespace Acme\Bundle\SmartOrderBundle\State;
use Oro\Bundle\EntityRecognitionBundle\State\StateMapperInterface;
use Oro\Bundle\OrderRecognitionBundle\State\StateInterface;
class LineItemNotesState implements StateInterface
{
public function __construct(private readonly StateMapperInterface $stateMapper)
{
}
#[\Override]
public function update(\BackedEnum $state, array $indexes = []): void
{
$this->stateMapper->set($state, '[lineItems][$index][notes]', $indexes);
}
}
services:
Acme\Bundle\SmartOrderBundle\State\LineItemNotesState:
arguments:
- '@Oro\Bundle\EntityRecognitionBundle\State\StateMapperInterface'
Inject the writer into the processor that maps the field and call $this->notesState->update(StateEnum::VALID, [$index]) for each line item. The processor must run before RecognitionDataProcessor (priority 30), otherwise the entry never reaches the mapping data. For the processor side of this example, see Map Additional Payload Fields.
Note
Every path under [lineItems] takes part in the progress calculation described below. An entry set to invalid for any line item marks the whole lineItems field invalid, so record invalid only when the back-office user must review the value.
Field Weights and Progress
The mapping data determines the progress value shown for a draft order. Two providers, both registered in Resources/config/progress.yml, compute it:
Oro\Bundle\OrderRecognitionBundle\Provider\FieldWeightProviderInterfacegetWeight(string $fieldName): intreturns the weight of a top-level mapping data key. The defaultFieldWeightProviderkeeps the weights in a protected$weightsarray and returns0for unknown keys.Oro\Bundle\OrderRecognitionBundle\Provider\ProgressCalculationProviderInterfacecalculate(object $entity): floatreturns the progress of anOrderRecognitionentity in percent, andgetInvalidFields(object $entity): arrayreturns the top-level keys that are not valid.
The default weights are:
Mapping data key |
Weight |
|---|---|
|
70 |
|
10 |
|
5 |
|
5 |
|
5 |
|
5 |
ProgressCalculationProvider::calculate() works as follows:
Returns
100when the entity has no mapping data.For every top-level key of the mapping data, adds its weight to the total and, when the field is valid, to the valid sum. A scalar field is valid when it equals
valid; an array field such aslineItemsis valid only when every value of every item equalsvalid.Returns
100when the total weight is0; otherwise returns the rounded percentage of the valid sum, limited to the range from0to100.
Only keys that exist in the mapping data count, so an unknown key with weight 0 neither raises nor lowers the result, and a custom key without a configured weight is ignored.
Oro\Bundle\OrderRecognitionBundle\EventListener\ProgressListener is a Doctrine entity listener on OrderRecognition. On prePersist it stores the calculated value in initialProgress; on preFlush it recalculates the value and schedules an update of entityProgress when it changed. Oro\Bundle\OrderRecognitionBundle\Manager\ProgressManagerInterface::markAsComplete(object $entity, string $property) marks every state under one top-level key of the mapping data of an order valid.
Change the Weights or the Calculation
Both interfaces are aliases of the concrete classes in progress.yml, so a custom implementation replaces the default by re-pointing the alias. The following provider shifts the weight from the customer to the line items:
namespace Acme\Bundle\SmartOrderBundle\Provider;
use Oro\Bundle\OrderRecognitionBundle\Provider\FieldWeightProvider as BaseFieldWeightProvider;
class FieldWeightProvider extends BaseFieldWeightProvider
{
protected array $weights = [
'customer' => 50,
'lineItems' => 30,
'customerUser' => 5,
'billingAddress' => 5,
'shippingAddress' => 5,
'payment_term_7c4f1e8e' => 5,
];
}
services:
Acme\Bundle\SmartOrderBundle\Provider\FieldWeightProvider: ~
Oro\Bundle\OrderRecognitionBundle\Provider\FieldWeightProviderInterface:
alias: Acme\Bundle\SmartOrderBundle\Provider\FieldWeightProvider
As with any redefinition, the custom bundle must be loaded after OroOrderRecognitionBundle for its alias to take precedence. Add a key to $weights when a custom state writer introduces a new top-level mapping data key; otherwise the key has weight 0 and does not affect the progress. To change the calculation itself, implement ProgressCalculationProviderInterface and re-point the ProgressCalculationProviderInterface alias the same way.