Pricing

Reference - Variables, Functions & Types

Complete technical lookup for Phasio pricing equations: built-in functions, the specification, requisition, revision and processPricing objects, the level cheat sheet, and a debugging guide.

Complete technical lookup for Phasio pricing equations. No explanations - just the facts.

Built-in functions

done(price, duration?, reviewRequired?)

Ends a Level 1 or Level 2 equation. Not available at Level 3.

ParameterTypeRequiredDescription
pricenumberYesUnit price returned to Phasio
durationnumberNoEstimated production time in hours
reviewRequiredbooleanNotrue blocks auto-approval and flags for manual review
done(49.90)                        // price only
done(49.90, 2.5)                   // price + duration
done(49.90, 2.5, true)             // price + duration + review flag
done(49.90, printTimeHours, width > 300 || height > 300)

// Object style — equivalent
done({ price: calculatedPrice, reviewRequired: true })
done({ price: calculatedPrice, duration: printTimeHours, reviewRequired: condition })

Automatic triggering: the gate also fires if the computed price is 0, negative, or NaN. Customers never see a broken price.

Review reason flags. done() has no reason string parameter. To surface why a part was flagged, use named output variables with 0/1 values - operators see these in the override panel alongside the part:

const _reasonFillRatio     = variable('REVIEW: fill ratio too low',       fillRatio < 0.15 ? 1 : 0)
const _reasonSurfaceComplex = variable('REVIEW: surface complexity high',  swvRatio  < 0.30 ? 1 : 0)

done(estimatedPrice, printTime, fillRatio < 0.15 || swvRatio < 0.30)

A value of 1 means the flag is active. Name the variable to read like a sentence.

addLineItem({ name, price })

Adds a charge or discount to the order. Level 3 only. Positive = charge, negative = discount.

addLineItem({ name: 'Express shipping', price: 25.00 })
addLineItem({ name: 'Volume discount', price: -15.00 })
addLineItem({ name: 'Minimum order charge', price: round(200 - subtotal, 2) })

variable(name, defaultValue)

Exposes a field in the operator quote UI for per-order override. Level 1 and Level 2 only. Always operator-facing - never visible to customers. Returns the operator-overridden value if set, otherwise defaultValue.

const printTime = variable('printTime', round(calculatedHours, 2))
const unitPrice = variable('unitPrice', round(baseCost, 2))  // always wrap final price

Variable values from Level 1 are accessible in Level 2 via processPricing.variables['name'].

Using variables as mode-switch flags. Because variable() lets operators set any value per-order, it doubles as an internal configuration toggle. Set a sensible default; the operator overrides it when needed:

// Default 1 = Standard 45° tilt. Operator sets to 0 for Direct Build (flat on bed).
const buildMode = variable('buildMode', 1)  // 1 = standard, 0 = direct

const timePerLayer = buildMode === 1
  ? variable('timePerLayer', round(avgCrossSectionMm2 / scanSpeed, 2))   // 45° tilt
  : variable('timePerLayer', round(length * width / scanSpeed, 2))        // flat on bed

This is the recommended way to run two process variants under one equation - no need to duplicate materials.

Using variables as output-only info flags - e.g. review reasons, see the done() section above.

createBands(bands, base?)

Creates a tiered lookup function. Returns the value mapped to the highest threshold ≤ input. base is returned when input is below all thresholds (default 0).

const getDiscount = createBands({ 10: 0.95, 50: 0.90, 100: 0.85 }, 1.0)
getDiscount(1)    // 1.0   (below all thresholds → base)
getDiscount(10)   // 0.95
getDiscount(75)   // 0.90  (highest threshold ≤ 75 is 50)
getDiscount(200)  // 0.85

round(value, precision?)

Rounds to precision decimal places (default 0).

round(12.345, 2)   // 12.35
round(12.345)      // 12

specification object (Level 1 and Level 2)

FieldTypeUnitDescription
specification.volumenumbermm³Actual material volume
specification.areanumbermm²Total surface area
specification.lengthnumbermmBounding box length
specification.widthnumbermmBounding box width
specification.heightnumbermmBounding box height
specification.convexHullVolumenumbermm³Tightest convex envelope
specification.shrinkWrapVolumenumbermm³Tight wrap following overhangs - use for powder bed
specification.minBoundingBoxVolumenumbermm³Minimum bounding box volume - use for CNC raw stock
specification.infillobject (nullable)-Infill setting. Access as infill?.value ?? 0.20
specification.precisionobject (nullable)-Layer height setting. Access as precision?.value ?? 0.20

Bounding box axis convention

width is always the largest dimension, height the medium, length the smallest. Phasio computes the minimum bounding box from the uploaded geometry - part rotation in the viewer has no effect on these values. The orientation tool and Fixed Orientation Constraint affect nesting and production planning, not pricing geometry.

Destructuring shorthand used at the top of most equations:

const { volume, area, length, width, height, convexHullVolume, shrinkWrapVolume, infill, precision } = specification

requisition object (Level 1 and Level 2)

FieldTypeDescription
requisition.quantitynumberNumber of parts ordered
requisition.leadTime.namestringLead time tier name, e.g. 'Economy', 'Standard', 'Express', 'Overnight'

revision object (Level 1 and Level 2)

FieldTypeDescription
revision.watertightnumber1 = closed mesh, 0 = open mesh. Flag open mesh for review.
revision.minimumWallThicknessnumberThinnest wall in mm. Pro / Factory Floor plans only. Use to gate fragile geometry.

material object (Level 1 and Level 2)

FieldTypeDescription
material.variables['key']number or stringCustom variable set on the material in Phasio. Must exist on the material before saving the equation. Always use bracket notation.

Materials cannot be shared across processes

Each process has its own material list. If you need two processes that use the same physical material (e.g. Standard SLA and Direct Build SLA), create the material entries separately per process. Use material.variables and equation logic to handle any pricing differences - the equation itself is where process variants diverge, not the material configuration.

customer object (Level 1 and Level 2)

FieldTypeDescription
customer.organisationNamestring (nullable)Organisation name. Access as customer.organisationName ?? ''

processPricing object (Level 2 only)

FieldTypeDescription
processPricing.pricenumberUnit price returned by the Level 1 equation for this part
processPricing.variablesobjectAll variable() values set in Level 1, keyed by name
const partPrice = processPricing.price
const printTime = processPricing.variables['printTime']  // only if Level 1 exposed it

Level 3 context

VariableTypeDescription
partsarrayAll line items in the order
subtotalnumberSum of all part prices (Level 1 + Level 2) before order-level adjustments

Each item in parts:

FieldTypeDescription
part.specificationobjectFull specification (same fields as above)
part.requisitionobjectFull requisition (quantity, lead time)
part.pricenumberLevel 1 unit price
part.postProcessingPricenumberTotal Level 2 price

Level comparison cheat sheet

Level 1Level 2Level 3
RunsPer line itemPer line item × post-processOnce per order
specificationVia parts[i].specification
requisitionVia parts[i].requisition
material.variables
revision
customer
processPricing
parts (all line items)
subtotal
done()
addLineItem()
variable()
createBands()
round()
reviewRequired gate

Sandbox constraints

ConstraintDetail
No network accessfetch(), XMLHttpRequest, and all I/O are unavailable
No Node globalsprocess, require, module, __dirname do not exist
No timerssetTimeout, setInterval, Promise are unavailable
TypeScript onlyThe equation body must be valid TypeScript
Currency is implicitPrices are unitless numbers - never include currency symbols
done() is requiredLevel 1 and Level 2 equations that don't call done() produce no price

Debugging guide

Use variable() to surface intermediate values in the Phasio quote UI during development. They appear in the override panel when you open any quote.

// Add during development — remove before going live
const _debug_shellVolume   = variable('DEBUG shellVolume mm3',  shellVolume)
const _debug_printTime     = variable('DEBUG printTime hours',  round(rawPrintTime / 3600, 4))
const _debug_materialCost  = variable('DEBUG materialCost',     materialCost)
const _debug_removalRatio  = variable('DEBUG removalRatio',     round(removalRatio, 3))

Workflow:

  1. Upload a simple test part with known dimensions (e.g. 20×20×20mm cube - volume ≈ 8,000 mm³, area ≈ 2,400 mm²)
  2. Calculate the expected price by hand
  3. Add DEBUG variable fields for each intermediate value
  4. Run the equation and compare to your hand calculation - the first mismatch is the bug
  5. Test edge cases: qty=1 and qty=500, oversized part, open mesh, missing infill setting
  6. Remove all DEBUG variables before going live

Error reference

ErrorCauseFix
done is not a functionCalled done() at Level 3Use addLineItem() at Level 3
processPricing is not definedUsed processPricing at Level 1Only available at Level 2
variable is not a functionUsed variable() at Level 3Not available at Level 3
Cannot read property of undefinedmaterial.variables['key'] doesn't exist on this materialCreate the variable on every material that uses this equation
Equation saves but throws on real partsVariable exists on test material but not othersAudit all materials using this equation
infill is undefinedAccessed infill.value directlyUse infill?.value ?? 0.20
precision is undefinedAccessed precision.value directlyUse precision?.value ?? 0.20
expense.type == 'HOURLY' always falseEnum value needs .name()Use expense.type.name() == 'HOURLY'
Price is 0 and part is flagged for reviewEquation produced falsy priceAuto-trigger: fix the equation or set a valid calculated price

Common material variable keys

Convention only - must be created per material before use.

KeyTypical valueProcess
materialDensity1.24 g/cm³FDM, SLS, MJF
materialCostPerKg28FDM
resinCostPerLiter95SLA, DLP
costPerCm30.18MJF, SLS
setupCost80MJF, SLS, CNC
baseSpeedNozzle60 mm/sFDM
wallSpeedFactor0.5FDM
infillSpeedFactor1.0FDM
supportSpeedFactor0.8FDM
printerCostPerHour8FDM
machineCostPerH12SLA
buildRateMm3PerHour5000Metal LPBF
machiningCostPerCm32.50CNC
rawMaterialCostPerKg15CNC
mouldCost600Vacuum casting
mouldLifespan20Vacuum casting
castingCostPerPart18Vacuum casting
minUnitPrice5.00All processes (floor price)

Last updated on

On this page