Skip to content

Fox formula engine

Fox evaluates mathematical formulas over live and historical PV (Process Variable) data. It supports one-shot REST evaluation, real-time WebSocket subscriptions that re-evaluate as data changes, and a validation endpoint for UI-driven formula editing.

Expression syntax

Formulas reference PV data through tokens — variable names wrapped in curly braces:

{temperature} * 1.8 + 32

Each token must be bound to a PV name in the request's bindings field:

{
  "expression": "{temperature} * 1.8 + 32",
  "bindings": {
    "temperature": "ROOM:TEMP:READING"
  }
}

Token names (e.g. temperature) can contain letters, digits, and underscores. Every token in the expression must have a binding, and every binding must appear in the expression — mismatches produce a parse error.

PV names in bindings may optionally include ca:// or pva:// protocol prefixes — these are stripped internally when resolving data, so ca://ROOM:TEMP:READING and ROOM:TEMP:READING are equivalent. You do not need to include the prefix, but it won't cause an error if present.

Operators

Category Operators
Arithmetic +, -, *, /, // (floor div), % (modulo), ** (power)
Comparison ==, !=, <, <=, >, >=
Boolean and, or, not (also C-style: &&, ||, !)
Unary +, -, not / !
Ternary x if condition else y

C-style boolean operators (&&, ||, !) are normalized to Python equivalents before evaluation.

Available functions

All functions operate element-wise on arrays (timeseries data) and accept scalars.

Category Functions
Trigonometric sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh
Exponential / Logarithmic exp, log, log10, ln, sqrt, pow, abs
Rounding floor, ceil
Angle conversion radians, degrees
Reductions mean, sum, min, max, std, var, ptp, count_nonzero
Element-wise where(condition, x, y), clip(value, low, high)

Functions use NumPy implementations internally. You can access np.* and math.* constants directly (e.g., np.pi), but only the allowlisted functions above are callable — other attributes like math.factorial are not exposed.

Constants

Constant Value
pi 3.14159...
e 2.71828...
inf Positive infinity
nan Not a number
True Boolean true
False Boolean false
None Null / missing

Formula kinds

The kind field in a formula spec determines how inputs are treated and what result shape is returned:

Kind Input treatment Result
scalar Uses the latest sample from each PV Single value + timestamp
timeseries Uses all samples in the time window Array of timestamps + values
waveform Uses the latest waveform array from each PV Single array of values

Alignment and index modes

When a formula references multiple PVs, their time series may not share the same timestamps. Two parameters control how they are combined:

Alignment mode (align)

Determines how to produce a value at a timestamp where a PV has no exact sample.

Mode Behavior
last (default) Forward-fill: use the most recent sample at or before the target time
linear Linear interpolation between the two bracketing samples
nearest Use the closest sample by absolute time difference
none Only exact timestamp matches produce values; gaps are None

Index mode (index)

Determines which set of timestamps to evaluate the formula at.

Mode Behavior
union (default) All unique timestamps from all input PVs
intersection Only timestamps present in every input PV
grid Regular cadence at gridDtNs intervals (requires gridDtNs > 0)
first_input Use the first PV's timestamps as the output index

Error codes

Error HTTP Meaning
formula_parse_error 400 Syntax error, missing/unused bindings, nested braces
formula_unsafe 400 Disallowed operations (imports, lambdas, unsafe attributes)
formula_input_missing 422 Referenced PV not found or has no data
formula_type_error 422 Type mismatch during evaluation
formula_dimension_mismatch 422 Array shape incompatibility between inputs
formula_eval_timeout 504 Computation exceeded time limit
formula_circular_reference 400 Cyclic dependency detected

All errors return JSON with code and detail fields:

{
  "code": "formula_parse_error",
  "detail": "Unbound variable: {temp}"
}