Skip to content

Pricer

The pricer computes prices for a BuilderProduct dataclass. As a reminder the BuilderProduct dataclass hides the concept of version: it is for a single DB BuilderProductVersion, a priori the latest one.

Pricing logic: in the backend or in Turing?

Pricing logic is split between Turing and the backend. We have a bit of overlap (eg because it’s practical to be able to fully compute a price in Turing), but when there’s an overlap we are clear on which is the source of truth.

  • Computing the pure premium (cost for Alan) of each guarantee => in Turing
  • Using the pure_premiums to compute a price (so adding taxes, option mutualization, price_structure, etc) => in the backend
    • Here Turing can do it as well, but it's not used as a source of truth (eg: useful for internal algo but never shown to the customer)
  • At renewal, computing the price_increase => in Turing
    • The input for the price_increase are things like: current_loss_ratio, target_loss_ratio, medical_inflation hypothesis, mandatory guarantee changes and their expected impact on the price, etc
  • At renewal, using the price_increase to compute the price => in the backend
    • The price_increase is only one of many inputs the backend takes to compute the new_price. Users are able to edit the price_increase, coverage, price_structure and more in the offer-builder.

Key concepts

  • BuilderTarget: See Target.
  • Coverage: A collection of coverage rules, ie reimbursement parameters for a collection of guarantees
  • Demographics: Depending on the population avg_age, male_ratio, location, etc we can expect different health consumption behavior, and so different pure_premiums for each Policy member (the primary, partner and children). Then, the average policy composition (ratio of partner per primary, ratio of 1st, 2nd, etc child per primary) influences the average policy pure_premium (and similarly for the average policy premium and average policy price).
  • Past result correction factor: A multiplicative factor to apply to a pure_premium. This factor represents information that we know but that is not in the pricer. It is equal to 1 by default, and can be adjusted if the user want to adjust the pricer output.
    • :bulb: At renewal, we compute a past_results_correction_factor = pricer_evolution_factor * (1 + price_increase). The pricer_evolution_factor can be seen as a regularization factor so that, if the price_increase is 0%, the output price is the same as last year.
  • Membership fees: A monetary amount, the component of the price that we expect is Alan gross profit. See Price below.
  • Policy: A Policy reresents a family who enrolls on an insurance plan with Alan. In the policy, there's always a "primary" (the person subscribing to Alan) and then this person can add "dependents", ie their partner and their children.
  • Pure premium: For a given population, the monetary amount we expect to reimburse a guarantee coverage rule, or for a full coverage depending on context. A coverage pure_premium is equal to the sum of the coverage_rule pure_premium.
  • Premium: For a given pure_premium & target loss ratio, the monetary amount we should charge a member to reach said loss ratio. Beyond the target_loss_ratio factor, a key difference between pure_premium and premium is that pure_premium is a cost for Alan and premium is something the member pays: so between the 2, we add taxes and other costs or benefits we transmit to the member. See Price below. About additional costs / benefits in the premium in France: there can be an "action sociale" which is a contribution from a population to another population (eg: active workers pay a bit more to reduce the premium of retirees).
  • Price: What we charge to a member. It is made of premium + membership_fee, where premium is meant to cover the cost of reimbursing the member and the membership_fee is meant to be Alan's gross profit.
  • PriceComponent & PriceComponentType: In some cases, we want to split the price into several components. Note that those are NOT th premiums & membership_fees split. EG in France: we sometimes want to split the price into a PriceComponentType.fr_responsible and PriceComponentType.fr_non_responsible part because there is a tax advantage on the responsible part.
  • PriceTarget: See Target.
  • Price structure: The way the price is spread across policy (or family) members.
    • EG: A typical rice_structure in France would be to have the primary, partner and first enrolled child pay the same amount each, and then other enrolled children are covered for free.
    • :bulb: Changing the price_structure influences whether primaries enroll their dependents or not (eg: if dependent can be enrolled for free, we can expect much more dependents to be enrolled). We model that by taking into account the price_structure when we compute demographics.
  • Pricing configuration: Generic term regrouping settings that the users can change to fine-tune the price. Those can includes:
    • the pricer version
    • the price structure: repartition of costs amongst the policy members
    • a target Loss Ratio
    • a margin percentage
    • a past result correction factor (acquisition) or a price increase (renewal)
    • etc
  • Target: Depending on context, it can either mean:
    • BuilderTarget: A BuilderProduct is created for 1 or several BuilderTarget (typically a company or a sub-population of a company). When there are several BuilderTargets, we price in 2 steps: first we compute a price for each BuilderTarget, and then we compute the final price which is a weighted average of each target and the number of people in the BuilderTarget population. See the pricing logic section below for more details.
    • PriceTarget: A sub-population for which we compute a price. EG in France: people who leaves in Alsace-Moselle & people who leaves elsewhere (we say they are on "RΓ©gime GΓ©nΓ©ral"). In the pricer output, there is a price for each PriceTarget and we will write each price to the output offer. When a company subscribe to the offer, the billing logic will determine which price should apply to each policy.
  • Target loss ratio: loss_ratio = actual_cost_of_claims / pure_premium. The target_loss_ratio is an input of the pricer, allowing the user to fine-tune the price by accepting some loss (target_loss_ratio > 100%) or adding some safety margin (target_loss_ratio < 100%).

Pricing logic overview in the backend

  1. Pre-pricing step:
    • This step is meant to run country specific logic that must happen before pricing.
    • For example in France, this is where we re-assess the closest coverage_level (the coverage_level is a parameter in the guarantee pure_premiums lookup).
  2. For each BuilderTarget:
    • a. Get the manual demographics if any
    • b. Compute the demographics based on the BuilderTarget characteristics & other pricing configurations. Manual demographics overwrite computed ones on a line-by-line basis (it's possible to input partial manual demographics).
    • Note that, at renewal, we create ManualDemographics equal to the actual demographics of the company. This is a convenient way to override the computed demographics when we know the exact company population.
    • c. Compute the pure_premium of each coverage and for each PriceTarget.
      • To do this, we look-up the pure_premium of each guarantees in the coverage (we do a linear regression between the 2 closest pure_premium_entries for the guarantee), and then sum the pure_premiums accross guarantees.
      • We apply the past_results_correction_factor if any to obtain a corrected_pure_premium
    • d. post-process the pure_premiums:
      • This step is meant to run country specific logic.
      • For example in France, this is where:
        • we tag a coverage as being "responsible" or not and split the responsible from the non-responsible pure_premium parts.
        • we substract the base pure_premium from option pure_premiums, so that option price comes on top of the base price (in France, an option coverage is always better or equal than the base coverage).
    • e. Compute the premium & price:
      • To get the premium, we add taxes and other country-specific fees to the pure_premium.
      • We then compute the policy_average_premium (see "Demographics" in the Key Concepts section above).
      • Then the policy_average_price by adding the membership fees to the policy_average_premium.
      • Then we split the policy_average_price into prices by using the price_structure_coefficients.
  3. Finally, we aggregate (weighted average on the number of beneficiaries) the price by PriceTarget and round it to obtain the final price for the product.
  4. The pricer also outputs metrics, such as the gross profit (GP), GP per member, annual recurring revenue (ARR), ARR per member, etc

Pricer Deep dive

This guide provides a explanation of how the pricing system works in the offer builder component.

1. Core Concepts and Terminology

Before diving into the pricing flow, let's understand the key concepts:

Builder Product

A BuilderProduct is the main container for an insurance product offering. It is composed by product versions that include:

  • Coverage definitions
  • Target populations (who will be insured)
  • Pricing configuration
  • Country-specific settings

Builder Target

A BuilderTarget represents a specific segment of the population that will be covered by the insurance product. For example:

  • Different professional categories within a company (e.g., cadres/non-cadres in France)
  • Different companies

Each target has its own demographic characteristics that affect pricing.

Builder Coverage

A BuilderCoverage defines what is covered by the insurance product. It includes:

  • Coverage rules (what guarantees/medical services are covered and at what level)
  • Pricing structure (how premiums are calculated)

Pricer Target

A PricedTarget is the result of pricing a BuilderTarget. It contains:

  • Reference to the original builder target
  • List of coverages with calculated premiums

Premiums

  • Pure Premiums: The expected cost of claims without prudence margin, loss ratio adjustment or taxes.
  • Health premiums: Pure premiums + prudence margin of 3%
  • Net premiums: Health premiums / loss ratio
  • Gross premiums: Net premiums + taxes
  • Price: Gross premiums + membership fees

Price Component Identifier

A PriceComponentIdentifier is a class that identifies different types of price components:

@dataclass(frozen=True, kw_only=True)
class PriceComponentIdentifier:
    price_target: PriceTarget = None
    price_component_type: str | None = None

This class is used to distinguish between different pricing segments and component types:

  • price_target: Identifies the target population segment for pricing
  • price_component_type: Identifies the type of price component

Country-Specific Price Targets and Components

France

French-Specific Price Targets

In France, there are two main social security regimes defined in FrPriceTarget:

class FrPriceTarget(AlanBaseEnum):
    regime_general = "regime_general"  # General regime (default)
    alsace_moselle = "alsace_moselle"  # Special regime for Alsace-Moselle region
  • regime_general: The default social security regime in France
  • alsace_moselle: A special regime for the Alsace-Moselle region, which typically has lower premiums due to a different social security system

When used in a PriceComponentIdentifier, the regime_general value is represented as None (the default), while alsace_moselle is represented as the string "alsace_moselle".

French-Specific Price Component Types

In France, there are different types of price components defined in FrPriceComponentType:

class FrPriceComponentType(AlanBaseEnum):
    fr_responsible = "fr_responsible"       # Responsible contract component
    fr_non_responsible = "fr_non_responsible" # Non-responsible contract component
    fr_total = "fr_total"                   # Sum of responsible and non-responsible
  • fr_responsible: Represents the "responsible" part of the coverage, which complies with the French "contrat responsable" regulations
  • fr_non_responsible: Represents the "non-responsible" part of the coverage, which doesn't comply with the regulations and is subject to higher taxes
  • fr_total: Used internally to represent the sum of responsible and non-responsible components (not used for final prices)

Each price component type has a different tax rate:

  • fr_responsible: Lower tax rate
  • fr_non_responsible: Higher tax rate

French-Specific Target Populations

In France, there are also different professional categories defined in FrTargetPopulation:

class FrTargetPopulation(AlanBaseEnum):
    cadres = "cadres"
    non_cadres = "non_cadres"

These categories are used in the demographic calculations and target population segmentation. Each BuilderTarget can represent a different professional category, allowing the system to calculate different premiums for different segments of the population.

Belgium

Belgian-Specific Price Component Types

In Belgium, there are different types of price components defined in BePriceComponentType:

class BePriceComponentType(AlanBaseEnum):
    be_nihdi_contribution = "be_nihdi_contribution"  # Hospitalization coverage
    be_basic = "be_basic"                           # Non-hospitalization coverage

Belgium applies different tax rates based on coverage type:

  • be_nihdi_contribution: 19.25% tax (9.25% base + 10% NIHDI contribution) for hospitalization coverage
  • be_basic: 9.25% tax (base rate only) for non-hospitalization coverage

The NIHDI (National Institute for Health and Disability Insurance) contribution is an additional tax applied specifically to hospitalization benefits.

Belgian-Specific Pricer Locations

Belgium has three pricer locations defined in BePricerLocation:

class BePricerLocation(AlanBaseEnum):
    bxl = "bxl"  # Brussels
    wal = "wal"  # Wallonia
    fla = "fla"  # Flanders

The pricer location is determined based on the postal code of the target and affects the pure premium calculations. Note that this is different from France's alsace/moselle price targets.

Belgian-Specific Coverage Types

Belgium has three main coverage types that affect pricing:

  • Hospitalization
  • Ambulatory
  • Flexben (flexible benefits)

These coverage types are used to determine the target margin mapping key, which in turn affects the membership fee ratio.

2. High-Level Pricing Flow

flowchart TD
    A["BuilderProduct (unpriced product)"] --> B["Pre-process builder product (validate, prepare for pricing"]
    B --> C[Calculate demographics for each target]
    C --> D["Price each target individually (multithreaded)"]
    D --> E[Update builder product with priced coverages]
    E --> F[Aggregate priced targets]
    F --> G[Update builder coverage pricing]
    G --> H[Compute output metrics]
    H --> I["BuilderProduct (priced product)"]
Hold "Alt" / "Option" to enable pan & zoom

3. Detailed Step-by-Step Process

3.1 Initialization and Validation

  • The system retrieves country-specific implementations through dependency injection
  • It validates the pricer version (or sets a default if none is provided)
  • It checks if the product is already priced (to avoid re-pricing)
  • It verifies that the product has at least one target population

3.2 Pre-Processing

  • The system either makes a deep copy of the builder product (if bypassing pre-processing)
  • Or it calls a country-specific pre-processing function that can:
    • Validate the product structure
    • Set default values for missing fields
    • Apply country-specific adjustments
    • Prepare the product for pricing

3.3 Get Demographics

  • The system retrieves manual demographics for each target population
  • Manual demographics are user-provided demographic data that override system estimates. Note that manual demographics can have partial data, meaning some demographic fields can be left empty, so not overriding the system estimates for those fields.
  • The data is validated to ensure it's complete and consistent
  • The result is a dictionary mapping target IDs to their manual demographics
  • The system computes demographics by merging manual demographics with system-computed demographics (based on the target's characteristics).

3.4 Price Each Target

What happens in this step:

  • The system prices each target population individually
  • Each target is processed by the price_product_for_target function
  • The result is a list of PricedTarget objects

3.4.1 Target Pricing Process

The function price_product_for_target handles pricing for a single target:

flowchart TD
    A[BuilderTarget] --> B["Compute pure premiums for each<br/>coverage"]
    B --> C["Post-process pure premiums<br/>(country-specific)"]
    C --> D["Compute premiums from pure<br/>premiums"]
    D --> E[PricedTarget]

    classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    classDef process fill:#f3e5f5,stroke:#4a148c,stroke-width:2px

    class A,E startEnd
    class B,C,D process
Hold "Alt" / "Option" to enable pan & zoom

3.4.2 Pure Premiums Calculation

For each coverage, the system:

def _compute_coverage_pure_premiums(
    builder_product, builder_target, builder_coverage, pricer_version,
    manual_demographics, force_option_demographics=False
):
    # Compute demographics for this target and coverage
    demographics = compute_demographics(
        builder_product=builder_product,
        builder_target=builder_target,
        builder_coverage=builder_coverage,
        pricer_version=pricer_version,
        manual_demographics=manual_demographics,
        force_option_demographics=force_option_demographics
    )

    # Get pure premiums inputs
    pure_premiums_inputs = dependency.get_pure_premiums_inputs(
        builder_product, builder_target, builder_coverage
    )

    # Compute pure premiums for each input
    pure_premiums_outputs = [
        compute_pure_premiums(
            pp_input, demographics, pricer_version,
            past_results_correction_factor=builder_coverage.past_results_correction_factor,
        )
        for pp_input in pure_premiums_inputs
    ]

    # Create CoverageWithPurePremiums object
    return CoverageWithPurePremiums.from_builder_coverage(
        builder_coverage=builder_coverage,
        demographics=demographics,
        pure_premiums_outputs=pure_premiums_outputs,
    )

Demographics Computation:

  • The system computes demographic information for the target and coverage
  • This includes age distribution, gender ratio, family composition, etc.
  • For the base coverage: Manual demographics (if provided) override system estimates

Pure Premiums Inputs:

  • The system gets country-specific inputs for pure premium calculation
  • In France, this includes creating inputs for different price targets (regime_general, alsace_moselle) and price component types (fr_responsible, fr_non_responsible, fr_total)
  • In Belgium, this includes creating inputs for different price component types (be_nihdi_contribution, be_basic) based on whether the coverage rules are for hospitalization or non-hospitalization

Pure Premiums Calculation:

  • For each input, the system calculates pure premiums
  • Pure premiums represent the expected cost of claims without margins
  • The calculation takes into account:
    • Demographics of the target population
    • Coverage details (what's covered and at what level)
    • Past results correction factor (to adjust for how the pricer has evolved over time)

Result Creation:

  • The system creates a CoverageWithPurePremiums object
  • This object contains:
    • The original builder coverage
    • The computed demographics
    • The calculated pure premiums outputs

3.4.3 Post-Processing Pure Premiums

  • The system calls a country-specific function to post-process the pure premiums
  • In France, this function:
    • Aggregates responsible and non-responsible components
    • Converts fr_total components into fr_responsible and fr_non_responsible components
    • Applies special handling for Alsace-Moselle price targets
  • In Belgium, the default implementation is used, which simply returns the input coverages with pure premiums unchanged

3.4.4 Premiums Calculation

After computing pure premiums, the system calculates actual premiums:

def _compute_coverage_with_premiums(
    builder_product, builder_target, coverage_with_pure_premiums
):
    # Get membership fee ratio
    membership_fee_ratio = get_membership_fee_ratio(
        builder_product=builder_product,
        builder_coverage=coverage_with_pure_premiums,
        builder_target=builder_target,
    )

    # Compute premiums from pure premiums
    premiums_outputs = [
        compute_premiums_from_pure_premiums_output(
            builder_coverage=coverage_with_pure_premiums,
            pure_premiums_output=pp_output,
            demographics=coverage_with_pure_premiums.demographics,
            membership_fee_ratio=membership_fee_ratio,
        )
        for pp_output in coverage_with_pure_premiums.pure_premiums
    ]

    # Create CoverageWithPremiums object
    return CoverageWithPremiums(
        builder_coverage_id=coverage_with_pure_premiums.id,
        demographics=coverage_with_pure_premiums.demographics,
        pure_premiums_outputs=coverage_with_pure_premiums.pure_premiums,
        premiums_outputs=premiums_outputs,
    )

Membership Fee Ratio:

  • The system calculates the membership fee ratio
  • This ratio determines what portion of the premium goes to membership fees
  • It can vary by product, coverage, and target
  • In Belgium, this is calculated based on target gross profit per member, which depends on:
    • Coverage type (hospitalization, ambulatory, flexben)
    • Bucket size (small, medium, large, very_large)

Premiums Calculation:

  • For each pure premium output, the system calculates premiums
  • Premiums include:
    • Pure premiums (expected cost of claims)
    • Margins (profit margin for the insurer)
    • Fees (administrative fees, membership fees, etc.)
    • Taxes (if applicable)
  • The calculation takes into account:
    • Demographics of the target population
    • Membership fee ratio
    • Coverage details
    • Price component type (which affects tax rates)

Result Creation:

  • The system creates a CoverageWithPremiums object
  • This object contains:
    • Reference to the builder coverage
    • The computed demographics
    • The pure premiums outputs
    • The calculated premiums outputs

3.5 Update Builder Product with Priced Coverages

  • For each priced target and coverage:
    • It retrieves the corresponding builder coverage
    • It computes a PricerPricedCoverage object from the coverage with premiums
    • It converts this to a PricedCoverage object
    • It adds this to the builder coverage's list of priced coverages

3.6 Aggregate Priced Targets

  • The system aggregates the priced targets to get overall pricing
  • This aggregation:
    • Groups coverages by ID
    • Sums demographics across targets
    • Computes weighted averages of premiums based on the number of primaries
    • Creates aggregated CoverageWithPremiums objects

3.6.1 Aggregation Process

The aggregation process is handled by the aggregate_priced_targets function and the CoverageWithPremiums.aggregate method:

  • The system groups coverages by their builder coverage ID
  • For each group, it calls the CoverageWithPremiums.aggregate method
  • This method:
    • Sums demographics across targets
    • Computes weighted averages of pure premiums based on the number of beneficiaries
    • Computes weighted averages of premiums based on the number of policies
    • Creates an aggregated CoverageWithPremiums object

3.7 Update Builder Coverage Pricing

  • For each aggregated coverage with premiums:
    • The system retrieves the corresponding builder coverage
    • It computes an aggregated priced coverage
    • It sets the pricing on the builder coverage
    • It updates the cost estimates for each coverage rule

3.8 Compute Output Metrics and Return

  • The system computes output metrics for the builder product
  • These metrics include:
    • Total premiums
    • Average premiums per primary/partner/child
    • Loss ratios
    • Margins
    • etc
  • It returns the updated builder product with these metrics

4. Final Output

The final output is a BuilderProduct with:

  • Priced coverages for each target
  • Aggregated pricing for each coverage
  • Output metrics for the entire product