Skip to content
All posts
ConfigurationInsurers

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.

The BimaStack team · · 8 min read

A workflow says how a number is worked out, one step at a time: read the base rate, apply the age loading, take off the no-claim discount, add the levies. Each step is a node, and you connect what one step produces to what the next one needs. You can write a workflow as text or draw it on a canvas. Both are the same workflow, and both are checked the same way.

A first workflow

motor-premium.wf
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
inputA value given to the workflowThe sum insured, the travellers’ ages
formulaWorks out one valuevalue * rate / 100
lookupFinds a configured item by key: a formula, a block, a connectionWhich NCD formula applies to this tier
grouplookupReads a set of rows from configuration as a listEvery row of a rate table
transformWorks over a list: COUNT, SUM, MAX, MIN, FILTER, MAP, FIRST_MATCHCount the children under 21
recordfield(s)Takes fields out of a rowThe rate of the matching row
decisionChooses a branch; the other is skippedOnly price windscreen cover if it was chosen
aggregateAdds to or subtracts from a running totalBase, then + loading, then − discount
fetchCalls an outside system over HTTP or a databaseA vehicle valuation service
blockRuns a reusable piece of workflowRead a rate from a table
outputReturns valuespremium, tax, total

The running total

Loadings and discounts work on the total so far, not on the sum insured, so their order changes the answer. A workflow makes the order part of its shape: each aggregate step takes the previous total and one contribution, and hands its new total to the next.

A base of 4%, then +10% loading, then −5% discount
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
}

On a sum insured of 250,000 the base is 10,000. 10,000 plus 10% is 11,000; 5% off 11,000 is 10,450. Worked against the original 10,000 instead, the discount would be 500 and the answer 10,500. Because each step names the total it builds on, the order can’t be lost, and a chain whose order is ambiguous (one total feeding two next steps) is refused before it can run.

Optional covers: a zero, or a skipped branch

There are two ways to make something optional, and they mean different things:

  • 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.
A decision: each branch ends in its own output
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
}

otherwise -> names the step that runs on “no”; guard: ties a step to “yes”. A step fed by a skipped step is skipped too, which is why each branch has its own output. The outputs of every output step merge into one result.

Blocks: build once, use everywhere

A block is a piece of workflow with named inputs and outputs, saved on its own and called like a single step. The caller sees only its inputs and outputs. Blocks can call other blocks, up to six levels deep, and can never call themselves.

  • 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

A transform works over a list without a loop in sight. MAP runs a formula (or a block) once per item; FILTER keeps the items a condition accepts; FIRST_MATCH keeps the first; COUNT, SUM, MAX and MIN reduce a list to one number.

Each traveller age-rated, then summed (extract)
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.amount

The audit trail keeps every item: which traveller got which factor, which family member a filter left out and why. Workflows in depth covers lists, rows and records fully.

Checked 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.

A workflow of more than about 30 steps gets a warning suggesting you split part of it into a block. Warnings never stop you; errors do.

Try it, then publish it

  1. 1

    Run it on sample values

    Simulate the workflow you are editing, unsaved, against your real tables and settings for any date. You get the outputs and the trace: each step’s inputs and outputs, which branch fired, which settings and versions were read.

    Open the Studio
  2. 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. 3

    Approve and publish

    A second person approves the workflow and its formula links, then they are published from a date. From then on, a product runs it by its key.

    Approvals

Price your first product

We’ll build its pricing workflow with you, from your own rate card.

Book a demo