Let's talk
engineering

A pricing engine where the business owns the formulas and the code owns nothing

A manufacturer quoting bespoke equipment prices each item by adding up the cost of its features, and each feature has its own calculation. Some are area-based, some are weight-based, some involve a conversion factor, and the rules differ by material grade and by which variant of the feature the customer chose.

There were 362 such formulas. None of them are in the source code, and none of them ever will be.

That is the design decision worth writing about, because the alternative — encoding pricing rules as application logic — is what most systems do, and it produces a specific and expensive failure: every commercial change becomes a development ticket, and the people who understand the pricing cannot see whether the software agrees with them.

The boundary

A formula computes the cost of one feature, not one product. That granularity matters. Quantity, discount and tax are applied later at the quotation level, so a formula only ever answers a single narrow question: what does this one feature cost, at these dimensions, in this material.

Formulas are stored as text, authored by the business, and evaluated at quote time against a context assembled from five categories of variable: the item’s dimensions, the material slots, bare rate codes, intermediate working values, and the result.

The material slot handling is the neatest part. Each slot yields a pair of variables — one being the rate multiplied by a factor, one being the bare rate. The same pair works for every kind of factor the business uses (thickness, weight per running foot, unit conversion, or none at all) because conversion factors are stored already computed rather than being derived at evaluation time. One evaluation path, four business concepts, no branching on factor type anywhere in the engine.

That is a good general move: push the variation into the data, so the code has one case instead of four. Every conditional you remove from an evaluator is a conditional that cannot disagree with the business’s understanding of the rule.

Evaluating text safely

The obvious way to evaluate a user-supplied arithmetic expression is the language’s eval. That is also arbitrary remote code execution, in a system where the expressions are authored through a web interface.

The evaluator here is pure — no database access, no eval — and works in two stages.

First, normalisation. Business users write multiplication the way a spreadsheet does: a multiplication symbol, a division symbol, or a bare letter x between two operands. These are rewritten into proper operators, and the rewrite is applied repeatedly until the text stops changing, because one pass misses adjacent cases. The rewrite has to be careful not to corrupt identifiers that legitimately contain the letter — a variable named with a trailing X must survive.

Second, parsing and validation. The expression is parsed into an abstract syntax tree using the language’s own parser in expression mode, and then the tree is walked and every node checked against a whitelist: numeric constants, names, the four arithmetic operators, unary sign, and parentheses. Anything else — a function call, an attribute access, a comparison, a string, an import — is rejected.

This is the security boundary, and it is worth being precise about why it is a good one. It is an allow-list over a parsed structure, not a deny-list over text. Deny-lists over text are defeated by encoding, whitespace, and constructs the author did not think of. An allow-list over an AST is defeated by nothing, because a node that is not in the list cannot be executed regardless of how it was written.

Two deliberate behaviours in the evaluator are worth noting because they are judgement calls rather than technical necessities:

Division by zero returns zero rather than raising. A missing rate should not blow up an entire quotation. This is arguably wrong in a financial system and right in this one, and the reason it is right is that the failure is surfaced elsewhere with a diagnostic rather than being hidden — which brings us to the most useful part of the design.

Every step rounds to two decimal places before being written back into the context. Rounding at each step rather than at the end means the software’s arithmetic matches what a person gets working through the same formula on paper, which is how the business verifies it. Mathematically less accurate; operationally correct, because a quotation that a person cannot reproduce by hand is a quotation nobody trusts.

Failure that explains itself

When a price cannot be computed, the engine does not return zero. It returns a structured reason: no costing template exists for this combination, the formula is inactive, the sub-feature does not match, a wildcard sub-feature is needed, evaluation genuinely produced zero, or — the interesting one — a lookup anomaly, meaning a compatible template exists but was not selected, which is a defect to report rather than a data gap to fill.

These are surfaced per feature so the interface can explain why a price is missing rather than showing a blank.

This matters more than it sounds. A configurable engine has a failure mode that a hard-coded one does not: a price can fail because of data rather than code, and the person who can fix it is a business user, not a developer. If the engine returns zero silently, every data gap becomes a support ticket that a developer has to diagnose. If it returns “no template for this material grade”, the person who maintains the templates fixes it themselves in a minute. The diagnostics are what make configurability actually reduce your workload rather than merely relocate it.

Template resolution, and specificity

With 362 formulas mapped across features, sub-features and material grades, choosing which formula applies is its own problem. Resolution ranks candidates by specificity: an exact material grade beats a grade-agnostic one; an exact sub-feature beats a wildcard; a wildcard beats a catch-all default; ties break on an explicit priority value.

This is the same shape as CSS specificity or routing precedence, and it has the same property: it works well and it is very hard to debug when it does not, because the rule that fired is invisible. Which is why the lookup-anomaly diagnostic exists — it is the engine reporting that its own resolution disagreed with what a compatible-template search would suggest.

Where governance beats cleverness

One formula in production referenced a dimension variable that did not exist. Not a typo the parser would catch — a well-formed expression referring to a name that was never defined, which meant that particular option would fail at calculation time, every time.

It was not found by a user complaining. It was found by dumping every formula and classifying every identifier in every one of them into a category: dimension, material slot, rate code, intermediate. Anything that fell into none of those categories was flagged. The census ended with zero unknowns.

That is the technique worth stealing. When the business owns a body of expressions, you own the census. Periodically enumerate every identifier in every formula and prove each one resolves. It is cheap, it runs in seconds, and it finds the class of defect that no amount of testing will, because you cannot write a test for a data error in a row you do not know exists.

Validation at save time was added for the same reason — expression syntax checked against the AST whitelist, and every slot token required to correspond to a defined material slot before the formula can be stored. Catching it at authoring time is better than catching it in a census, and a census is much better than catching it in a quotation.

The principle

Put the rules where the people who own them can see and change them, and put the guarantees in code: a safe evaluator, a specificity rule, save-time validation, structured failure reasons, and a census that proves every reference resolves.

The line to hold is that the software should never be the authority on what a price is, only on whether the calculation is well-formed. Once you cross that line — once a commercial rule lives in a conditional in a service class — you have made yourself the bottleneck on every pricing decision the business will ever make.

Working on something like this?

We build this kind of software, and we staff the teams that do.

Get in touch