Skip to main content

Math Expressions

The math expression language lets you compute derived values anywhere a number is expected — in math parameters on Definitions, in CID cells, in custom-view filter and accent expressions, and soon in KPI plugins.

The language is small on purpose. It's deterministic, has no side effects, and runs identically in two implementations:

  • TypeScript (@race/math-engine) — used by the API
  • Dart (apps/client/lib/src/math/) — used by the Flutter client

Both pass the same 113-test corpus. If something works in one, it works in the other.

Quick example

PaceToLeader = LapTimeMs - Session.BestLapTimeMs
WeightDistribution = (CornerWeightFL + CornerWeightFR) / Total * 100
TyreLifeRemaining = If(NewMileage > 0, 100 * (1 - Mileage / NewMileage), 0)

Syntax

The parser is a Pratt parser supporting:

  • Number literals: 42, 3.14, 1.6e3
  • String literals: "text" or 'text'
  • Boolean: true, false
  • Identifiers and member access: Session.BestLapTimeMs, Lap.parameters.BrakeTemp
  • Function calls: Min(a, b), Avg(samples)
  • Operators: + - * / %, comparison < <= > >= == !=, logical && ||, unary ! -
  • Parentheses for grouping

There are no statements, no assignments inside an expression, no loops, no closures. Everything is a single expression that evaluates to a value.

Scope traversal

Math expressions resolve identifiers against a hierarchical scope. When you write Session.BestLapTimeMs inside a Lap-scope expression, the evaluator walks from Lap → its parent Session and looks up BestLapTimeMs. Available scopes are documented per entity (Lap can see Run / RunSheet / Session / Car / Event, etc.).

Units

A typed parameter can declare a dimension and defaultUnit. When the math evaluator pulls a parameter value, it can attach the unit metadata so subsequent operations are unit-aware. In v0.1.0 this is opt-in — see the note under Units and Dimensions for how to switch it on.

Available functions

There are 35 built-in functions today (packages/math-engine/src/functions.ts). Highlights:

  • AggregationsMin, Max, Sum, Avg, Mean, Median, StdDev, Count, First, Last, BestX
  • NumericRound, Floor, Ceil, Abs, Sqrt, Pow, Log
  • LogicIf(cond, then, else), And, Or, Not, IfNull
  • Lookup / interpolationLookup, LookupClosest, Interpolate, LinFit, Filter
  • TextConcatenate, Substring, Length, ToUpper, ToLower, Contains, Split, TextJoin
  • ConversionToNumber, ToText, FormatNumber

See Math Expression Language for the full list with signatures and examples.

The KPI engine in packages/kpi-engine adds telemetry-specific intrinsics — Channel, Window, RollingAvg, GateAbove, GateBelow, EdgeCount, Delta, Where, etc. See KPI Expression Language for that catalogue.

Plugin-extensible

Math functions can be added by a plugin's math interface. The plugin declares the function names + arity; the host calls the plugin's call(name, args, ctx). The seeded rolling-avg-rpm plugin exposes a single rollingAvgRpm(samples, windowSec) function this way.

See Writing a Plugin.

What you can do today

  • Author math parameters in Admin → Definitions (/admin/definitions) — there's a live evaluator dialog that parses your expression and evaluates it against an editable sample-scope JSON.
  • Author CID cell expressions in Admin → Custom Views (/admin/custom-views) — the CID editor supports cell-level math expressions.
  • Author single-table filter and rowAccent expressions in the custom-view editor.

What's coming

  • KPI plugin extensions surfaced in autocomplete — KPI intrinsics already live alongside math functions in the engine layer; the editor's autocomplete still lists math functions only.
  • Scope-aware autocomplete — the Definition editor's math field autocomplete only shows local parameters today. Wiring it to show parent-scope parameters is in the queue.
  • Inline error markers — currently parse errors show in the evaluator dialog; pushing them inline into the editor is pending.