> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Formula Reference

> Complete reference for the KaireonAI formula engine used in computed fields on Categories and Decision Flows.

## How Formulas Work

KaireonAI includes a safe, sandboxed formula engine for computing personalized values at decision time. The engine follows a strict pipeline:

1. **Tokenizer** — Breaks the formula string into tokens (numbers, strings, identifiers, operators, punctuation)
2. **Parser** — Recursive-descent parser builds an Abstract Syntax Tree (AST) from the token stream
3. **Evaluator** — Walks the AST and computes the result structurally

The engine uses **no `eval`, `Function`, or `vm`** — all evaluation is structural, making it safe by design. Any formula that fails to parse or evaluate returns `null` rather than throwing an error.

Formulas are defined on **computed custom fields** within Categories. At decision time, the Recommend API evaluates each formula per candidate offer and merges the results into the response.

## Variable Namespaces

Formulas can reference offer custom fields plus every namespace of the [canonical decision context](/decisioning/decision-flows#the-decision-context-auto-assembled), which the engine assembles automatically at decision time (no Enrich node required — the Enrich node is an override):

| Namespace      | Source                                                                                                    | Example                                    | When Available                                                                        |
| -------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| `fieldName`    | Other custom field values on the offer                                                                    | `base_rate`, `offer_price`                 | Always — values come from the offer's custom fields                                   |
| `customer.*`   | The base `customer` schema row, loaded automatically                                                      | `customer.loan_amount`, `customer.balance` | When the tenant has a `customer` schema and the row exists for this customer          |
| `<entity>.*`   | Each active Schema Join with `autoEnrich: true`, one-to-many rows rolled up via the join's `aggregations` | `accounts.balance_sum`, `accounts.count`   | When active auto-enrich [Schema Joins](/api-reference/schema-joins) are configured    |
| `behavior.*`   | Behavioral-metric values for this customer, keyed by the slugified metric name                            | `behavior.converts_30d`                    | When the customer has computed [Behavioral Metric](/studio/behavioral-metrics) values |
| `journey.*`    | Active journey enrollment state (`journey.current`, `journey.step`)                                       | `journey.current`                          | When the customer is actively enrolled in a [Journey](/studio/journeys)               |
| `attributes.*` | Request-time attributes from the Recommend API body                                                       | `attributes.tier`, `attributes.channel`    | When the caller passes `attributes` in the Recommend request                          |

Variables are resolved as a flat key-value map. Dot notation (e.g., `customer.loan_amount`) is part of the identifier — the engine treats `customer.loan_amount` as a single variable name, not an object property access.

## Operators

### Arithmetic Operators

| Operator    | Description                                   | Example                 | Precedence |
| ----------- | --------------------------------------------- | ----------------------- | ---------- |
| `+`         | Addition (numbers) or concatenation (strings) | `base_rate + 1.5`       | 4          |
| `-`         | Subtraction                                   | `price - discount`      | 4          |
| `*`         | Multiplication                                | `quantity * unit_price` | 5          |
| `/`         | Division (returns `null` on divide by zero)   | `total / count`         | 5          |
| `%`         | Modulo (returns `null` on mod by zero)        | `index % 3`             | 5          |
| `-` (unary) | Negation                                      | `-score`                | 6          |

### Comparison Operators

All comparison operators return `1` for true and `0` for false. String comparisons support only `==` and `!=`.

| Operator | Description                    | Example                         | Precedence |
| -------- | ------------------------------ | ------------------------------- | ---------- |
| `>`      | Greater than                   | `customer.age > 18`             | 3          |
| `<`      | Less than                      | `customer.score < 50`           | 3          |
| `>=`     | Greater than or equal          | `customer.age >= 21`            | 3          |
| `<=`     | Less than or equal             | `balance <= 0`                  | 3          |
| `==`     | Equal (numbers or strings)     | `attributes.tier == "gold"`     | 3          |
| `!=`     | Not equal (numbers or strings) | `customer.status != "inactive"` | 3          |

### Precedence Summary (highest to lowest)

| Level | Operators                        |
| ----- | -------------------------------- |
| 6     | Unary `-`                        |
| 5     | `*`, `/`, `%`                    |
| 4     | `+`, `-`                         |
| 3     | `>`, `<`, `>=`, `<=`, `==`, `!=` |
| 1     | `? :` (ternary)                  |

Parentheses `()` override default precedence.

## Functions

The engine includes 19 built-in functions, grouped below. Each argument is
evaluated first; if any argument is `null`, the function returns `null`.

### Numeric

**`min(a, b)`** — the smaller of two numbers. `min(customer.rate, 25.0)`

**`max(a, b)`** — the larger of two numbers. `max(customer.score, 0)`

**`round(value)` / `round(value, decimals)`** — rounds to the nearest integer, or to *N* decimal places when a second argument is supplied. `round(total * 0.0825, 2)`

**`abs(value)`** — absolute value. `abs(customer.balance)`

### Logic

**`if(condition, valueIfTrue, valueIfFalse)`** — returns one of two values based on the condition (a functional alternative to the `? :` operator). `if(customer.age >= 21, "eligible", "ineligible")`

### Null handling

**`coalesce(a, b, ...)`** — returns the first non-null argument (minimum two arguments). Result type matches the first non-null value. `coalesce(customer.rate, base_rate, 5.0)`

### String

**`concat(a, b, ...)`** — joins all arguments as strings (minimum two arguments). `concat("Hello ", customer.name)`

**`upper(s)`** / **`lower(s)`** — upper- or lower-cases a string. `upper(customer.state)`

**`trim(s)`** — removes leading and trailing whitespace. `trim(customer.name)`

**`replace(s, find, replacement)`** — replaces every occurrence of `find`. `replace(customer.phone, "-", "")`

**`left(s, n)` / `right(s, n)`** — the first / last *n* characters. `left(customer.zip, 3)`

**`substring(s, start, length)`** — a substring of `length` characters from `start` (0-based). `substring(customer.id, 0, 4)`

### Type & utility

**`cast(value, type)`** — converts `value` to `"float"`, `"integer"`, `"string"`, or `"boolean"`. `cast(attributes.qty, "integer")`

**`mask(s, pattern)`** — masks a string, keeping the last *N* characters where the pattern contains `{N}`. `mask(customer.card, "****{4}")` → `****1234`

**`hash(s, algorithm)`** — hex digest using `"sha256"`, `"sha512"`, or `"md5"`. `hash(customer.email, "sha256")`

**`now()`** — the current time as an ISO-8601 string.

**`date_format(value, format)`** — formats a date/timestamp using `YYYY`, `MM`, `DD`, `HH`, `mm`, `ss` tokens (UTC). `date_format(customer.joined_at, "YYYY-MM-DD")`

<Note>
  String functions require string arguments and numeric functions require numeric
  arguments — a type mismatch returns `null`. Unknown function names, and functions
  called with the wrong number of arguments, also return `null`.
</Note>

## Ternary Expressions

Ternary expressions provide conditional logic:

```
condition ? valueIfTrue : valueIfFalse
```

The condition is evaluated first. A condition is **truthy** if it is a non-zero number or a non-empty string. A condition of `0`, `""`, or `null` is falsy.

```
customer.age >= 21 ? "eligible" : "ineligible"
```

Ternary expressions can be nested using parentheses:

```
customer.qty > 100 ? 0.50 : (customer.qty > 50 ? 0.75 : 1.00)
```

If the condition evaluates to `null`, the entire ternary returns `null`.

## Type Coercion

Computed fields declare an `outputType` of either `number` or `text`. This is a metadata hint used for validation and display — the formula engine itself returns whatever type the expression produces:

* Arithmetic operations return numbers
* String operations (`+` on two strings, `concat`) return strings
* Comparison operators always return `1` or `0` (numbers)
* `coalesce` returns the type of the first non-null argument

When defining a computed field, choose the `outputType` that matches your formula's expected result. The Categories API rejects any computed field that omits either a `formula` string or an `outputType` of `number` or `text`.

## Error Handling

The formula engine is designed to fail gracefully. Every error condition returns `null` rather than throwing:

| Condition                                   | Behavior                                                |
| ------------------------------------------- | ------------------------------------------------------- |
| Division by zero (`10 / 0`)                 | Returns `null`                                          |
| Modulo by zero (`10 % 0`)                   | Returns `null`                                          |
| Missing or undefined variable               | Returns `null` (use `coalesce` to provide a default)    |
| `null` variable value                       | Returns `null` (null propagates through all operations) |
| Syntax error or unparseable formula         | Returns `null`                                          |
| Mismatched parentheses                      | Returns `null`                                          |
| Empty formula                               | Returns `null`                                          |
| Unknown function name                       | Returns `null`                                          |
| Wrong argument count for a function         | Returns `null`                                          |
| Type mismatch (e.g., arithmetic on strings) | Returns `null`                                          |
| Unterminated string literal                 | Returns `null`                                          |

**Null propagation rule:** If any operand in a binary operation is `null`, the entire operation returns `null`. This means `5 + null` is `null`, not `5`. Use `coalesce` to guard against missing values.

## Real-World Formulas

### Simple arithmetic

```
base_rate * 1.1
```

Applies a 10% markup to the offer's `base_rate` custom field.

### Customer data lookup

```
customer.balance * 0.02
```

Computes 2% of the customer's balance (loaded via Enrich stage).

### Request-time conditional

```
attributes.tier == "gold" ? 500 : 200
```

Returns a different reward amount based on the tier passed in the Recommend request.

### Fallback with coalesce

```
coalesce(customer.preferred_rate, base_rate, 5.0)
```

Uses the customer's preferred rate if available, falls back to the offer's base rate, then to a hardcoded default of 5.0.

### String personalization

```
concat("Hello ", customer.first_name)
```

Builds a personalized greeting from enriched customer data.

### Rounded calculation

```
round(customer.loan_amount * base_rate / 100, 2)
```

Computes a monthly interest amount rounded to 2 decimal places.

### Conditional discount

```
base_price * (1 - (customer.loyalty_years > 5 ? 0.15 : 0.05))
```

Applies a 15% discount for customers with more than 5 loyalty years, otherwise 5%.

### Clamping with min/max

```
max(min(calculated_rate, 25.0), 2.5)
```

Ensures a rate stays within the 2.5 to 25.0 range.

### Multi-variable calculation

```
(customer.income - customer.expenses) * risk_factor
```

Computes disposable income multiplied by an offer-level risk factor.

### Channel-aware text

```
attributes.channel == "email" ? concat(customer.name, ", check out ", offer_name) : offer_name
```

Returns a personalized message for email, or only the offer name for other channels.

### Tiered pricing with nested ternary

```
customer.qty > 100 ? 0.50 : (customer.qty > 50 ? 0.75 : 1.00)
```

Three-tier pricing: high volume at 0.50, medium at 0.75, standard at 1.00.

### Scoring with null safety

```
coalesce(customer.score, 0) * offer.weight + coalesce(customer.bonus, 0)
```

Builds a weighted score with safe defaults for missing values.

## Validation

### UI Validation

The Business Hierarchy page (Category editor) includes a **Validate** button next to each computed field. Clicking it parses the formula in place and returns either a green check or a red error message describing exactly what failed (unbalanced parentheses, unknown function, missing operand, and so on), so authors can fix mistakes before saving.

### API Validation

The Categories API enforces the same rules on every create and update request:

* Every field with `type: "computed"` must have a non-empty `formula` string
* Every computed field must have an `outputType` of either `"number"` or `"text"`

Missing either property is rejected with a `400 Bad Request` and a validation error describing which field failed.

## Related

<CardGroup cols={3}>
  <Card title="Business Hierarchy" icon="sitemap" href="/studio/business-hierarchy">
    Define Categories with computed custom fields
  </Card>

  <Card title="Decision Flows" icon="diagram-project" href="/decisioning/decision-flows">
    Configure Enrich and Compute stages in flows
  </Card>

  <Card title="Composable Pipeline" icon="layer-group" href="/data/transforms/composable-pipeline">
    V2 pipeline architecture with compute nodes
  </Card>
</CardGroup>
