Policies

Build policy documents from the quote response

Endorsement steps decide which schedules, forms and clauses go on a policy. A Mustache or Handlebars template then reads the quote response to choose which sections appear and to fill them in, for any line of business.

How it works

Price, respond, render

Ptally does not hold your document layouts. It gives you a response with every decision already made, so your template only has to display it.

01 · Price
The model picks the paperwork
Alongside the premium, endorsement steps run rules against the risk. Each rule that fires adds a form code, such as a schedule, an endorsement or a clause.
02 · Respond
One response holds everything
The quote response carries the price, the inputs, every output field, the excesses and the endorsements that fired. It is the only data the document needs.
03 · Render
Your template does the layout
A Mustache or Handlebars template turns the response into a schedule. Form codes switch sections on and off, and output fields fill them in.
Endorsements

Rules that choose the forms

Each item is a rule with a form code as its value. Rules that fire are listed in the response's endorsements array, with the step key, the rule name, the code and its description.

Endorsement step in a fleet model
{
  "id": "policy_forms",
  "key": "policy_forms",
  "step": "endorsements",
  "name": "Policy forms",
  "def": [],
  "items": [
    {
      "id": "telematics",
      "name": "Telematics fitted",
      "exp": "{{telematics}} == true",
      "value": "END_TELEMATICS",
      "description": "Telematics data condition applies."
    },
    {
      "id": "hazardous_goods",
      "name": "Carries hazardous goods",
      "exp": "{{hazardous_goods}} == true",
      "value": "END_HAZ_GOODS",
      "description": "ADR hazardous goods extension applies."
    },
    {
      "id": "young_drivers",
      "name": "Drivers under 25",
      "exp": "{{drivers_under_25}} > 0",
      "value": "END_YOUNG_DRIVER",
      "description": "Young driver excess and named-driver condition apply."
    },
    {
      "id": "vehicle_schedule",
      "name": "More than 20 vehicles",
      "exp": "{{vehicle_count}} > 20",
      "value": "SCHED_VEHICLES",
      "description": "Vehicles listed in a separate schedule."
    }
  ]
}
Quote response, trimmed
{
  "quote_reference": "<uuid>",
  "version_reference": "<uuid>",
  "result": 18420.64,
  "valid": true,
  "input": {
    "vehicle_count": 24,
    "telematics": true,
    "hazardous_goods": false,
    "drivers_under_25": 2,
    "region": "north_west"
  },
  "output": {
    "result": 18420.64,
    "valid": true,
    "risk_premium": 16447,
    "ipt": 1973.64,
    "compulsory_excess": 500
  },
  "refers": [],
  "exclusions": [],
  "endorsements": [
    {
      "step": "policy_forms",
      "name": "Telematics fitted",
      "exp": "{{telematics}} == true",
      "result": true,
      "value": "END_TELEMATICS",
      "description": "Telematics data condition applies."
    },
    {
      "step": "policy_forms",
      "name": "Drivers under 25",
      "exp": "{{drivers_under_25}} > 0",
      "result": true,
      "value": "END_YOUNG_DRIVER",
      "description": "Young driver excess and named-driver condition apply."
    },
    {
      "step": "policy_forms",
      "name": "More than 20 vehicles",
      "exp": "{{vehicle_count}} > 20",
      "result": true,
      "value": "SCHED_VEHICLES",
      "description": "Vehicles listed in a separate schedule."
    }
  ]
}

Three of the four rules fired. No hazardous goods are carried, so END_HAZ_GOODS is absent and its section won't be printed. See the API for the full response.

Templates

The response is the template data

Turn the form codes into flags, then pass the whole response to your template. Flags show or hide sections, partials hold each form's wording, and output fields fill in the figures.

Render the quote
import Mustache from "mustache";

// The response from POST /quotes/:project_reference,
// or a saved quote from GET /quotes/:quote_reference
const quote = await res.json();

// Turn the form codes into flags the template can test
const forms = Object.fromEntries(
  quote.endorsements
    .filter((e) => e.step === "policy_forms")
    .map((e) => [e.value, true])
);

const html = Mustache.render(schedule, { ...quote, forms }, partials);
schedule.mustache
<h1>Fleet policy schedule</h1>
<p>Policy {{quote_reference}} · {{input.vehicle_count}} vehicles</p>

<table>
  <tr><td>Premium</td><td>£{{output.risk_premium}}</td></tr>
  <tr><td>Insurance Premium Tax</td><td>£{{output.ipt}}</td></tr>
  <tr><td>Total payable</td><td>£{{result}}</td></tr>
  <tr><td>Compulsory excess</td><td>£{{output.compulsory_excess}}</td></tr>
</table>

{{#forms.SCHED_VEHICLES}}{{> vehicle_schedule}}{{/forms.SCHED_VEHICLES}}
{{#forms.END_TELEMATICS}}{{> telematics_condition}}{{/forms.END_TELEMATICS}}
{{#forms.END_HAZ_GOODS}}{{> hazardous_goods}}{{/forms.END_HAZ_GOODS}}
{{#forms.END_YOUNG_DRIVER}}{{> young_driver}}{{/forms.END_YOUNG_DRIVER}}

<h2>Endorsements</h2>
{{#endorsements}}
  <p><strong>{{value}}</strong> {{name}}. {{description}}</p>
{{/endorsements}}

The syntax works in Mustache and Handlebars. Liquid, Jinja or a PDF generator that takes JSON work the same way.

Rendered schedule
Fleet policy schedule
Policy 7f3c…e21a · 24 vehicles
Premium
£16,447.00
Insurance Premium Tax
£1,973.64
Total payable
£18,420.64
Compulsory excess
£500
SCHED_VEHICLESSchedule of vehicles · 24 vehicles
END_TELEMATICSTelematics condition
END_HAZ_GOODSHazardous goods extension · not included
END_YOUNG_DRIVERYoung driver condition
Other steps

Every step feeds the document

Endorsements choose the sections. The rest of the model supplies what goes in them and whether a document should be issued at all.

StepDocument partHow it is used
endorsementsForms, schedules and clausesEach rule that fires adds a code. The template uses the codes to choose which sections and wordings appear.
excessesExcess scheduleRules that add excesses for a risk, such as young drivers or high-value items. Expose the figure as an output field to print it.
exclusion · referWhether to issue at allA declined quote comes back with valid false, so no document is produced. A referred quote can be held for an underwriter before it is issued.
calculationPremium breakdownNet premium, tax, fees, commission and instalments, each exposed as an output field and printed to the penny.
collectionWording lookupsTables of territory clauses, insurer details, regulator text or limits by product, looked up per risk rather than hard-coded in the template.
transformDisplay valuesLabels, dates and formatted strings shaped for the document, such as a policy period or a cover description.
Why do it this way

Documents that match the price

Rules about cover live in one place, are tested with the rating, and are published as one version.

Accurate
Priced and worded by the same rules
The model version that priced the quote also chose its forms. The document can't attach a clause the rating didn't account for, or miss one it did.
Any line of business
Your forms, your codes
Fleet, property, pet, cyber or a US filed programme. Form codes are whatever your wordings library already uses, and one model can carry several endorsement steps.
Traceable
Every section has a reason
Each endorsement in the response names the rule and the expression that fired it. With ?debug=true you also see the values it used.
Repeatable
Re-render any time
Quotes are stored with their version reference. Fetch a saved quote and render it again for a reissue, a mid-term change or an audit.