# Module Script Filtering

## MCP Filter — Scripting Reference

### Overview

The `search` method on `ModuleScripting` accepts MCP-style filters — a human-readable filter format that uses **display names** and natural operator names instead of raw MongoDB queries.

Internally, filters are transformed through the `McpFilterTransformer` → `QueryFilterProcessor` pipeline into MongoDB queries.

```python
# MCP filter (search) — uses display names + operators
Module('Customer').search({ "Status": "Active" })

# Raw MongoDB filter (findAll) — uses internal field paths
Module('Customer').findAll({ "$and": [{ "payload.f_abc123": "Active" }] })
```

Both approaches query the same data. Use `search` when you want readable, maintainable filters. Use `findAll` when you need raw MongoDB control.

***

### API

#### `search(filters, includes?, sort?, direction?, take?)`

Returns a list of `FETIASEntity` matching the MCP filter.

| Parameter   | Type               | Default      | Description                       |
| ----------- | ------------------ | ------------ | --------------------------------- |
| `filters`   | JObject            | *(required)* | MCP-style filter object           |
| `includes`  | string\[]          | `null`       | Fields to include (display names) |
| `sort`      | string             | `null`       | Field display name to sort by     |
| `direction` | QuerySortDirection | `Ascending`  | Sort direction                    |
| `take`      | int                | `0`          | Max results (0 = unlimited)       |

```python
results = Module('Customer').search(
    { "Status": "Active", "Score": { "min": 80 } },
    sort="Score",
    direction=QuerySortDirection.Descending,
    take=10
)
```

***

### Filter Format

#### Simple equality

Pass a plain value to match exactly:

```json
{ "Priority": "High" }
{ "Score": 95 }
{ "state": "Open" }
```

#### Operator objects

Pass an object with operator keys for advanced matching:

```json
{ "Title": { "contains": "report" } }
{ "Score": { "min": 80, "max": 100 } }
{ "DueDate": { "after": "2026-01-01" } }
```

#### Multiple fields (implicit AND)

Multiple fields in the same object are AND-ed together:

```json
{
  "Priority": "High",
  "Status": "Active",
  "Score": { "min": 80 }
}
```

#### Explicit AND/OR grouping

Use `and`/`or` arrays for logical grouping. Nesting is unlimited.

```json
{ "or": [{ "Status": "Active" }, { "Priority": "High" }] }
```

```json
{
  "and": [
    { "state": "Open" },
    { "or": [
      { "Priority": "High" },
      { "Score": { "min": 90 } }
    ]}
  ]
}
```

***

### Operator Reference

#### Text operators

| Operator     | Aliases                                       | Description                        |
| ------------ | --------------------------------------------- | ---------------------------------- |
| `is`         | `equal`, `equals`, `eq`                       | Exact match                        |
| `not`        | `isNot`, `ne`, `neq`, `notEqual`, `notEquals` | Not equal                          |
| `contains`   |                                               | Substring match (case-insensitive) |
| `startsWith` |                                               | Starts with value                  |
| `endsWith`   |                                               | Ends with value                    |
| `excludes`   | `notContains`, `doesNotContain`               | Does not contain substring         |

```json
{ "Title": { "contains": "report" } }
{ "Name": { "startsWith": "A" } }
{ "Status": { "not": "Cancelled" } }
{ "Notes": { "excludes": "draft" } }
```

#### Number operators

| Operator  | Aliases                                                                   | Description                           |
| --------- | ------------------------------------------------------------------------- | ------------------------------------- |
| `min`     | `gte`, `minimum`, `atLeast`, `from`, `greaterThanEqual`, `greaterOrEqual` | Greater than or equal (>=)            |
| `above`   | `gt`, `moreThan`, `greaterThan`                                           | Greater than (>)                      |
| `max`     | `lte`, `maximum`, `atMost`, `to`, `lessThanEqual`, `lessOrEqual`          | Less than or equal (<=)               |
| `below`   | `lt`, `under`, `lessThan`                                                 | Less than (<)                         |
| `between` | `range`                                                                   | Range — pass `{ "min": N, "max": N }` |

```json
{ "Score": { "min": 80 } }
{ "Amount": { "below": 1000 } }
{ "Rating": { "between": { "min": 1, "max": 5 } } }
```

Combine `min` and `max` on the same field for a range:

```json
{ "Amount": { "min": 1000, "max": 5000 } }
```

#### Date operators

| Operator   | Aliases                   | Description                          |
| ---------- | ------------------------- | ------------------------------------ |
| `after`    | `since`, `from_date`      | On or after date (>=)                |
| `before`   | `until`, `to_date`        | On or before date (<=)               |
| `within`   | `around`                  | Current time window (e.g. this week) |
| `upcoming` | `in`, `next`, `coming`    | Next N time units from now           |
| `past`     | `ago`, `previous`, `last` | Previous N time units from now       |

Date range with ISO 8601 strings:

```json
{ "DueDate": { "after": "2026-01-01", "before": "2026-12-31" } }
```

Relative date operators use `{ "duration": "day"|"week"|"month", "offset": N }`:

```json
{ "DueDate": { "upcoming": { "duration": "day", "offset": 7 } } }
{ "CreatedDate": { "past": { "duration": "month", "offset": 3 } } }
{ "UpdatedDate": { "within": { "duration": "week", "offset": 1 } } }
```

#### User operators

Pass `"me"` as a simple string value on any user field (standard or custom):

```json
{ "assignee": "me" }
{ "createdBy": "me" }
{ "updatedBy": "me" }
{ "Reviewer": "me" }
```

The operator object form `{ "me": true }` also works:

```json
{ "Reviewer": { "me": true } }
```

| Syntax           | Aliases                 | Description                         |
| ---------------- | ----------------------- | ----------------------------------- |
| `"me"` (string)  |                         | Shorthand — works on any user field |
| `{ "me": true }` | `myself`, `currentUser` | Operator object form                |

#### Null checks

| Operator | Aliases                                                                          | Description            |
| -------- | -------------------------------------------------------------------------------- | ---------------------- |
| `empty`  | `none`, `null`, `isNull`, `isEmpty`, `blank`, `isBlank`, `isNone`                | Field is null or empty |
| `exists` | `any`, `notNull`, `isNotNull`, `notEmpty`, `isNotEmpty`, `hasValue`, `isNotNone` | Field has a value      |

```json
{ "Notes": { "empty": true } }
{ "Assignee": { "exists": true } }
```

#### Boolean (YesNo) operators

| Operator | Aliases            | Description    |
| -------- | ------------------ | -------------- |
| `yes`    | `true`, `isTrue`   | Field is true  |
| `no`     | `false`, `isFalse` | Field is false |

```json
{ "Active": { "yes": true } }
{ "Archived": { "no": true } }
```

#### Table/List field operators

Filter rows within a Table or List field using the `rows` array. Each element is a sub-field condition using the same syntax as top-level filters.

```json
{
  "Lines": {
    "rows": [
      { "Item": { "contains": "Widget" } },
      { "Quantity": { "min": 5 } }
    ]
  }
}
```

All sub-field conditions are AND-ed — matching rows must satisfy every condition.

| Syntax                                      | Description                                  |
| ------------------------------------------- | -------------------------------------------- |
| `{ "rows": [...] }`                         | Match rows where all sub-conditions are true |
| `{ "rows": [...], "operator": "excludes" }` | Exclude entries where any row matches        |
| `{ "empty": true }`                         | Table/list is empty                          |
| `{ "exists": true }`                        | Table/list has at least one row              |

***

### Standard Fields

Standard fields can be used alongside custom module fields. They are automatically resolved to internal field IDs.

| Field name      | Maps to        | Description                              |
| --------------- | -------------- | ---------------------------------------- |
| `state`         | state          | Entry state                              |
| `documentId`    | documentId     | Document ID                              |
| `createdBy`     | createdBy      | Creator username (supports `"me"`)       |
| `assignee`      | assignTo       | Assigned user (supports `"me"`)          |
| `updatedBy`     | lastChangeBy   | Last modifier username (supports `"me"`) |
| `createdDate`   | createdDate    | Creation date                            |
| `updatedDate`   | lastChangeDate | Last modification date                   |
| `due`           | due            | Due date                                 |
| `date`          | date           | Entry date                               |
| `search`        | documentId     | Text search on document ID               |
| `createdAfter`  | createdDate    | Alias for `createdDate` >=               |
| `createdBefore` | createdDate    | Alias for `createdDate` <=               |
| `updatedAfter`  | lastChangeDate | Alias for `updatedDate` >=               |
| `updatedBefore` | lastChangeDate | Alias for `updatedDate` <=               |

```json
{ "state": "Open", "assignee": "me" }
{ "createdBy": "me" }
{ "createdDate": { "after": "2026-01-01" } }
```

***

### Examples

#### 1. Simple equality

```python
Module('Customer').search({ "Status": "Active" })
```

#### 2. Text search with number range

```python
Module('PurchaseOrder').search({
    "Vendor": { "contains": "Acme" },
    "Amount": { "min": 1000, "max": 5000 }
})
```

#### 3. Date range on module field

```python
Module('Leave').search({
    "Start Date": { "after": "2026-03-01", "before": "2026-03-31" }
})
```

#### 4. Current user's tasks due soon

```python
Module('Task').search({
    "assignee": "me",
    "due": { "upcoming": { "duration": "day", "offset": 7 } }
})
```

#### 5. OR across states

```python
Module('Leave').search({
    "or": [
        { "state": "Approved" },
        { "state": "Submitted" }
    ]
})
```

#### 6. Nested AND/OR

```python
Module('PurchaseOrder').search({
    "and": [
        { "state": "Open" },
        { "or": [
            { "Priority": "High" },
            { "Amount": { "min": 10000 } }
        ]}
    ]
})
```

#### 7. Table row matching

```python
Module('PurchaseOrder').search({
    "Lines": {
        "rows": [
            { "Item": { "contains": "Widget" } },
            { "Quantity": { "min": 5 } }
        ]
    }
})
```

#### 8. Exclude rows + null check

```python
Module('PurchaseOrder').search({
    "Lines": {
        "rows": [{ "Quantity": { "min": 10 } }],
        "operator": "excludes"
    },
    "Approval Note": { "empty": true }
})
```

#### 9. Boolean + text exclusion

```python
Module('PurchaseOrder').search({
    "Urgent": { "yes": true },
    "Category": { "not": "Internal" }
})
```

#### 10. Entries updated in the past 3 months

```python
Module('Customer').search({
    "updatedDate": { "past": { "duration": "month", "offset": 3 } }
})
```

#### 11. With sort, direction, and limit

```python
results = Module('Customer').search(
    { "Status": "Active" },
    sort="Score",
    direction=QuerySortDirection.Descending,
    take=20
)
```

***

### Notes

* All operator names are **case-insensitive**. Use whichever alias feels most natural.
* Custom fields are referenced by **display name** (e.g. `"Priority"`, `"Start Date"`).
* Standard fields (e.g. `"state"`, `"assignee"`) are resolved automatically.
* The `search` method always returns `List<FETIASEntity>`.
* Use `findAll` / `find` / `count` / `iterate` for raw MongoDB filters when needed.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://inistate.gitbook.io/home/advanced/module-script-filtering.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
