Skip to main content

Condition System

The Condition System is a powerful visual builder that creates JSON condition trees for filtering and routing messages. Conditions are stored as JSON in the database and evaluated at runtime in the V8 isolate.

How Conditions Work

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Condition Lifecycle │
├─────────────────────────────────────────────────────────────────┤
│ 1. ADMIN UI: Visual Condition Builder │
│ └─ User builds conditions with drag/drop interface │
│ │
│ 2. API SUBMISSION: POST /api/workflow-entities │
│ └─ Condition tree sent as JSON in request body │
│ │
│ 3. DATABASE: PostgreSQL JSON column │
│ └─ condition: Json? in WorkflowEntity table │
│ │
│ 4. CONSUMER: V8 Evaluation │
│ └─ evaluateCondition(conditionTree, message) │
│ │
│ 5. RESULT: Boolean (true/false) │
│ └─ Determines workflow branching │
└─────────────────────────────────────────────────────────────────┘

Database Schema

The condition is stored as a JSON column in the WorkflowEntity table:

model WorkflowEntity {
id String @id @default(uuid())
organizationId String
environmentId String
name String
condition Json? // Condition tree structure
script String? @db.Text
resultStatus String?
modelId String?
tfCondition String @default("Single Path")
logicField String?
prompt String? @db.Text
// ... other fields
}

Condition Tree Structure

TypeScript Interfaces

// Individual Rule
interface ConditionRule {
type: 'rule';
field: string; // e.g., "message.payload.event.type"
comparison: string; // e.g., "equals", "contains", "greater_than"
value: any; // The value to compare against
}

// Group of Conditions
interface ConditionGroup {
type: 'group';
operator: 'AND' | 'OR';
conditions: Condition[]; // Array of rules or nested groups
}

type Condition = ConditionRule | ConditionGroup;

JSON Examples

Simple Rule:

{
"type": "rule",
"field": "message.payload.metadata.league",
"comparison": "equals",
"value": "nfl"
}

AND Group with Two Rules:

{
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.payload.metadata.league",
"comparison": "equals",
"value": "nfl"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "touchdown"
}
]
}

Nested Logic (AND with nested OR):

{
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.payload.metadata.league",
"comparison": "equals",
"value": "nfl"
},
{
"type": "group",
"operator": "OR",
"conditions": [
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "touchdown"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "field_goal"
}
]
},
{
"type": "rule",
"field": "message.payload.game.quarter",
"comparison": "greater_than",
"value": 2
}
]
}

Available Comparison Operators

OperatorLabelApplicable TypesGenerated JavaScript
equalsEquals (==)string, number, booleanfield === value
not_equalsNot Equals (!=)string, number, booleanfield !== value
containsContainsstringString(field).includes(value)
not_containsDoes Not Containstring!String(field).includes(value)
greater_thanGreater Thannumberfield > value
less_thanLess Thannumberfield < value
greater_than_or_equalGreater or Equalnumberfield >= value
less_than_or_equalLess or Equalnumberfield <= value
starts_withStarts WithstringString(field).startsWith(value)
ends_withEnds WithstringString(field).endsWith(value)
existsExistsanyfield !== undefined
not_existsDoes Not Existanyfield === undefined
regexMatches Regexstringnew RegExp(value).test(String(field))

Default Field Paths

The condition builder provides these pre-defined fields:

Field PathLabelType
message.organizationIdOrganization IDstring
message.environmentIdEnvironment IDstring
message.typeMessage Typestring
message.payload.event.typeEvent Typestring
message.payload.event.play_typePlay Typestring
message.payload.metadata.event_typeMetadata Event Typestring
message.payload.metadata.leagueLeaguestring
message.payload.game.quarterQuarternumber
message.payload.game.statusGame Statusstring
customCustom Path...custom
Custom Paths

Select "Custom Path..." to enter any valid JavaScript path, such as:

  • message.user.roles[0]
  • message.payload.data.nested.value
  • env.MY_CUSTOM_VARIABLE

V8 Evaluation

At runtime, the consumer evaluates conditions using the evaluateCondition() function:

// In the V8 isolate context
const result = evaluateCondition(conditionTree, message);
// result is true or false

The evaluator recursively processes the condition tree:

  1. Rule: Compare message[field] against value using comparison
  2. AND Group: All conditions must be true
  3. OR Group: At least one condition must be true

Evaluation Flow

┌─────────────────────────────────────────────────────────────┐
│ evaluateCondition(tree, message) │
├─────────────────────────────────────────────────────────────┤
│ IF tree.type === 'rule': │
│ └─ Compare message[field] with value using comparison │
│ └─ Return boolean │
│ │
│ IF tree.type === 'group': │
│ └─ IF operator === 'AND': │
│ └─ Return ALL conditions === true │
│ └─ IF operator === 'OR': │
│ └─ Return ANY condition === true │
└─────────────────────────────────────────────────────────────┘

Visual Condition Builder

The admin UI provides a visual builder for creating conditions:

Features

  • Drag & Drop — Rearrange conditions and groups
  • Nested Logic — Unlimited nesting of AND/OR groups
  • Type-Aware — Comparisons filtered by field type
  • Live Preview — See generated JavaScript in real-time
  • Color-Coded — Visual hierarchy by nesting level

UI Components

ComponentPurpose
ConditionBuilderMain container, "Add Condition" button
ConditionGroupAND/OR group with nested conditions
ConditionRuleSingle field/comparison/value rule
ConditionPreviewGenerated JavaScript display

Condition CRUD Operations

Conditions are created, read, updated, and deleted as part of workflow entities via the /api/workflow-entities endpoint.

API Endpoints

OperationMethodEndpointPermission Required
Create Entity with ConditionPOST/api/workflow-entitiesentities:create
Read Entity with ConditionGET/api/workflow-entities/{id}entities:read
Update Entity ConditionPUT/api/workflow-entities/{id}entities:update
Delete EntityDELETE/api/workflow-entities/{id}entities:delete
List EntitiesGET/api/workflow-entitiesentities:read

Create Request

To create a workflow entity with a condition, send a POST request with the condition tree in the condition field:

POST /api/workflow-entities
Content-Type: application/json
Cookie: appSession=<session_cookie>

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "NFL Touchdown Filter",
"workflowEntityTypeId": "<event-type-uuid>",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.payload.metadata.league",
"comparison": "equals",
"value": "nfl"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "touchdown"
}
]
},
"tfCondition": "Single Path"
}

Response:

{
"id": "770e8400-e29b-41d4-a716-446655440002",
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "NFL Touchdown Filter",
"workflowEntityTypeId": "<event-type-uuid>",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [...]
},
"tfCondition": "Single Path",
"createdAt": "2025-12-15T10:00:00.000Z",
"updatedAt": "2025-12-15T10:00:00.000Z",
"workflowEntityType": {
"id": "<event-type-uuid>",
"name": "Event",
"showConditions": true,
"showScript": true,
"showLogic": true
},
"arguments": [],
"logic": []
}

Update Request

To update an existing entity's condition:

PUT /api/workflow-entities/{id}
Content-Type: application/json

{
"condition": {
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "touchdown"
}
}

Important:

  • The entire condition tree is replaced on update (not merged)
  • Set condition: null to remove the condition entirely
  • The entity will always execute if no condition is defined

Clear Condition

To remove a condition and make the entity always execute:

PUT /api/workflow-entities/{id}
Content-Type: application/json

{
"condition": null
}

Read Condition

To retrieve an entity with its condition:

GET /api/workflow-entities/{id}

Response includes:

  • condition: The JSON condition tree (or null if none)
  • workflowEntityType.showConditions: Whether this entity type supports conditions

To find entities by name (useful for finding specific conditions):

GET /api/workflow-entities?organizationId={id}&environmentId={id}&search=touchdown

Condition Service (Frontend)

The frontend uses WorkflowEntityService to manage entities with conditions:

import { workflowEntityService } from '@/app/services/workflowEntityService';

// Create entity with condition
const entity = await workflowEntityService.create({
organizationId: 'org-uuid',
environmentId: 'env-uuid',
name: 'NFL Touchdown Filter',
workflowEntityTypeId: 'event-type-uuid',
condition: {
type: 'group',
operator: 'AND',
conditions: [
{ type: 'rule', field: 'message.type', comparison: 'equals', value: 'touchdown' }
]
},
tfCondition: 'Single Path'
});

// Update condition
await workflowEntityService.update(entity.id, {
condition: {
type: 'rule',
field: 'message.type',
comparison: 'equals',
value: 'field_goal'
}
});

// Clear condition
await workflowEntityService.update(entity.id, {
condition: null
});

// Get entity with condition
const retrieved = await workflowEntityService.getById(entity.id);
console.log(retrieved.condition); // The condition tree

UI Condition Builder Integration

The ConditionBuilder React component provides the visual interface for creating conditions:

Component Usage

import { ConditionBuilder } from '@/app/components/conditions/ConditionBuilder';
import { Condition } from '@/app/types/condition';

function EventEntityForm() {
const [condition, setCondition] = useState<Condition | null>(null);

return (
<ConditionBuilder
value={condition}
onChange={setCondition}
availableFields={DEFAULT_FIELDS} // Optional: custom fields
/>
);
}

ConditionBuilder Props

PropTypeDescription
valueCondition | nullCurrent condition tree
onChange(condition: Condition | null) => voidCalled when condition changes
availableFieldsFieldDefinition[]Optional custom field definitions

ConditionBuilder Behavior

  1. No condition: Shows "Add Condition" button
  2. Root group: Always creates a group as root (required structure)
  3. Nested rules: Rules can only exist within groups
  4. Live preview: Shows generated JavaScript code
  5. Remove button: Clears condition back to null

Real-World Example: NFL Events

Here's an actual condition for filtering NFL scoring events:

{
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.payload.metadata.league",
"comparison": "equals",
"value": "nfl"
},
{
"type": "group",
"operator": "OR",
"conditions": [
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "touchdown"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "field_goal"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "safety"
}
]
}
]
}

Generated JavaScript:

(message.payload.metadata.league === "nfl") && 
((message.payload.event.play_type === "touchdown") ||
(message.payload.event.play_type === "field_goal") ||
(message.payload.event.play_type === "safety"))

JavaScript Code Generation

The condition system generates JavaScript code for the live preview. This code shows exactly what will be evaluated at runtime.

Code Generation Function

The generateConditionJs function converts condition trees to JavaScript:

import { generateConditionJs } from '@/app/types/condition';

const condition = {
type: 'group',
operator: 'AND',
conditions: [
{ type: 'rule', field: 'message.type', comparison: 'equals', value: 'touchdown' },
{ type: 'rule', field: 'message.score', comparison: 'greater_than', value: 6 }
]
};

const js = generateConditionJs(condition);
// Output: "((message.type === \"touchdown\") && (message.score > 6))"

Value Type Handling

The code generator intelligently handles value types:

Input ValueType DetectionOutput
"hello"string"hello"
42number42
"42"number (string)42
"true"booleantrue
"false"booleanfalse
truebooleantrue

Comparison Code Generation

ComparisonGenerated Code
equals(field === value)
not_equals(field !== value)
contains(String(field).includes(value))
not_contains(!String(field).includes(value))
greater_than(field > value)
less_than(field < value)
greater_than_or_equal(field >= value)
less_than_or_equal(field <= value)
starts_with(String(field).startsWith(value))
ends_with(String(field).endsWith(value))
exists(field !== undefined && field !== null)
not_exists(field === undefined || field === null)
regex(new RegExp(value).test(String(field)))

Common Condition Patterns

Pattern 1: Message Type Filter

Filter by message type (most common pattern):

{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "user.signup"
}

Pattern 2: Event Category with Type

Filter by event category AND specific type:

{
"type": "group",
"operator": "AND",
"conditions": [
{ "type": "rule", "field": "message.payload.metadata.league", "comparison": "equals", "value": "nfl" },
{ "type": "rule", "field": "message.payload.event.play_type", "comparison": "equals", "value": "touchdown" }
]
}

Pattern 3: Multiple Alternatives (OR)

Match any of multiple values:

{
"type": "group",
"operator": "OR",
"conditions": [
{ "type": "rule", "field": "message.type", "comparison": "equals", "value": "touchdown" },
{ "type": "rule", "field": "message.type", "comparison": "equals", "value": "field_goal" },
{ "type": "rule", "field": "message.type", "comparison": "equals", "value": "safety" }
]
}

Pattern 4: Range Check

Check if a numeric value is within a range:

{
"type": "group",
"operator": "AND",
"conditions": [
{ "type": "rule", "field": "message.payload.game.quarter", "comparison": "greater_than_or_equal", "value": 3 },
{ "type": "rule", "field": "message.payload.game.quarter", "comparison": "less_than_or_equal", "value": 4 }
]
}

Pattern 5: Existence Check

Check if a field exists before evaluating:

{
"type": "group",
"operator": "AND",
"conditions": [
{ "type": "rule", "field": "message.user.premium", "comparison": "exists", "value": null },
{ "type": "rule", "field": "message.user.premium", "comparison": "equals", "value": true }
]
}

Pattern 6: Regex Pattern Matching

Match complex string patterns:

{
"type": "rule",
"field": "message.payload.description",
"comparison": "regex",
"value": "^[A-Z]\\. [A-Z][a-z]+ (scores|throws)"
}

Pattern 7: Exclude Specific Values (NOT pattern)

Match everything except specific values:

{
"type": "group",
"operator": "AND",
"conditions": [
{ "type": "rule", "field": "message.type", "comparison": "not_equals", "value": "timeout" },
{ "type": "rule", "field": "message.type", "comparison": "not_equals", "value": "commercial_break" }
]
}

Best Practices

Condition Design

  1. Start Simple — Begin with single rules, add complexity as needed
  2. Use AND for Required — All conditions must pass
  3. Use OR for Alternatives — Any condition can pass
  4. Test with Sample Data — Verify logic before deployment

Performance

  1. Order Matters — Put most-likely-to-fail conditions first in AND groups
  2. Avoid Deep Nesting — Keep nesting to 3-4 levels max
  3. Use Specific Fields — More specific field paths are faster

Debugging

Use print() in scripts to debug condition evaluation:

// After condition evaluation
print('Condition result:', ___result___);
print('Message type:', message.type);
print('Event play_type:', message.payload?.event?.play_type);

Error Handling

Conditions handle missing fields gracefully:

  • Accessing undefined properties returns undefined
  • undefined === "value" evaluates to false
  • exists check can verify a field before comparing

Condition Validation

Root Must Be Group

The condition builder enforces that the root element must be a group:

// ✅ Valid - Root is a group
{
"type": "group",
"operator": "AND",
"conditions": [
{ "type": "rule", "field": "message.type", "comparison": "equals", "value": "x" }
]
}

// ❌ Invalid - Root is a rule (builder will wrap in group)
{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "x"
}

Empty Conditions

Empty condition arrays always evaluate to true:

{
"type": "group",
"operator": "AND",
"conditions": []
}
// Evaluates to: true (no conditions to fail)

Null Condition

A null condition means "always execute":

{
"condition": null
}
// Entity always executes


Complete Entity Creation Examples

These examples show complete JSON payloads for creating workflow entities with various condition types via the API.

Example: Simple Event Entity with Single Rule Condition

Create an Event that triggers only for user signup messages:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "User Signup Filter",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Filters for user signup events",
"tfCondition": "Single Path",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "user.signup"
}
]
}
}

Example: Event with True/False Branching

Create an Event that branches based on premium user status:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Check Premium Status",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Routes premium and basic users differently",
"tfCondition": "True/False",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.user.subscription",
"comparison": "equals",
"value": "premium"
}
]
}
}

Example: Event with Multi-Branch Routing

Create an Event that routes based on message category:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Route by Category",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Routes messages to different handlers based on category",
"tfCondition": "Multi",
"logicField": "category",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "content.published"
}
]
},
"script": "var category = message.payload.category || 'general';",
"logic": [
{ "name": "Sports", "value": "sports" },
{ "name": "News", "value": "news" },
{ "name": "Entertainment", "value": "entertainment" },
{ "name": "Other", "value": "*" }
]
}

Example: Event with Iterable Array Processing

Create an Event that processes each item in an array:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Process Order Items",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Processes each item in an order separately",
"tfCondition": "Iterable",
"logicField": "message.order.items",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "order.created"
}
]
}
}

Example: Complex Nested Condition for Sports Events

Create an Event with complex AND/OR logic for NFL scoring plays:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "NFL Scoring Events",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Filters for NFL touchdowns, field goals, and safeties in Q4",
"tfCondition": "Single Path",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.payload.metadata.league",
"comparison": "equals",
"value": "nfl"
},
{
"type": "group",
"operator": "OR",
"conditions": [
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "touchdown"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "field_goal"
},
{
"type": "rule",
"field": "message.payload.event.play_type",
"comparison": "equals",
"value": "safety"
}
]
},
{
"type": "rule",
"field": "message.payload.game.quarter",
"comparison": "equals",
"value": 4
}
]
}
}

Example: Event with Regex and String Conditions

Create an Event that uses regex pattern matching:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Email Domain Filter",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Filters for corporate email addresses",
"tfCondition": "Single Path",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.user.email",
"comparison": "exists",
"value": null
},
{
"type": "rule",
"field": "message.user.email",
"comparison": "regex",
"value": "^[a-zA-Z0-9._%+-]+@(company\\.com|corporate\\.org)$"
},
{
"type": "rule",
"field": "message.user.email",
"comparison": "not_contains",
"value": "test"
}
]
}
}

Example: Numeric Range with Multiple Conditions

Create an Event that filters by numeric ranges:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "High Value Orders",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Filters for high-value orders that need special handling",
"tfCondition": "True/False",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "order.placed"
},
{
"type": "rule",
"field": "message.order.total",
"comparison": "greater_than_or_equal",
"value": 1000
},
{
"type": "rule",
"field": "message.order.itemCount",
"comparison": "greater_than",
"value": 5
},
{
"type": "group",
"operator": "OR",
"conditions": [
{
"type": "rule",
"field": "message.customer.tier",
"comparison": "equals",
"value": "gold"
},
{
"type": "rule",
"field": "message.customer.tier",
"comparison": "equals",
"value": "platinum"
}
]
}
]
}
}

Example: Event with Arguments and Script

Create an Event that uses arguments for configurable thresholds:

{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"environmentId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Configurable Alert Threshold",
"workflowEntityTypeId": "<event-type-uuid>",
"description": "Alerts when metrics exceed configurable thresholds",
"tfCondition": "True/False",
"condition": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "rule",
"field": "message.type",
"comparison": "equals",
"value": "metric.report"
}
]
},
"script": "var meetsThreshold = message.payload.value > parseFloat(alertThreshold); ___result___ = meetsThreshold;",
"arguments": [
{
"argumentName": "alertThreshold",
"argumentValue": "100",
"argumentDescription": "Threshold value that triggers an alert"
}
]
}

Multi-Branch Logic Examples

Here are comprehensive examples of multi-branch logic patterns used in real workflows.

Pattern: Event Type Router

Route different event types to appropriate handlers:

{
"name": "Event Type Router",
"tfCondition": "Multi",
"logicField": "eventType",
"script": "var eventType = message.payload.event?.type || 'unknown';",
"logic": [
{ "name": "Touchdown Handler", "value": "touchdown" },
{ "name": "Field Goal Handler", "value": "field_goal" },
{ "name": "Interception Handler", "value": "interception" },
{ "name": "Fumble Handler", "value": "fumble" },
{ "name": "Default Handler", "value": "*" }
]
}

Pattern: Geographic Router

Route by geographic region:

{
"name": "Geographic Router",
"tfCondition": "Multi",
"logicField": "region",
"script": "var country = message.user.country; var region = ['US','CA','MX'].includes(country) ? 'americas' : ['GB','DE','FR','IT'].includes(country) ? 'europe' : ['JP','CN','KR','AU'].includes(country) ? 'apac' : 'other';",
"logic": [
{ "name": "Americas", "value": "americas" },
{ "name": "Europe", "value": "europe" },
{ "name": "Asia-Pacific", "value": "apac" },
{ "name": "Rest of World", "value": "other" }
]
}

Pattern: Sentiment-Based Router

Route based on computed sentiment:

{
"name": "Sentiment Router",
"tfCondition": "Multi",
"logicField": "sentiment",
"script": "var score = message.analysis?.sentimentScore || 0; var sentiment = score > 0.5 ? 'positive' : score < -0.5 ? 'negative' : 'neutral';",
"logic": [
{ "name": "Positive Response", "value": "positive" },
{ "name": "Neutral Response", "value": "neutral" },
{ "name": "Negative Response", "value": "negative" }
]
}

Pattern: Time-Based Router

Route based on time of day:

{
"name": "Time-Based Router",
"tfCondition": "Multi",
"logicField": "timeSlot",
"script": "var hour = new Date().getHours(); var timeSlot = hour >= 9 && hour < 17 ? 'business' : hour >= 17 && hour < 22 ? 'evening' : 'overnight';",
"logic": [
{ "name": "Business Hours", "value": "business" },
{ "name": "Evening Hours", "value": "evening" },
{ "name": "Overnight", "value": "overnight" }
]
}