Let's talk
engineering

Store the working, not just the answer, and features you have not thought of become cheap

A customer queries a line six weeks after the quotation went out. They want to know why a work table came to what it came to.

The sheet rate has moved twice since. A material was superseded. The estimator who built the quote is on site. Recomputing the line today produces a different number, and the difference is not explainable without knowing what the inputs were on the day.

If all you stored was the price, you have nothing. You can describe the method and you cannot reconstruct the number, and a bespoke price you cannot reconstruct is a price you end up conceding.

What gets stored

Every calculated line keeps the complete output of the pricing run as structured data alongside the figures: for each feature, which rule ran, the ordered steps, the expression at each step, the value each step produced, the full variable context the expressions were evaluated against, and the rates that were resolved for every material and every rate code.

The per-feature detail is stored again on the feature row itself, so a single feature’s working can be pulled without loading the whole line.

That is a lot of data per line, and it is worth being clear that this is a deliberate trade. It makes the rows fat. In exchange, the arithmetic behind every quotation the business has ever sent is reproducible from storage without depending on the current state of the rate tables, the current state of the formulas, or the current state of the catalogue.

For anything where a number was communicated to a third party and might be disputed, that is the correct trade almost every time. The storage is cheap. The argument is not.

What it paid for that nobody had planned

The reason this article exists is what happened afterwards.

Two features were later built that were not in anyone’s plan when the trace was stored, and both were built almost entirely on top of it.

The first is a bill of materials for a quotation — an aggregation of what material is actually consumed across every line, so the workshop can see what has to be bought before the job is accepted. Its source of truth is the stored traces. It does not re-run the pricing engine, it does not need the rate tables, and it works on quotations priced months ago under rates that no longer exist.

The second is a cutting list. That one needed something the trace does not carry, and the contrast is instructive.

The part the trace could not supply

A cutting list has to know the rectangular pieces that get cut from sheet — a top panel of this width by that height, a back panel, two side panels — and then nest them onto standard sheet sizes with as little waste as possible.

The trace records the arithmetic that produced a cost. It does not record geometry, because the pricing rule never needed geometry. A rule that computes the area of a top and multiplies by a rate knows the area and has no concept of the panel.

So the cutting list required a separate per-product configuration: a list of pieces, each with an expression for its width and height over the same dimension codes the pricing rules use, a quantity per unit, and a material grouping. The nesting itself is a simple guillotine first-fit-decreasing placement onto the two standard sheet sizes.

The lesson is not that the trace failed. It is that a trace preserves what the calculation needed, and features you build later may need something the calculation never needed. Storing the working buys you a great deal and it does not buy you everything, and the boundary is exactly the set of facts the original computation actually used.

The heuristic I am not comfortable with

The bill of materials has one genuinely weak part.

To report how much of a material is consumed, it needs a physical quantity — kilograms, square feet, running metres. The trace holds the whole variable context, so the quantity is in there somewhere, computed by some intermediate step. Nothing says which variable it is.

So the extraction scans the stored context for variable names that look like quantities — weight, area, length, volume and a handful of variants — in a preference order, weight first, then area, then the rest. It skips the variables that represent rates rather than amounts. If it finds nothing it returns nothing, and the column is omitted rather than being filled with a guess.

That last behaviour is the only thing that makes it defensible. It is still pattern matching on names that a business chose for its own reasons, and it works here because the naming happens to be consistent. A second business naming its intermediate values differently gets a bill of materials with no quantities and no explanation why.

The correct design is to let a rule declare which of its steps produces the physical quantity — one flag on one step, authored by the same person who authors the rule. Then the extraction is a lookup rather than a guess, and it works for any tenant.

That is not built. The honest position is that the quantity column in the bill of materials is best-effort and the system knows when it has failed, which is better than nothing and is not the same as correct.

Because the working is stored, the interface can show it. An estimator can open a feature and see the steps, the intermediate values and the resolved rates that produced its cost.

That capability was later extended in a small but telling way: an information control on each feature in the quoting flow that shows the pricing rule behind it — its code, its name and its steps — gated behind a company setting so a business that does not want its estimators reading formulas can switch it off.

Both of those are consequences of the same decision. A system that keeps its working can explain itself; a system that keeps only results can only assert. Every explanatory feature in the product became cheap the day the trace was persisted, and none of them would have been affordable otherwise.

The rule

When a computed number leaves your system and reaches a person outside it, store the inputs and the intermediate steps that produced it, not only the result. Assume the rates, the rules and the catalogue will all have changed by the time anyone asks.

And when you later find yourself inferring something from that stored data by matching on names, stop and add a declaration instead. The inference will work on the data you have and fail on the next tenant, quietly.

Working on something like this?

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

Get in touch