A per-traveller travel model that rates each traveller by age, then applies destination and trip length.
Travel insurance covers medical costs, cancellation and other losses while a customer is away. A single policy often covers several travellers of different ages, and cost depends heavily on destination and trip length.
Each traveller is priced individually by a child model that looks up a base premium by age band. The parent model adds the travellers together, then applies a destination factor and a trip length factor, plus a policy fee.
This is the modules pattern: one quote carries a list of travellers, and the same child model rates each one. Travellers over 85 and trips over 90 days are declined, and long USA trips are referred.
Three further factors adjust the policy premium: the cover level chosen, whether winter sports are included and whether pre-existing medical conditions are declared.
| Field | Label | Type | Default | Allowed values |
|---|---|---|---|---|
destination_region | Destination region | string | europe | europe, worldwide_ex_usa, worldwide_inc_usa |
trip_length_days | Trip length (days) | integer | 7 | 3, 7, 14, 21, 30, 60 |
travelers | Travelers | array | list | A list of items, each rated by the child model |
cover_level | Cover level | string | standard | essential, standard, premium |
winter_sports | Winter sports cover | boolean | false | false, true |
medical_conditions | Declared medical conditions | boolean | false | false, true |
Steps run in order. Each one can use the inputs and the outputs of every step before it.
Look up a multiplier by destination region. Worldwide + USA is the most expensive.
{
"id": "region_rates",
"key": "region_rates",
"step": "collection",
"name": "Destination region loading",
"retain": true,
"collection": [
["destination_region", "region_factor"],
["europe", 1],
["worldwide_ex_usa", 1.6],
["worldwide_inc_usa", 2.2]
],
"format": {
"region_factor": {
"key": "region_factor",
"label": "Destination region factor",
"exp": "collection.filter(destination_region=={{destination_region}}).map(region_factor).first()",
"type": "decimal",
"def": 1
}
}
}Longer trips attract a load. 7 days = 1.0, 30 days ≈ 2.3, capped at 3.0.
{
"id": "trip_length_calc",
"key": "trip_length_calc",
"step": "calculation",
"name": "Trip length factor",
"def": 1,
"formula": "min(1 + ({{trip_length_days}} - 7) * 0.06, 3.0)"
}Iterate the travelers[] array. Child model returns each traveler's base premium; parent sums into policy total and finds the oldest.
Child model. Prices a single traveler by age band.
Age-band base rate. Look up a per-traveler base premium by age band. Older travelers cost more.
{
"id": "per_traveler_rating",
"key": "per_traveler_rating",
"step": "modules",
"name": "Per-traveler rating",
"format": {
"travelers_base_total": {
"key": "travelers_base_total",
"label": "Sum of per-traveler base premiums",
"exp": "collection.sum(result)",
"type": "decimal",
"def": 0
},
"travelers_max_age": {
"key": "travelers_max_age",
"label": "Oldest traveler age",
"exp": "collection.map(age).max()",
"type": "integer",
"def": 0
}
},
"child_model_steps": [
{
"id": "age_band_rates",
"key": "age_band_rates",
"step": "collection",
"name": "Age-band base rate",
"retain": true,
"collection": [
["age_max", "band_base"],
[17, 22],
[40, 28],
[55, 38],
[70, 60],
[80, 110],
[200, 200]
],
"format": {
"band_base": {
"key": "band_base",
"label": "Age-band base premium",
"exp": "collection.filter(age_max>={{age}}).map(band_base).first()",
"type": "decimal",
"def": 28
}
}
}
]
}Higher cover levels pay more for medical expenses, cancellation and baggage, so they cost more.
{
"id": "cover_level_rates",
"key": "cover_level_rates",
"step": "collection",
"name": "Cover level factor",
"retain": true,
"collection": [
["cover_level_value", "cover_level_factor_rel"],
["essential", 0.8],
["standard", 1],
["premium", 1.35]
],
"format": {
"cover_level_factor": {
"key": "cover_level_factor",
"label": "Cover level factor",
"exp": "collection.filter(cover_level_value=={{cover_level}}).map(cover_level_factor_rel).first()",
"type": "decimal",
"def": 1
}
}
}Skiing and snowboarding carry a much higher chance of injury and rescue costs.
{
"id": "winter_sports_rates",
"key": "winter_sports_rates",
"step": "collection",
"name": "Winter sports factor",
"retain": true,
"collection": [
["winter_sports_value", "winter_sports_factor_rel"],
[false, 1],
[true, 1.4]
],
"format": {
"winter_sports_factor": {
"key": "winter_sports_factor",
"label": "Winter sports factor",
"exp": "collection.filter(winter_sports_value=={{winter_sports}}).map(winter_sports_factor_rel).first()",
"type": "decimal",
"def": 1
}
}
}Pre-existing medical conditions that are declared and covered increase the expected medical cost.
{
"id": "medical_rates",
"key": "medical_rates",
"step": "collection",
"name": "Declared medical conditions factor",
"retain": true,
"collection": [
["medical_conditions_value", "medical_factor_rel"],
[false, 1],
[true, 1.5]
],
"format": {
"medical_factor": {
"key": "medical_factor",
"label": "Declared medical conditions factor",
"exp": "collection.filter(medical_conditions_value=={{medical_conditions}}).map(medical_factor_rel).first()",
"type": "decimal",
"def": 1
}
}
}Sum of per-traveler bases × region factor × trip-length factor, plus £5 policy fee. Multiplied by the cover level factor, winter sports factor, declared medical conditions factor.
{
"id": "policy_premium",
"key": "policy_premium",
"step": "calculation",
"name": "Policy premium",
"def": 0,
"formula": "round(({{travelers_base_total}} * {{region_factor}} * {{trip_length_calc}} * {{cover_level_factor}} * {{winter_sports_factor}} * {{medical_factor}}) + 5, 2)"
}Decline travelers over 85 or trips longer than 90 days: outside standard product terms.
{
"id": "travel_exclusions",
"key": "travel_exclusions",
"step": "exclusion",
"name": "Travel exclusions",
"def": false,
"items": [
{
"id": "traveler_too_old",
"name": "Traveler over 85",
"exp": "{{travelers_max_age}} > 85",
"def": false
},
{
"id": "trip_too_long",
"name": "Trip over 90 days",
"exp": "{{trip_length_days}} > 90",
"def": false
}
]
}Refer USA trips longer than 21 days for underwriter review.
{
"id": "travel_refers",
"key": "travel_refers",
"step": "refer",
"name": "Travel refers",
"def": false,
"items": [
{
"id": "long_usa_trip",
"name": "Long USA trip",
"exp": "{{destination_region}} == 'worldwide_inc_usa' && {{trip_length_days}} > 21",
"def": false
}
]
}This quote was priced by the Ptally rating engine. The trace below follows it through every step to the final result.
{
"destination_region": "europe",
"trip_length_days": 7,
"travelers": [
{
"age": 30
}
],
"cover_level": "premium",
"winter_sports": true,
"medical_conditions": false
}region_factor = 1min(1 + (7 - 7) * 0.06, 3.0) = 1Item 1 {"age":30} → 28travelers_base_total = 28 · travelers_max_age = 30cover_level_factor = 1.35winter_sports_factor = 1.4medical_factor = 1round((28 * 1 * 1 * 1.35 * 1.4 * 1) + 5, 2) = 57.92Traveler over 85: not firedTrip over 90 days: not firedLong USA trip: not firedSelect travel as the default model for a new project, then change the rates, rules and tests to match your product. Our consultants can do this for you, with no implementation fee.