Formula Authoring Guide

This guide explains how to write rule formulas (Conditions + Actions) in the Flow Builder.

1. Overview

A rule has two logical parts: - Condition (When): A boolean expression that decides whether the rule fires. Use * to match always. - Action (Then): One or more assignment lines that compute and export variables downstream.

2. Assignments

Syntax: variable_name := expression Rules: - One assignment per line. - Left-hand side (LHS) must be an identifier: [a-zA-Z_][a-zA-Z0-9_]*. - Right-hand side (RHS) must not be empty and must start with a value (identifier, number, string, or (). - Separate statements using a newline (preferred) or ;.

3. Export Semantics

The variables that become available to downstream nodes follow this precedence order: 1. Explicit export line: export a, b, c (commas or spaces allowed) 2. Return line: return result_variable (optional sugar for a single-variable export) 3. Default: If neither export nor return is present, ONLY the last assignment’s variable is exported.

Rationale: Keeping the default narrow prevents accidentally leaking many temporaries and keeps dependency graphs simpler. Use export to intentionally expose multiple values.

Examples: ``` # Implicit export (no export/return) => ONLY last variable exported base := usage_minutes * unit_price price := base * discount_factor # only price exported

Explicit export

base := usage_minutes * unit_price raw := base * discount_factor final := raw * tax_rate export base, final # exports base + final

Return form (single variable)

subtotal := usage_minutes * unit_price adjusted := subtotal * 0.95 return adjusted # only ‘adjusted’ exported ```

4. Comments

Start a line with # to add a comment. Comments are ignored by validation and export logic. # Apply base rate price := usage_minutes * unit_price

5. String Literals

Use single or double quotes: err_not_found := "Not found" err_localized := 'No encontrado' Contents of strings are ignored for unknown-variable detection, so words inside quotes won’t trigger errors.

6. Variables, Scope & Propagation

  • A variable becomes available to downstream nodes only if exported (per semantics above).
  • Intermediate assignments in the same formula that are not exported remain local.
  • Validation builds a directed graph of nodes and propagates only exported variables forward along edges.
  • You cannot reliably read a variable before it is produced upstream; referencing it early triggers an unknown variable error.

7. Condition Syntax

Conditions can use: - Comparison operators: == != > < >= <= - Logical operators: AND OR NOT - Parentheses for grouping - Identifiers, numeric literals, and string literals Conditions must NOT contain assignments (:=).

Example: (usage_minutes > 100 AND customer_role == 'premium') OR region == "EU"

8. Errors & Messages

| Error Message | Meaning | Fix | |—————|———|—–| | Assignment not allowed in condition | Found := in condition section | Remove assignment or move to Action | | Only one assignment allowed per line | Multiple := in one line | Split into separate lines | | Right-hand side required | Missing RHS after := | Add an expression | | Right-hand side must start with value or ( not operator | RHS begins with operator like + or = | Prepend a value or wrap in parentheses | | Right-hand side must contain a value | Empty or invalid RHS | Add a literal / variable | | Unbalanced parentheses | Parenthesis mismatch | Balance ( and ) | | Unknown: var1, var2 | Referenced variables not yet known | Export earlier or fix name |

Unknown variables list aggregates unresolved identifiers (excluding keywords, numbers, locals, and string contents).

9. Best Practices

  • Use snake_case for variable names (net_price, base_minutes).
  • Keep formulas small; extract logic into earlier nodes when variables are reused.
  • Use an explicit export when exposing more than one variable for clarity.
  • Add comments to document non-obvious business logic.
  • Group related calculations together and export only what downstream truly needs.

10. Common Patterns

# Multi-step with explicit export
base := usage_minutes * unit_price
scaled := base * region_multiplier
final := scaled * tax_rate
export final


# Export multiple values
subtotal := usage_minutes * unit_price
vat := subtotal * vat_rate
export subtotal, vat


# Conditional guard (always true example)
*  # condition
# action
result := usage_minutes * unit_price

11. Collection Projection & Aggregations

You can traverse arrays and aggregate values directly in formulas.

Projection Syntax

items[*].price   # expands to project(items, "price")
order.items[*].price  # nested: expands to deep_project(order, "items", "price")

Rules:
- Always include [*] on the array segment. Shorthand without it is not supported.
- items (or the nested segment) should be an array of hashes/objects. Missing fields become nil.

Aggregation / List Functions (Collections – Phases 1 & 2)

All dual-mode (array or variadic where meaningful). Phase 2 adds transformation, conditional, and averaging helpers.

Function Description Notes
sum Sum of numeric / numeric-string values Non-numeric ignored; empty => 0
count Count non-nil values Works on array or variadic
min Minimum numeric value Returns nil if none
max Maximum numeric value Returns nil if none
first First element of array Wraps scalars automatically
last Last element of array  
take First N elements Negative / zero => []
map Transform each element with an expression referencing item Errors yield nil in position
filter Keep elements where predicate referencing item is truthy Invalid predicate => []
distinct Remove duplicate elements Ruby equality semantics
average / avg Mean of numeric / numeric-string values Empty => nil
sumif Sum items (or projection) where predicate true Projection optional
countif Count elements where predicate true  
trace Pass-through value (instrumentation hook) Currently no-op

Numeric coercion: numeric-looking strings (e.g. “5”, “3.5”) are converted; others skipped.

Nested projection: - Use dot notation with a single [*] at the array segment: order.items[*].price - Internally, nested patterns are evaluated via deep_project(root, "path", "field") for robust traversal. - Deeper chains are supported as repeated nested segments (e.g., a.b[*].c[*].d evaluates stepwise). Always place [*] on each array segment.

Examples

total := sum(items[*].price) range_min := min(items[*].price); range_max := max(items[*].price) head_two := take(items[*].price, 2) smallest := min(["5", 2, "3.5"]) # => 2 largest := max(["5", 2, "3.5"]) # => 5.0 first_price := first(items[*].price)

Edge Cases

sum([])           # => 0
min([])           # => nil
sum(["x", nil])  # => 0
count([nil, 1, 0]) # => 2 (nil excluded, 0 counts)
take(items[*].price, -2) # => []

Future (possible extensions): group_by, reduce, multi-level projection (items[*].addresses[*].city).

Providing Arrays via Metadata

You can define array attributes directly on a Calculation through the UI by selecting the new Array type. The value accepts either: - A JSON array (preferred): [ {"price": 2.5, "name": "A"}, {"price": 3.0, "name": "B"} ] - A comma or newline separated list of scalars: red, green, blue["red", "green", "blue"]

Parsing rules: - If the value starts with [ and ends with ], we attempt JSON.parse. If it yields an array, it’s used as-is (objects/hashes allowed). - Otherwise, the raw string is split on commas or newlines, trimmed, and empty segments removed.

Usage inside formulas: total := sum(items[*].price) # items is an array of hashes with a price key first_name := first(users[*].name) # users is an array of hashes/objects colors_count := count(colors) # colors is an array of strings average_price := average(project(items, "price"))

Edge considerations: - Malformed JSON falls back to the simple splitter (so [1, becomes ["[1"]). Use valid JSON for nested structures. - Empty string value becomes an empty array []. - Boolean / numeric coercion is not applied inside array elements; provide numbers directly if needed.

This enables richer default data sets for rules without requiring runtime parameters.

12. Migration Notes

Temporary experimentation (Sept 2025) briefly exported all assigned variables by default. We reverted to: only the last assignment is exported unless an explicit export or return is present. If you authored formulas during that window expecting implicit multi-variable export, add an export line listing each variable you need downstream.

13. FAQ

### No Implicit Default Variables There are no built-in default variables like price or metadata automatically injected. Variables come from: 1. Calculation metadata keys (each key is exposed as its own variable) 2. Runtime parameters you pass at evaluation time 3. Variables exported from upstream rules

Note on deprecated templates: When the FEATURE_TEMPLATES flag is enabled, template schema defaults are merged first (lowest priority) and can seed context. By default this feature is disabled and templates are soft-hidden; prefer metadata.

Validator & Collection Functions

The editor and validator recognize collection & aggregation functions (project, deep_project, sum, count, min, max, first, last, take, map, filter, distinct, average/avg, sumif, countif, trace). Using them will not generate unknown-variable warnings. If you see an unknown warning, check spelling and parentheses. ### Autocomplete & Function Hints When typing inside the Action (formula) editor, a suggestion dropdown appears as soon as you begin a token. It includes: - Inputs (calculation metadata & runtime inputs) - Previously exported variables upstream - Operators (contextual to condition vs formula) - Functions (collection helpers)

Advanced behavior: - Ranking favors exact matches, then prefix, then substring. Functions receive a mild boost for discoverability. - Function insertion places the caret inside parentheses. For pattern functions (e.g. MAP, FILTER, SUMIF) a skeleton is provided (MAP(array, item.price * 1.0)). Replace array and expression as needed. - Condition editor exposes a starter template when you begin typing if (inserts grouped logical pattern) to encourage explicit grouping with parentheses. - Footer hints in the dropdown remind you: Tab/Enter accept, Esc closes, arrow keys navigate. - Aliases are de‑duplicated: when you type a longer prefix (avera) only AVERAGE is shown (not AVG). Typing avg shows the alias entry. - Inside an export line, function suggestions are suppressed (only variables/inputs make sense there).

Function suggestions display a short description plus a usage signature, e.g.: SUM(array_or_values) # Adds numbers FILTER(array, predicate) # Keeps elements where predicate true PROJECT(array, "field") # Extract field from each element

Accept a suggestion with Tab or Enter. For functions, parentheses are inserted and the caret is placed inside. Example: typing ave and pressing Tab yields: AVERAGE() with the cursor ready for arguments. Aliases (e.g. AVG) appear as separate entries.

If the dropdown obstructs your view, press Escape to dismiss it temporarily.

Q: Do I need both return and export? A: No. If an export line exists it wins; return is optional sugar for a single variable. Without either, the last assignment wins.

Q: Can I export a variable I didn’t assign? A: Exporting an unknown variable will surface an unknown error.

Q: Are variables case-sensitive? A: Yes (totalTotal).


Last updated: 2025-09-11