# DOMS toolkit

The DOMS toolkit helps users define factors which should be considered when making the decision for the best fulfillment location.

The system allows to define fences and ratings:

* [**Fences**](https://docs.fulfillmenttools.com/documentation/by-pillar/advanced-order-routing/fences): Exclude facilities from order routing according to the fences selected
* [**Ratings**](https://docs.fulfillmenttools.com/documentation/by-pillar/advanced-order-routing/ratings): Weigh facilities against each other according to the ratings selected

## Toolkit fences

### ToolkitFence fields in detail

<details>

<summary>Click to see what each field does</summary>

**entity1**

This describes the entity type for the left-hand side. In case of conditional rules this is the entity that gets evaluated first.

**entity2**

This describes the entity type for the right-hand side. In case of conditional rules this is the entity that gets evaluated second (or never), but more to that later.

**active**

Whether this fence is active or not

**name**

The name, has to be unique

**nameLocalized**

An object containing the localized names for different languages

**description -*****\[optional]***

An optional description to say what the fence has to do. Although optional, it is strongly recommended that you do give it a meaningful description.

**descriptionLocalized -*****\[optional]***

An object containing the localized descriptions for different languages

**order**

A number determining whether the fence should be executed before or after different fences. Lower numbers have precedence over higher numbers.

</details>

### Rules for toolkit fences

There are two rule types which can be defined:

#### Conditional rule

Use "rule" to set up a conditional rule. Can be used for comparing each side against a fixed value (e.g., order line item quantity > 3). Incompatible with using the comparisonRule field. Conditional rules test a certain property of an entity (`Order`, `Facility`, `Listing`) against a predefined static value.

<details>

<summary>Steps for defining a conditional rule</summary>

**operator**

What the positive evaluation of the left hand side means for the execution of the fence as a whole. For now, there is only the equality-operator.

**leftPart - ToolkitRulePart**

The first part that is evaluated. Its evaluation result determines whether the right part gets evaluated.

**rightPart - ToolkitRulePart**

The second part that is evaluated if the first evaluation passes. Its evaluation result determines the final outcome of the fence.

**How to define a ToolkitRulePart**

A `ToolkitRulePart` consists of an array of predicates and a connector that determines how they are connected together.

**predicateConnector**

**OR** will make the rule part true if *any* of the predicates evaluate to true. **AND** will only make the expression true if all predicates evaluate to true.

**predicates**

An array of one or more [`ToolkitPredicate`s](#how-to-define-a-toolkitpredicate). A predicate consists of the following fields:

* propertyPath
* entityOperator
* expectedValue
* transformation *(optional)*
* transformationArgs *(optional, depends on transformation used)*

**How to define a `ToolkitPredicate`**

You have to define at least one predicate. In the following we'll explain what each field is used for.

**propertyPath**

A [JSONPath](#intro-to-jsonpath) defining the property to look at.

**entityOperator**

The operators determine how a property of an entity should be compared against a given value. They can be divided up into two groups: Single value entity operators and array entity operators.

**Single value entity operators**

These operators can only be used for single values, not for arrays, such as `VALUE_EQUALS`

* `VALUE_EQUALS` -> *actual value equals expected value (e.g. 2 == 2)*
* `VALUE_NOT_EQUALS` -> *actual value unequals expected value (e.g. 2 != 3)*
* `VALUE_CONTAINS` -> *actual value contains expected value (e.g. "HELLO WORLD" contains "HELLO")*
* `VALUE_NOT_CONTAINS` -> actual value does not contain expected value (e.g. "HELLO WORLD" does not contain "HI")
* `LESS_THAN` -> *actual value less than expected value (e.g. 2 < 3)*
* `LESS_EQUALS` -> *actual value less than or equals expected value (e.g. 2 <= 3, 3 <= 3)*
* `GREATER_THAN` -> *actual value greater than expected value (e.g. 3 > 2)*
* `GREATER_EQUALS` -> *actual value greater than or equals expected value (e.g. 3 >= 2, 2 >= 2)*

**Array entity operators**

These operators can only be used for arrays, not for single values. They can be grouped into three categories: `ANY`, `EVERY`, `NONE`. Within each category, either any, every or no value has meet the criteria. The condition may be anything of the aforementioned single value entity operators. Here are some examples on how this may look:

* `ANY_VALUE_EQUALS`
* `EVERY_VALUE_GREATER_EQUALS` (which is equivalent to `NO_VALUE_LESS_THAN`)
* `NO_VALUE_CONTAINS` (which is equivalent to `EVERY_VALUE_NOT_CONTAINS`)

***ANY, EVERY and NONE***

**ANY**

Requires that **at least one** element of the array satisfies the following condition. *It is not required that more than one element match but if the array is empty this condition will **NOT** be fulfilled.*

**EVERY**

Requires that **every** element of the array satisfies the following condition. *If the array is empty, this condition will be true because there is no element that does not fulfill the condition.*

**NONE**

Requires that **no** element satisfies the following condition, meaning that **every** element **does not satisfy** the condition. *This condition will always be fulfilled, unless there is an element that satisfies the condition, including for empty arrays.*

**Single value operators vs array operators**

*If your actual value is an array, you have to use an array operator. When is that the case? - If you use a wildcard operator (`*` or `?()`) you get an array of elements (such as `$.orderLineItems[*]`). However, if you **do** use a transformer (such as `COUNT` to get the number of elements or `SUM` to add the quantities of each `OrderLineItem`), then you will get a single value and must use a single value operator.*

**transformation -*****\[optional]***

A [transformation](#intro-to-transformers)

**transformationArgs -*****\[optional]***

The args for the transformation, if required.

**expectedValue**

The expected value the property should match Examples:

* `2` - a `number`
* `"WAREHOUSE"` - a `string`
* `"2024-02-19T16:16:38.107Z"` - a `date-string`

</details>

#### Comparison rule

Use "comparisonRule" to set up a comparison rule. Comparison rules compare a certain property of the left-hand side (`Order`) to a certain property of the right-hand side (`Order`, `Facility`, `Listing`). Incompatible with using the rule field.

An example of this could be an order with products of a certain brand demanding a facility tagged with the same brand.

<details>

<summary>Steps for defining a comparison rule</summary>

**predicateConnector**

**OR** will make the rule part true if *any* of the predicates evaluate to true. **AND** will only make the expression true if all predicates evaluate to true.

**predicates**

An array of one or more [`ToolkitComparisonPredicate`s](#how-to-define-a-toolkitcomparisonpredicate).

**How to define a `ToolkitComparisonPredicate`?**

**rightPropertyPath**

A string containing a [JSONPath](#intro-to-jsonpath) for the right side entity.

**leftPropertyPath**

A string containing a [JSONPath](#intro-to-jsonpath) for the right side entity.

**entityOperator**

One of the following:

* `LEFT_CONTAINS_RIGHT` - the left hand side should contain all values from the right hand side
* `RIGHT_CONTAINS_LEFT` - the right hand side should contain all values from the left hand side
* `ALL_MATCHES` - the arrays of values should be exactly equal
* `NO_MATCHES` - Values should be disjoint, even when comparing a list with a single value (blacklist case)

**leftTransformation -*****\[optional]***

The [transformation](#intro-to-transformers) for the left hand side.

**leftTransformationArgs -*****\[optional]***

***

The transformation arguments for the left [transformation](#intro-to-transformers), if required.

**rightTransformation -*****\[optional]***

The [transformation](#intro-to-transformers) for the right hand side.

**rightTransformationArgs -*****\[optional]***

The transformation arguments for the right [transformation](#intro-to-transformers), if required.

</details>

## Toolkit ratings

Toolkit ratings follow nearly exact same syntax as [toolkit fences](#how-to-define-a-toolkit-fence) with a few key differences:

* `order`: since all ratings get evaluated either way the order field does not exist for ratings
* `maxPenalty`: the penalty which determines the weight of the rating in comparison to other ratings

## Introduction to JSONPath

It always begins with `$` referring to the entity, followed by a path to the nested property. (e.g. `$.orderLineItems[*].quantity`) Used in:

* [`ToolkitPredicate`](#how-to-define-a-toolkitpredicate)
* [`ToolkitComparisonPredicate`](#how-to-define-a-toolkitcomparisonpredicate)

<details>

<summary>Steps for using a JSON path to define rules</summary>

* `$.tenantOrderId` -> `$` refers to the order entity while `tenantOrderId` refers to the tenantOrderId. The `.` indicates that tenantOrderId is a nested property of `Order`
* `$.orderLineItems[*].article` -> Gets the articles from all orderLineItems, resulting in an array
* `$.orderLineItems[?(@.quantity > 3)].article` -> Same as last time but only gets the articles from OrderLineItems with a quantity greater than 3

To sum up we can say the following: nested properties can be accessed using the `.` while nested array properties such as `orderLineItems`, `tags` or `stickers` can be accessed using the array brackets `[` and `]` with an asterisk `*` to indicate a wildcard.

If you want to be more specific, instead of a wildcard `*` you can use the `?()` syntax. Inside the parentheses `(`, `)` you can then define an arbitrary expression using the `@` symbol to refer to the entity being tested.

If you are for instance on an `OrderLineItem` such as in our example, the `@` refers to the `OrderLineItem` that is just being evaluated. You can then access all of its nested attributes and test for an arbitrary condition. This could be the quantity as in our previous example but it could also be more complex such as the following expression:

`$.orderLineItems[?(@.tags.find(tag => tag.id === 'color' && tag.value === 'red'))].article`

This gets us the articles of all `OrderLineItems` that have at least one tag of type `color` with the value `red`.

***Note**: It is recommended though to keep your expressions as simple as they can possibly be without compromising on their functionality to avoid mistakes leading to misbehavior of the fence/rating.*

</details>

* The JSONPath documentation can be found [here](https://github.com/json-path/JsonPath)
* A helpful tool for testing JSONPath expressions can be found [here](https://jsonpath.com/)

## Introduction to transformers

Transformers allow to modify the input of a `ToolkitPredicate` before it gets evaluated. This can serve multiple purposes, maybe you only want to look at the number of elements, get the total price of all `OrderLineItem`s or just want to consider the beginning of the `tenantOrderId`.

These are the transformations currently available in the platform.

* `SUM`: sums the number values of each element - requires an array of elements
* `COUNT`: counts the number of elements - requires an array of elements
* `SUBSTRING`: gets a substring of a value, requires `start` and `end` parameters
* `LAST`: gets the last `n` chars of a string - required `length` parameter

### Count transformation

```json
{
"propertyPath": "$.orderLineItems[*]",
"transformation": "COUNT",
"entityOperator": "GREATER_THAN",
"expectedValue": 10
}
```

Applies to orders with 10 `OrderLineItems` or more.

### Sum transformation

```json
{
"propertyPath": "$.orderLineItems[*].quantity",
"transformation": "SUM",
"entityOperator": "GREATER_THAN",
"expectedValue": 100
}
```

Applies to orders where all `OrderLineItems` in sum have a total quantity of 100.

### Substring transformation

```json
{
"propertyPath": "$.orderLineItems[*].article.tenantArticleId",
"transformation": "SUBSTRING",
"entityOperator": "ANY_VALUE_EQUALS",
"expectedValue": "Coca",
"transformationArgs": [0, 4]
}
```

Applies to orders where any `OrderLineItem` has a `tenantArticleId` that begins with "Coca".

### Last chars transformation

```json
{
"propertyPath": "$.orderLineItems[*].article.tenantArticleId",
"transformation": "LAST",
"entityOperator": "ANY_VALUE_EQUALS",
"expectedValue": "Christmas Special"
}
```

Applies to orders where any `OrderLineItem` has a `tenantArticleId` that ends with "Christmas Special"

## Introduction to time specifications

It is possible to define a time specification as an expected value, enabling dynamic comparisons during routing processes. For example, a timestamp stored in a `customAttribute` can be compared against the current time using predefined expressions.

* **`{today}`** represents the current day, based on the system's configured time zone. This expression is used to refer to a date value (e.g. `2025-08-07`).
* **`{now}`** represents the current timestamp, calculated in UTC+0. This expression is used to refer to a specific timestamp (e.g. `2025-08-07T18:00:00.000Z`).

This functionality, for example, allows routing logic to determine whether an item is eligible for release to sales, based on time-based conditions.

{% hint style="info" %}
When using the `"entityOperator": "EQUALS"` for timestamp comparisons, be aware that it includes minutes and seconds. This may lead to unintended mismatches unless the timestamps are precisely aligned.
{% endhint %}

```json
{
"propertyPath": "$.customAttributes.releaseDate",
"transformation": "null",
"entityOperator": "LESS_EQUALS",
"expectedValue": "{today}",
"entity": "LISTING"
}
```
