%% docs/cpq-subsystem.mmd %% %% Novel Systems — inside the CPQ engine, as merged. %% %% WHY THERE IS A SECOND DIAGRAM. %% %% docs/architecture.mmd draws the whole platform and gives CPQ four boxes: %% the sandbox, the browser engine, the proxy route, the server engine. That is %% the right resolution for "how does a request get from a browser to Postgres" %% and the wrong resolution for the question a reviewer actually asked, which %% was how the pricing subsystem is laid out internally. A review in August 2026 %% listed "CPQ subsystem layout" as a missing artifact while standing on a page %% that served the request-flow render. It was not wrong. Zooming in on a %% diagram drawn for a different scale does not answer a different question. %% %% So this file is the zoom. It draws one call to calculateQuote() and nothing %% else: what is read, in what order the arithmetic happens, where each rounding %% mode applies, and which of the two engines each box belongs to. %% %% WHAT IT IS A DRAWING OF. %% %% backend/src/services/cpq-engine.ts and lib/cpq-engine.ts, both at the commit %% this was rendered from. Every constant on a node is declared in one of those %% two files and checked by one of the three parity scripts drawn at the bottom. %% Nothing here is aspirational and nothing here is a status — see the header of %% architecture.mmd and scripts/check-diagram-current.mjs for why that rule %% exists and what it cost to learn. %% %% THE STRUCTURAL CLAIM WORTH DRAWING. %% %% Two engines compute the same prices and cannot import from each other. One is %% bundled into the browser so the marketing calculator can respond without a %% network round trip; the other must never reach a browser, because it reads %% rate cards and a rate card is the customer's commercial position. The %% duplication is forced. What holds them together is the row of checks along the %% bottom — a fixture neither engine imports, held against both. See D-002 and %% D-010. %% %% Render: double-click render-diagram.command in the repository root. It renders %% every docs/*.mmd through the Chrome already installed on the machine. %% mermaid-cli is not used; it needs a Chromium that does not exist for %% arm64 Linux, which is what this repository is built on. flowchart TB %% ── Callers ──────────────────────────────────────────────────────────── subgraph callers["Two callers, two engines, one arithmetic"] direction LR sandbox["CPQ sandbox
components/cpq — marketing page
no network, no rate card"] apicall["POST /api/v1/quotes/calculate
Bearer JWT · ADMIN · SALES"] end browserEngine["lib/cpq-engine.ts
bundled to the browser
list prices only, published in the module"] serverEngine["services/cpq-engine.ts — calculateQuote()
never bundled to a browser"] %% ── What the server reads before it can price anything ───────────────── subgraph inputs["Resolved before pricing, inside withTenant()"] direction LR skus[("inventory_skus
costPerUnitCents · yieldFactor · type")] tiers[("labor_tiers
hourlyRateCents · multiplier")] end %% ── The gate ─────────────────────────────────────────────────────────── %% Drawn before the arithmetic because that is where it runs. A margin that %% cannot be honoured is refused before a single line item is costed. gate{{"Margin gate — before any price exists
appliedMargin > 0.95 → 400
appliedMargin < 0.50 without an approved override → 400, not a clamp
a quote that silently differs from the request is worse than an error"}} %% ── Per line item ────────────────────────────────────────────────────── subgraph line["calculateLineItem() — once per line"] direction TB validate["assertDimensions · assertSkuType
6" floor · 144" width · 180" drop · 500 per line
fabric is FABRIC, motor is MOTOR — types are checked, not assumed"] area["openingSquareFeet = W × D ÷ 144
the divisor is written out, not pre-computed"] subgraph components["Component costs — each rounded half-up to the cent"] direction TB fabric["fabric · sq ft
opening × yieldFactor × 1.15 × qty
waste applied on top of yield, not folded into it,
so a contractor can argue with either
"] hardware["hardware · each
yieldFactor × qty"] motor["motor · each (optional)
yieldFactor × qty"] trim["trim · linear in (optional)
width × yieldFactor × qty"] labour["labour · hours
0.75/unit + 0.05/sq ft + 0.5 if motorised
rate = hourlyRateCents × tier multiplier
the multiplier scales the rate, never the hours —
a costly market pays more, it does not take longer
"] end cost["costCents = Σ already-rounded components"] price["priceCents = roundCentsUp( cost ÷ (1 − margin) )
costs are measurements and round to the nearest;
this is a constraint and rounds away from the violation
"] end %% ── Cut list, deliberately off to the side ───────────────────────────── cutlist["calculateCutList( W, D )
tube = W − 1.25 · hembar = W − 1.00
fabric = (W − 1.50) × (D + 4.00 wrap)
derived from the finished opening, never from a costed intermediate"] cutnote["A work order needs cut sizes and has no business
asking for a price to get them. Coupling a shop-floor fact
to a commercial one is how the two come apart."] %% ── Totals and the assertion ─────────────────────────────────────────── totals["totals = Σ line items
the sum of rounded lines, not a re-rounding of the unrounded sum —
the lines on the printed proposal have to add up to the total under them
"] assertion{{"realisedMargin(total) < 0.50 → 400
should be unreachable
which is why it is here: it catches a future change to
the rounding direction, the line pricing or the totalling
"}} result["QuoteCalculation
lineItems · components · cutList · totals · assumptions
marginFloor and appliedMargin are both in the response"] %% ── Downstream ───────────────────────────────────────────────────────── persist["POST /api/v1/quotes → withTenant()
one transaction, one declared tenant"] approved(["quote.approved
after commit"]) downstream["webhook dispatcher · integration fan-out
drawn in full at /architecture.png"] %% ── Flows ────────────────────────────────────────────────────────────── sandbox --> browserEngine apicall --> serverEngine skus --> serverEngine tiers --> serverEngine serverEngine --> gate gate -->|"margin accepted"| validate validate --> area area --> fabric area --> labour validate --> hardware validate --> motor validate --> trim fabric --> cost hardware --> cost motor --> cost trim --> cost labour --> cost cost ==> price validate ==>|"from W and D alone"| cutlist cutlist -.- cutnote price --> totals cutlist --> result totals --> assertion assertion -->|"holds"| result result --> persist persist --> approved approved --> downstream %% ── What holds the two engines together ──────────────────────────────── subgraph parity["The two engines cannot import each other, so this is what pins them"] direction LR fixture[("fixtures/cut-list-parity.json
read by both checks, imported by neither engine
pins the values, not the constants")] cutcheck["check:cutlist-parity
both files declare the same four allowances"] valuecheck["check:cpq-parity + backend/test/cpq-engine.test.ts
each engine reproduces the fixture's numbers"] margincheck["check:margin-policy
both files declare MARGIN_FLOOR 0.50"] end paritynote["Identical constants still produce different cut sizes if one engine
transforms its input before subtracting from it. That is not hypothetical:
the browser engine snapped to the quarter inch and cut a 30.1" opening
to 29.00" where the API cut 28.85" — from constants that agreed.
So the fixture pins the answers."] browserEngine -.- cutcheck serverEngine -.- cutcheck browserEngine -.- valuecheck serverEngine -.- valuecheck browserEngine -.- margincheck serverEngine -.- margincheck cutcheck -.- fixture valuecheck -.- fixture fixture -.- paritynote %% ── Styling ──────────────────────────────────────────────────────────── classDef engine fill:#eef2ff,stroke:#4338ca,color:#312e81 classDef boundary fill:#fee2e2,stroke:#b91c1c,stroke-width:2px,color:#7f1d1d classDef store fill:#f0fdf4,stroke:#15803d,color:#14532d classDef annotation fill:#f8fafc,stroke:#94a3b8,stroke-dasharray:3 3,color:#475569 classDef arith fill:#fffbeb,stroke:#b45309,color:#78350f class browserEngine,serverEngine engine class gate,assertion,price,persist boundary class skus,tiers,fixture store class cutnote,paritynote annotation class fabric,hardware,motor,trim,labour,area,cost,totals,cutlist arith