Workflows: how a premium is worked out, step by step
Steps wired together, readable as text and as a diagram: running totals, decisions, blocks and lists, checked before they run and tried before they are live.
A first workflow
workflow "motor-premium" {
input vehicleValue
input driverAge
formula basePremium {
expression: vehicleValue * 1.5 / 100
}
formula ageFactor {
in driverAge : NUMBER
expression: if(driverAge < 25, 1.15, 1.0)
}
formula finalPremium {
expression: basePremium * ageFactor
}
output premium {
}
connect vehicleValue -> basePremium
connect driverAge -> ageFactor
connect basePremium -> finalPremium
connect ageFactor -> finalPremium
connect finalPremium -> premium
}input declares what the workflow is given. A bare input is a number; others say their type: input coverStart : DATE. formula is one calculation in the formula language. The names it uses are its inputs. output is what the workflow returns. Numbers are rounded to the cent here, and only here. connect wires a value into a step. connect basePremium -> finalPremium feeds basePremium’s result into finalPremium, under the name basePremium.
The steps you can use
| Step | Does | Example |
|---|---|---|
The running total
workflow "chain" {
input sumInsured
formula basePremium {
expression: sumInsured * 0.04
}
formula zero {
expression: 0
}
formula loading {
expression: runningTotal * 0.10
}
formula discount {
expression: runningTotal * 0.05
}
aggregate afterBase {
sign: ADD
}
aggregate afterLoading {
sign: ADD
}
aggregate afterDiscount {
sign: SUBTRACT
}
output total {
}
connect sumInsured -> basePremium
connect zero -> afterBase.currentTotal
connect basePremium -> afterBase.contribution
connect afterBase -> loading.runningTotal
connect afterBase -> afterLoading.currentTotal
connect loading -> afterLoading.contribution
connect afterLoading -> discount.runningTotal
connect afterLoading -> afterDiscount.currentTotal
connect discount -> afterDiscount.contribution
connect afterDiscount -> total.premium
}Optional covers: a zero, or a skipped branch
Gate it in a formula when it is one line that should show as zero when not chosen: if(roadRescue, 3000, 0). The line is still on the breakdown, so a reviewer can see it was considered. Branch with a decision when a whole part of the calculation shouldn’t run at all: windscreen cover’s own rate rows are never even looked up for a quote that didn’t choose it. Every step on the untaken branch is marked skipped, never failed.
workflow "windscreen" {
input hasWindscreen : BOOLEAN
input windscreenValue
decision wantsWindscreen {
in hasWindscreen : BOOLEAN
expression: hasWindscreen
otherwise -> noWindscreen
}
formula windscreenCharge {
expression: windscreenValue * 0.10
guard: wantsWindscreen.true
}
formula noWindscreen {
expression: 0
}
output charged {
}
output notCharged {
}
connect hasWindscreen -> wantsWindscreen
connect windscreenValue -> windscreenCharge
connect windscreenCharge -> charged.windscreen
connect noWindscreen -> notCharged.windscreen
}Blocks: build once, use everywhere
Platform blocks (their keys start protected:) are shipped and kept by BimaStack: reading a rate table, a tax pass, a commission pass, short-period proration. You call them; you can’t edit them. Your own blocks are drafted, approved and versioned like workflows. One NCD block can serve every motor product. Parameters are fixed values a call passes in, such as which rate table to read: param tableCode = "motor-private-base-rate". Extract a block from a selection on the canvas: the selected steps become a new draft block, and you get the step that replaces them.
Lists: every traveller, every member, every row
transform ageFactors {
op: MAP
elements: age : LIST<NUMBER>
binding: formula "travel-age-factor"
}
transform perTraveller {
op: MAP
elements: factor : LIST<NUMBER>
context: rate : NUMBER
expression: rate * factor
}
transform total {
op: SUM
elements: amount : LIST<NUMBER>
}
connect travellerAges -> ageFactors.age
connect ageFactors.mapped -> perTraveller.factor
connect baseRate.rate -> perTraveller.rate
connect perTraveller.mapped -> total.amountChecked before it can run
Every connection joins steps and ports that exist, and of matching types (a yes/no can’t feed a number). Every required input of every step is connected, and none is connected twice. There are no loops, and no step that nothing can reach. Every running-total chain has one clear order. Every block and formula it names exists, with matching inputs and outputs. Every formula passes its own checks; its errors are shown on the workflow’s own line and column.
Try it, then publish it
- 1
- 2
Save it as a draft
Importing text saves the workflow and its blocks as drafts, plus a draft link for each formula written in it. Nothing is live yet. - 3