Write safe formulas and handle money
On this page
Use ordinary fixed, per-unit and percentage rules when they describe the price clearly. A formula is useful when arithmetic genuinely combines inputs, such as a base charge plus several quantities. It is not executable JavaScript.
Begin with the units
For USD, $80 is 8000 minor units and $2.10 is 210. For JPY, ¥80 is 80; for KWD, 80 dinars is 80000. The estimator declares the exponent explicitly. The engine does not look it up or format a currency string.
Changing USD to BRL changes the currency label, not the numeric rates or an exchange rate. Changing an exponent without converting amounts can change prices by a factor of ten or one hundred.
A formula result is also in minor units. If a currency input is entered as major units, include the appropriate conversion in the formula. Do not multiply every numeric answer by 100: bedrooms and distance are quantities, not money.
Build a numeric expression
For a simplified moving calculation:
base_price + bedrooms * bedroom_rate + distance * mileage_rateDeclare the constants as decimal strings:
{
"base_price": "18000",
"bedroom_rate": "8000",
"mileage_rate": "210"
}With bedrooms = 3 and distance = 22, the formula returns 46620, or $466.20. It does not include piano or weekend charges unless separate rules add them.
Replace the equivalent base/bedroom/distance rules if you use this combined formula. Leaving those rules in place would charge the same work twice.
What the parser accepts
The advanced formula editor accepts numeric literals, identifiers, parentheses, unary minus and + - * /. Multiplication and division take precedence; equal-precedence operations associate left to right.
10 + 2 * 3 → 16
(10 + 2) * 3 → 36
20 / 2 / 2 → 5Functions, property access, assignments and JavaScript constructs are rejected. Math.max(...), customer.price and fetch(...) are not formulas. Use output minimum/maximum settings for bounds and conditions for branching.
parseFormula from the Engine creates the same portable arithmetic AST that the builder stores. For example:
import { parseFormula } from "@openquotestack/engine";
const expression = parseFormula("bedrooms * 8000");{
"op": "*",
"left": { "variable": "bedrooms" },
"right": { "value": "8000" }
}Handle missing variables explicitly
Formula variables must be numeric fields or declared constants. A constant cannot shadow a field key. Unknown variables fail validation; a known optional variable that is absent during calculation also fails.
If a question is conditional, guard the formula rule with the same availability condition. When an optional quantity should mean zero, give it a valid authored default or use a per-unit rule, whose missing-optional behavior already skips the charge.
Division by zero and unsafe totals fail. Do not catch an error and show a partial quote as a successful price.
Understand rounding boundaries
The engine uses exact rational intermediates and rounds each rule once, half away from zero. Two separate rules for 0.5 minor units each become 1 + 1 = 2. One formula adding the two halves becomes 1. Splitting or combining rules can therefore change the final penny.
This is a reason to preserve rule boundaries and immutable revisions. It is also a reason to test fractional quantities and discounts after a refactor, even if the formulas appear algebraically equivalent.
Format the returned result
Presentation belongs outside the Engine. For a returned result, use its declared exponent rather than assuming cents:
const formatted = new Intl.NumberFormat("pt-BR", {
style: "currency",
currency: result.currency,
}).format(result.totalMinor / 10 ** result.minorUnits);This formats already calculated monetary data. It should not be used to calculate a new total by adding formatted strings or binary decimal prices.
See the Engine API, Schema reference and pricing contract for the authoritative validation and arithmetic rules.