# Evaluating Expressions via the API

Canonical page: https://www.adaptabletools.com/docs/adaptable-ql-expression-evaluation

- AdapTableQL can evaluate an Expression as an explicit call — no Module has to own it
- Use it when you want the same engine as Grid Filter, Alerts and Calculated Columns, but from your own code
- There are 4 methods available to meet all potential use cases and the types of Expressions AdapTable offers

AdapTable typically evaluates an Expression in response to an event in the UI.

For example when running a Grid Filter, calculating a Calculated Column value, or rendering a Cell with a Conditional Style, AdapTable will hand over the evaluation to AdapTableQL which will return the result.

However it is possible to leverage AdapTableQL programmatically - to evaluate an Expression entirely via the API.

- This is one of 2 ways in which [an Expression is evaluated outside the UI](https://www.adaptabletools.com/docs/adaptable-ql-expression-evaluating/index.md)
- The other is [Server Evaluation](https://www.adaptabletools.com/docs/adaptable-server-evaluation/index.md) when you will evaluate Expressions on your server

```ts
const result = adaptableApi.expressionApi.evaluateStandardExpression({
  expression: '[price] * [qty] > 1000',
  primaryKeyValue: 42,
});

if (result.success) {
  console.log(result.value); // true | false
} else {
  console.warn(result.errorMessage);
}
```

- AdapTableQL still evaluates the Expression
- The difference is that you ask for the result yourself, instead of hanging it off a Grid Filter, Calculated Column etc.

## Why evaluate via API

There are many potential use cases where you might want to evaluate an Expression using the API.

For instance you might want to:

- **ask a question once** (“does this row pass?”, “what is this value?”) outside of a Module
- drive **your own UI** — a toolbar button or custom form — from an Expression the user typed or you shipped
- run a **what-if**: would this row still match if `price` were 120?
- filter **your own array** of rows, not the grid

- The Expression string is **the same one** you would put in Initial State or the Expression Editor
- AdapTableQL evaluates it exactly the same way as it would evaluate if invoked by AdapTable's UI

## API Methods

AdapTable provides 4 Expression-evaluation methods in [Expression API](https://www.adaptabletools.com/docs/technical-reference-expression/index.md):

- `evaluateStandardExpression`
- `evaluateAggregatedExpression`
- `filterRowNodes`
- `filterDataSet`

These are designed to be able to meet a range of potential use cases:

| You want                                         | Call                                |
| ------------------------------------------------ | ----------------------------------- |
| a `true` / `false` result                        | `evaluateStandardExpression`        |
| a value for **one row**                          | `evaluateStandardExpression`        |
| the **rows that match**                          | `filterRowNodes` or `filterDataSet` |
| a snapshot (`SUM`, `AVG`, `GROUP_BY`, `RANK`, …) | `evaluateAggregatedExpression`      |

### `evaluateStandardExpression`

Use this function to evaluate a Standard Expression against a single row.

The Expression determines the return value: a boolean condition will yields true / false, while a value Expression will yield that value.

### `evaluateStandardExpression`

Evaluates a Standard Expression against one row

Needs **exactly one** row target: `rowNode`, `primaryKeyValue` or `data`.

```ts
evaluateStandardExpression(
  request: EvaluateStandardExpressionRequest
): ExpressionEvaluationResult<unknown>;
```

| Property | Type | Description |
| --- | --- | --- |
| [cellValueOverride](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#cellvalueoverride) | [`CellValueOverride`](https://www.adaptabletools.com/docs/reference/cellvalueoverride.md) | What-if override: substitute this column&#39;s value on the row being<br/>evaluated, without mutating data. |
| [columns](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#columns) | [`ExternalDataColumn`](https://www.adaptabletools.com/docs/reference/externaldatacolumn.md)`[]` | dataType metadata for keys on `data` that are not grid columns.<br/>Ignored unless `data` is provided. |
| [columnScope](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#columnscope) | `string` | Column id substituted for `$SCOPE` references. `$SCOPE` expressions fail<br/>with an evaluation error when this is not provided. |
| [data](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#data) | `Record<string, unknown>` | A plain-object row, evaluated via the external-data host (no AG Grid<br/>`valueGetter`s — values are whatever the object contains). Mutually<br/>exclusive with `rowNode` / `primaryKeyValue`. |
| [dataChangedEvent](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#datachangedevent) | [`CellDataChangedInfo`](https://www.adaptabletools.com/docs/reference/celldatachangedinfo.md) | Cell change consumed by the data-change functions (`ANY_CHANGE`,<br/>`CHANGED_COLUMN`, …). Those functions fail with an evaluation error when<br/>this is not provided. |
| [expression](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#expression) | `string` | Standard AdaptableQL Expression to evaluate. |
| [primaryKeyValue](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#primarykeyvalue) | `unknown` | Alternative row target: resolved via the grid&#39;s primary key. |
| [rowNode](https://www.adaptabletools.com/docs/reference/evaluatestandardexpressionrequest.md#rownode) | `IRowNode` | The grid row to evaluate against. |

```ts
adaptableApi.expressionApi.evaluateStandardExpression({
  expression: '[price] * 1.19',
  rowNode,
});

adaptableApi.expressionApi.evaluateStandardExpression({
  expression: "[make] = 'Toyota'",
  data: {make: 'Toyota', price: 350},
});

// Would this row pass if price became 120?
adaptableApi.expressionApi.evaluateStandardExpression({
  expression: '[price] > 100',
  primaryKeyValue: 42,
  cellValueOverride: {columnId: 'price', value: 120},
});
```

- `columnScope` — required when the Expression uses [$SCOPE](https://www.adaptabletools.com/docs/adaptable-ql-expression-advanced-scope-function/index.md)
- `dataChangedEvent` — required when the Expression uses [Relative Change](https://www.adaptabletools.com/docs/adaptable-ql-expression-relative-change/index.md) (`ANY_CHANGE`, `PERCENT_CHANGE`, …)
- `module` — emulate a Module's function pool (default is the full `'Expression'` pool)

- If you are looping `evaluateStandardExpression` over every row, use `filterRowNodes` / `filterDataSet` instead
- Validation runs once, and matching rows keep their source order and object identity

### `evaluateAggregatedExpression`

Use this function to evaluate an Aggregation Expression over a set of rows (the grid, a narrowed subset, or an array you supply).

It returns a snapshot of the result — the aggregated value, any `GROUP_BY` breakdown, and a per-row projection.

When the Expression is a condition, you also get whether it matches.

### `evaluateAggregatedExpression`

Evaluates an Aggregation Expression and returns a snapshot (value, groups, per-row projection, rowCount). Conditions also set matches.

Returns a **snapshot** computed once. It does not live-update; call again when the data changes (or use an Observable).

```ts
evaluateAggregatedExpression(
  request: EvaluateAggregatedExpressionRequest
): ExpressionEvaluationResult<AggregatedExpressionEvaluationValue>;
```

The snapshot:

| Property | Type | Description |
| --- | --- | --- |
| [getValueForRow](https://www.adaptabletools.com/docs/reference/aggregatedexpressionevaluationvalue.md#getvalueforrow) | `(row: IRowNode \| Record<string, unknown>) => unknown` | Per-row projection of the aggregation — the row&#39;s group value for<br/>`GROUP_BY` expressions, and the only meaningful output of per-row<br/>functions (`RANK`, `LAG`, `CUMULATIVE`, `PERCENTAGE`, …). Returns<br/>`undefined` when a `WHERE` clause excluded the row&#39;s group.<br/><br/>Accepts an `IRowNode` for grid-target evaluations, or a data-set row<br/>object when the call used `dataSet`. |
| [groupValues](https://www.adaptabletools.com/docs/reference/aggregatedexpressionevaluationvalue.md#groupvalues) | `\{ groupKeys: unknown[]; value: number; \}[]` | Per-group values when the expression uses `GROUP_BY` (absent otherwise).<br/>For multi-level `GROUP_BY`, entries include the intermediate group levels<br/>(`groupKeys.length`  number of group levels). Note: `Date` group values<br/>are keyed as timestamps (numbers). |
| [matches](https://www.adaptabletools.com/docs/reference/aggregatedexpressionevaluationvalue.md#matches) | `boolean` | Present when the Expression is a condition (e.g. `SUM([PnL]) > '5M'`).<br/>`true` when at least one aggregation group satisfies the condition. |
| [rowCount](https://www.adaptabletools.com/docs/reference/aggregatedexpressionevaluationvalue.md#rowcount) | `number` | Number of rows aggregated (after any `WHERE` filter). Lets callers detect<br/>partial data, e.g. under the server-side row model where only loaded rows<br/>are available client-side. |
| [value](https://www.adaptabletools.com/docs/reference/aggregatedexpressionevaluationvalue.md#value) | `number \| undefined` | The whole-set aggregation value. |

When the Expression is a **condition** (`SUM{[PnL]} > '5M'`), `matches` is whether at least one aggregation group satisfies it. You keep the number **and** the yes / no.

Default target is all loaded grid rows. Narrow with `rowScope` / `rowFilter`, or pass `dataSet` (`dataSet` cannot be combined with `rowScope` / `rowFilter`).

| Property | Type | Description |
| --- | --- | --- |
| [columns](https://www.adaptabletools.com/docs/reference/evaluateaggregatedexpressionrequest.md#columns) | [`ExternalDataColumn`](https://www.adaptabletools.com/docs/reference/externaldatacolumn.md)`[]` | dataType metadata for keys on `dataSet` rows that are not grid columns.<br/>Ignored unless `dataSet` is provided. |
| [dataSet](https://www.adaptabletools.com/docs/reference/evaluateaggregatedexpressionrequest.md#dataset) | `Record<string, unknown>[]` | Aggregate over these plain-object rows instead of the grid. AG Grid<br/>`valueGetter`s do not run. Mutually exclusive with `rowScope` / `rowFilter`. |
| [expression](https://www.adaptabletools.com/docs/reference/evaluateaggregatedexpressionrequest.md#expression) | `string` | Aggregated AdaptableQL Expression to evaluate. |
| [rowFilter](https://www.adaptabletools.com/docs/reference/evaluateaggregatedexpressionrequest.md#rowfilter) | `(rowNode: IRowNode) => boolean` | Extra predicate applied to the scoped grid rows (logical AND with any<br/>`WHERE` clause in the expression). Receives AG Grid `IRowNode`s.<br/>Mutually exclusive with `dataSet`. |
| [rowScope](https://www.adaptabletools.com/docs/reference/evaluateaggregatedexpressionrequest.md#rowscope) | `'all' \| 'visible'` | Grid-row set to aggregate over. `'all'` (default) uses every loaded row;<br/>`'visible'` uses rows the user currently sees (after grid / AdapTable<br/>filters). Group rows are excluded in both cases. Mutually exclusive with<br/>`dataSet`. |

```ts
adaptableApi.expressionApi.evaluateAggregatedExpression({
  expression: 'AVG{[price]}',
  rowScope: 'visible',
  rowFilter: rowNode => rowNode.data.region === 'EMEA',
});

const snapshot = adaptableApi.expressionApi.evaluateAggregatedExpression({
  expression: 'SUM{[price], GROUP_BY([make])}',
  dataSet: myRows,
});

const limit = adaptableApi.expressionApi.evaluateAggregatedExpression({
  expression: "SUM{[PnL]} > '5M'",
});
if (limit.success && limit.value.matches) {
  // the SUM breached — limit.value.value is the SUM itself
}
```

- Under the [Server-Side Row Model](https://www.adaptabletools.com/docs/dev-guide-row-models-server-overview/index.md) only currently loaded rows exist client-side, so the snapshot is partial
- Check `rowCount` before trusting `value`. The same is true of Aggregated Modules today

### Observable Expressions

There is no API method to subscribe to an Observable Expression.

- `evaluateStandardExpression` can evaluate an Observable Expression (e.g. `HIGHEST{[price], WITHIN(1h)}`) against a grid row, returning its current value
- Observable Expressions are grid-only, so passing `data` or `dataSet` is rejected

To react **live** to an Observable pattern, create an [Observable Alert](https://www.adaptabletools.com/docs/handbook-alerting-observables/index.md) and listen to the [Alert Fired Event](https://www.adaptabletools.com/docs/technical-reference-alerting/index.md), or display the value in an [Observable Calculated Column](https://www.adaptabletools.com/docs/handbook-calculated-column-observable/index.md).

### `filterRowNodes`

Use this function to return the grid rows that match a boolean Expression.

It evaluates the Expression once per row and returns the matching `IRowNode`s in source order.

### `filterRowNodes`

Returns the grid rows matching a boolean Expression, in source order.

Use this instead of looping `evaluateStandardExpression` over every row. Validation runs once.

```ts
filterRowNodes(
  request: FilterRowNodesRequest
): ExpressionEvaluationResult<IRowNode[]>;
```

| Property | Type | Description |
| --- | --- | --- |
| [cellValueOverride](https://www.adaptabletools.com/docs/reference/filterrownodesrequest.md#cellvalueoverride) | [`CellValueOverride`](https://www.adaptabletools.com/docs/reference/cellvalueoverride.md) | Applied to each candidate row as it is evaluated (&quot;which rows would<br/>match if this column were this value?&quot;). |
| [expression](https://www.adaptabletools.com/docs/reference/filterrownodesrequest.md#expression) | `string` | Boolean AdaptableQL Expression to evaluate against each row. |
| [rowFilter](https://www.adaptabletools.com/docs/reference/filterrownodesrequest.md#rowfilter) | `(rowNode: IRowNode) => boolean` | Extra predicate applied before the expression (logical AND). |
| [rowScope](https://www.adaptabletools.com/docs/reference/filterrownodesrequest.md#rowscope) | `'all' \| 'visible'` | Grid-row set to filter. `'all'` (default) uses every loaded row;<br/>`'visible'` uses rows the user currently sees. |

```ts
const emea = adaptableApi.expressionApi.filterRowNodes({
  expression: '[region] = "EMEA" AND [notional] > 1000000',
  rowScope: 'visible',
});
```

- `rowScope: 'all' | 'visible'` — `'visible'` is the rows the user currently sees
- `rowFilter` — an extra `IRowNode` predicate, AND-ed with the Expression
- `cellValueOverride` is applied to **every** candidate row (“which rows would match if `[x]` were `y`”)

Validation runs once. A per-row evaluation error fails the **whole** call — you do not get a silent partial list.

### `filterDataSet`

Use this function to filter an array of your own rows with a boolean Expression.

Same behaviour as `filterRowNodes`, but the input and output are your objects rather than grid row nodes.

### `filterDataSet`

Returns the subset of a caller-supplied array matching a boolean Expression, in original order and with the same object references.

Same as `filterRowNodes`, but over an array you supply rather than the grid.

```ts
filterDataSet(
  request: FilterDataSetRequest
): ExpressionEvaluationResult<Record<string, unknown>[]>;
```

| Property | Type | Description |
| --- | --- | --- |
| [columns](https://www.adaptabletools.com/docs/reference/filterdatasetrequest.md#columns) | [`ExternalDataColumn`](https://www.adaptabletools.com/docs/reference/externaldatacolumn.md)`[]` | dataType metadata for keys that are not grid columns. |
| [dataSet](https://www.adaptabletools.com/docs/reference/filterdatasetrequest.md#dataset) | `Record<string, unknown>[]` | Plain-object rows to filter. Matching objects are returned by reference. |
| [expression](https://www.adaptabletools.com/docs/reference/filterdatasetrequest.md#expression) | `string` | Boolean AdaptableQL Expression to evaluate against each row. |

```ts
const matching = adaptableApi.expressionApi.filterDataSet({
  expression: '[price] > 100',
  dataSet: myRows,
});
```

Validation runs once. A per-row evaluation error fails the **whole** call — you do not get a silent partial list.

**Example: AdapTableQL: Evaluating Expressions via the API**

Calling all 4 Expression evaluation methods from toolbar buttons

- The `Evaluate` Toolbar has 4 buttons; each calls one method and shows the result in the System Status:
  - **Large Open Trades** - `filterRowNodes` with `[notional] > 75000 AND [status] = 'Open'`
  - **What If Price +10%** - `evaluateStandardExpression` on trade 1, with and without a `cellValueOverride` raising its Price by 10%
  - **Check Proposed Orders** - `filterDataSet` over an array of orders which are not in the grid
  - **P&L by Desk** - `evaluateAggregatedExpression` with `SUM{[pnl], GROUP_BY([desk])}`, reading `groupValues`

- Click each button and read the result in the System Status
- Change trade 1's `Status` to `Closed` and click **Large Open Trades** again
- Change a `P&L` value and click **P&L by Desk** - the snapshot is recalculated on each call

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template]
  columnDefs.ts
  evaluations.ts
  gridOptions.ts
  index.ts             [template]
  onAdaptableReady.ts  [template]
  rowData.ts
  styles.css           [template]
index.html             [template]
package.json           [template]
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {AdaptableOptions} from '@adaptabletools/adaptable';
import {Trade} from './rowData';
import {
  checkProposedOrders,
  findLargeOpenTrades,
  showPnlByDesk,
  whatIfPriceRises,
} from './evaluations';

export const adaptableOptions: AdaptableOptions<Trade> = {
  primaryKey: 'id',
  adaptableId: 'Evaluating Expressions via the API',
  dashboardOptions: {
    customToolbars: [
      {
        name: 'evaluate',
        title: 'Evaluate',
        toolbarButtons: [
          {
            label: 'Large Open Trades',
            onClick: (_button, context) => findLargeOpenTrades(context.adaptableApi),
          },
          {
            label: 'What If Price +10%',
            onClick: (_button, context) => whatIfPriceRises(context.adaptableApi),
          },
          {
            label: 'Check Proposed Orders',
            onClick: (_button, context) => checkProposedOrders(context.adaptableApi),
          },
          {
            label: 'P&L by Desk',
            onClick: (_button, context) => showPnlByDesk(context.adaptableApi),
          },
        ],
      },
    ],
  },
  initialState: {
    Dashboard: {
      Tabs: [{Name: 'Evaluate', Toolbars: ['evaluate', 'SystemStatus']}],
    },
    StatusBar: {
      StatusBars: [{Key: 'Center Panel', StatusBarPanels: ['SystemStatus']}],
    },
    Theme: {CurrentTheme: 'dark'},
    FormatColumn: {
      FormatColumns: [
        {
          Name: 'two-decimals',
          Scope: {ColumnIds: ['price', 'notional']},
          DisplayFormat: {Formatter: 'NumberFormatter', Options: {FractionDigits: 2}},
        },
        {
          Name: 'trade-date',
          Scope: {ColumnIds: ['tradeDate']},
          DisplayFormat: {Formatter: 'DateFormatter', Options: {Pattern: 'dd MMM yyyy'}},
        },
      ],
    },
    Layout: {
      CurrentLayout: 'Trades',
      Layouts: [
        {
          Name: 'Trades',
          TableColumns: [
            'id',
            'ticker',
            'desk',
            'side',
            'quantity',
            'price',
            'notional',
            'pnl',
            'status',
          ],
          AutoSizeColumns: true,
        },
      ],
    },
  },
};
```

**`src/evaluations.ts`**

```ts
import {AdaptableApi} from '@adaptabletools/adaptable';
import {Trade} from './rowData';

const formatNumber = (value: number) => value.toLocaleString(undefined, {maximumFractionDigits: 2});

// filterRowNodes - which grid rows match?
export const findLargeOpenTrades = (api: AdaptableApi) => {
  const result = api.expressionApi.filterRowNodes({
    expression: "[notional] > 75000 AND [status] = 'Open'",
  });
  if (!result.success) {
    api.systemStatusApi.setErrorSystemStatus(result.errorMessage);
    return;
  }
  const tradeIds = result.value.map((rowNode) => (rowNode.data as Trade).id);
  api.systemStatusApi.setInfoSystemStatus(
    `${tradeIds.length} large Open trades: ${tradeIds.join(', ')}`,
    "filterRowNodes: [notional] > 75000 AND [status] = 'Open'"
  );
};

// evaluateStandardExpression with cellValueOverride - a what-if on one row
export const whatIfPriceRises = (api: AdaptableApi) => {
  const expression = '[price] * [quantity] > 100000';
  const trade = api.gridApi.getRowNodeForPrimaryKey(1)?.data as Trade | undefined;
  if (!trade) {
    return;
  }
  const newPrice = Math.round(trade.price * 1.1 * 100) / 100;
  const now = api.expressionApi.evaluateStandardExpression({expression, primaryKeyValue: 1});
  const whatIf = api.expressionApi.evaluateStandardExpression({
    expression,
    primaryKeyValue: 1,
    cellValueOverride: {columnId: 'price', value: newPrice},
  });
  if (!now.success || !whatIf.success) {
    api.systemStatusApi.setErrorSystemStatus('What-if evaluation failed');
    return;
  }
  api.systemStatusApi.setInfoSystemStatus(
    `Trade 1 (${trade.ticker}) over 100k: now ${now.value}; at price ${newPrice} it would be ${whatIf.value}`,
    `evaluateStandardExpression: ${expression}`
  );
};

// filterDataSet - filter an array which is not in the grid
const proposedOrders = [
  {ticker: 'AAPL', desk: 'Tech', quantity: 200, price: 189.5},
  {ticker: 'NVDA', desk: 'Tech', quantity: 300, price: 880.0},
  {ticker: 'XOM', desk: 'Energy', quantity: 500, price: 118.4},
  {ticker: 'GS', desk: 'Financials', quantity: 200, price: 451.0},
];

export const checkProposedOrders = (api: AdaptableApi) => {
  const expression = "[quantity] * [price] <= 100000 AND [desk] != 'Energy'";
  const result = api.expressionApi.filterDataSet({expression, dataSet: proposedOrders});
  if (!result.success) {
    api.systemStatusApi.setErrorSystemStatus(result.errorMessage);
    return;
  }
  const accepted = result.value.map((order) => order.ticker);
  api.systemStatusApi.setSuccessSystemStatus(
    `${accepted.length} of ${proposedOrders.length} proposed orders pass: ${accepted.join(', ')}`,
    `filterDataSet: ${expression}`
  );
};

// evaluateAggregatedExpression - a snapshot with a GROUP_BY breakdown
export const showPnlByDesk = (api: AdaptableApi) => {
  const expression = 'SUM{[pnl], GROUP_BY([desk])}';
  const result = api.expressionApi.evaluateAggregatedExpression({expression});
  if (!result.success) {
    api.systemStatusApi.setErrorSystemStatus(result.errorMessage);
    return;
  }
  const byDesk = (result.value.groupValues ?? [])
    .map((group) => `${group.groupKeys.join(' / ')}: ${formatNumber(group.value)}`)
    .join(' | ');
  api.systemStatusApi.setInfoSystemStatus(
    `P&L by Desk - ${byDesk}`,
    `evaluateAggregatedExpression: ${expression}`
  );
};
```

**`src/rowData.ts`**

```ts
export interface Trade {
  id: number;
  tradeDate: Date;
  ticker: string;
  desk: string;
  side: 'Buy' | 'Sell';
  quantity: number;
  price: number;
  notional: number;
  pnl: number;
  status: 'Open' | 'Closed';
}

type TradeInput = Omit<Trade, 'tradeDate' | 'notional'>;

const trades: TradeInput[] = [
  {id: 1, ticker: 'AAPL', desk: 'Tech', side: 'Buy', quantity: 500, price: 189.2, pnl: 1200, status: 'Open'},
  {id: 2, ticker: 'MSFT', desk: 'Tech', side: 'Buy', quantity: 200, price: 415.5, pnl: 850, status: 'Closed'},
  {id: 3, ticker: 'NVDA', desk: 'Tech', side: 'Sell', quantity: 100, price: 875.0, pnl: -2100, status: 'Open'},
  {id: 4, ticker: 'JPM', desk: 'Financials', side: 'Buy', quantity: 400, price: 198.7, pnl: 640, status: 'Open'},
  {id: 5, ticker: 'XOM', desk: 'Energy', side: 'Buy', quantity: 800, price: 118.2, pnl: -320, status: 'Closed'},
  {id: 6, ticker: 'AAPL', desk: 'Tech', side: 'Sell', quantity: 300, price: 190.1, pnl: 270, status: 'Closed'},
  {id: 7, ticker: 'GS', desk: 'Financials', side: 'Sell', quantity: 150, price: 452.3, pnl: -950, status: 'Open'},
  {id: 8, ticker: 'CVX', desk: 'Energy', side: 'Buy', quantity: 350, price: 156.4, pnl: 410, status: 'Open'},
  {id: 9, ticker: 'NVDA', desk: 'Tech', side: 'Buy', quantity: 250, price: 868.4, pnl: 25000, status: 'Open'},
  {id: 10, ticker: 'JPM', desk: 'Financials', side: 'Sell', quantity: 200, price: 199.9, pnl: 240, status: 'Closed'},
  {id: 11, ticker: 'XOM', desk: 'Energy', side: 'Sell', quantity: 600, price: 119.0, pnl: 480, status: 'Open'},
  {id: 12, ticker: 'MSFT', desk: 'Tech', side: 'Buy', quantity: 150, price: 417.2, pnl: -380, status: 'Open'},
  {id: 13, ticker: 'GS', desk: 'Financials', side: 'Buy', quantity: 100, price: 449.8, pnl: 520, status: 'Closed'},
  {id: 14, ticker: 'CVX', desk: 'Energy', side: 'Sell', quantity: 250, price: 157.1, pnl: -175, status: 'Closed'},
  {id: 15, ticker: 'AAPL', desk: 'Tech', side: 'Buy', quantity: 400, price: 188.7, pnl: 900, status: 'Open'},
  {id: 16, ticker: 'JPM', desk: 'Financials', side: 'Buy', quantity: 300, price: 197.6, pnl: -460, status: 'Open'},
];

// One trade a day from 1 September, each at a different time
export const rowData: Trade[] = trades.map((trade, index) => ({
  ...trade,
  tradeDate: new Date(2026, 8, index + 1, 9, 30 + index * 7),
  notional: Math.round(trade.quantity * trade.price * 100) / 100,
}));
```

**`src/columnDefs.ts`**

```ts
import {ColDef} from 'ag-grid-enterprise';
import {Trade} from './rowData';

export const columnDefs: ColDef<Trade>[] = [
  {field: 'id', headerName: 'Trade Id', cellDataType: 'number'},
  {field: 'tradeDate', headerName: 'Trade Date', cellDataType: 'date'},
  {field: 'ticker', headerName: 'Ticker', cellDataType: 'text'},
  {field: 'desk', headerName: 'Desk', cellDataType: 'text', enableRowGroup: true},
  {field: 'side', headerName: 'Side', cellDataType: 'text'},
  {field: 'quantity', headerName: 'Quantity', cellDataType: 'number', type: 'rightAligned'},
  {field: 'price', headerName: 'Price', cellDataType: 'number', type: 'rightAligned'},
  {field: 'notional', headerName: 'Notional', cellDataType: 'number', type: 'rightAligned'},
  {field: 'pnl', headerName: 'P&L', cellDataType: 'number', type: 'rightAligned'},
  {field: 'status', headerName: 'Status', cellDataType: 'text'},
];
```

**`src/gridOptions.ts`**

```ts
import {GridOptions, themeBalham} from 'ag-grid-enterprise';

import {columnDefs} from './columnDefs';
import {rowData, Trade} from './rowData';

export const gridOptions: GridOptions<Trade> = {
  theme: themeBalham,
  defaultColDef: {
    resizable: true,
    sortable: true,
    editable: true,
    filter: true,
    floatingFilter: true,
  },
  columnDefs: columnDefs,
  rowData: rowData,
  sideBar: true,
  suppressMenuHide: true,
  columnMenu: 'new',
  cellSelection: true,
  statusBar: {
    statusPanels: [
      {statusPanel: 'agTotalRowCountComponent', align: 'left'},
      {statusPanel: 'agFilteredRowCountComponent'},
      {
        key: 'Center Panel',
        statusPanel: 'AdaptableStatusPanel',
        align: 'center',
      },
    ],
  },
};
```

The rest of [`ExpressionApi`](https://www.adaptabletools.com/docs/reference/expressionapi.md) is supporting: `isValid*` before you evaluate, `getColumnsFromExpression`, `getASTForExpression` (also used by [Server Evaluation](https://www.adaptabletools.com/docs/adaptable-server-evaluation/index.md)).

## Function Results

Synchronous evaluate / filter methods **never throw** for expected input errors (typos, unknown columns, missing row, unavailable functions).

```ts
type ExpressionEvaluationResult<T> =
  | {success: true; value: T}
  | {success: false; errorMessage: string};
```

Always check `success` before reading `value`.

The Expression decides what `value` is.

A condition such as `[price] > 100` yields `true` / `false`. `[price] * 1.19` yields a number.

### Evaluating your own data

When you pass `data` or `dataSet`, AdapTableQL reads values from the objects (the column `field`, or `columnId` if there is no field).

- AG Grid `valueGetter`s do **not** run against `data` / `dataSet`
- The object must already contain the values you want evaluated

Column metadata (data type, queryable) comes from the live grid.

For keys that are **not** grid columns, pass `columns`:

```ts
adaptableApi.expressionApi.evaluateStandardExpression({
  expression: "[region] = 'EMEA'",
  data: {price: 100, region: 'EMEA'},
  columns: [{columnId: 'region', dataType: 'text'}],
});
```

- if a `columnId` is both a grid column and listed in `columns`, your override wins
- a column present in neither is unknown — evaluation fails with a clear error
- `QUERY`, `VAR` and the Calendar still use the live AdapTable instance
- functions that need a grid change stream, group rows or display values are not available

Comparison behaviour (case sensitivity, date `IN`) follows `expressionOptions` on the live grid.

- There are a number of "Gotchas" that you need to be aware of when evaluating Expressions via the API:
  - **Check `success`.** The API does not throw for bad Expressions or missing rows
  - **This is not Server Evaluation.** `evaluateAdaptableQLExternally` does not apply here
  - **One row target** for `evaluateStandardExpression`. Pass exactly one of `rowNode`, `primaryKeyValue` or `data`
  - **`$SCOPE` and Relative Change need extra context.** Provide `columnScope` / `dataChangedEvent` or evaluation fails
  - **`module` changes the function pool.** Default is every function. Pass Module name to see what that Module allows
  - **Aggregations are snapshots.** Re-evaluate if you need them to track the grid. Read `matches` when the Expression is a condition
  - **SSRM aggregations are partial.** Use `rowCount`
  - **Observables cannot use `data` / `dataSet`**, and cannot be subscribed to via the API
  - **Filter helpers fail the whole call** if any row errors

### FAQ

**Can I use the API to evaluate an Expression that lives in a Module?**
Yes, read the `Rule` / `Query` string from State and pass it to the matching `evaluate*` / `filter*` method

**Can I evaluate against rows that are not in the grid?**
Yes, pass `data` (one row) or `dataSet` (many rows). Remember that `valueGetter`s will not run

**Does this replace Grid Filter?**
No, Grid Filter still owns what the user sees. 
Use `filterRowNodes` when _your code_ needs the matching rows.
