Formula language
The small, sandboxed expression language for pricing rules a form cannot express: syntax, functions, variables, hook points and a cookbook.
This page is the reference for Quotometry’s formula language, which you use for the few pricing rules that a number in a form cannot express: “double the setup charge under five pieces”, “add a handling fee that scales with the bounding box”, “send anything over 400 mm tall to review”.
Formulas are written in the configuration editor, type-checked when you save, and compiled once when the configuration is published. A configuration that refers to a variable that does not exist cannot be published, so the mistake is caught at your desk, not in front of a buyer.
Before you begin
- A plan that includes formulas.
- A draft configuration to edit, and a part to try it on in the sample-quote panel.
The language
A subset of CEL and nothing more. There are no loops, no I/O, no clock and no randomness. An expression is at most 4 KiB, nests at most 32 levels deep, and runs on a budget of 10 000 operations; a formula that would not finish is refused rather than run.
- Arithmetic
+ - * / %, comparison< <= > >= == !=, logic&& || !and the ternarycond ? a : b. - Membership:
sel.material in ["petg", "asa"].inis an operator, not a function. - Numbers are exact, and equality on them is exact. Strings have no escape sequences.
- Division by zero,
NaNand infinity are errors, not values.
Functions
| Function | Returns |
|---|---|
min(a, b, …), max(a, b, …) | The smallest or largest argument. |
abs(x) | The absolute value. |
ceil(x), floor(x) | x rounded up or down to a whole number. |
round(x[, places]) | x rounded to a whole number, or to places decimal places. |
sqrt(x), ln(x), exp(x), pow(a, b) | Square root, natural logarithm, e to the power x, a to the power b. |
clamp(x, low, high) | x, limited to the range from low to high. |
interp(x, table.name) | Linear interpolation between the rows of a table you defined in the configuration. |
tier(x, table.name) | Step lookup: the value of the last row whose threshold x has reached. |
Variables
Every variable below is available to every line-level formula:
| Group | Variables |
|---|---|
| Quantity | qty |
| Geometry | part.volume_cm3 part.area_cm2 part.bbox.x part.bbox.y part.bbox.z (mm) part.bbox_volume_cm3 part.hull_volume_cm3 part.complexity part.mass_g |
| Estimate | est.material_g est.support_g est.machine_h est.labour_h est.batches est.parts_per_batch est.confidence |
| Cost | cost.material cost.machine cost.labour cost.subtotal |
| Price | price.before: the computed unit price, in the hooks that adjust it |
Each configuration adds its own:
sel.<option id>: what the buyer chose. A dropdown is text, a toggle is a boolean, and a count or a slider is a number, sosel.infill > 40andsel.material == "petg"type-check only where they make sense.const.<name>andtable.<name>: your own constants and lookup tables.
cost.* variables are costs, not prices. They exist so you can write a rule about your own economics; nothing
computed from them reaches a buyer except as part of the final price.
Where formulas hook in
| Hook | Type | What it does |
|---|---|---|
adjust_unit_price | number | Multiplies the computed unit price. 1 changes nothing. |
override_unit_price | number | Replaces the unit price outright. Use it sparingly: everything before it still runs. |
extra_lines.<id> | number | Adds a priced line with that id, for the whole line rather than per piece. The buyer sees it if your visibility settings allow. |
| Option charges | number | A charge computed instead of a fixed amount. |
| Review rules | boolean | true sends the line to a person at the shop. |
Cookbook
A setup surcharge that fades out with quantity
adjust_unit_price: qty < 5 ? 1.35 : qty < 20 ? 1.15 : 1.0
A handling fee that depends on the part’s footprint
extra_lines.handling: max(2.50, part.bbox.x * part.bbox.y / 10000 * 3.00)
Charge more for a material you dislike running
adjust_unit_price: sel.material in ["tpu", "pc"] ? 1.25 : 1.0
A discount table you can edit without touching the formula
Define a table volume_discount with the rows (10, 0.95), (50, 0.90) and (200, 0.85), then:
adjust_unit_price: tier(qty, table.volume_discount)
tier steps: 49 pieces get 0.95 and 50 get 0.90. Use interp when you want the value to slide smoothly
between rows instead.
Send tall, thin parts to a person
review rule: part.bbox.z > 400 && part.bbox.x < 60 && part.bbox.y < 60
Recover a fixed cost per batch rather than per part
An extra line is an amount for the whole line, so charge the batch cost once per batch:
extra_lines.batch_setup: est.batches * 8.00
Refuse to price a part automatically when the estimate is uncertain
review rule: est.confidence < 0.6
Test a formula before anyone sees it
The configuration editor’s sample-quote panel prices a real part against your draft on every change. On plans that include it, the backtest re-prices up to your last 500 quote lines with the draft and shows which ones move and by how much. Write the formula, watch the number, then publish.
Next steps
- Simulator and golden parts: check a draft against the prices you know are right.
- Drafts, versions and publishing: what happens when you publish.
- Errors:
formula_errorand the other line-state reasons.