A number you can replay

Drafts change, published versions don't, and a quote freezes every price and rate it used. So any number can be traced back to what produced it, and a number sent to someone never changes.

Project
STP: Vehicle Cost Platform, part 5 of 10
Published
October 5, 2026
Reading time
3 min read

When a margin looks wrong in a review, the first question is "wrong compared to what?". Answering it needs the exact structure, prices and rates the number came from, and most cost tools only keep the latest of each. STP is built so that every number can be traced back to what produced it, and so that a number sent to someone never changes afterwards.

Three kinds of BOM state

  • A working BOM is a draft. It's autosaved as one document, and every save increments its revision.
  • A published BOM is a frozen copy of a draft at one revision, under a version label (v1, v2). Its structure never changes, and its status can only go from RELEASED to OBSOLETE. The draft stays editable, to prepare the next version.
  • A quote is a computation of a published BOM, frozen along with everything it used.
Working BOMa draft, autosavedstructureeditablepricesnoneratesnonechanges+1 revision per savePublished BOMa frozen copy of one revisionstructurefrozenpricesnoneratesnonechangesRELEASED → OBSOLETE onlyQuotea computation, frozenstructureits version'spricesfrozen, with revisionsratesfrozen, with revisionschangesneverrev 1rev 2rev 3rev 4save on rev 3409: rev 4 existsv1, obsoleterev 2quote of v1, frozenv2, releasedrev 4quote of v2, frozenpublishcompute + freeze
Each kind of state, and what it holds. Only a quote carries prices and rates, and nothing changes it once it exists.

A save that carries an old revision is refused with a 409. That's optimistic concurrency control: nobody waits for a lock to save, but nobody overwrites a version they haven't seen.

Publishing copies the whole structure into relational rows, so it used to be the slowest operation in STP: 8.6 seconds for 20,000 parts, until the inserts were batched and it came down to 2.2.

Why a published BOM holds no prices

Prices move constantly: the controller fetches supplier updates every 5 seconds, and exchange rates change every few seconds to minutes. Structure changes when engineering decides. If a published BOM carried prices, every price update would mean either a new version or a version that quietly stopped matching its own label. So a published BOM holds structure only, and prices are resolved from the current offers each time it's computed.

A quote is a photograph

Creating a quote copies, for every part active in the chosen configuration, its supplier, offer revision, price, currency, quantity and validity, along with every exchange rate and its revision, all in one transaction. A quote is valid until the earliest expiry among its active offers, and an option that isn't selected doesn't shorten it.

Live offers and ratesQuotescrew, FR supplier2.15 EURrev 6screw, FR supplier2.05 EURrev 4screw, US supplier2.30 USDrev 9screw, US supplier2.45 USDrev 6EUR/USD rate1.15rev 13EUR/USD rate1.09rev 9copiedquote createdtime →
Offers and rates keep moving after the quote is made. The quote keeps the values and revisions it used, so it says the same thing later.

After that, nothing touches it. A new price or a new rate applies to new computations and new quotes, never to an existing one. A quote can't even be created while one active part has no valid price, because an incomplete quote would look final while being wrong.

Every line explains itself

Here is one entry of a quote's snapshot, the screw from the reference fixture, with its price in its supplier's currency, followed by the rates the quote used:

{
  "component_id": "…",
  "supplier_code": "SUPPLIER-01",
  "revision": 1,
  "price": "2",
  "currency": "EUR",
  "weight": "0.1",
  "carbon": "0.03",
  "quantity": "6",
  "validated_until": "…"
}
"exchange_rates": { "EUR/USD": { "rate": "1.08", "revision": 8 } }

A live computation returns the same evidence for each part, plus the converted amount and, when a volume is set, the price tier reached. The UI shows the rates under every result ("EUR/USD: 1.08, revision 8").

Scenarios follow the same principle. A "what if" never modifies the BOM it starts from, and a scenario worth keeping becomes a new draft while the original version stays as it was.

One editor at a time

Only one person edits a given BOM at a time. The edit lock lives in PostgreSQL, lasts two minutes and is renewed every 30 seconds, so it survives a backend restart and every backend instance sees the same holder. Merging concurrent edits to a graph is a project of its own, and a lock with a visible holder and an admin takeover covered how BOMs are actually edited, so we kept the limit and listed it as one.