PricingDeep Dive

How Equations Work

How Phasio runs three levels of pricing equations per order and how done() and variable() connect them.

Phasio pricing runs three types of equations per order. This page explains what each level does, how data flows between them, and the two mechanisms - done() and variable() - that make it work.


The three-level stack

Order placed

  ├─ Level 1: Process equation        ← runs once per line item
  │    done(unitPrice, duration?, reviewRequired?)

  ├─ Level 2: Post-process equations  ← runs once per selected post-process, per part
  │    done(unitPrice, duration?, reviewRequired?)
  │    sees processPricing from Level 1

  └─ Level 3: Order-level script      ← runs once for the whole order
       addLineItem({ name, price })
       sees all parts and the subtotal

Level 1 - Process pricing

Runs once per line item. Receives the part geometry, material, customer, and order details. Returns a unit price.

Available: specification, requisition, revision, material, workflow, customer, variable(), createBands(), round(), done()

Not available: other parts in the order, post-process prices

Level 2 - Post-process pricing

Runs once per post-process the customer selected, for each part. Has everything Level 1 has plus the Level 1 result.

Additional: processPricing.price, processPricing.variables

Level 2 done() supports the full signature: done(price, duration?, reviewRequired?)

Level 3 - Order-level

Runs once for the entire order after all Level 1 and Level 2 equations complete.

Available: parts (all line items), subtotal, addLineItem(), createBands(), round()

Not available: done(), variable(), material, individual part geometry directly (access via parts[i].specification)


Unit price vs total price

This is the most common mistake. Phasio equations return a unit price. The platform multiplies by quantity automatically.

// ✅ Correct — return the price for ONE part
done(50.00)
// If quantity = 10, the order total for this line is €500

// ❌ Wrong — multiplying by quantity yourself
done(50.00 * quantity)  // at qty=10 this charges €5,000

The only time you use quantity in pricing arithmetic is when you're amortising a fixed cost across the order:

// Setup fee shared across all parts in this order
const setupCost    = material.variables['setupCost']
const setupPerPart = round(setupCost / quantity, 2)  // ✅ this is correct

done() - completing the calculation

done() must be called at the end of every Level 1 and Level 2 equation. If it's not called, no price is returned.

done(price)                              // price only
done(price, durationHours)              // price + estimated production time
done(price, durationHours, true)        // price + duration + requires manual review
done(price, durationHours, condition)   // condition is a boolean expression

reviewRequired = true prevents the order from being auto-accepted. The operator sees a review flag in Phasio. Use it for:

  • Parts larger than your machines can handle
  • Very thin walls or open meshes
  • Unusual geometry or high removal ratios
  • Post-processes that need manual colour approval
// Level 1 — gate oversized parts
const reviewRequired = length > 300 || width > 300 || height > 300
done(variable('unitPrice', unitPrice), duration, reviewRequired)

// Level 2 — gate unapproved colours
const APPROVED_COLORS = ['Black', 'White', 'Grey', 'Red']
const needsReview = !APPROVED_COLORS.includes(specification.color)
done(variable('unitPrice', unitPrice), 0, needsReview)

variable() - the UI override mechanism

variable() exposes a value as an editable field in the Phasio quote UI. The operator can override the equation's calculated value before confirming a price.

// The equation calculates a default; the operator can change it in the UI
const machineTimeHours = variable('machineTime', round(calculatedHours, 2))
const unitPrice      = variable('unitPrice', round(baseCost, 2))

Mental model: think of variable() as an Excel cell. The equation provides a formula; variable() lets the operator type a new value into the cell for this specific order.

Rules:

  • Available at Level 1 and Level 2 only. Not at Level 3.
  • Always wrap the final price: variable('unitPrice', calculatedPrice)
  • Use it whenever an experienced operator would sometimes look at the calculated value and want to adjust it
  • Variable values from Level 1 are readable in Level 2 via processPricing.variables['name']

Level 1 → Level 2 communication

Level 2 can read two things from Level 1:

  • processPricing.price - the unit price Level 1 returned
  • processPricing.variables - all values that Level 1 exposed via variable()
// Level 1 — expose support volume for downstream use
const supportVolume = variable('supportVolume', round(calculatedSupport, 2))
done(unitPrice)

// Level 2 — read Level 1 price and supportVolume
const partPrice  = processPricing.price
const supportVolume  = processPricing.variables['supportVolume'] / 1000  // mm3 -> cm3
const supportRemovalRate = variable('Rate per cm3', 1.50)
const unitPrice    = variable('unitPrice', round(supportVolume * supportRemovalRate, 2))
done(unitPrice)

Warning

Be careful with connecting Level 1 & 2 equations via the variables(), as this creates a dependency between the two. Use this feature only if overwriting a Level 1 variable should reflect in the Level 2 equations. A useful example is support volume.


Design rules

One rule: put logic at the lowest level that can see what it needs.

  • Per-part geometry or material cost → Level 1
  • Post-process cost that depends on part geometry or print time → Level 2
  • Order-wide minimums, surcharges, shipping → Level 3

Never duplicate logic. If Level 1 calculated material cost, Level 2 shouldn't recalculate it. Pass it forward via variable().

Level 3 can only add or remove line items. It cannot change a part's unit price. If you need to discount a specific part, do it at Level 1 using customer.organisationName or requisition.quantity.

Last updated on

On this page