Skip to content

Api reference

components.offer_builder.public.api

OfferBuilderCommand module-attribute

OfferBuilderCommand = CreateOfferFromBuilderProductCommand

OfferBuilderCreationCommand module-attribute

OfferBuilderCreationCommand = (
    CreateBuilderProductFromProductChangeCommand
    | DuplicateBuilderProductCommand
    | CreateBuilderProductFromTemplateCommand
    | CreateBuilderProductFromLiveProductCommand
    | CreateBuilderProductSelfAmendmentAlternativesCommand
)

calculate_membership_fee_ratio_from_gross_profit

calculate_membership_fee_ratio_from_gross_profit(
    target_gross_profit_per_member,
    current_pure_premium_per_member,
    target_loss_ratio,
)

Calculate the membership fee ratio needed to achieve a gross profit target.

Source code in components/offer_builder/public/api.py
def calculate_membership_fee_ratio_from_gross_profit(
    target_gross_profit_per_member: Decimal,
    current_pure_premium_per_member: Decimal,
    target_loss_ratio: Decimal,
) -> Decimal:
    """Calculate the membership fee ratio needed to achieve a gross profit target."""
    from components.offer_builder.subcomponents.pricer.protected.pricer_api import (
        calculate_membership_fee_ratio_from_gross_profit as _calculate_membership_fee_ratio_from_gross_profit,
    )

    return _calculate_membership_fee_ratio_from_gross_profit(
        target_gross_profit_per_member,
        current_pure_premium_per_member,
        target_loss_ratio,
    )

convert_self_serve_offer_to_amendment_settings

convert_self_serve_offer_to_amendment_settings(offer_id)

Convert a self serve offer to EsHealthAmendmentSettings for Spain proposal builder form pre-filling.

Source code in components/offer_builder/public/api.py
def convert_self_serve_offer_to_amendment_settings(
    offer_id: int,
) -> EsHealthAmendmentSettings:
    """
    Convert a self serve offer to EsHealthAmendmentSettings
    for Spain proposal builder form pre-filling.
    """
    builder_product = BuilderProductRepository().get(offer_id)
    return convert_builder_product_to_amendment_settings(builder_product)

get_builder_coverage_validations

get_builder_coverage_validations(
    builder_product_version_id, validation_targets
)

Get coverage issues for a given builder product version.

Source code in components/offer_builder/public/api.py
def get_builder_coverage_validations(
    builder_product_version_id: int,
    validation_targets: list[ValidationTarget],
) -> list[BuilderProductCoverageValidation]:
    """
    Get coverage issues for a given builder product version.
    """
    app_dependency = get_app_dependency()
    builder_product = BuilderProductRepository().get(
        builder_product_version_id=builder_product_version_id
    )
    builder_product = app_dependency.get_builder_product_for_validation(
        builder_product=builder_product,
        validation_targets=validation_targets,
    )
    country_specific_parameters = app_dependency.get_coverage_validation_parameters(
        builder_product
    )

    return internal_get_builder_coverage_validations(
        builder_product, country_specific_parameters
    )

get_builder_templates_for_account

get_builder_templates_for_account(account_ref)

Get builder templates for an account

Source code in components/offer_builder/public/api.py
def get_builder_templates_for_account(account_ref: str) -> list[BuilderTemplate]:
    """Get builder templates for an account"""
    app_dependency = get_app_dependency()
    country = app_dependency.get_country_code()
    country_specific_filters = app_dependency.get_template_country_specific_filters(
        account_ref
    )
    repository = BuilderTemplateRepository()
    templates = repository.search(
        country=country, country_specific_data_filters=country_specific_filters
    )
    return [BuilderTemplate.from_entity(template) for template in templates]

get_industry_from_ape_code

get_industry_from_ape_code(ape_code)

Get industry name from APE code using the APE code to industry mapping.

Parameters:

Name Type Description Default
ape_code str

The APE code to look up

required

Returns:

Type Description
str | None

The industry name if found, None otherwise

Source code in components/offer_builder/public/api.py
def get_industry_from_ape_code(ape_code: str) -> str | None:
    """
    Get industry name from APE code using the APE code to industry mapping.

    Args:
        ape_code: The APE code to look up

    Returns:
        The industry name if found, None otherwise
    """
    from components.offer_builder.internal.v1.queries.ape_code_to_industry_mapping import (
        get_industry_from_ape_code as _get_industry_from_ape_code,
    )

    industry, _ = _get_industry_from_ape_code(ape_code)
    return industry

get_many_builder_products

get_many_builder_products(builder_product_ids)

Get many builder products by id

Source code in components/offer_builder/public/api.py
def get_many_builder_products(
    builder_product_ids: Iterable[int],
) -> list[BuilderProduct]:
    """Get many builder products by id"""
    repository = BuilderProductRepository()
    internal_builder_products = repository.get_many(builder_product_ids)
    return [
        _get_public_builder_product_from_internal(internal_builder_product)
        for internal_builder_product in internal_builder_products
    ]

get_self_serve_offers

get_self_serve_offers(cmd, commit=True)

Public API for self-serve flow. Returns self-serve offers.

May raise AsyncValueBeingBuiltException on first call while products are being created.

Source code in components/offer_builder/public/api.py
def get_self_serve_offers(
    cmd: GetSelfServeOffersCommand,
    commit: bool = True,
) -> list[SelfServeOffer]:
    """Public API for self-serve flow. Returns self-serve offers.

    May raise AsyncValueBeingBuiltException on first call while products are being created.
    """
    from components.offer_builder.internal.command_handlers.get_self_serve_offers import (
        GetSelfServeOffersCommandHandler,
    )

    offer_ids = GetSelfServeOffersCommandHandler.handle_command(cmd, commit=commit)

    internal_products = BuilderProductRepository().get_many(offer_ids)

    templates = BuilderTemplateRepository().search(
        country=get_app_dependency().get_country_code(),
        builder_product_ids=[
            mandatory(p.product_template_id) for p in internal_products
        ],
    )
    templates_by_product_id = {t.builder_product_id: t for t in templates}

    return [
        SelfServeOffer.from_builder_product(
            product=product,
            template=templates_by_product_id[mandatory(product.product_template_id)],
        )
        for product in internal_products
    ]

get_target_margin

get_target_margin(
    mapping_type,
    mapping_key,
    number_of_employees,
    country_code,
)

Get target margin ratio for a mapping type, key and number of employees.

Source code in components/offer_builder/public/api.py
def get_target_margin(
    mapping_type: str, mapping_key: str, number_of_employees: int, country_code: str
) -> TargetMargin | None:
    """Get target margin ratio for a mapping type, key and number of employees."""
    from components.offer_builder.subcomponents.pricer.protected.pricer_api import (
        get_target_margin as _get_target_margin,
    )

    return _get_target_margin(
        mapping_type=mapping_type,
        mapping_key=mapping_key,
        number_of_employees=number_of_employees,
        country_code=country_code,
    )

handle_create_builder_product_self_amendment_alternatives

handle_create_builder_product_self_amendment_alternatives(
    cmd, commit=True
)

Handle the creation of a renewal alternatives builder product.

Source code in components/offer_builder/public/api.py
def handle_create_builder_product_self_amendment_alternatives(
    cmd: CreateBuilderProductSelfAmendmentAlternativesCommand,
    commit: bool = True,
) -> BuilderProductAlternativesTuple[BuilderProduct]:
    """
    Handle the creation of a renewal alternatives builder product.
    """
    from components.offer_builder.internal.command_handlers.create_builder_product_self_amendment_alternatives import (
        CreateBuilderProductSelfAmendmentAlternativesCommandHandler,
    )

    alternative_builder_products = (
        CreateBuilderProductSelfAmendmentAlternativesCommandHandler.handle_command(
            cmd=cmd, commit=commit
        )
    )

    return BuilderProductAlternativesTuple[BuilderProduct](
        _get_public_builder_product_from_internal(alternative_builder_products[0])
        if alternative_builder_products[0]
        else None,
        _get_public_builder_product_from_internal(alternative_builder_products[1])
        if alternative_builder_products[1]
        else None,
    )

handle_create_manual_demographics_command

handle_create_manual_demographics_command(
    command, commit=True
)

Handles the command to create manual demographics.

Source code in components/offer_builder/public/api.py
def handle_create_manual_demographics_command(
    command: CreateManualDemographicsCommand,
    commit: bool = True,
) -> None:
    """Handles the command to create manual demographics."""
    CreateManualDemographicsCommandHandler.handle_command(
        cmd=command,
        commit=commit,
    )

handle_delete_builder_products_command

handle_delete_builder_products_command(
    command, commit=True
)

Handles the command to delete builder products matching filters.

Source code in components/offer_builder/public/api.py
def handle_delete_builder_products_command(
    command: DeleteBuilderProductsCommand,
    commit: bool = True,
) -> None:
    """Handles the command to delete builder products matching filters."""
    from components.offer_builder.internal.command_handlers.delete_builder_products import (
        DeleteBuilderProductsCommandHandler,
    )

    DeleteBuilderProductsCommandHandler.handle_command(
        cmd=command,
        commit=commit,
    )

handle_offer_builder_command

handle_offer_builder_command(cmd, commit=True)

Handle a command from the offer builder component.

Source code in components/offer_builder/public/api.py
def handle_offer_builder_command(
    cmd: OfferBuilderCommand,
    commit: bool = True,
    # FIXME: return value should probably be smarter than than, to fix when we add cases in the match statement
) -> Offer[str]:
    """
    Handle a command from the offer builder component.
    """
    from components.offer_builder.internal.command_handlers.create_offer_from_builder_product import (
        CreateOfferFromBuilderProductCommandHandler,
    )

    match cmd:
        case CreateOfferFromBuilderProductCommand():
            return CreateOfferFromBuilderProductCommandHandler.handle_command(
                cmd=cmd, commit=commit
            )
        case _:
            raise NotImplementedError(f"Unknown command: {cmd}")

handle_offer_builder_creation_command

handle_offer_builder_creation_command(cmd, commit=True)

Create a builder product from a product change command.

Source code in components/offer_builder/public/api.py
def handle_offer_builder_creation_command(
    cmd: OfferBuilderCreationCommand,
    commit: bool = True,
) -> BuilderProduct:
    """
    Create a builder product from a product change command.
    """
    from components.offer_builder.internal.command_handlers.create_builder_product_from_product_change import (
        CreateBuilderProductFromProductChangeCommandHandler,
    )
    from components.offer_builder.internal.command_handlers.create_builder_product_from_template import (
        CreateBuilderProductFromTemplateCommandHandler,
    )
    from components.offer_builder.internal.command_handlers.duplicate_builder_product import (
        DuplicateBuilderProductCommandHandler,
    )

    match cmd:
        case CreateBuilderProductFromProductChangeCommand():
            builder_product = (
                CreateBuilderProductFromProductChangeCommandHandler.handle_command(
                    cmd=cmd, commit=commit
                )
            )
        case CreateBuilderProductFromLiveProductCommand():
            builder_product = (
                CreateBuilderProductFromLiveProductCommandHandler.handle_command(
                    cmd=cmd, commit=commit
                )
            )
        case DuplicateBuilderProductCommand():
            builder_product = DuplicateBuilderProductCommandHandler.handle_command(
                cmd=cmd, commit=commit
            )
        case CreateBuilderProductFromTemplateCommand():
            builder_product = (
                CreateBuilderProductFromTemplateCommandHandler.handle_command(
                    cmd=cmd, commit=commit
                )
            )
        case _:
            raise NotImplementedError(f"Unknown command: {cmd}")

    return _get_public_builder_product_from_internal(builder_product)

handle_update_builder_products_after_prospect_update_command

handle_update_builder_products_after_prospect_update_command(
    command, commit=True
)

Handles the command to update products after prospect update.

Source code in components/offer_builder/public/api.py
def handle_update_builder_products_after_prospect_update_command(
    command: UpdateBuilderProductsAfterProspectUpdateCommand,
    commit: bool = True,
) -> None:
    """Handles the command to update products after prospect update."""
    UpdateBuilderProductsAfterProspectUpdateCommandHandler.handle_command(
        cmd=command,
        commit=commit,
    )

search_builder_products

search_builder_products(
    builder_product_ids=None,
    builder_scenario_ids=None,
    prospect_refs=None,
    account_refs=None,
    origin=None,
    tags=None,
    is_frozen=None,
    is_priced=None,
    builder_product_max_age_in_days=None,
    include_templates=False,
)

Search builder products by various filters.

Filtering conditions are: - "AND"ed together. - None by default: in this case, the condition is ignored in the search. - When they depend on the version, they are applied to the current_version (latest one).

Returns:

Type Description
list[BuilderProduct]

List of builder products that match the filters.

Source code in components/offer_builder/public/api.py
def search_builder_products(
    builder_product_ids: list[int] | None = None,
    builder_scenario_ids: list[UUID | None] | None = None,
    prospect_refs: list[str] | None = None,
    account_refs: list[str] | None = None,
    origin: BuilderProductOrigin | None = None,
    tags: list[BuilderProductTag] | None = None,
    is_frozen: bool | None = None,
    is_priced: bool | None = None,
    builder_product_max_age_in_days: int | None = None,
    include_templates: bool = False,
) -> list[BuilderProduct]:
    """Search builder products by various filters.

    Filtering conditions are:
    - "AND"ed together.
    - None by default: in this case, the condition is ignored in the search.
    - When they depend on the version, they are applied to the current_version (latest one).

    Returns:
        List of builder products that match the filters.
    """
    builder_product_repository = BuilderProductRepository()
    builder_product_summaries = builder_product_repository.search(
        builder_product_ids=builder_product_ids,
        builder_scenario_ids=builder_scenario_ids,
        prospect_refs=prospect_refs,
        account_refs=account_refs,
        origin=origin,
        tags=tags,
        is_frozen=is_frozen,
        builder_product_max_age_in_days=builder_product_max_age_in_days,
        include_templates=include_templates,
    )
    if is_priced:
        builder_product_summaries = [
            builder_product_summary
            for builder_product_summary in builder_product_summaries
            if builder_product_summary.is_priced
        ]
    internal_builder_products = builder_product_repository.get_many(
        [mandatory(builder_product.id) for builder_product in builder_product_summaries]
    )
    return [
        _get_public_builder_product_from_internal(builder_product)
        for builder_product in internal_builder_products
    ]

search_manual_demographics_for_prospect

search_manual_demographics_for_prospect(prospect_ref)

Search manual demographics for a prospect.

Source code in components/offer_builder/public/api.py
def search_manual_demographics_for_prospect(
    prospect_ref: str,
) -> list[ManualDemographics]:
    """Search manual demographics for a prospect."""
    return ManualDemographicsRepository().search(prospect_refs=[prospect_ref])

validate_plan_readiness

validate_plan_readiness(
    builder_product_id, builder_product_version_id=None
)

Checks that the builder product is ready to be converted into a plan. If not an error is returned.

Currrently checks that: * product is priced * product doesn't have any blockers

Source code in components/offer_builder/public/api.py
def validate_plan_readiness(
    builder_product_id: int,
    builder_product_version_id: int | None = None,
) -> OfferBuilderErrorCode | None:
    """Checks that the builder product is ready to be converted into a plan. If not
    an error is returned.

    Currrently checks that:
    * product is priced
    * product doesn't have any blockers
    """
    from components.offer_builder.internal.business_logic.validation import (
        validate_plan_readiness as internal_validate_plan_readiness,
    )

    repository = BuilderProductRepository()
    builder_product = repository.get(builder_product_id, builder_product_version_id)

    dependency = get_app_dependency()
    builder_product_validation_parameters = (
        dependency.get_builder_product_validation_parameters(builder_product)
    )
    coverage_validation_parameters = dependency.get_coverage_validation_parameters(
        builder_product
    )

    return internal_validate_plan_readiness(
        builder_product,
        builder_product_validation_parameters,
        coverage_validation_parameters,
    )

components.offer_builder.public.commands

create_builder_product_from_competitor_product_command

CreateBuilderProductFromCompetitorProductCommand dataclass

CreateBuilderProductFromCompetitorProductCommand(
    *,
    competitor_product_id,
    origin=BuilderProductOrigin.offer_builder,
    builder_scenario_id=None,
    creator_profile_id=None
)

Command to create a builder product from an existing competitor product.

A CompetitorProduct represents a product from a competitor that was uploaded and parsed. This command copies the competitor's coverage structure to create a new builder product.

builder_scenario_id class-attribute instance-attribute
builder_scenario_id = None
competitor_product_id instance-attribute
competitor_product_id
creator_profile_id class-attribute instance-attribute
creator_profile_id = None
origin class-attribute instance-attribute
origin = offer_builder

create_builder_product_from_live_product_command

CreateBuilderProductFromLiveProductCommand dataclass

CreateBuilderProductFromLiveProductCommand(
    *,
    account_id,
    product_id,
    tags,
    origin,
    builder_scenario_id=None,
    creator_profile_id=None
)

Command to create a builder product from a live product.

Uses the sent account_id and product_id to fetch product changes and use the default one, with the default pricing strategy. See CreateBuilderProductFromProductChangeCommand for more details.

account_id instance-attribute
account_id
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = None
creator_profile_id class-attribute instance-attribute
creator_profile_id = None
origin instance-attribute
origin
product_id instance-attribute
product_id
tags instance-attribute
tags

create_builder_product_from_product_change_command

CreateBuilderProductFromProductChangeCommand dataclass

CreateBuilderProductFromProductChangeCommand(
    *,
    product_change_id,
    tags,
    origin,
    pricing_strategy=ProductChangePricingStrategy.default_price_increase,
    builder_scenario_id=None,
    creator_profile_id=None
)

Command to create a builder product from a product change.

A ProductChange is a renewal concept, that includes: - An existing offer (ProductChange.product_id) to which an account is currently subscribed to and that we aim to renew - A set of new coverage_rules to use for the renewed offer - One or several pricing strategies to chose from

builder_scenario_id class-attribute instance-attribute
builder_scenario_id = None
creator_profile_id class-attribute instance-attribute
creator_profile_id = None
origin instance-attribute
origin
pricing_strategy class-attribute instance-attribute
pricing_strategy = default_price_increase
product_change_id instance-attribute
product_change_id
tags instance-attribute
tags

create_builder_product_from_template_command

CreateBuilderProductFromTemplateCommand dataclass

CreateBuilderProductFromTemplateCommand(
    *,
    template_builder_product_id,
    builder_entities,
    origin,
    tags,
    price_structure=None,
    builder_scenario_id=None,
    creator_profile_id=None,
    account_id,
    target_population=None,
    participation_percent=None
)

Bases: DataClassJsonMixin

Command requesting to create a new BuilderProduct from a template

account_id instance-attribute
account_id
builder_entities instance-attribute
builder_entities
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = None
creator_profile_id class-attribute instance-attribute
creator_profile_id = None
origin instance-attribute
origin
participation_percent class-attribute instance-attribute
participation_percent = None
price_structure class-attribute instance-attribute
price_structure = None
tags instance-attribute
tags
target_population class-attribute instance-attribute
target_population = None
template_builder_product_id instance-attribute
template_builder_product_id

create_builder_product_self_amendment_alternatives_command

CreateBuilderProductSelfAmendmentAlternativesCommand dataclass

CreateBuilderProductSelfAmendmentAlternativesCommand(
    *,
    reference_renewal_builder_product_id,
    with_lower_premiumness,
    with_higher_premiumness,
    keep_reference_builder_product=True
)

Bases: DataClassJsonMixin

Command to create renewal alternatives for builder product based on a reference product.

keep_reference_builder_product class-attribute instance-attribute
keep_reference_builder_product = True
reference_renewal_builder_product_id instance-attribute
reference_renewal_builder_product_id
with_higher_premiumness instance-attribute
with_higher_premiumness
with_lower_premiumness instance-attribute
with_lower_premiumness

create_manual_demographics

CreateManualDemographicsCommand dataclass

CreateManualDemographicsCommand(
    *,
    account_ref,
    prospect_ref,
    number_of_employees=None,
    number_of_covered_employees=None,
    average_age=None,
    number_of_male=None,
    number_of_cadre=None,
    number_of_partners=None,
    number_of_children=None,
    number_of_first_children=None,
    number_of_first_two_children=None,
    number_of_single=None,
    number_of_ani=None,
    number_of_option_taker=None,
    skip_update_builder_products=False,
    country=None
)

Bases: DataClassJsonMixin

Command to create a manual demographics entry.

account_ref instance-attribute
account_ref
average_age class-attribute instance-attribute
average_age = None
country class-attribute instance-attribute
country = None
from_entity classmethod
from_entity(
    manual_demographics,
    account_ref,
    skip_update_builder_products=False,
)

Create a command from a ManualDemographics entity.

Parameters:

Name Type Description Default
manual_demographics ManualDemographics

The entity to convert from

required
account_ref str | None

Account reference (required since it's not stored in the entity)

required
skip_update_builder_products bool

Whether to skip updating builder products

False

Returns:

Type Description
Self

CreateManualDemographicsCommand instance

Source code in components/offer_builder/public/commands/create_manual_demographics.py
@classmethod
def from_entity(
    cls,
    manual_demographics: ManualDemographics,
    account_ref: str | None,
    skip_update_builder_products: bool = False,
) -> Self:
    """
    Create a command from a ManualDemographics entity.

    Args:
        manual_demographics: The entity to convert from
        account_ref: Account reference (required since it's not stored in the entity)
        skip_update_builder_products: Whether to skip updating builder products

    Returns:
        CreateManualDemographicsCommand instance
    """
    from components.offer_builder.internal.country_specific.fr.enums.fr_target_population import (
        FrTargetPopulation,
    )

    cadre_ratio = None
    if manual_demographics.target_population_ratios is not None:
        cadre_ratio = manual_demographics.target_population_ratios.get(
            FrTargetPopulation.cadres
        )

    return cls(
        account_ref=account_ref,
        country=manual_demographics.country,
        prospect_ref=manual_demographics.prospect_ref,
        number_of_employees=manual_demographics.number_of_primaries,
        number_of_covered_employees=manual_demographics.number_of_primaries,
        average_age=manual_demographics.average_age,
        number_of_male=denormalize_ratio(
            manual_demographics.male_ratio, manual_demographics.number_of_primaries
        ),
        number_of_cadre=denormalize_ratio(
            cadre_ratio, manual_demographics.number_of_primaries
        ),
        number_of_partners=denormalize_ratio(
            manual_demographics.partner_ratio,
            manual_demographics.number_of_primaries,
        ),
        number_of_children=denormalize_ratio(
            manual_demographics.child_ratio, manual_demographics.number_of_primaries
        ),
        number_of_first_children=denormalize_ratio(
            manual_demographics.first_child_ratio,
            manual_demographics.number_of_primaries,
        ),
        number_of_first_two_children=denormalize_ratio(
            manual_demographics.first_two_children_ratio,
            manual_demographics.number_of_primaries,
        ),
        number_of_single=denormalize_ratio(
            manual_demographics.single_ratio,
            manual_demographics.number_of_primaries,
        ),
        number_of_ani=denormalize_ratio(
            manual_demographics.non_paying_ratio,
            manual_demographics.number_of_primaries,
        ),
        number_of_option_taker=denormalize_ratio(
            manual_demographics.option_taker_ratio,
            manual_demographics.number_of_primaries,
        ),
        skip_update_builder_products=skip_update_builder_products,
    )
number_of_ani class-attribute instance-attribute
number_of_ani = None
number_of_cadre class-attribute instance-attribute
number_of_cadre = None
number_of_children class-attribute instance-attribute
number_of_children = None
number_of_covered_employees class-attribute instance-attribute
number_of_covered_employees = None
number_of_employees class-attribute instance-attribute
number_of_employees = None
number_of_first_children class-attribute instance-attribute
number_of_first_children = None
number_of_first_two_children class-attribute instance-attribute
number_of_first_two_children = None
number_of_male class-attribute instance-attribute
number_of_male = None
number_of_option_taker class-attribute instance-attribute
number_of_option_taker = None
number_of_partners class-attribute instance-attribute
number_of_partners = None
number_of_single class-attribute instance-attribute
number_of_single = None
prospect_ref instance-attribute
prospect_ref
skip_update_builder_products class-attribute instance-attribute
skip_update_builder_products = False
to_entity
to_entity(country)

Transforms the command to a ManualDemographics entity.

Source code in components/offer_builder/public/commands/create_manual_demographics.py
def to_entity(self, country: OfferBuilderSupportedCountry) -> ManualDemographics:
    """Transforms the command to a ManualDemographics entity."""
    from components.offer_builder.internal.country_specific.fr.enums.fr_target_population import (
        FrTargetPopulation,
    )

    number_of_primaries = (
        self.number_of_covered_employees or self.number_of_employees
    )

    cadre_ratio = normalize_ratio(self.number_of_cadre, number_of_primaries)
    target_population_ratios: dict[str | None, float] | None = None
    if cadre_ratio is not None:
        target_population_ratios = {
            FrTargetPopulation.cadres.value: cadre_ratio,
            FrTargetPopulation.non_cadres.value: 1 - cadre_ratio,
        }

    child_ratio = normalize_ratio(self.number_of_children, number_of_primaries)

    first_child_ratio = None
    if self.number_of_first_children is not None:
        first_child_ratio = normalize_ratio(
            self.number_of_first_children, number_of_primaries
        )

    first_two_children_ratio = None
    if self.number_of_first_two_children is not None:
        first_two_children_ratio = normalize_ratio(
            self.number_of_first_two_children, number_of_primaries
        )

    return ManualDemographics(
        prospect_ref=self.prospect_ref,
        version=None,
        number_of_primaries=number_of_primaries,
        number_of_covered_employees=self.number_of_covered_employees,
        average_age=self.average_age,
        male_ratio=normalize_ratio(self.number_of_male, number_of_primaries),
        target_population_ratios=target_population_ratios,
        partner_ratio=normalize_ratio(self.number_of_partners, number_of_primaries),
        child_ratio=child_ratio,
        non_paying_ratio=normalize_ratio(self.number_of_ani, number_of_primaries),
        coverage_taker_ratios=None,  # TODO: allow storing in the database
        country=country,
        single_ratio=normalize_ratio(self.number_of_single, number_of_primaries),
        first_child_ratio=first_child_ratio,
        first_two_children_ratio=first_two_children_ratio,
        option_taker_ratio=normalize_ratio(
            self.number_of_option_taker, number_of_primaries
        ),
    )

create_offer_from_builder_product_command

CreateOfferFromBuilderProductCommand dataclass

CreateOfferFromBuilderProductCommand(
    *, builder_product_id, builder_product_version_id=None
)

Command requesting to create an offer from a BuilderProduct.

Parameters:

Name Type Description Default
builder_product_id int | None

The ID of the BuilderProduct to create the offer from.

required
builder_product_version_id int | None

Optional - The ID of the BuilderProductVersion to

None
builder_product_id instance-attribute
builder_product_id
builder_product_version_id class-attribute instance-attribute
builder_product_version_id = None

delete_builder_products_command

DeleteBuilderProductsCommand dataclass

DeleteBuilderProductsCommand(
    *,
    builder_product_ids=None,
    account_refs=None,
    origin=None,
    delete_empty_scenarios=False
)

Bases: DataClassJsonMixin

Command requesting to delete multiple builder products matching filters.

Filters are AND-ed. None means "not filtered".

Parameters:

Name Type Description Default
builder_product_ids list[int] | None

Specific product IDs to delete.

None
account_refs list[str] | None

Delete products belonging to these accounts.

None
origin BuilderProductOrigin | None

Delete products with this origin.

None
is_frozen

Filter by frozen status.

required
delete_empty_scenarios bool

If True, delete scenarios left empty after product deletion.

False
account_refs class-attribute instance-attribute
account_refs = None
builder_product_ids class-attribute instance-attribute
builder_product_ids = None
delete_empty_scenarios class-attribute instance-attribute
delete_empty_scenarios = False
origin class-attribute instance-attribute
origin = None

duplicate_builder_product_command

DuplicateBuilderProductCommand dataclass

DuplicateBuilderProductCommand(
    *,
    builder_product_id,
    builder_product_version_id=None,
    creator_profile_id=None,
    account_ref=NOT_SET,
    target_population=NOT_SET,
    builder_scenario_id=NOT_SET,
    origin=NOT_SET,
    tags=NOT_SET,
    display_name=NOT_SET,
    force_new_prospect_with_same_price=NOT_SET
)

Command requesting to duplicate a BuilderProduct

Parameters:

Name Type Description Default
builder_product_id int

The ID of the BuilderProduct to duplicate.

required
creator_profile_id UUID | None

The profile ID of the creator of the duplicated product.

None
account_ref class-attribute instance-attribute
account_ref = NOT_SET
builder_product_id instance-attribute
builder_product_id
builder_product_version_id class-attribute instance-attribute
builder_product_version_id = None
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = NOT_SET
creator_profile_id class-attribute instance-attribute
creator_profile_id = None
display_name class-attribute instance-attribute
display_name = NOT_SET
force_new_prospect_with_same_price class-attribute instance-attribute
force_new_prospect_with_same_price = NOT_SET
origin class-attribute instance-attribute
origin = NOT_SET
tags class-attribute instance-attribute
tags = NOT_SET
target_population class-attribute instance-attribute
target_population = NOT_SET

get_self_serve_offers_command

GetSelfServeOffersCommand dataclass

GetSelfServeOffersCommand(*, input, use_async=True)

Command requesting to get or create self-serve offers.

input instance-attribute
input
use_async class-attribute instance-attribute
use_async = True

update_builder_products_after_prospect_update_command

UpdateBuilderProductsAfterProspectUpdateCommand dataclass

UpdateBuilderProductsAfterProspectUpdateCommand(
    *, prospect_ref, previous_alan_account_id
)

Bases: DataClassJsonMixin

Command requesting to update builder products after a prospect update.

When a prospect moves to another account, this command finds all builder products associated with the prospect and updates their account_ref if all targets are in the new account.

If not all prospects belong to the new account the product is not updated.

Parameters:

Name Type Description Default
prospect_ref str

The ID of the prospect that was updated.

required
previous_alan_account_id str

The previous account ID of the prospect (before update).

required
previous_alan_account_id instance-attribute
previous_alan_account_id
prospect_ref instance-attribute
prospect_ref

components.offer_builder.public.constants

PRUDENCE_MARGIN_RATE module-attribute

PRUDENCE_MARGIN_RATE = 0.03

UNLIMITED_NTH_CHILDREN_FREE module-attribute

UNLIMITED_NTH_CHILDREN_FREE = 10

components.offer_builder.public.country_specific

be

dependencies

offer_builder_dependency module-attribute
offer_builder_dependency = BeOfferBuilderDependency()

es

dependencies

offer_builder_dependency module-attribute
offer_builder_dependency = EsOfferBuilderDependency()

self_serve

EsHealthSelfServeCustomData dataclass
EsHealthSelfServeCustomData()

Bases: SelfServeCustomData

ES health-specific self-serve input data.

to_builder_prospect_kwargs
to_builder_prospect_kwargs()

Return kwargs for BuilderProspectAPISchema from ES-specific data.

Source code in components/offer_builder/public/country_specific/es/self_serve.py
def to_builder_prospect_kwargs(self) -> dict[str, Any]:
    """Return kwargs for BuilderProspectAPISchema from ES-specific data."""
    return {}

fr

convert_coverage_rules

convert_builder_coverage_to_fr_coverage_rules
convert_builder_coverage_to_fr_coverage_rules(
    builder_coverage,
)

Convert a BuilderCoverage SQLAlchemy model to French local coverage rules.

This public method retrieves the guarantee catalog and converts the global coverage rules from a BuilderCoverage into French-specific coverage rules and selected care types.

/!\ This method should only be used to migrate legacy code relying on deprecated_coverage_rules.

Parameters:

Name Type Description Default
builder_coverage BuilderCoverage

The BuilderCoverage SQLAlchemy model to convert

required

Returns:

Type Description
list[BuilderProductCoverageRule]

A tuple containing:

list[BuilderProductSelectedCareType]
  • List of BuilderProductCoverageRule models (FR coverage rules)
tuple[list[BuilderProductCoverageRule], list[BuilderProductSelectedCareType]]
  • List of BuilderProductSelectedCareType models (FR selected care types)

Raises:

Type Description
FrCoverageRuleConversionError

If the conversion fails due to mapping issues

Source code in components/offer_builder/public/country_specific/fr/convert_coverage_rules.py
@request_cached()
def convert_builder_coverage_to_fr_coverage_rules(
    builder_coverage: BuilderCoverage,
) -> tuple[list[BuilderProductCoverageRule], list[BuilderProductSelectedCareType]]:
    r"""
    Convert a BuilderCoverage SQLAlchemy model to French local coverage rules.

    This public method retrieves the guarantee catalog and converts the global
    coverage rules from a BuilderCoverage into French-specific coverage rules
    and selected care types.

    /!\ This method should only be used to migrate legacy code relying on deprecated_coverage_rules.

    Args:
        builder_coverage: The BuilderCoverage SQLAlchemy model to convert

    Returns:
        A tuple containing:
        - List of BuilderProductCoverageRule models (FR coverage rules)
        - List of BuilderProductSelectedCareType models (FR selected care types)

    Raises:
        FrCoverageRuleConversionError: If the conversion fails due to mapping issues
    """
    if not builder_coverage.id:
        raise ValueError(
            "BuilderCoverage must have an id to convert to FR coverage rules"
        )

    guarantee_catalog = get_guarantee_catalog()

    # Import here to get the BuilderCoverageDefinition from the model's coverage_rules
    from components.offer_builder.internal.models.mappers.builder_coverage import (
        BuilderCoverageMapper,
    )

    # Convert the SQLAlchemy model to entity to get the BuilderCoverageDefinition
    builder_coverage_entity = BuilderCoverageMapper.to_entity(builder_coverage)

    return internal_convert_coverage_from_global_to_local(
        guarantee_catalog=guarantee_catalog,
        builder_coverage_id=builder_coverage.id,
        coverage=builder_coverage_entity,
    )

dependencies

offer_builder_dependency module-attribute
offer_builder_dependency = FrOfferBuilderDependency()

presenters

validation
BuilderProductValidationFrAdditionalArgs dataclass
BuilderProductValidationFrAdditionalArgs(
    *,
    ccn_codes=None,
    collective_agreement_ids=None,
    is_child=None,
    panier_maitrise_percent_reimbursement_ss=None,
    panier_libre_percent_reimbursement_ss=None,
    panier_maitrise_percent_reimbursement_ss_after_count_limit_reached=None,
    panier_libre_percent_reimbursement_ss_after_count_limit_reached=None,
    contact_lenses_reimbursed_cumulative_limit=None,
    contact_lenses_inside_network_non_reimbursed_cumulative_limit=None,
    contact_lenses_outside_network_non_reimbursed_cumulative_limit=None,
    glasses_2_simple_inside_network_max_reimbursement_per_care=None,
    glasses_2_simple_outside_network_max_reimbursement_per_care=None,
    optam_value=None,
    non_optam_value=None,
    cost_estimates_total_cents=None,
    cost_estimates_total_cents_previous_coverage=None,
    scope=None
)

Bases: BuilderProductValidationCountrySpecificAdditionalArgs

FR-specific additional arguments for builder product validations.

ccn_codes class-attribute instance-attribute
ccn_codes = None
collective_agreement_ids class-attribute instance-attribute
collective_agreement_ids = None
contact_lenses_inside_network_non_reimbursed_cumulative_limit class-attribute instance-attribute
contact_lenses_inside_network_non_reimbursed_cumulative_limit = (
    None
)
contact_lenses_outside_network_non_reimbursed_cumulative_limit class-attribute instance-attribute
contact_lenses_outside_network_non_reimbursed_cumulative_limit = (
    None
)
contact_lenses_reimbursed_cumulative_limit class-attribute instance-attribute
contact_lenses_reimbursed_cumulative_limit = None
cost_estimates_total_cents class-attribute instance-attribute
cost_estimates_total_cents = None
cost_estimates_total_cents_previous_coverage class-attribute instance-attribute
cost_estimates_total_cents_previous_coverage = None
glasses_2_simple_inside_network_max_reimbursement_per_care class-attribute instance-attribute
glasses_2_simple_inside_network_max_reimbursement_per_care = (
    None
)
glasses_2_simple_outside_network_max_reimbursement_per_care class-attribute instance-attribute
glasses_2_simple_outside_network_max_reimbursement_per_care = (
    None
)
is_child class-attribute instance-attribute
is_child = None
non_optam_value class-attribute instance-attribute
non_optam_value = None
optam_value class-attribute instance-attribute
optam_value = None
panier_libre_percent_reimbursement_ss class-attribute instance-attribute
panier_libre_percent_reimbursement_ss = None
panier_libre_percent_reimbursement_ss_after_count_limit_reached class-attribute instance-attribute
panier_libre_percent_reimbursement_ss_after_count_limit_reached = (
    None
)
panier_maitrise_percent_reimbursement_ss class-attribute instance-attribute
panier_maitrise_percent_reimbursement_ss = None
panier_maitrise_percent_reimbursement_ss_after_count_limit_reached class-attribute instance-attribute
panier_maitrise_percent_reimbursement_ss_after_count_limit_reached = (
    None
)
scope class-attribute instance-attribute
scope = None
BuilderProductValidationFrScope

Bases: AlanBaseEnum

ccn_compliance class-attribute instance-attribute
ccn_compliance = 'ccn_compliance'
responsibility class-attribute instance-attribute
responsibility = 'responsibility'

self_serve

FrHealthSelfServeCustomData dataclass
FrHealthSelfServeCustomData(
    *, ccn_code, ape_code=None, company_creation_year=None
)

Bases: SelfServeCustomData

FR health-specific self-serve input data.

ape_code class-attribute instance-attribute
ape_code = None
ccn_code instance-attribute
ccn_code
company_creation_year class-attribute instance-attribute
company_creation_year = None
to_builder_prospect_kwargs
to_builder_prospect_kwargs()

Return kwargs for BuilderProspectAPISchema from FR-specific data.

Source code in components/offer_builder/public/country_specific/fr/self_serve.py
def to_builder_prospect_kwargs(self) -> dict[str, Any]:
    """Return kwargs for BuilderProspectAPISchema from FR-specific data."""
    return {
        "ccn_code": self.ccn_code,
        "ape_code": self.ape_code,
        "company_creation_year": self.company_creation_year,
    }

test_helpers

mocks
mock_combinatory_guarantees
mock_combinatory_guarantees(mocker)

Strip mandatory flag from combinatory guarantees — allows tests relying on conversion to run without defining glasses guarantees.

Source code in components/offer_builder/public/country_specific/fr/test_helpers/mocks.py
def mock_combinatory_guarantees(mocker: MockerFixture) -> None:
    """Strip mandatory flag from combinatory guarantees —
    allows tests relying on conversion to run without defining
    glasses guarantees.
    """
    from components.offer_builder.internal.country_specific.fr.coverage_translation.guarantee_mappings import (
        COMBINATORY_GUARANTEES,
        CombinatoryGuarantee,
    )

    mocker.patch(
        "components.offer_builder.internal.country_specific.fr.coverage_translation.convert_coverage_from_global_to_local.COMBINATORY_GUARANTEES",
        {
            k: CombinatoryGuarantee(alternatives=v.alternatives)
            for k, v in COMBINATORY_GUARANTEES.items()
        },
    )

components.offer_builder.public.dependencies

COMPONENT_NAME module-attribute

COMPONENT_NAME = 'offer_builder'

OfferBuilderDependency

Bases: OfferBuilderPricerDependency, OfferBuilderManualDemographicsDependency, ABC, Generic[PriceTarget, PriceComponentType]

Abstract class defining the dependencies needed by the Offer Builder component.

create_amendment_builder_product abstractmethod

create_amendment_builder_product(
    product_change_id,
    product_type,
    builder_scenario_id,
    creator_profile_id,
)

Create an amendment builder product from the given command. This method should raise if the product type is not supported in the country.

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def create_amendment_builder_product(
    self,
    product_change_id: UUID,
    product_type: BuilderProductType,
    builder_scenario_id: UUID | None,
    creator_profile_id: UUID | None,
) -> str:
    """Create an amendment builder product from the given command.
    This method should raise if the product type is not supported in the country.
    """

get_all_products

get_all_products(
    account_ref=NOT_SET, builder_scenario_ids=None
)

Retrieve all products for the given account (and optionally scenario). If this method needs to be overridden, it's recommended calling super().get_all_products() and returning those products along the additional local products to be handled in the Offer Builder.

Parameters:

Name Type Description Default
account_ref NotSet[str]

The identifier of the account to retrieve products for.

NOT_SET
builder_scenario_ids list[UUID | None] | None

The identifiers of the scenarios to retrieve products for. If the element is NOT_SET, returns all products, whatever the account. If None, returns the products not associated to any account.

None
Source code in components/offer_builder/public/dependencies.py
def get_all_products(
    self,
    account_ref: NotSet[str] = NOT_SET,
    builder_scenario_ids: list[UUID | None] | None = None,
) -> list[ProductSummary]:
    """Retrieve all products for the given account (and optionally scenario).
    If this method needs to be overridden, it's recommended calling super().get_all_products()
    and returning those products along the additional local products to be handled in the Offer
    Builder.

    Args:
        account_ref: The identifier of the account to retrieve products for.
        builder_scenario_ids: The identifiers of the scenarios to retrieve products for.
            If the element is NOT_SET, returns all products, whatever the account. If None, returns
            the products not associated to any account.
    """
    from components.offer_builder.internal.repositories.builder_product_repository import (
        BuilderProductRepository,
    )

    builder_product_summaries = BuilderProductRepository().search(
        builder_scenario_ids=builder_scenario_ids,
        account_refs=[account_ref] if is_set(account_ref) else None,
    )
    return [
        ProductSummary.from_builder_product_summary(p)
        for p in builder_product_summaries
    ]

get_builder_product_coverage_constraint_violations abstractmethod

get_builder_product_coverage_constraint_violations(
    builder_product, country_specific_parameters
)

Returns coverage constraint violations for all coverages of a given builder product

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_builder_product_coverage_constraint_violations(
    self,
    builder_product: BuilderProduct,
    country_specific_parameters: CoverageValidationParameters,
) -> list[BuilderProductCoverageValidation]:
    """Returns coverage constraint violations for all coverages of a given builder product"""

get_builder_product_coverage_infos

get_builder_product_coverage_infos(
    builder_product, country_specific_parameters
)

Returns coverage infos for all coverages of a given builder product

Source code in components/offer_builder/public/dependencies.py
def get_builder_product_coverage_infos(
    self,
    builder_product: BuilderProduct,  # noqa: ARG002
    country_specific_parameters: CoverageValidationParameters,  # noqa: ARG002
) -> list[BuilderProductCoverageValidation]:
    """Returns coverage infos for all coverages of a given builder product"""
    return []

get_builder_product_for_validation

get_builder_product_for_validation(
    builder_product, validation_targets
)

Updates the builder product metadata for validation.

Source code in components/offer_builder/public/dependencies.py
def get_builder_product_for_validation(
    self,
    builder_product: BuilderProduct,
    validation_targets: list[ValidationTarget],  # noqa: ARG002
) -> BuilderProduct:
    """Updates the builder product metadata for validation."""
    return deepcopy(builder_product)

get_builder_product_issues abstractmethod

get_builder_product_issues(builder_product)

Returns country specific issues for the given builder product

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_builder_product_issues(
    self, builder_product: BuilderProduct
) -> list[BuilderProductValidation]:
    """Returns country specific issues for the given builder product"""

get_builder_product_min_participation

get_builder_product_min_participation(builder_product)

Returns the minimum participation allowed for the given builder product

Source code in components/offer_builder/public/dependencies.py
def get_builder_product_min_participation(
    self,
    builder_product: BuilderProduct,  # noqa: ARG002
) -> BuilderProductParticipationPercent:
    """Returns the minimum participation allowed for the given builder product"""
    return BuilderProductParticipationPercent(
        primary=0,
        partner=0,
        child=0,
    )

get_builder_product_validation_parameters

get_builder_product_validation_parameters(builder_product)

Returns the parameters for builder product validation

Source code in components/offer_builder/public/dependencies.py
def get_builder_product_validation_parameters(
    self, builder_product: BuilderProduct
) -> BuilderProductValidationParameters:
    """Returns the parameters for builder product validation"""
    from components.offer_builder.subcomponents.manual_demographics.protected.manual_demographics import (
        get_manual_demographics_for_targets,
    )

    builder_prospects = list(
        CompositeAccountRepository().get_or_create_builder_prospects(
            account_id=UUID(mandatory(builder_product.account_ref)),
        )
    )
    manual_demographics_by_target_id = get_manual_demographics_for_targets(
        builder_targets=builder_product.builder_targets, validate=False
    )

    return BuilderProductValidationParameters(
        builder_prospects=builder_prospects,
        manual_demographics_by_builder_target_id=manual_demographics_by_target_id,
    )

get_competitor_product_closest_template_filters

get_competitor_product_closest_template_filters(
    competitor_product, builder_targets
)

Returns the country specific filters to apply when retrieving the closest builder template for a competitor product.

Source code in components/offer_builder/public/dependencies.py
def get_competitor_product_closest_template_filters(
    self,
    competitor_product: CompetitorProduct,  # noqa: ARG002
    builder_targets: list[BuilderTarget],  # noqa: ARG002
) -> dict[str, set[str]]:
    """
    Returns the country specific filters to apply when retrieving the closest builder template for a competitor product.
    """
    return {}

get_country_code abstractmethod

get_country_code()

Returns the country code

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_country_code(self) -> OfferBuilderSupportedCountry:
    """Returns the country code"""

get_coverage_validation_parameters abstractmethod

get_coverage_validation_parameters(builder_product)

Returns the country-specific parameters for coverage validation

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_coverage_validation_parameters(
    self, builder_product: BuilderProduct
) -> CoverageValidationParameters:
    """Returns the country-specific parameters for coverage validation"""

get_denoised_guarantee_coverage_validations

get_denoised_guarantee_coverage_validations(
    builder_product, coverage_validations
)

Denoise coverage validations at guarantee level by removing redundancy and keeping the most constraining ones.

Source code in components/offer_builder/public/dependencies.py
def get_denoised_guarantee_coverage_validations(
    self,
    builder_product: BuilderProduct,  # noqa: ARG002
    coverage_validations: list[BuilderProductCoverageValidation],
) -> list[BuilderProductCoverageValidation]:
    """
    Denoise coverage validations at guarantee level by removing redundancy and keeping the most constraining ones.
    """
    return coverage_validations

get_eligibility_item_display_names abstractmethod

get_eligibility_item_display_names(eligibility_item_refs)

Returns the display names of the eligibility items.

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_eligibility_item_display_names(
    self, eligibility_item_refs: list[str]
) -> list[str]:
    """Returns the display names of the eligibility items."""

get_offer_payload abstractmethod

get_offer_payload(builder_product)

Get the offer payload from the given builder_product.

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_offer_payload(
    self, builder_product: BuilderProduct
) -> OfferPayload[PriceTarget, PriceComponentType]:
    """
    Get the offer payload from the given builder_product.
    """

get_plan_builder_product_and_version_for_company_on abstractmethod

get_plan_builder_product_and_version_for_company_on(
    company_ref, on_date
)

Returns the builder product ID and version ID for the current active health insurance plan of the given company on the specified date.

Parameters:

Name Type Description Default
company_ref str

The string identifier of the company to look up

required
on_date date

The date to check for active subscription

required

Returns:

Type Description
tuple[int, int] | None

A tuple of (builder_product_id, builder_product_version_id) if an active plan is found,

tuple[int, int] | None

None otherwise.

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_plan_builder_product_and_version_for_company_on(
    self, company_ref: str, on_date: date
) -> tuple[int, int] | None:
    """
    Returns the builder product ID and version ID for the current active health insurance plan
    of the given company on the specified date.

    Args:
        company_ref: The string identifier of the company to look up
        on_date: The date to check for active subscription

    Returns:
        A tuple of (builder_product_id, builder_product_version_id) if an active plan is found,
        None otherwise.
    """

get_product_status

get_product_status(product_id, product_type)

Returns the status (export ready, proposal ready) for a given product.

Parameters:

Name Type Description Default
product_id str

The identifier of the product.

required
product_type str

The type of the product (e.g., 'health', 'prevoyance').

required

Returns:

Type Description
ProductStatus

ProductStatus with is_export_ready and is_proposal_ready flags.

Source code in components/offer_builder/public/dependencies.py
def get_product_status(self, product_id: str, product_type: str) -> ProductStatus:
    """Returns the status (export ready, proposal ready) for a given product.

    Args:
        product_id: The identifier of the product.
        product_type: The type of the product (e.g., 'health', 'prevoyance').

    Returns:
        ProductStatus with is_export_ready and is_proposal_ready flags.
    """
    if product_type == HEALTH_PRODUCT_TYPE:
        from components.offer_builder.public.api import validate_plan_readiness

        is_proposal_ready = validate_plan_readiness(int(product_id)) is None
        return ProductStatus(
            product_id=int(product_id),
            is_export_ready=is_proposal_ready,
            is_proposal_ready=is_proposal_ready,
        )
    raise ValueError(f"Unsupported product_type: {product_type}")

get_template_country_specific_filters

get_template_country_specific_filters(account_ref)

Returns the filters to apply when retrieving builder templates.

Parameters:

Name Type Description Default
account_ref str

The account reference to get filters for.

required

Returns:

Name Type Description
dict[str, set[str]] | None

A dictionary of filter keys and their corresponding values to match against country_specific_data.

dict[str, set[str]] | None

All templates matching one of the provided filters will be returned.

dict[str, set[str]] | None

To disable filtering, return None (default behavior).

Examples dict[str, set[str]] | None
  • filters {"key1": ["value1", "value2"], "key2": ["value3"], "key3": []} should return templates that either have
    • key1 (of country_specific_data) set to value1 or value2 OR
    • key2 set to value3
  • filters {} should not return any templates (as it does not provide any possible match)
Source code in components/offer_builder/public/dependencies.py
def get_template_country_specific_filters(
    self,
    account_ref: str,  # noqa: ARG002
) -> dict[str, set[str]] | None:
    """Returns the filters to apply when retrieving builder templates.

    Args:
        account_ref: The account reference to get filters for.

    Returns:
        A dictionary of filter keys and their corresponding values to match against country_specific_data.
        All templates matching one of the provided filters will be returned.
        To disable filtering, return None (default behavior).

        Examples:
            - filters {"key1": ["value1", "value2"], "key2": ["value3"], "key3": []} should return templates that either have
                - key1 (of country_specific_data) set to value1 or value2
                OR
                - key2 set to value3
            - filters {} should not return any templates (as it does not provide any possible match)
    """
    return None

is_adding_coverage_allowed

is_adding_coverage_allowed(builder_product)

Check if a new coverage can be added to this product.

Source code in components/offer_builder/public/dependencies.py
def is_adding_coverage_allowed(
    self,
    builder_product: BuilderProduct,  # noqa: ARG002
) -> bool:
    """Check if a new coverage can be added to this product."""
    return True

is_removing_coverage_allowed

is_removing_coverage_allowed(
    builder_product, coverage_index
)

Check if a coverage can be removed from this product.

Source code in components/offer_builder/public/dependencies.py
def is_removing_coverage_allowed(
    self,
    builder_product: BuilderProduct,
    coverage_index: int,  # noqa: ARG002
) -> bool:
    """Check if a coverage can be removed from this product."""
    return len(builder_product.coverages) > 1

is_template_available_for_self_serve

is_template_available_for_self_serve(
    template_name, country_specific_data
)

Determine if a builder template should be available for self-serve.

Parameters:

Name Type Description Default
template_name str

The template name identifier.

required
country_specific_data dict[str, Any]

Country-specific metadata of the template.

required

Returns:

Type Description
bool

True if the template should be available for self-serve flows.

Source code in components/offer_builder/public/dependencies.py
def is_template_available_for_self_serve(
    self,
    template_name: str,  # noqa: ARG002
    country_specific_data: dict[str, Any],  # noqa: ARG002
) -> bool:
    """Determine if a builder template should be available for self-serve.

    Args:
        template_name: The template name identifier.
        country_specific_data: Country-specific metadata of the template.

    Returns:
        True if the template should be available for self-serve flows.
    """
    return False

list_product_changes

list_product_changes(account_id)

Retrieve all product changes for the given account. If this method needs to be overridden, it's recommended calling super().list_product_changes() and returning those products along the additional local product changes to be handled in the Offer Builder.

Parameters:

Name Type Description Default
account_id UUID

The identifier of the account to retrieve product changes for.

required
Source code in components/offer_builder/public/dependencies.py
def list_product_changes(
    self,
    account_id: UUID,
) -> list[ProductChangePresenter]:
    """Retrieve all product changes for the given account.
    If this method needs to be overridden, it's recommended calling super().list_product_changes()
    and returning those products along the additional local product changes to be handled in the Offer
    Builder.

    Args:
        account_id: The identifier of the account to retrieve product changes for.
    """
    from components.offer_builder.internal.repositories.turing_product_change_repository import (
        TuringProductChangeRepository,
    )

    turing_product_change_presenters: list[ProductChangePresenter] = []
    turing_product_changes = TuringProductChangeRepository().search(
        account_ids=[account_id],
    )
    for turing_product_change in turing_product_changes:
        # TODO 2025-09-31 @anatole.juge: We're querying the DB in a loop, it's bad
        # We don't trust the builder product's builder targets as they might be outdated
        # Some older products have no builder targets at all
        builder_targets = get_current_builder_targets(
            product_id=turing_product_change.product_id,
            account_id=turing_product_change.account_id,
        )
        turing_product_change_presenters.append(
            ProductChangePresenter.from_turing_product_change_entity(
                turing_product_change=turing_product_change,
                targets=[
                    ProductChangeTargetPresenter.from_builder_target_entity(
                        builder_target
                    )
                    for builder_target in builder_targets
                ],
            )
        )
    return turing_product_change_presenters

make_coverage_list_for_template

make_coverage_list_for_template(
    coverage_definition_by_index, coverage_indexes
)

Returns a list of builder coverages from their definitions when creating a builder template.

Source code in components/offer_builder/public/dependencies.py
def make_coverage_list_for_template(
    self,
    coverage_definition_by_index: dict[int, BuilderCoverageDefinition],  # noqa: ARG002
    coverage_indexes: list[int],  # noqa: ARG002
) -> list[BuilderCoverage]:
    """
    Returns a list of builder coverages from their definitions when creating a builder template.
    """
    return []

on_builder_scenario_duplicated

on_builder_scenario_duplicated(
    source_builder_scenario_id, new_builder_scenario_id
)

Called when a builder scenario is duplicated.

Parameters:

Name Type Description Default
source_builder_scenario_id UUID

The identifier of the source builder scenario.

required
new_builder_scenario_id UUID

The identifier of the new builder scenario.

required
Source code in components/offer_builder/public/dependencies.py
def on_builder_scenario_duplicated(
    self,
    source_builder_scenario_id: UUID,  # noqa: ARG002
    new_builder_scenario_id: UUID,  # noqa: ARG002
) -> None:
    """Called when a builder scenario is duplicated.

    Args:
        source_builder_scenario_id: The identifier of the source builder scenario.
        new_builder_scenario_id: The identifier of the new builder scenario.
    """
    return

on_builder_scenarios_deleted

on_builder_scenarios_deleted(
    builder_scenario_ids,
    builder_product_ids_in_scenarios,
    builder_product_repository,
)

Called when a builder scenario is deleted (in a side effect so that the scenario is deleted even if this method raises an error).

Parameters:

Name Type Description Default
builder_scenario_ids Sequence[UUID]

The identifiers of the builder scenarios that was deleted.

required
builder_product_ids_in_scenarios Sequence[int]

The identifiers of the builder_products

required
builder_product_repository BuilderProductRepository

Instance of BuilderProductRepository

required
Source code in components/offer_builder/public/dependencies.py
def on_builder_scenarios_deleted(
    self,
    builder_scenario_ids: Sequence[UUID],  # noqa: ARG002
    builder_product_ids_in_scenarios: Sequence[int],
    builder_product_repository: BuilderProductRepository,
) -> None:
    """Called when a builder scenario is deleted (in a side effect so that the
    scenario is deleted even if this method raises an error).

    Args:
        builder_scenario_ids: The identifiers of the builder scenarios that was deleted.
        builder_product_ids_in_scenarios: The identifiers of the builder_products
        that were linked to the deleted scenarios. This is required because this is
        called after the scenarios are deleted (so we can't find the products linked
        to them anymore).
        builder_product_repository: Instance of BuilderProductRepository
    """
    builder_product_repository.delete_many(
        builder_product_ids=builder_product_ids_in_scenarios
    )

participation_affects_pricing

participation_affects_pricing()

Whether changing participation invalidates pricing and requires recomputation.

Override in country-specific dependencies where participation affects pure premium calculations (e.g., ES uses participation buckets).

Returns:

Type Description
bool

True if participation changes should invalidate existing prices,

bool

False otherwise (default).

Source code in components/offer_builder/public/dependencies.py
def participation_affects_pricing(self) -> bool:
    """Whether changing participation invalidates pricing and requires recomputation.

    Override in country-specific dependencies where participation affects
    pure premium calculations (e.g., ES uses participation buckets).

    Returns:
        True if participation changes should invalidate existing prices,
        False otherwise (default).
    """
    return False

populate_coverage_validations_additional_args

populate_coverage_validations_additional_args(
    coverage_validations, country_specific_parameters
)

Populate country-specific additional args for coverage validations.

Source code in components/offer_builder/public/dependencies.py
def populate_coverage_validations_additional_args(
    self,
    coverage_validations: list[BuilderProductCoverageValidation],
    country_specific_parameters: CoverageValidationParameters,  # noqa: ARG002
) -> list[BuilderProductCoverageValidation]:
    """
    Populate country-specific additional args for coverage validations.
    """
    return coverage_validations

post_process_created_coverage

post_process_created_coverage(builder_coverage)

Updates the coverage after being created. E.g., in Belgium we derive its price structure from its limits.

Source code in components/offer_builder/public/dependencies.py
def post_process_created_coverage(
    self,
    builder_coverage: BuilderCoverage,
) -> BuilderCoverage:
    """Updates the coverage after being created.
    E.g., in Belgium we derive its price structure from its limits.
    """
    return builder_coverage

post_process_product_created_from_template

post_process_product_created_from_template(builder_product)

Post-process the builder product created from a template

Source code in components/offer_builder/public/dependencies.py
def post_process_product_created_from_template(
    self, builder_product: BuilderProduct
) -> BuilderProduct:
    """Post-process the builder product created from a template"""
    return builder_product

update_coverage_country_specific_data

update_coverage_country_specific_data(
    builder_coverage, builder_product
)

Updates coverage country-specific data.

Source code in components/offer_builder/public/dependencies.py
def update_coverage_country_specific_data(
    self,
    builder_coverage: BuilderCoverage,  # noqa: ARG002
    builder_product: BuilderProduct,  # noqa: ARG002
) -> None:
    """Updates coverage country-specific data."""
    return None

validate_target_population abstractmethod

validate_target_population(target_population)

Should raise if target_population is not valid in the country

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def validate_target_population(self, target_population: str | None) -> None:
    """Should raise if target_population is not valid in the country"""

get_app_dependency

get_app_dependency()

Retrieve the Offer Builder dependency from the current app.

Source code in components/offer_builder/public/dependencies.py
def get_app_dependency() -> OfferBuilderDependency[str, str]:
    """Retrieve the Offer Builder dependency from the current app."""
    from flask import current_app

    return cast(
        "OfferBuilderDependency[str, str]",
        cast("CustomFlask", current_app).get_component_dependency(COMPONENT_NAME),
    )

set_app_dependency

set_app_dependency(dependency)

Set the Offer Builder dependency of the current app.

Source code in components/offer_builder/public/dependencies.py
def set_app_dependency(
    dependency: OfferBuilderDependency[PriceTarget, PriceComponentType],
) -> None:
    """Set the Offer Builder dependency of the current app."""
    from flask import current_app

    cast("CustomFlask", current_app).add_component_dependency(
        COMPONENT_NAME, dependency
    )

components.offer_builder.public.entities

builder_coverage

BuilderCoverage dataclass

BuilderCoverage(
    *,
    id,
    premiumness,
    price_by_price_target,
    price_structure,
    selected_categories
)

Bases: DataClassJsonMixin

Public BuilderCoverage entity

id instance-attribute
id
premiumness instance-attribute
premiumness
price_by_price_target instance-attribute
price_by_price_target
price_structure instance-attribute
price_structure
selected_categories instance-attribute
selected_categories

builder_coverage_rule

BuilderCoverageDefinition dataclass

BuilderCoverageDefinition(
    *,
    selected_categories,
    coverage_rules,
    propagated_parameters,
    aggregated_limit_parameters,
    country_specific_data
)

Bases: DataClassJsonMixin

A coverage definition without configuration nor pricing.

aggregated_limit_parameters instance-attribute
aggregated_limit_parameters
country_specific_data instance-attribute
country_specific_data
coverage_rules instance-attribute
coverage_rules
get_coverage_rule
get_coverage_rule(guarantee_ref)
Source code in components/offer_builder/internal/entities/builder_coverage.py
def get_coverage_rule(self, guarantee_ref: str) -> BuilderCoverageRule | None:
    return next(
        (
            rule
            for rule in self.coverage_rules
            if rule.guarantee_ref == guarantee_ref
        ),
        None,
    )
propagated_parameters instance-attribute
propagated_parameters
selected_categories instance-attribute
selected_categories

BuilderCoverageRule dataclass

BuilderCoverageRule(
    *,
    id,
    guarantee_ref,
    bundle_choice_ref,
    expression_type,
    parameters,
    eligibility_items_refs,
    annual_cost_estimates=None
)

Bases: DataClassJsonMixin

A coverage rule with associated parameters, eligibility items and cost estimates

annual_cost_estimates class-attribute instance-attribute
annual_cost_estimates = None
bundle_choice_ref instance-attribute
bundle_choice_ref
eligibility_items_refs instance-attribute
eligibility_items_refs
equals_ignoring_id
equals_ignoring_id(other)
Source code in components/offer_builder/internal/entities/builder_coverage.py
def equals_ignoring_id(self, other: BuilderCoverageRule) -> bool:
    return (
        self.guarantee_ref == other.guarantee_ref
        and self.bundle_choice_ref == other.bundle_choice_ref
        and self.expression_type == other.expression_type
        and set(self.parameters) == set(other.parameters)
        and set(self.eligibility_items_refs) == set(other.eligibility_items_refs)
        and set(self.annual_cost_estimates or [])
        == set(other.annual_cost_estimates or [])
    )
expression_type instance-attribute
expression_type
get_parameter
get_parameter(parameter_type)
Source code in components/offer_builder/internal/entities/builder_coverage.py
def get_parameter(
    self, parameter_type: GuaranteeParameterType
) -> CoverageRuleParameter | None:
    return next(
        (param for param in self.parameters if param.type == parameter_type), None
    )
guarantee_ref instance-attribute
guarantee_ref
id instance-attribute
id
parameters instance-attribute
parameters
to_guarantee_catalog_coverage_rule
to_guarantee_catalog_coverage_rule()
Source code in components/offer_builder/internal/entities/builder_coverage.py
def to_guarantee_catalog_coverage_rule(self) -> GuaranteeCatalogCoverageRule:
    return GuaranteeCatalogCoverageRule(
        guarantee_business_id=self.guarantee_ref,
        bundle_choice_business_id=self.bundle_choice_ref,
        expression_type=self.expression_type,
        parameters=[
            GuaranteeCatalogCoverageRuleParameter(
                type=param.type, value=param.value
            )
            for param in self.parameters
        ],
        eligibility_items=self.eligibility_items_refs,
    )

BuilderCoverageRuleCostEstimate dataclass

BuilderCoverageRuleCostEstimate(
    *,
    price_target,
    primary_cents,
    partner_cents,
    child_cents
)

Bases: DataClassJsonMixin

The annual cost estimates for a coverage rule

child_cents instance-attribute
child_cents
partner_cents instance-attribute
partner_cents
price_target instance-attribute
price_target
primary_cents instance-attribute
primary_cents

builder_entity

BuilderEntity dataclass

BuilderEntity(*, company_ref=None, prospect_ref=None)

Bases: DataClassJsonMixin

Must contain at least one company_ref or a prospect_ref, possibly both

__post_init__
__post_init__()

Validates that the entity is correct

Source code in components/offer_builder/public/entities/builder_entity.py
def __post_init__(self) -> None:
    """Validates that the entity is correct"""
    if self.company_ref is None and self.prospect_ref is None:
        raise ValueError("At least one of company_ref or prospect_ref must be set")
company_ref class-attribute instance-attribute
company_ref = None
prospect_ref class-attribute instance-attribute
prospect_ref = None

builder_product

BuilderProduct dataclass

BuilderProduct(
    *,
    id,
    country,
    account_ref,
    version_id,
    version_number,
    tags,
    target_population,
    primary_participation_percent,
    partner_participation_percent,
    child_participation_percent,
    builder_targets,
    coverages,
    created_at
)

Bases: DataClassJsonMixin

Public BuilderProduct entity

account_ref instance-attribute
account_ref
builder_targets instance-attribute
builder_targets
child_participation_percent instance-attribute
child_participation_percent
country instance-attribute
country
coverages instance-attribute
coverages
created_at instance-attribute
created_at
id instance-attribute
id
partner_participation_percent instance-attribute
partner_participation_percent
primary_participation_percent instance-attribute
primary_participation_percent
tags instance-attribute
tags
target_population instance-attribute
target_population
version_id instance-attribute
version_id
version_number instance-attribute
version_number

builder_product_type

BuilderProductType

Bases: AlanBaseEnum

Type of builder product we are providing inputs for.

health class-attribute instance-attribute
health = 'health'
prevoyance class-attribute instance-attribute
prevoyance = 'prevoyance'

builder_target

BuilderTarget dataclass

BuilderTarget(
    *,
    id=uuid.uuid4(),
    prospect_ref,
    main_business_activity_code,
    industry_framework_agreement_code
)

Bases: DataClassJsonMixin

Public BuilderTarget entity

id class-attribute instance-attribute
id = field(default_factory=uuid4)
industry_framework_agreement_code instance-attribute
industry_framework_agreement_code
main_business_activity_code instance-attribute
main_business_activity_code
prospect_ref instance-attribute
prospect_ref

builder_template

BuilderTemplate dataclass

BuilderTemplate(
    *,
    id,
    display_name,
    template_name,
    builder_product_id,
    country_specific_data,
    is_available_for_self_serve=False
)

Builder template entity.

builder_product_id instance-attribute
builder_product_id
country_specific_data instance-attribute
country_specific_data
display_name instance-attribute
display_name
from_entity staticmethod
from_entity(builder_template)

Convert an internal builder template to a public builder template.

Source code in components/offer_builder/public/entities/builder_template.py
@staticmethod
def from_entity(
    builder_template: InternalBuilderTemplate,
) -> BuilderTemplate:
    """Convert an internal builder template to a public builder template."""
    return BuilderTemplate(
        id=builder_template.id,
        display_name=builder_template.display_name,
        template_name=builder_template.template_name,
        builder_product_id=builder_template.builder_product_id,
        country_specific_data=builder_template.country_specific_data,
        is_available_for_self_serve=builder_template.is_available_for_self_serve,
    )
id instance-attribute
id
is_available_for_self_serve class-attribute instance-attribute
is_available_for_self_serve = False
template_name instance-attribute
template_name

dependencies

OfferBuilderDependencyParameters dataclass

OfferBuilderDependencyParameters()

Bases: DataClassJsonMixin

Abstract class for country-specific parameters to send in dependencies

coerce classmethod
coerce(value)
FIXME: we should inherit from Coercible once #67954 is merged
Source code in components/offer_builder/public/entities/dependencies.py
@classmethod
def coerce(cls: type[Self], value: Any) -> Self:
    """
    # FIXME: we should inherit from Coercible once #67954 is merged
    """
    if isinstance(value, cls):
        return value
    raise TypeError(f"{value} is not of type {cls.__name__}")

factories

builder_coverage

BuilderCoverageFactory

Bases: Factory

Factory for the public BuilderCoverage dataclass

Meta

Part of the factory contract

model class-attribute instance-attribute
model = BuilderCoverage
id class-attribute instance-attribute
id = Sequence(lambda n: n)
premiumness class-attribute instance-attribute
premiumness = None
price_by_price_target class-attribute instance-attribute
price_by_price_target = LazyFunction(
    lambda: {
        None: Prices(
            total=PriceDetails(
                primary_cents=5000,
                partner_cents=4500,
                child_cents=3000,
                family_cents=0,
            ),
            membership_fee_ratio=Decimal("0.12"),
        )
    }
)
price_structure class-attribute instance-attribute
price_structure = LazyFunction(lambda: standard(balanced))
selected_categories class-attribute instance-attribute
selected_categories = []

builder_product

BuilderProductFactory

Bases: Factory[BuilderProduct]

Factory for the public BuilderProduct dataclass

Meta

Part of the factory contract

model class-attribute instance-attribute
model = BuilderProduct
account_ref class-attribute instance-attribute
account_ref = uuid4(cast_to=str)
builder_targets class-attribute instance-attribute
builder_targets = List([SubFactory(BuilderTargetFactory)])
child_participation_percent class-attribute instance-attribute
child_participation_percent = None
country class-attribute instance-attribute
country = fr
coverages class-attribute instance-attribute
coverages = List([SubFactory(BuilderCoverageFactory)])
created_at class-attribute instance-attribute
created_at = LazyFunction(lambda: utcnow())
id class-attribute instance-attribute
id = Sequence(lambda n: n)
partner_participation_percent class-attribute instance-attribute
partner_participation_percent = None
primary_participation_percent class-attribute instance-attribute
primary_participation_percent = 50
tags class-attribute instance-attribute
tags = LazyFunction(list)
target_population class-attribute instance-attribute
target_population = None
version_id class-attribute instance-attribute
version_id = Sequence(lambda n: n)
version_number class-attribute instance-attribute
version_number = 0

builder_target

BuilderTargetFactory

Bases: Factory[BuilderTarget]

Factory for the public BuilderTarget dataclass

Meta

Part of the factory contract

model class-attribute instance-attribute
model = BuilderTarget
id class-attribute instance-attribute
id = Sequence(lambda _: uuid4(cast_to=None))
industry_framework_agreement_code class-attribute instance-attribute
industry_framework_agreement_code = '1576'
main_business_activity_code class-attribute instance-attribute
main_business_activity_code = '0000Z'
prospect_ref class-attribute instance-attribute
prospect_ref = Sequence(lambda _: uuid4(cast_to=str))

demographics

ManualDemographicsEmptyFactory

Bases: ManualDemographicsFactory

Factory for ManualDemographics where all optional fields are None.

average_age class-attribute instance-attribute
average_age = None
child_ratio class-attribute instance-attribute
child_ratio = None
country class-attribute instance-attribute
country = fr
coverage_taker_ratios class-attribute instance-attribute
coverage_taker_ratios = None
first_child_ratio class-attribute instance-attribute
first_child_ratio = None
first_two_children_ratio class-attribute instance-attribute
first_two_children_ratio = None
male_ratio class-attribute instance-attribute
male_ratio = None
non_paying_ratio class-attribute instance-attribute
non_paying_ratio = None
number_of_covered_employees class-attribute instance-attribute
number_of_covered_employees = None
number_of_primaries class-attribute instance-attribute
number_of_primaries = None
option_taker_ratio class-attribute instance-attribute
option_taker_ratio = None
partner_ratio class-attribute instance-attribute
partner_ratio = None
single_ratio class-attribute instance-attribute
single_ratio = None
target_population_ratios class-attribute instance-attribute
target_population_ratios = None
ManualDemographicsFactory

Bases: Factory[ManualDemographics]

Factory for ManualDemographics.

Meta

Meta class for ManualDemographicsFactory.

model class-attribute instance-attribute
model = ManualDemographics
average_age class-attribute instance-attribute
average_age = 30
child_ratio class-attribute instance-attribute
child_ratio = 0.5
country class-attribute instance-attribute
country = fr
coverage_taker_ratios class-attribute instance-attribute
coverage_taker_ratios = {0: 1.0, 1: 0.5}
first_child_ratio class-attribute instance-attribute
first_child_ratio = 0.5
first_two_children_ratio class-attribute instance-attribute
first_two_children_ratio = 0.5
id class-attribute instance-attribute
id = Faker('uuid4', cast_to=None)
male_ratio class-attribute instance-attribute
male_ratio = 0.5
non_paying_ratio class-attribute instance-attribute
non_paying_ratio = 0.0
number_of_covered_employees class-attribute instance-attribute
number_of_covered_employees = 1
number_of_primaries class-attribute instance-attribute
number_of_primaries = 1
option_taker_ratio class-attribute instance-attribute
option_taker_ratio = 0.5
partner_ratio class-attribute instance-attribute
partner_ratio = 0.5
prospect_ref class-attribute instance-attribute
prospect_ref = Faker('uuid4', cast_to=str)
single_ratio class-attribute instance-attribute
single_ratio = 0.5
target_population_ratios class-attribute instance-attribute
target_population_ratios = {'pop1': 1}
version class-attribute instance-attribute
version = 0

prevoyance_builder_product_for_budget

PrevoyanceBuilderProductForBudgetFactory

Bases: AlanBaseFactory['PrevoyanceBuilderProductForBudget']

Meta
model class-attribute instance-attribute
model = PrevoyanceBuilderProductForBudget
account_ref class-attribute instance-attribute
account_ref = uuid4()
display_name class-attribute instance-attribute
display_name = text(max_nb_chars=20)
prevoyance_offer_ref class-attribute instance-attribute
prevoyance_offer_ref = uuid4()

price_details

PriceDetailsFactory

Bases: Factory

Meta
model class-attribute instance-attribute
model = PriceDetails
child_cents class-attribute instance-attribute
child_cents = 200
family_cents class-attribute instance-attribute
family_cents = 0
partner_cents class-attribute instance-attribute
partner_cents = 200
primary_cents class-attribute instance-attribute
primary_cents = 200

prices

PricesFactory

Bases: Factory

Meta
model class-attribute instance-attribute
model = Prices
membership_fee_ratio class-attribute instance-attribute
membership_fee_ratio = Decimal(0.12)
total class-attribute instance-attribute
total = SubFactory(PriceDetailsFactory)

manual_demographics

ManualDemographics dataclass

ManualDemographics(
    *,
    id=uuid.uuid4(),
    prospect_ref,
    version=None,
    number_of_primaries=None,
    number_of_covered_employees=None,
    average_age=None,
    male_ratio=None,
    target_population_ratios=None,
    partner_ratio=None,
    child_ratio=None,
    non_paying_ratio=None,
    coverage_taker_ratios=None,
    single_ratio=None,
    first_child_ratio=None,
    first_two_children_ratio=None,
    option_taker_ratio=None,
    origin=None,
    country=None
)

Manual demographics allow defining custom demographics (that override the demographics estimated by the pricer) when we have information about a prospect and the prospect is sufficiently large so that we can tailor the offer to this demographics.

average_age class-attribute instance-attribute
average_age = None
child_ratio class-attribute instance-attribute
child_ratio = None
country class-attribute instance-attribute
country = None
coverage_taker_ratios class-attribute instance-attribute
coverage_taker_ratios = None
first_child_ratio class-attribute instance-attribute
first_child_ratio = None
first_two_children_ratio class-attribute instance-attribute
first_two_children_ratio = None
id class-attribute instance-attribute
id = field(default_factory=uuid4)
is_empty property
is_empty

True if the user didn't fill any input. Note: some fields are filled by default so we don't check them.

is_valid property
is_valid

Boolean indicating whether or not the manual demographics are valid

male_ratio class-attribute instance-attribute
male_ratio = None
non_paying_ratio class-attribute instance-attribute
non_paying_ratio = None
number_of_covered_employees class-attribute instance-attribute
number_of_covered_employees = None
number_of_primaries class-attribute instance-attribute
number_of_primaries = None
option_taker_ratio class-attribute instance-attribute
option_taker_ratio = None
origin class-attribute instance-attribute
origin = None
partner_ratio class-attribute instance-attribute
partner_ratio = None
prospect_ref instance-attribute
prospect_ref
single_ratio class-attribute instance-attribute
single_ratio = None
target_population_ratios class-attribute instance-attribute
target_population_ratios = None
version class-attribute instance-attribute
version = None

manual_prices

ManualPricesAPISchema dataclass

ManualPricesAPISchema(
    primary_cents,
    partner_cents,
    family_cents,
    child_cents,
    nth_children_free,
)

Bases: DataClassJsonMixin

Defines the price structure as a set of desired prices.

For options the prices represent the incremental cost of the option compared to the base coverage.

__post_init__
__post_init__()

Ensures that the desired manual prices are allowed

Source code in components/offer_builder/public/entities/manual_prices.py
def __post_init__(self) -> None:
    """Ensures that the desired manual prices are allowed"""
    if self.primary_cents <= 0:
        raise ValueError("Primary cents has to be strictly positive")

    if self.partner_cents < 0:
        raise ValueError("Partner cents has to be zero or positive")

    if self.family_cents < 0:
        raise ValueError("Family cents has to be zero or positive")

    if self.child_cents < 0:
        raise ValueError("Child cents has to be zero or positive")

    if self.nth_children_free not in {2, 3, UNLIMITED_NTH_CHILDREN_FREE}:
        raise ValueError(
            f"nth_children_free has to be 2 or 3 or {UNLIMITED_NTH_CHILDREN_FREE}"
        )
child_cents instance-attribute
child_cents
family_cents instance-attribute
family_cents
nth_children_free instance-attribute
nth_children_free
partner_cents instance-attribute
partner_cents
primary_cents instance-attribute
primary_cents
to_price_structure
to_price_structure()

Converts manual prices to a price structure that can be understood by the pricing logic.

Source code in components/offer_builder/public/entities/manual_prices.py
def to_price_structure(
    self,
) -> PriceStructure:
    """Converts manual prices to a price structure that can be understood by the pricing logic."""
    return PriceStructure.tailored(
        partner_coefficient=Decimal(self.partner_cents) / self.primary_cents,
        child_coefficient=Decimal(self.child_cents) / self.primary_cents,
        family_coefficient=Decimal(self.family_cents) / self.primary_cents,
        nth_children_free=self.nth_children_free,
    )

ManualPricesSetAPISchema dataclass

ManualPricesSetAPISchema(coverages)

Bases: DataClassJsonMixin

Set of desired manual prices. There has to be exactly as many manual prices defined as there are coverages on the builder product.

__post_init__
__post_init__()

Ensures that all desired manual prices share a similar price structure

Source code in components/offer_builder/public/entities/manual_prices.py
def __post_init__(self) -> None:
    """Ensures that all desired manual prices share a similar price structure"""
    if len(self.coverages) < 2:
        return

    base = self.coverages[0]
    for option in self.coverages[1:]:
        if (base.partner_cents == 0) != (option.partner_cents == 0):
            raise ValueError(
                "Inconsistent price structures, either all partner cents should be 0, or none of them"
            )

        if (base.family_cents == 0) != (option.family_cents == 0):
            raise ValueError(
                "Inconsistent price structures, either all family cents should be 0, or none of them"
            )

        if (base.child_cents == 0) != (option.child_cents == 0):
            raise ValueError(
                "Inconsistent price structures, either all child cents should be 0, or none of them"
            )

        if base.nth_children_free != option.nth_children_free:
            raise ValueError(
                "Inconsistent price structures, all nth_children_free should be equal"
            )
coverages instance-attribute
coverages

price_details

PRICER_ROUND_PRICE_PRECISION_IN_CENTS module-attribute

PRICER_ROUND_PRICE_PRECISION_IN_CENTS = 50

PriceDetails dataclass

PriceDetails(
    primary_cents=0,
    partner_cents=0,
    child_cents=0,
    family_cents=0,
)

Bases: DataClassJsonMixin

Details of price for each group of persons: - primary: price for the primary holder of the insurance policy - partner: price for the partner of the primary - child: price for the children (may be per child, for several children or for all children depending on context) - family: price for the family of the primary, excluding the primary (so partner + children)

add
add(other)

Add two PriceDetails instances together and return a new PriceDetails instance

Source code in components/offer_builder/public/entities/price_details.py
def add(self, other: PriceDetails) -> PriceDetails:
    """
    Add two PriceDetails instances together and return a new PriceDetails instance
    """
    return self.__class__(
        primary_cents=self.primary_cents + other.primary_cents,
        partner_cents=self.partner_cents + other.partner_cents,
        child_cents=self.child_cents + other.child_cents,
        family_cents=self.family_cents + other.family_cents,
    )
child_cents class-attribute instance-attribute
child_cents = 0
family_cents class-attribute instance-attribute
family_cents = 0
multiply
multiply(multiplier)

Multiply the price details by a multiplier and return a new PriceDetails instance

Source code in components/offer_builder/public/entities/price_details.py
def multiply(self, multiplier: Union[int, float, Decimal]) -> PriceDetails:
    """
    Multiply the price details by a multiplier and return a new PriceDetails instance
    """
    return self.__class__(
        primary_cents=round(self.primary_cents * multiplier),
        partner_cents=round(self.partner_cents * multiplier),
        child_cents=round(self.child_cents * multiplier),
        family_cents=round(self.family_cents * multiplier),
    )
partner_cents class-attribute instance-attribute
partner_cents = 0
primary_cents class-attribute instance-attribute
primary_cents = 0
round
round(precision_cents=None)

Round the price details to a given precision and return a new PriceDetails instance

Source code in components/offer_builder/public/entities/price_details.py
def round(self, precision_cents: Optional[int] = None) -> PriceDetails:
    """
    Round the price details to a given precision and return a new PriceDetails instance
    """
    if precision_cents is None:
        precision_cents = PRICER_ROUND_PRICE_PRECISION_IN_CENTS

    return self.__class__(
        primary_cents=self._round_price(self.primary_cents, precision_cents),
        partner_cents=self._round_price(self.partner_cents, precision_cents),
        child_cents=self._round_price(self.child_cents, precision_cents),
        family_cents=self._round_price(self.family_cents, precision_cents),
    )
substract
substract(other)

Subtract one PriceDetails instance from another and return a new PriceDetails instance

Source code in components/offer_builder/public/entities/price_details.py
def substract(self, other: PriceDetails) -> PriceDetails:
    """
    Subtract one PriceDetails instance from another and return a new PriceDetails instance
    """
    return self.__class__(
        primary_cents=self.primary_cents - other.primary_cents,
        partner_cents=self.partner_cents - other.partner_cents,
        child_cents=self.child_cents - other.child_cents,
        family_cents=self.family_cents - other.family_cents,
    )
sum classmethod
sum(price_details)

Sum a list of PriceDetails instances

Parameters:

Name Type Description Default
price_details list[PriceDetails]

List of PriceDetails instances to sum together

required

Returns:

Name Type Description
PriceDetails

A new PriceDetails instances with summed values.

NB PriceDetails

Passing an empty list will results in 0 values for all fields.

Source code in components/offer_builder/public/entities/price_details.py
@classmethod
def sum(cls, price_details: list[PriceDetails]) -> PriceDetails:
    """
    Sum a list of PriceDetails instances

    Args:
        price_details: List of PriceDetails instances to sum together

    Returns:
        A new PriceDetails instances with summed values.
        NB: Passing an empty list will results in 0 values for all fields.
    """
    return cls(
        primary_cents=sum(p.primary_cents for p in price_details),
        partner_cents=sum(p.partner_cents for p in price_details),
        child_cents=sum(p.child_cents for p in price_details),
        family_cents=sum(p.family_cents for p in price_details),
    )
weighted_average classmethod
weighted_average(price_details, weights)

Compute the weighted average of a list of PriceDetails instances

Source code in components/offer_builder/public/entities/price_details.py
@classmethod
def weighted_average(
    cls, price_details: Sequence[PriceDetails], weights: Sequence[int]
) -> PriceDetails:
    """
    Compute the weighted average of a list of PriceDetails instances
    """
    if len(price_details) != len(weights):
        raise ValueError("Price details and weights must have the same length")
    return cls(
        primary_cents=int(
            get_weighted_average(
                [p.primary_cents for p in price_details],
                weights,
            )
        ),
        partner_cents=int(
            get_weighted_average(
                [p.partner_cents for p in price_details],
                weights,
            )
        ),
        child_cents=int(
            get_weighted_average(
                [p.child_cents for p in price_details],
                weights,
            )
        ),
        family_cents=int(
            get_weighted_average(
                [p.family_cents for p in price_details],
                weights,
            )
        ),
    )
with_price_increase
with_price_increase(price_increase)

Increase the price details by a price_increase ratio and return a new PriceDetails instance.

price_increase: float, the ratio to increase the price by. EG: 0.05 for a 5% increase.

Source code in components/offer_builder/public/entities/price_details.py
def with_price_increase(self, price_increase: float) -> PriceDetails:
    """
    Increase the price details by a price_increase ratio and return a new PriceDetails instance.

    price_increase: float, the ratio to increase the price by. EG: 0.05 for a 5% increase.
    """
    return self.multiply(1 + price_increase)
zeros classmethod
zeros()

Return a new PriceDetails instance with all values set to 0

Source code in components/offer_builder/public/entities/price_details.py
@classmethod
def zeros(cls) -> PriceDetails:
    """
    Return a new PriceDetails instance with all values set to 0
    """
    return cls(primary_cents=0, partner_cents=0, child_cents=0, family_cents=0)

pricer

CoverageWithPurePremiums dataclass

CoverageWithPurePremiums(
    *,
    selected_categories,
    coverage_rules,
    propagated_parameters,
    aggregated_limit_parameters,
    country_specific_data,
    id,
    coverage_index,
    coverage_type,
    premiumness,
    price_structure,
    past_results_correction_factor,
    target_loss_ratio,
    membership_fee_ratio,
    priced_coverages,
    pricing,
    last_updated_at,
    custom_settings,
    demographics,
    pure_premiums
)

Bases: BuilderCoverage

demographics instance-attribute
demographics
from_builder_coverage staticmethod
from_builder_coverage(
    builder_coverage, demographics, pure_premiums_outputs
)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/coverage_with_pure_premiums.py
@staticmethod
def from_builder_coverage(
    builder_coverage: BuilderCoverage,
    demographics: PricerDemographics,
    pure_premiums_outputs: list[PurePremiumsOutput],
) -> CoverageWithPurePremiums:
    return CoverageWithPurePremiums(
        # inherited from BuilderCoverage
        # TODO only keep a subset of the attributes? or only the identifier?
        id=builder_coverage.id,
        coverage_index=builder_coverage.coverage_index,
        coverage_type=builder_coverage.coverage_type,
        premiumness=builder_coverage.premiumness,
        price_structure=builder_coverage.price_structure,
        past_results_correction_factor=builder_coverage.past_results_correction_factor,
        target_loss_ratio=builder_coverage.target_loss_ratio,
        membership_fee_ratio=builder_coverage.membership_fee_ratio,
        selected_categories=builder_coverage.selected_categories,
        coverage_rules=builder_coverage.coverage_rules,
        propagated_parameters=builder_coverage.propagated_parameters,
        aggregated_limit_parameters=builder_coverage.aggregated_limit_parameters,
        priced_coverages=builder_coverage.priced_coverages,
        pricing=builder_coverage.pricing,
        last_updated_at=builder_coverage.last_updated_at,
        custom_settings=builder_coverage.custom_settings,
        country_specific_data=builder_coverage.country_specific_data,
        # CoverageWithPurePremiums specific attributes
        demographics=demographics,
        pure_premiums=pure_premiums_outputs,
    )
pure_premiums instance-attribute
pure_premiums

Premiums dataclass

Premiums(
    *, primary_premium=0, partner_premium=0, child_premium=0
)
__add__
__add__(other)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def __add__(self, other: Premiums) -> Premiums:
    return self.sum([self, other])
__mul__
__mul__(factor)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def __mul__(self, factor: float) -> Premiums:
    return Premiums(**{key: value * factor for key, value in asdict(self).items()})
__rmul__
__rmul__(factor)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def __rmul__(self, factor: float) -> Premiums:
    return self * factor
__sub__
__sub__(other)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def __sub__(self, other: Premiums) -> Premiums:
    return self + (other * -1)
__truediv__
__truediv__(factor)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def __truediv__(self, factor: float) -> Premiums:
    return self * (1 / factor)
child_premium class-attribute instance-attribute
child_premium = 0
divide_by_number_of_beneficiaries
divide_by_number_of_beneficiaries(demographics)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def divide_by_number_of_beneficiaries(
    self, demographics: PricerDemographics
) -> Premiums:
    return Premiums(
        primary_premium=self.primary_premium / demographics.number_of_primaries,
        partner_premium=self.partner_premium / demographics.number_of_partners,
        child_premium=self.child_premium / demographics.number_of_children,
    )
get_policy_average_total_premiums
get_policy_average_total_premiums(demographics)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def get_policy_average_total_premiums(
    self, demographics: PricerDemographics
) -> float:
    return (
        self.primary_premium
        + self.partner_premium * demographics.partner_ratio
        + self.child_premium * demographics.child_ratio
    )
multiply_by_number_of_beneficiaries
multiply_by_number_of_beneficiaries(demographics)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def multiply_by_number_of_beneficiaries(
    self, demographics: PricerDemographics
) -> Premiums:
    return Premiums(
        primary_premium=self.primary_premium * demographics.number_of_primaries,
        partner_premium=self.partner_premium * demographics.number_of_partners,
        child_premium=self.child_premium * demographics.number_of_children,
    )
partner_premium class-attribute instance-attribute
partner_premium = 0
primary_premium class-attribute instance-attribute
primary_premium = 0
sum classmethod
sum(premiums)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
@classmethod
def sum(cls, premiums: Sequence[Premiums]) -> Premiums:
    return cls(
        primary_premium=sum(premium.primary_premium for premium in premiums),
        partner_premium=sum(premium.partner_premium for premium in premiums),
        child_premium=sum(premium.child_premium for premium in premiums),
    )
to_builder_coverage_rule_cost_estimate
to_builder_coverage_rule_cost_estimate(price_target)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/premiums.py
def to_builder_coverage_rule_cost_estimate(
    self, price_target: PriceTarget
) -> BuilderCoverageRuleCostEstimate:
    annual_cost_cents: Premiums = self * 12 * 100
    return BuilderCoverageRuleCostEstimate(
        price_target=price_target,
        primary_cents=int(annual_cost_cents.primary_premium),
        partner_cents=int(annual_cost_cents.partner_premium),
        child_cents=int(annual_cost_cents.child_premium),
    )

PriceComponentIdentifier dataclass

PriceComponentIdentifier(
    *, price_target=None, price_component_type=None
)
price_component_type class-attribute instance-attribute
price_component_type = None
price_target class-attribute instance-attribute
price_target = None

PricerDemographics dataclass

PricerDemographics(
    *,
    average_age,
    male_ratio,
    target_population_ratios,
    manual_demographics_id=None,
    number_of_primaries,
    partner_ratio,
    child_ratio,
    non_paying_ratio,
    coverage_taker_ratio,
    single_ratio=None,
    first_child_ratio=None,
    first_two_children_ratio=None,
    option_taker_ratio=None
)

Bases: PurePremiumsDemographicsInput

child_ratio instance-attribute
child_ratio
coverage_taker_ratio instance-attribute
coverage_taker_ratio
first_child_ratio class-attribute instance-attribute
first_child_ratio = None
first_two_children_ratio class-attribute instance-attribute
first_two_children_ratio = None
manual_demographics_id class-attribute instance-attribute
manual_demographics_id = None
non_paying_ratio instance-attribute
non_paying_ratio
number_of_children property
number_of_children
number_of_partners property
number_of_partners
number_of_primaries instance-attribute
number_of_primaries
option_taker_ratio class-attribute instance-attribute
option_taker_ratio = None
partner_ratio instance-attribute
partner_ratio
single_ratio class-attribute instance-attribute
single_ratio = None
sum staticmethod
sum(demographics)
Source code in components/offer_builder/subcomponents/pricer/internal/entities/pricer_demographics.py
@staticmethod
def sum(demographics: list[PricerDemographics]) -> PricerDemographics:
    manual_demographics_ids = {d.manual_demographics_id for d in demographics}
    weights = [d.number_of_primaries for d in demographics]

    all_target_populations = set(
        target_population
        for d in demographics
        for target_population in d.target_population_ratios.keys()
    )

    return PricerDemographics(
        manual_demographics_id=manual_demographics_ids.pop()
        if len(manual_demographics_ids) == 1
        else None,
        number_of_primaries=sum(weights),
        average_age=get_weighted_average(
            [d.average_age for d in demographics], weights
        ),
        male_ratio=get_weighted_average(
            [d.male_ratio for d in demographics], weights
        ),
        target_population_ratios={
            target_population: get_weighted_average(
                [
                    d.target_population_ratios.get(target_population, 0)
                    for d in demographics
                ],
                weights,
            )
            for target_population in all_target_populations
        },
        partner_ratio=get_weighted_average(
            [d.partner_ratio for d in demographics], weights
        ),
        child_ratio=get_weighted_average(
            [d.child_ratio for d in demographics], weights
        ),
        non_paying_ratio=get_weighted_average(
            [d.non_paying_ratio for d in demographics], weights
        ),
        coverage_taker_ratio=get_weighted_average(
            [d.coverage_taker_ratio for d in demographics], weights
        ),
        single_ratio=get_weighted_average_or_none(
            [d.single_ratio for d in demographics], weights
        ),
        first_child_ratio=get_weighted_average_or_none(
            [d.first_child_ratio for d in demographics], weights
        ),
        first_two_children_ratio=get_weighted_average_or_none(
            [d.first_two_children_ratio for d in demographics], weights
        ),
        option_taker_ratio=get_weighted_average_or_none(
            [d.option_taker_ratio for d in demographics], weights
        ),
    )
to_computed_demographics
to_computed_demographics()
Source code in components/offer_builder/subcomponents/pricer/internal/entities/pricer_demographics.py
def to_computed_demographics(self) -> ComputedDemographics:
    return ComputedDemographics(
        number_of_primaries=self.number_of_primaries,
        average_age=self.average_age,
        male_ratio=self.male_ratio,
        target_population_ratios=self.target_population_ratios,
        non_paying_ratio=self.non_paying_ratio,
        coverage_taker_ratio=self.coverage_taker_ratio,
        partner_ratio=self.partner_ratio,
        child_ratio=self.child_ratio,
        single_ratio=self.single_ratio,
        first_child_ratio=self.first_child_ratio,
        first_two_children_ratio=self.first_two_children_ratio,
        option_taker_ratio=self.option_taker_ratio,
    )

PurePremiumsInput dataclass

PurePremiumsInput(
    *,
    identifier,
    coverage_rules,
    aggregated_limit_parameters,
    partner_primary_price_ratio,
    family_primary_price_ratio,
    location=None,
    industry=None,
    country_specific_input=None
)
aggregated_limit_parameters instance-attribute
aggregated_limit_parameters
country_specific_input class-attribute instance-attribute
country_specific_input = None
coverage_rules instance-attribute
coverage_rules
family_primary_price_ratio instance-attribute
family_primary_price_ratio
identifier instance-attribute
identifier
industry class-attribute instance-attribute
industry = None
location class-attribute instance-attribute
location = None
partner_primary_price_ratio instance-attribute
partner_primary_price_ratio

SnapshotTestCase

Bases: TestCase

The module provides tools and assertion methods to compare any dictionary results to a previously saved snapshot. All snapshots are named using the test method name which call assertMatchSnapshot(). It saves the results in JSON format.

should_rewrite_snapshot: Defines if the actual result should be snapshot and overwritten instead of compared use_latest_version: Defines if the test should use the latest version instead of a specific version skip_snapshot_comparison: Defines if the test should skip the snapshot comparison

assert_match_snapshot
assert_match_snapshot(actual, tolerance=0, **kwargs)

This assertion method behaves differently depending on the should_rewrite_snapshot flag.

  • When the flag is true, it stores the dictionary into a serialized JSON
  • When the flag is false, it compares the current actual value to the previous serialized value

It uses strict assertDictEqual assertion

  • tolerance is the acceptable relative distance between actual & expected float / int (0.01: 1%)
  • kwargs are the same than assertAlmostEquals (places / msg / delta)
Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
def assert_match_snapshot(self, actual: dict, tolerance: int = 0, **kwargs):  # type: ignore[type-arg,no-untyped-def]
    """
    This assertion method behaves differently depending on the should_rewrite_snapshot flag.

    - When the flag is true, it stores the dictionary into a serialized JSON
    - When the flag is false, it compares the current actual value to the previous serialized value

    It uses strict assertDictEqual assertion

    - tolerance is the acceptable relative distance between actual & expected float / int (0.01: 1%)
    - kwargs are the same than assertAlmostEquals (places / msg / delta)

    """

    if self.skip_snapshot_comparison:
        return

    # Get test_ prefixed method in the stack to use it as file name
    test_name = [x.function for x in inspect.stack() if "test_" in x.function][0]

    if self.should_rewrite_snapshot:
        self.save(test_name, actual)
    else:
        expected = self.load(test_name)
        normalized_actual = self._normalize_none_keys(actual)
        try:
            self._assert_deep_almost_equal(
                expected, normalized_actual, tolerance=tolerance, **kwargs
            )
        except AssertionError as e:
            self.failures.append(SnapshotFailure(test_name, str(e)))
            raise e
directory instance-attribute
directory
failures class-attribute instance-attribute
failures = []
load classmethod
load(test_name)
Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
@classmethod
def load(cls, test_name) -> dict:  # type: ignore[type-arg,no-untyped-def]
    with open(f"{cls.directory}/{test_name}.json") as f:
        expected = json.load(f)
    return expected  # type: ignore[no-any-return]
load_template_versions classmethod
load_template_versions()

Load template versions from {directory}/template_versions.json.

Returns:

Type Description
dict[str, int]

Dictionary mapping template names to version numbers.

dict[str, int]

Returns empty dict if file doesn't exist.

Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
@classmethod
def load_template_versions(cls) -> dict[str, int]:
    """
    Load template versions from {directory}/template_versions.json.

    Returns:
        Dictionary mapping template names to version numbers.
        Returns empty dict if file doesn't exist.
    """
    filename = f"{cls.directory}/template_versions.json"
    if not exists(filename):
        return {}
    with open(filename) as f:
        return json.load(f)  # type: ignore[no-any-return]
save classmethod
save(test_name, result)
Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
@classmethod
def save(cls, test_name: str, result: dict) -> None:  # type: ignore[type-arg]
    current_logger.info(f"{test_name} test rewrite results")
    filename = f"{cls.directory}/{test_name}.json"
    if not exists(dirname(filename)):
        makedirs(dirname(filename))

    normalized_result = cls._normalize_none_keys(result)

    with open(filename, "w") as f:
        json.dump(normalized_result, f, sort_keys=True, indent=4)
save_template_version classmethod
save_template_version(template_name, version)

Update template version in {directory}/template_versions.json.

Parameters:

Name Type Description Default
template_name str

Name of the template to update.

required
version int

Version number to save.

required
Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
@classmethod
def save_template_version(cls, template_name: str, version: int) -> None:
    """
    Update template version in {directory}/template_versions.json.

    Args:
        template_name: Name of the template to update.
        version: Version number to save.
    """
    filename = f"{cls.directory}/template_versions.json"
    if not exists(dirname(filename)):
        makedirs(dirname(filename))

    # Load existing versions or start with empty dict
    versions = cls.load_template_versions()
    versions[template_name] = version

    with open(filename, "w") as f:
        json.dump(versions, f, sort_keys=True, indent=4)
setUpClass classmethod
setUpClass()
Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
@classmethod
def setUpClass(cls) -> None:
    super().setUpClass()
    cls.directory = join(dirname(inspect.getfile(cls)), "snapshots")
    if cls.should_rewrite_snapshot:
        current_logger.info("Snapshot testing will rewrite results")
should_rewrite_snapshot class-attribute instance-attribute
should_rewrite_snapshot = False
skip_snapshot_comparison class-attribute instance-attribute
skip_snapshot_comparison = False
use_latest_version class-attribute instance-attribute
use_latest_version = False

TargetMargin dataclass

TargetMargin(
    *, target_margin_ratio, target_gross_profit_per_member
)

Bases: DataClassJsonMixin

Target margin for a given product (depending on the country, this target margin may be defined from the industry of the target, its size, the coverage premiumness, etc.).

target_gross_profit_per_member instance-attribute
target_gross_profit_per_member
target_margin_ratio instance-attribute
target_margin_ratio

prices

Prices dataclass

Prices(total, membership_fee_ratio)

Bases: DataClassJsonMixin

An insurance price, made of premiums and membership fees PriceDetails.

By default, we represent the price as a total amount and a membership fee ratio, so that's how the class is initialized. We provide alternative initializers from premiums and membership fees.

__post_init__
__post_init__()

Initialize the init=False fields

Source code in components/offer_builder/public/entities/prices.py
def __post_init__(self) -> None:
    """Initialize the init=False fields"""
    # NB: Must use object.__setattr__ for assignment in post_init for Frozen
    # dataclass, see doc: https://docs.python.org/3/library/dataclasses.html#frozen-instances
    object.__setattr__(
        self,
        "premiums",
        self.total.multiply(1 - self.membership_fee_ratio).round(1),
    )
    object.__setattr__(self, "membership_fees", self.total.substract(self.premiums))
add
add(other)

Add two Prices instances together and return a new Prices instance

Source code in components/offer_builder/public/entities/prices.py
def add(self, other: Prices) -> Prices:
    """Add two Prices instances together and return a new Prices instance"""
    return self.__class__.sum([self, other])
from_premiums_and_membership_fees classmethod
from_premiums_and_membership_fees(
    premiums, membership_fees
)

Alternate initialization method for Prices, used for old plans where membership_fee_ratio is not necessarily constant

Source code in components/offer_builder/public/entities/prices.py
@classmethod
def from_premiums_and_membership_fees(
    cls, premiums: PriceDetails, membership_fees: PriceDetails
) -> Prices:
    """
    Alternate initialization method for Prices, used for old plans where membership_fee_ratio
    is not necessarily constant
    """
    total = premiums.add(membership_fees)
    try:
        membership_fee_ratio = Decimal(
            membership_fees.primary_cents / total.primary_cents
        )
    except ZeroDivisionError:
        membership_fee_ratio = Decimal(0)
    prices = cls(total=total, membership_fee_ratio=membership_fee_ratio)

    # override premiums and membership_fees based on input
    # NB: Must use object.__setattr__ for assignment for Frozen dataclass,
    # see doc: https://docs.python.org/3/library/dataclasses.html#frozen-instances
    object.__setattr__(prices, "premiums", premiums)
    object.__setattr__(prices, "membership_fees", membership_fees)
    return prices
membership_fee_ratio instance-attribute
membership_fee_ratio
membership_fees class-attribute instance-attribute
membership_fees = field(init=False)
multiply
multiply(multiplier)

Multiply the price by a multiplier and return a new Prices instance

Source code in components/offer_builder/public/entities/prices.py
def multiply(self, multiplier: int | float | Decimal) -> Prices:
    """Multiply the price by a multiplier and return a new Prices instance"""
    return self.__class__(
        total=self.total.multiply(multiplier),
        membership_fee_ratio=self.membership_fee_ratio,
    )
premiums class-attribute instance-attribute
premiums = field(init=False)
round
round(precision_cents=None)

Round the price to a given precision and return a new Prices instance

Source code in components/offer_builder/public/entities/prices.py
def round(self, precision_cents: int | None = None) -> Prices:
    """Round the price to a given precision and return a new Prices instance"""
    rounded_total = self.total.round(precision_cents)
    return self.__class__(
        total=rounded_total,
        membership_fee_ratio=self.membership_fee_ratio,
    )
substract
substract(other)

Subtract one Prices instance from another and return a new Prices instance

Source code in components/offer_builder/public/entities/prices.py
def substract(self, other: Prices) -> Prices:
    """Subtract one Prices instance from another and return a new Prices instance"""
    return self.__class__.sum([self, other.multiply(-1)])
sum classmethod
sum(prices)

Sum a list of Prices instances

Parameters:

Name Type Description Default
prices Iterable[Prices]

Iterable of Prices instances to sum together

required

Returns:

Name Type Description
Prices

A new Prices instances with summed values and same membership fee ratio.

NB Prices

Passing an empty list will results in 0 values for all price fields.

Source code in components/offer_builder/public/entities/prices.py
@classmethod
def sum(cls, prices: Iterable[Prices]) -> Prices:
    """
    Sum a list of Prices instances

    Args:
        prices: Iterable of Prices instances to sum together

    Returns:
        A new Prices instances with summed values and same membership fee ratio.
        NB: Passing an empty list will results in 0 values for all price fields.
    """
    prices_as_list = list(prices)
    total_sum = PriceDetails.sum([p.total for p in prices_as_list])

    membership_fee_ratios = {p.membership_fee_ratio for p in prices_as_list}
    if len(membership_fee_ratios) == 1:
        membership_fee_ratio = membership_fee_ratios.pop()
    elif total_sum.primary_cents == 0:
        membership_fee_ratio = Decimal(0)
    else:
        membership_fee_sum = mandatory(
            PriceDetails.sum([p.membership_fees for p in prices_as_list])
        )
        membership_fee_ratio = Decimal(membership_fee_sum.primary_cents) / Decimal(
            total_sum.primary_cents
        )

    # NB: Not setting premiums and membership_fees here,
    # as they are calculated on post_init
    return cls(
        total=total_sum,
        membership_fee_ratio=membership_fee_ratio,
    )
total instance-attribute
total
weighted_average classmethod
weighted_average(prices, weights)

Compute the weighted average of a list of Prices instances

Source code in components/offer_builder/public/entities/prices.py
@classmethod
def weighted_average(
    cls, prices: Sequence[Prices], weights: Sequence[int]
) -> Prices:
    """
    Compute the weighted average of a list of Prices instances
    """
    if len(prices) == 0:
        raise ValueError(
            "Can't compute a weighted average of an empty list of prices"
        )
    if len(prices) != len(weights):
        raise ValueError("Prices and weights must have the same length")

    total_average = PriceDetails.weighted_average(
        [p.total for p in prices],
        weights,
    )

    # if all prices have the same membership fee ratio, use that directly to avoid
    # rounding effects
    membership_fee_ratios = {price.membership_fee_ratio for price in prices}
    if len(membership_fee_ratios) == 1:
        membership_fee_ratio = membership_fee_ratios.pop()
    else:
        fee_primary_cents = get_weighted_average(
            [p.membership_fees.primary_cents for p in prices], weights
        )
        try:
            membership_fee_ratio = Decimal(fee_primary_cents) / Decimal(
                total_average.primary_cents
            )
        except InvalidOperation:
            # NB: Shouldn't happen in real life. This is hacky but avoids a lot of
            # boilerplates in tests to make price outputs non-zero.
            membership_fee_ratio = membership_fee_ratios.pop()
    return cls(
        total=PriceDetails.weighted_average(
            [p.total for p in prices],
            weights,
        ),
        membership_fee_ratio=membership_fee_ratio,
    )
with_price_increase
with_price_increase(price_increase)

Increase the price by a price_increase ratio and return a new Prices instance

price_increase: float, the ratio to increase the price by. EG: 0.05 for a 5% increase.

Source code in components/offer_builder/public/entities/prices.py
def with_price_increase(self, price_increase: float) -> Prices:
    """
    Increase the price by a price_increase ratio and return a new Prices instance

    price_increase: float, the ratio to increase the price by. EG: 0.05 for a 5% increase.
    """
    return self.__class__(
        total=self.total.with_price_increase(price_increase),
        membership_fee_ratio=self.membership_fee_ratio,
    )

product

HEALTH_PRODUCT_TYPE module-attribute

HEALTH_PRODUCT_TYPE = 'health'

ProductSummary dataclass

ProductSummary(
    *,
    id,
    display_name,
    product_type,
    scenario_id,
    prospect_refs
)

Bases: DataClassJsonMixin

Summary of a product that should appear in the Offer Builder

id: Identifier of the product. display_name: User friendly name of the product, to display in the UI. product_type: The type of the product. Global implem only supports 'health' but local implementations may define other types. scenario_id: Identifier of the builder scenario that the product is associated with. propsect_refs: List of prospect refs for which the product is relevant.

display_name instance-attribute
display_name
from_builder_product_summary staticmethod
from_builder_product_summary(builder_product)

Convert a BuilderProductSummary into a ProductSummary

Source code in components/offer_builder/public/entities/product.py
@staticmethod
def from_builder_product_summary(
    builder_product: BuilderProductSummary,
) -> ProductSummary:
    """Convert a BuilderProductSummary into a ProductSummary"""
    return ProductSummary(
        id=mandatory(builder_product.id),
        display_name=builder_product.display_name,
        product_type=HEALTH_PRODUCT_TYPE,
        scenario_id=builder_product.builder_scenario_id,
        prospect_refs=builder_product.prospect_refs,
    )
id instance-attribute
id
product_type instance-attribute
product_type
prospect_refs instance-attribute
prospect_refs
scenario_id instance-attribute
scenario_id

product_status

ProductStatus dataclass

ProductStatus(
    *, product_id, is_export_ready, is_proposal_ready
)

Bases: DataClassJsonMixin

Status information for a product

product_id: The identifier of the product. is_export_ready: Whether or not the product is considered to be complete and usable in an export (eg: budget, offer narrative, etc). is_proposal_ready: Whether or not the product is considered to be complete and usable in a contract proposal.

is_export_ready instance-attribute
is_export_ready
is_proposal_ready instance-attribute
is_proposal_ready
product_id instance-attribute
product_id

self_serve

SelfServeBuilderProductSettings dataclass

SelfServeBuilderProductSettings(
    *,
    target_population,
    participation,
    price_structure_type=None
)

Bases: DataClassJsonMixin

Per-product-creation settings (participation, target population).

participation instance-attribute
participation
price_structure_type class-attribute instance-attribute
price_structure_type = None
target_population instance-attribute
target_population

SelfServeBuilderProductsInput dataclass

SelfServeBuilderProductsInput(
    *,
    prospect_name,
    onboarding_id,
    postal_code,
    number_of_primaries,
    custom_data,
    settings
)

Input for get_self_serve_builder_products.

Contains demographics, custom product-type data, and per-product settings.

custom_data instance-attribute
custom_data
number_of_primaries instance-attribute
number_of_primaries
onboarding_id instance-attribute
onboarding_id
postal_code instance-attribute
postal_code
prospect_name instance-attribute
prospect_name
settings instance-attribute
settings

SelfServeCustomData dataclass

SelfServeCustomData()

Bases: Coercible['SelfServeCustomData']

Base class for product-type and country-specific self-serve input data.

Each country/product combination defines its own child class. Use ChildClass.coerce(custom_data) to access typed fields.

to_builder_prospect_kwargs
to_builder_prospect_kwargs()

Return kwargs for BuilderProspectAPISchema from country-specific data.

Source code in components/offer_builder/public/entities/self_serve.py
def to_builder_prospect_kwargs(self) -> dict[str, Any]:
    """Return kwargs for BuilderProspectAPISchema from country-specific data."""
    return {}

self_serve_offer

SelfServeOffer dataclass

SelfServeOffer(
    *,
    builder_product_id,
    prospect_ref,
    name,
    template_name,
    premiumness,
    modules,
    settings
)

Public self-serve offer returned to consumers.

builder_product_id instance-attribute
builder_product_id
from_builder_product staticmethod
from_builder_product(product, template)

Convert an internal BuilderProduct + template into a SelfServeOffer.

Source code in components/offer_builder/public/entities/self_serve_offer.py
@staticmethod
def from_builder_product(
    product: BuilderProduct,
    template: BuilderTemplate,
) -> SelfServeOffer:
    """Convert an internal BuilderProduct + template into a SelfServeOffer."""
    builder_target = one(product.builder_targets)
    base_coverage = product.base_coverage

    settings = SelfServeOfferSettings(
        target_population=product.target_population,
        participation=product.participation_percent,
        price_structure_type=base_coverage.price_structure.type,
    )

    # Group by price targets to mimic future price segmentation introduced by InsurancePlan
    # eg: In FR we have 2 modules: one for RG, one for AlsaceMoselle
    price_targets: dict[PriceTarget, list[SelfServeOfferCoverage]] = {}
    for coverage in product.coverages:
        pricing = mandatory(coverage.pricing, "Offer should be priced")
        prices_by_target = get_builder_pricing_price_total_by_price_target(pricing)
        for target, prices in prices_by_target.items():
            # component_type=None is the aggregate total (from PremiumsOutput.sum()),
            # already captured by `price` — only include named components
            components = [
                SelfServeOfferCoveragePriceComponent(
                    component_type=bp.component_type,
                    price=SelfServeOfferCoveragePrice.from_price_details(
                        bp.price.total
                    ),
                )
                for bp in pricing.prices
                if bp.target == target and bp.component_type
            ]
            offer_coverage = SelfServeOfferCoverage(
                coverage_index=coverage.coverage_index,
                price=SelfServeOfferCoveragePrice.from_price_details(prices.total),
                price_components=components,
            )
            price_targets.setdefault(target, []).append(offer_coverage)

    modules = [
        SelfServeOfferModule(coverages=coverages, location=target)
        for target, coverages in price_targets.items()
    ]

    return SelfServeOffer(
        builder_product_id=mandatory(product.id),
        prospect_ref=builder_target.prospect_ref,
        name=product.display_name,
        template_name=template.template_name,
        premiumness=base_coverage.premiumness,
        modules=modules,
        settings=settings,
    )
modules instance-attribute
modules
name instance-attribute
name
premiumness instance-attribute
premiumness
prospect_ref instance-attribute
prospect_ref
settings instance-attribute
settings
template_name instance-attribute
template_name

SelfServeOfferCoverage dataclass

SelfServeOfferCoverage(
    *, coverage_index, price, price_components
)

A coverage with its index and price.

coverage_index instance-attribute
coverage_index
price instance-attribute
price
price_components instance-attribute
price_components

SelfServeOfferCoveragePrice dataclass

SelfServeOfferCoveragePrice(
    *,
    primary_cents,
    partner_cents,
    child_cents,
    family_cents
)

Price for a single coverage, per person type (in cents).

child_cents instance-attribute
child_cents
family_cents instance-attribute
family_cents
from_price_details staticmethod
from_price_details(price_details)

Build from a PriceDetails instance.

Source code in components/offer_builder/public/entities/self_serve_offer.py
@staticmethod
def from_price_details(price_details: PriceDetails) -> SelfServeOfferCoveragePrice:
    """Build from a PriceDetails instance."""
    return SelfServeOfferCoveragePrice(
        primary_cents=price_details.primary_cents,
        partner_cents=price_details.partner_cents,
        child_cents=price_details.child_cents,
        family_cents=price_details.family_cents,
    )
partner_cents instance-attribute
partner_cents
primary_cents instance-attribute
primary_cents

SelfServeOfferCoveragePriceComponent dataclass

SelfServeOfferCoveragePriceComponent(
    *, component_type, price
)

Price breakdown for a single price component type within a coverage.

component_type instance-attribute
component_type
price instance-attribute
price

SelfServeOfferModule dataclass

SelfServeOfferModule(*, coverages, location)

A module groups coverages for a specific location (price target).

coverages instance-attribute
coverages
location instance-attribute
location

SelfServeOfferSettings dataclass

SelfServeOfferSettings(
    *,
    target_population,
    participation,
    price_structure_type
)

Settings used to create the offer — mirrors input settings.

participation instance-attribute
participation
price_structure_type instance-attribute
price_structure_type
target_population instance-attribute
target_population

validation

BuilderProductValidationLevel

Bases: AlanBaseEnum

blocker class-attribute instance-attribute
blocker = 'blocker'
info class-attribute instance-attribute
info = 'info'
warning class-attribute instance-attribute
warning = 'warning'

validation_target

ValidationTarget dataclass

ValidationTarget(*, industry_framework_agreement_code)

Target that is used to validate the compliance of a product.

industry_framework_agreement_code instance-attribute
industry_framework_agreement_code

components.offer_builder.public.enums

base_premiumness

BasePremiumness

Bases: AlanBaseRankedEnum

Base class for premiumness levels.

We create 1 enum per country, and we use this base class to define the common methods.

⚠️ Order of enum declaration impacts the ranking of premiumness levels.

T module-attribute

T = TypeVar('T', bound='BasePremiumness')

base_pricer_version

BasePricerVersion

Bases: AlanBaseEnum

Base class for pricer versions.

We create 1 enum per country, and we use this base class to define the common methods.

get_default classmethod
get_default()

Returns the default pricer version.

Source code in components/offer_builder/public/enums/base_pricer_version.py
@classmethod
def get_default(cls: type[T]) -> T:
    """
    Returns the default pricer version.
    """
    return mandatory(cls.default())
is_deprecated classmethod
is_deprecated(pricer_version)

Returns True if the pricer version is deprecated, ie if it's before the default version.

Source code in components/offer_builder/public/enums/base_pricer_version.py
@classmethod
def is_deprecated(cls: type[T], pricer_version: str | T) -> bool:
    """
    Returns True if the pricer version is deprecated, ie if it's before the default version.
    """
    if isinstance(pricer_version, str):
        pricer_version = cls.validate(pricer_version)

    return pricer_version < cls.get_default()
is_supported classmethod
is_supported(pricer_version)

Returns True if the pricer version is supported.

Note that a deprecated version can still be supported. We typically support deprecated versions for ~3 months to allow ongoing deals to be concluded.

Source code in components/offer_builder/public/enums/base_pricer_version.py
@classmethod
def is_supported(cls: type[T], pricer_version: str | T) -> bool:
    """
    Returns True if the pricer version is supported.

    Note that a deprecated version can still be supported. We typically
    support deprecated versions for ~3 months to allow ongoing deals to be
    concluded.
    """
    return not cls(pricer_version).name.startswith("_")

INCLUDE_PRICE_RATIOS_PRICER_VERSION module-attribute

INCLUDE_PRICE_RATIOS_PRICER_VERSION = '2025.09'

NON_PAYING_RATIO_FIX_PRICER_VERSION module-attribute

NON_PAYING_RATIO_FIX_PRICER_VERSION = '2025.09'

T module-attribute

T = TypeVar('T', bound='BasePricerVersion')

builder_product_origin

BuilderProductOrigin

Bases: AlanBaseEnum

The tool or flow that created a builder product

acquisition class-attribute instance-attribute
acquisition = 'acquisition'
offer_builder class-attribute instance-attribute
offer_builder = 'offer_builder'
other class-attribute instance-attribute
other = 'other'
renewal_bot class-attribute instance-attribute
renewal_bot = 'renewal_bot'
self_amendment class-attribute instance-attribute
self_amendment = 'self_amendment'
signature_coverage_import class-attribute instance-attribute
signature_coverage_import = 'signature_coverage_import'
template_import class-attribute instance-attribute
template_import = 'template_import'

builder_product_tag

BuilderProductTag

Bases: AlanBaseEnum

Enum representing possible tags to be set on a builder product. BuilderProduct can be built with 2 main tags: - either "acquisition", if the builder product was created from a signature coverage, in an acquisition context - or "amendment", if the builder product was created from an existing offer/contract

  • "renewal" tag comes on top of "amendment", and is used to specify if products changes part of a renewal campaign were used to create the product
  • "merge" tag is used to specify if the product is a merge of multiple product changes
acquisition class-attribute instance-attribute
acquisition = 'acquisition'
amendment class-attribute instance-attribute
amendment = 'amendment'
merge class-attribute instance-attribute
merge = 'merge'
renewal class-attribute instance-attribute
renewal = 'renewal'

offer_builder_supported_country

OfferBuilderSupportedCountry

Bases: AlanBaseEnum

Enum representing possible countries supported by the offer builder.

be class-attribute instance-attribute
be = 'be'
es class-attribute instance-attribute
es = 'es'
fr class-attribute instance-attribute
fr = 'fr'

product_change_pricing_strategy

ProductChangePricingStrategy

Bases: AlanBaseEnum

Defines the pricing strategy we want to apply for product changes.

  • default_price_increase will use the main price change defined in turing product changes
  • min_price_increase will use the min_price_increase defined in turing product changes
default_price_increase class-attribute instance-attribute
default_price_increase = 'default_price_increase'
min_price_increase class-attribute instance-attribute
min_price_increase = 'min_price_increase'

components.offer_builder.public.errors

ManualDemographicsError

Bases: Exception

Custom exception class for manual demographics

PricerError

PricerError(error_type, description, **kwargs)

Bases: BaseErrorCode

Error class for guarantee catalog validation failures.

Initialize a guarantee catalog error with type and description.

Source code in components/offer_builder/subcomponents/pricer/protected/errors.py
def __init__(self, error_type: PricerErrorType, description: str, **kwargs: Any):
    """Initialize a guarantee catalog error with type and description."""
    super().__init__(None, error_type.value, description, **kwargs)

builder_product_already_priced classmethod

builder_product_already_priced(builder_product_id)

Create error if trying to price an already priced builder product.

Source code in components/offer_builder/subcomponents/pricer/protected/errors.py
@classmethod
def builder_product_already_priced(
    cls, builder_product_id: int | None
) -> PricerError:
    """Create error if trying to price an already priced builder product."""
    return cls(
        error_type=PricerErrorType.builder_product_already_priced,
        description=f"Builder product {builder_product_id} is already priced",
        builder_product_id=builder_product_id,
    )

missing_builder_targets classmethod

missing_builder_targets(builder_product_id)

Create error if trying to price a builder product without targets.

Source code in components/offer_builder/subcomponents/pricer/protected/errors.py
@classmethod
def missing_builder_targets(cls, builder_product_id: int | None) -> PricerError:
    """Create error if trying to price a builder product without targets."""
    return cls(
        error_type=PricerErrorType.missing_builder_targets,
        description=f"Builder product {builder_product_id} does not have any builder targets",
        builder_product_id=builder_product_id,
    )

unsupported_pricer_version classmethod

unsupported_pricer_version(pricer_version)

Create error if trying to use an unsupported pricer version.

Source code in components/offer_builder/subcomponents/pricer/protected/errors.py
@classmethod
def unsupported_pricer_version(
    cls, pricer_version: BasePricerVersion
) -> PricerError:
    """Create error if trying to use an unsupported pricer version."""
    return cls(
        error_type=PricerErrorType.unsupported_pricer_version,
        description=f"Pricer version {pricer_version} is not supported anymore",
        pricer_version=pricer_version,
    )

components.offer_builder.public.exceptions

AmbiguousDemographicsError

Bases: Exception

Exception raised when demographics estimation cannot be computed as several entries match the given parameters.

DemographicsModelError

Bases: Exception

Exception raised when the demographics estimation cannot be computed because of missing data.

components.offer_builder.public.queries

builder_template

get_closest_builder_template

get_closest_builder_template(
    filters,
    builder_product,
    builder_coverage_selector,
    exclude_guarantee_predicate=lambda _guarantee_ref: False,
)

Get the builder template matching the filters closest to the provided BuilderProduct. The comparison is performed by comparing the most relevant coverage (selected by builder_coverage_selector) of the provided BuilderProduct to the most relevant coverage of each candidate template.

Parameters:

Name Type Description Default
filters dict[str, set[str]]

The filters to apply to templates country specific data to retrieve candidate templates. This is formatted as a dictionary with country specific data keys as the filter name and values as the filter values.

required
builder_product BuilderProduct

The builder product to compare against the templates.

required
builder_coverage_selector Callable[[BuilderProduct], BuilderCoverage]

A function that extracts the relevant BuilderCoverage (for comparison) from a BuilderProduct.

required
exclude_guarantee_predicate Callable[[str], bool]

A function that is given a guarantee_ref, and returns True if the guarantee ref should be ignored for the purpose of finding a template.

lambda _guarantee_ref: False

Returns:

Type Description
BuilderTemplate | None

The closest builder template or None if no template matching the filters is found.

Source code in components/offer_builder/public/queries/builder_template.py
def get_closest_builder_template(
    filters: dict[str, set[str]],
    builder_product: BuilderProduct,
    builder_coverage_selector: Callable[[BuilderProduct], BuilderCoverage],
    exclude_guarantee_predicate: Callable[[str], bool] = lambda _guarantee_ref: False,
) -> BuilderTemplate | None:
    """Get the builder template matching the filters closest to the provided BuilderProduct.
    The comparison is performed by comparing the most relevant coverage (selected by builder_coverage_selector) of the provided
    BuilderProduct to the most relevant coverage of each candidate template.

    Args:
        filters: The filters to apply to templates country specific data to retrieve candidate templates. This is formatted as a
            dictionary with country specific data keys as the filter name and values as the filter values.
        builder_product: The builder product to compare against the templates.
        builder_coverage_selector: A function that extracts the relevant BuilderCoverage (for comparison) from a BuilderProduct.
        exclude_guarantee_predicate: A function that is given a guarantee_ref, and returns True if the guarantee ref should be
            ignored for the purpose of finding a template.

    Returns:
        The closest builder template or None if no template matching the filters is found.
    """
    builder_template_repository = BuilderTemplateRepository()
    templates = builder_template_repository.search(
        country=builder_product.country, country_specific_data_filters=filters
    )
    if not templates:
        return None

    builder_coverage = builder_coverage_selector(builder_product)

    builder_product_id_by_template_id = {
        template.id: template.builder_product_id for template in templates
    }

    builder_product_repository = BuilderProductRepository()
    builder_products = builder_product_repository.get_many(
        list(builder_product_id_by_template_id.values())
    )
    builder_product_by_id = {
        builder_product.id: builder_product for builder_product in builder_products
    }

    coverage_rule_per_template_and_guarantee_ref: dict[
        tuple[uuid.UUID, GuaranteeRef], BuilderCoverageRule
    ] = {}
    for template_id, builder_product_id in builder_product_id_by_template_id.items():
        template_coverage = builder_coverage_selector(
            builder_product_by_id[builder_product_id]
        )
        for rule in template_coverage.coverage_rules:
            coverage_rule_per_template_and_guarantee_ref[
                (template_id, rule.guarantee_ref)
            ] = rule

    closest_guarantees_count_per_template = {template.id: 0 for template in templates}

    for coverage_rule in builder_coverage.coverage_rules:
        if exclude_guarantee_predicate(coverage_rule.guarantee_ref):
            continue

        coverage_rule_level = _get_coverage_rule_level(coverage_rule)
        # compute the guarantee level for each template, then increment the count for the closest ones
        distances: dict[uuid.UUID, float] = {}
        for template in templates:
            template_rule = coverage_rule_per_template_and_guarantee_ref.get(
                (template.id, coverage_rule.guarantee_ref)
            )
            if (
                not template_rule
                # do not try comparing guarantees with different expression types
                or template_rule.expression_type != coverage_rule.expression_type
            ):
                continue
            distances[template.id] = abs(
                coverage_rule_level - _get_coverage_rule_level(template_rule)
            )

        if not distances:
            # guarantee not found in any template -> ignore it
            continue

        min_distance = min(distances.values())
        for template_id, distance in distances.items():
            if distance == min_distance:
                closest_guarantees_count_per_template[template_id] += 1

    max_closest_count = max(closest_guarantees_count_per_template.values())
    closest_template = next(
        template
        for template in templates
        if closest_guarantees_count_per_template[template.id] == max_closest_count
    )

    current_logger.info(
        f"Closest template is {closest_template.template_name} \n(# closest guarantees per template: {closest_guarantees_count_per_template})"
    )

    return BuilderTemplate.from_entity(closest_template)

competitor

get_competitors

get_competitors()
Source code in components/offer_builder/competitor_product/queries/competitor.py
def get_competitors() -> list[Competitor]:
    competitors = current_session.scalars(select(CompetitorSQLA)).all()
    return [
        Competitor(
            id=competitor.id,
            name=competitor.name,
            is_broker=competitor.is_broker,
            activity=competitor.activity,
        )
        for competitor in competitors
    ]

prevoyance_builder_product_for_budget

get_prevoyance_builder_product_for_budget_from_prevoyance_builder_product_id

get_prevoyance_builder_product_for_budget_from_prevoyance_builder_product_id(
    prevoyance_builder_product_id,
)

Get the prevoyance builder product for budget linked to a prevoyance builder product, if any. This is used to update the prevoyance_offer_ref of the prevoyance_builder_product_for_budget when the prevoyance_builder_product is published.

Parameters:

Name Type Description Default
prevoyance_builder_product_id UUID

The id of the prevoyance builder product

required

Returns:

Type Description
PrevoyanceBuilderProductForBudget | None

A prevoyance builder product for budget or None if no budget product is linked to the prevoyance builder product

Source code in components/offer_builder/public/queries/prevoyance_builder_product_for_budget.py
def get_prevoyance_builder_product_for_budget_from_prevoyance_builder_product_id(
    prevoyance_builder_product_id: UUID,
) -> PrevoyanceBuilderProductForBudget | None:
    """
    Get the prevoyance builder product for budget linked to a prevoyance builder product, if any.
    This is used to update the prevoyance_offer_ref of the prevoyance_builder_product_for_budget
    when the prevoyance_builder_product is published.

    Args:
        prevoyance_builder_product_id: The id of the prevoyance builder product

    Returns:
        A prevoyance builder product for budget or None if no budget product is linked to the prevoyance builder product
    """
    return current_session.scalars(
        select(PrevoyanceBuilderProductForBudget).filter(
            PrevoyanceBuilderProductForBudget.prevoyance_builder_product_ref
            == str(prevoyance_builder_product_id)
        )
    ).one_or_none()

components.offer_builder.public.testing

This file exposes functions that are useful for testing externally.

GuaranteeParametersOverrides module-attribute

GuaranteeParametersOverrides = dict[
    str, list[CoverageRuleParameter]
]

LoadTuringProductChangePricingInput dataclass

LoadTuringProductChangePricingInput(
    *,
    price_increase=not_set_field(float | None),
    min_price_increase=not_set_field(float | None),
    membership_fee_ratio=not_set_field(float | None),
    projected_loss_ratio=not_set_field(float | None)
)

Bases: DataClassJsonMixin

Pricing input linked to the TuringProductChange entity.

membership_fee_ratio class-attribute instance-attribute

membership_fee_ratio = not_set_field(float | None)

min_price_increase class-attribute instance-attribute

min_price_increase = not_set_field(float | None)

price_increase class-attribute instance-attribute

price_increase = not_set_field(float | None)

projected_loss_ratio class-attribute instance-attribute

projected_loss_ratio = not_set_field(float | None)

get_mock_handle_offer_builder_creation_command

get_mock_handle_offer_builder_creation_command(
    builder_product=None,
)

Returns a function suitable to mock handle_offer_builder_creation_command

  • Useful for letting dependencies test their code.
  • Users can pass a BuilderProduct (public dataclass) to customize the return value.

Usage:

mocker.patch(
    "components.offer_builder.public.api.handle_offer_builder_creation_command",
    side_effect=get_mock_price_builder_product(),
)

Source code in components/offer_builder/public/testing.py
def get_mock_handle_offer_builder_creation_command(
    builder_product: BuilderProduct | None = None,
) -> Callable[[OfferBuilderCreationCommand], BuilderProduct]:
    """
    Returns a function suitable to mock `handle_offer_builder_creation_command`

    - Useful for letting dependencies test their code.
    - Users can pass a BuilderProduct (public dataclass) to customize the return value.

    Usage:
    ```
    mocker.patch(
        "components.offer_builder.public.api.handle_offer_builder_creation_command",
        side_effect=get_mock_price_builder_product(),
    )
    ```
    """

    def mock_handle_offer_builder_creation_command(
        cmd: OfferBuilderCreationCommand,  # noqa: ARG001
        *_args: Any,
        **_kwargs: Any,
    ) -> BuilderProduct:
        if builder_product is None:
            return cast("BuilderProduct", BuilderProductFactory.create())
        return builder_product

    return mock_handle_offer_builder_creation_command

get_mock_price_builder_product

get_mock_price_builder_product(
    pricer_version, coverage_price_by_target
)

Returns a function suitable to mock price_builder_product

Usage:

mocker.patch(
    "components.offer_builder.internal.command_handlers.create_builder_product_from_template.price_builder_product",
    side_effect=get_mock_price_builder_product(pricer_version=..., coverage_price_by_target=[...]),
)

Source code in components/offer_builder/public/testing.py
def get_mock_price_builder_product(
    pricer_version: BasePricerVersion,
    coverage_price_by_target: list[dict[PriceTarget, Prices]],
) -> Callable[[InternalBuilderProduct], InternalBuilderProduct]:
    """Returns a function suitable to mock `price_builder_product`

    Usage:
    ```
    mocker.patch(
        "components.offer_builder.internal.command_handlers.create_builder_product_from_template.price_builder_product",
        side_effect=get_mock_price_builder_product(pricer_version=..., coverage_price_by_target=[...]),
    )
    ```
    """

    def mock_price_builder_product(
        builder_product: InternalBuilderProduct,
        *_args: Any,
        **_kwargs: Any,
    ) -> InternalBuilderProduct:
        from components.offer_builder.internal.entities.factories.builder_price import (
            BuilderPriceFactory,
        )
        from components.offer_builder.internal.entities.factories.builder_pricing import (
            BuilderPricingFactory,
        )

        coverages: list[InternalBuilderCoverage] = []

        for coverage, price_by_target in zip(
            builder_product.coverages, coverage_price_by_target
        ):
            coverages.append(
                replace(
                    coverage,
                    pricing=BuilderPricingFactory.create(
                        id=None,
                        pricer_version=pricer_version,
                        prices=[
                            BuilderPriceFactory.create(target=target, price=price)
                            for target, price in price_by_target.items()
                        ],
                    ),
                )
            )

        builder_product = replace(
            builder_product,
            base_coverage=coverages[0],
            option_coverages=coverages[1:],
        )
        assert builder_product.is_priced
        return builder_product

    return mock_price_builder_product

load_turing_product_change_from_builder_template

load_turing_product_change_from_builder_template(
    template_name,
    product_id,
    builder_product_id,
    builder_product_version_id=None,
    product_change_id=None,
    account_id=None,
    pricing_input=None,
    is_default=True,
    alternative_name="default",
    campaign_name=None,
    guarantee_parameter_overrides=None,
)

Loads a Turing product change from a builder template and creates a new turing product change instance.

Raises:

Type Description
ValueError

If the function is called in production mode.

Returns:

Type Description
None

None

Source code in components/offer_builder/public/testing.py
@do_not_run_in_prod
def load_turing_product_change_from_builder_template(
    template_name: str,
    product_id: str,
    builder_product_id: int,
    builder_product_version_id: int | None = None,
    product_change_id: UUID | None = None,
    account_id: UUID | None = None,
    pricing_input: LoadTuringProductChangePricingInput | None = None,
    is_default: bool = True,
    alternative_name: str = "default",
    campaign_name: str | None = None,
    guarantee_parameter_overrides: GuaranteeParametersOverrides | None = None,
) -> None:
    """
    Loads a Turing product change from a builder template and creates a new turing product change instance.

    Raises:
        ValueError: If the function is called in production mode.

    Returns:
        None
    """
    from components.guarantee_catalog.public.queries.guarantees_definition import (
        get_guarantee_catalog,
    )
    from components.offer_builder.internal.models.factories.turing_product_change import (
        TuringProductChangeFactory,
        TuringProductChangePricingFactory,
    )
    from components.offer_builder.internal.repositories.builder_product_repository import (
        BuilderProductRepository,
    )
    from components.offer_builder.internal.repositories.builder_template_repository import (
        BuilderTemplateRepository,
    )

    app_dependency = get_app_dependency()
    guarantee_catalog = get_guarantee_catalog()
    builder_template_repository = BuilderTemplateRepository()
    builder_product_repository = BuilderProductRepository()

    if pricing_input is None:
        pricing_input = LoadTuringProductChangePricingInput()

    template = one(
        builder_template_repository.search(
            template_name=template_name, country=app_dependency.get_country_code()
        ),
        f"Builder template {template_name} not found",
    )

    builder_template_product = builder_product_repository.get(
        template.builder_product_id
    )
    reference_builder_product = builder_product_repository.get(
        builder_product_id,
        builder_product_version_id=builder_product_version_id,
    )
    with (
        factory_build_add_to_session(),
    ):
        product_change_id = product_change_id or uuid4()
        TuringProductChangeFactory.build(
            id=product_change_id,
            product_id=product_id,
            builder_product_id=str(builder_product_id),
            builder_product_version_id=str(
                mandatory(reference_builder_product.version_id)
            ),
            account_id=account_id
            or UUID(mandatory(reference_builder_product.account_ref)),
            product_change_pricing=TuringProductChangePricingFactory.build(
                **pricing_input.to_dict()
            ),
            is_default=is_default,
            name=alternative_name,
            campaign_name=campaign_name or None,
        )
        for coverage in builder_template_product.coverages:
            _coverage_to_turing_product_coverage_rule(
                coverage=coverage,
                product_change_id=product_change_id,
                guarantee_catalog=guarantee_catalog,
                guarantee_parameter_overrides=guarantee_parameter_overrides or {},
            )

components.offer_builder.public.types

BundleChoiceRef module-attribute

BundleChoiceRef = str

BundleRef module-attribute

BundleRef = str

CategoryRef module-attribute

CategoryRef = str

CoverageIndex module-attribute

CoverageIndex = int

GuaranteeRef module-attribute

GuaranteeRef = str

PriceTarget module-attribute

PriceTarget = str | None

TargetPopulation module-attribute

TargetPopulation = str | None

components.offer_builder.public.v1

actions

prevoyance

delete_prevoyance_builder_products_for_budget
delete_prevoyance_builder_products_for_budget(ids)
Source code in components/offer_builder/internal/v1/actions/prevoyance_builder_product_for_budget.py
def delete_prevoyance_builder_products_for_budget(ids: list[UUID]) -> None:
    current_session.query(PrevoyanceBuilderProductForBudget).filter(  # noqa: ALN085
        PrevoyanceBuilderProductForBudget.id.in_(ids)
    ).delete()
duplicate_prevoyance_builder_product_for_budget
duplicate_prevoyance_builder_product_for_budget(
    id,
    builder_scenario_id=None,
    creator_profile_id=None,
    creator_id=None,
)
Source code in components/offer_builder/internal/v1/actions/prevoyance_builder_product_for_budget.py
def duplicate_prevoyance_builder_product_for_budget(
    id: uuid.UUID,
    builder_scenario_id: uuid.UUID | None = None,
    creator_profile_id: uuid.UUID | None = None,
    creator_id: int | None = None,
) -> uuid.UUID:
    product = get_or_raise_missing_resource(PrevoyanceBuilderProductForBudget, id)

    new_prevoyance_builder_product_ref = None
    if product.prevoyance_builder_product_ref:
        new_prevoyance_builder_product_ref = duplicate_prevoyance_builder_product(
            uuid.UUID(product.prevoyance_builder_product_ref), creator_id=creator_id
        )

    new_prevoyance_builder_product_for_budget = PrevoyanceBuilderProductForBudget(
        id=uuid4(),
        account_ref=product.account_ref,
        prevoyance_offer_ref=product.prevoyance_offer_ref,
        prevoyance_builder_product_ref=new_prevoyance_builder_product_ref,
        display_name=product.display_name,
        builder_scenario_id=builder_scenario_id or product.builder_scenario_id,
        competitor_product_id=product.competitor_product_id,
        origin=product.origin,
        tags=product.tags,
        creator_profile_id=creator_profile_id,
    )
    current_session.add(new_prevoyance_builder_product_for_budget)

    create_prevoyance_builder_targets(
        prevoyance_builder_product_for_budget_id=new_prevoyance_builder_product_for_budget.id,
        prospect_refs=[
            target.prospect_ref for target in product.prevoyance_builder_targets
        ],
    )
    current_session.flush()

    return new_prevoyance_builder_product_for_budget.id

controllers

prevoyance

CreateProductPostJsonArgs dataclass
CreateProductPostJsonArgs(
    prevoyance_offer_ref,
    account_ref,
    display_name,
    builder_entities,
    builder_scenario_id=None,
)

Arguments for create prevoyance builder product for budget request.

account_ref class-attribute instance-attribute
account_ref = field(
    metadata={"validate": OwnerController(NoOwner)}
)
builder_entities class-attribute instance-attribute
builder_entities = field(
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The entities we want to create the prev product for",
        "marshmallow_field": Function(
            deserialize=from_list_dict
        ),
    }
)
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = field(
    default=None,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The builder scenario in which the builder product should be created",
    },
)
display_name class-attribute instance-attribute
display_name = field(
    metadata={"validate": OwnerController(NoOwner)}
)
prevoyance_offer_ref class-attribute instance-attribute
prevoyance_offer_ref = field(
    metadata={"validate": OwnerController(NoOwner)}
)
CreateProductPostJsonSchema module-attribute
CreateProductPostJsonSchema = class_schema(
    CreateProductPostJsonArgs
)
CreateTailoredBuilderProductPostJsonArgs dataclass
CreateTailoredBuilderProductPostJsonArgs(
    account_ref,
    builder_entities,
    builder_scenario_id=None,
    existing_plan_id=None,
    target_population=None,
)

Arguments for create tailored prevoyance builder product request.

account_ref class-attribute instance-attribute
account_ref = field(
    metadata={"validate": OwnerController(NoOwner)}
)
builder_entities class-attribute instance-attribute
builder_entities = field(
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The entities we want to create the prev product for",
        "marshmallow_field": Function(
            deserialize=from_list_dict
        ),
    }
)
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = field(
    default=None,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The builder scenario in which the builder product should be created",
    },
)
existing_plan_id class-attribute instance-attribute
existing_plan_id = field(
    default=None,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "A PrevoyancePlan id to use a a template and to copy guarantees to the builder product",
    },
)
target_population class-attribute instance-attribute
target_population = field(
    default=None,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The target population for template",
        "marshmallow_field": Enum(
            ProfessionalCategory, by_value=True
        ),
    },
)
CreateTailoredBuilderProductPostJsonSchema module-attribute
CreateTailoredBuilderProductPostJsonSchema = class_schema(
    CreateTailoredBuilderProductPostJsonArgs
)
PrevoyanceOfferController

Bases: BaseController

endpoint_path class-attribute instance-attribute
endpoint_path = '/sales_xp/prevoyance'
SearchPostJsonArgs dataclass
SearchPostJsonArgs(ccn_codes)

Arguments for search prevoyance offers by CCN codes request.

ccn_codes instance-attribute
ccn_codes
SearchPostJsonSchema module-attribute
SearchPostJsonSchema = class_schema(SearchPostJsonArgs)
UpdatePatchJsonArgs dataclass
UpdatePatchJsonArgs(
    tranche_1_participation_percent=None,
    tranche_2_participation_percent=None,
    builder_entities=None,
    builder_scenario_id=NOT_SET,
    competitor_product_id=NOT_SET,
    display_name=NOT_SET,
)

Arguments for prevoyance patch request.

builder_entities class-attribute instance-attribute
builder_entities = field(
    default=None,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The entities we want to create the prev product for",
        "marshmallow_field": Function(
            deserialize=from_list_dict
        ),
    },
)
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = field(
    default=NOT_SET,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The builder scenario in which the builder product should be created",
    },
)
competitor_product_id class-attribute instance-attribute
competitor_product_id = field(
    default=NOT_SET,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The competitor product id to attach to the prevoyance product",
    },
)
display_name class-attribute instance-attribute
display_name = field(
    default=NOT_SET,
    metadata={
        "validate": OwnerController(NoOwner),
        "description": "The display name of the prevoyance product",
    },
)
tranche_1_participation_percent class-attribute instance-attribute
tranche_1_participation_percent = None
tranche_2_participation_percent class-attribute instance-attribute
tranche_2_participation_percent = None
UpdatePatchJsonSchema module-attribute
UpdatePatchJsonSchema = class_schema(UpdatePatchJsonArgs)
create_product
create_product(json_args, user)

Create a new prevoyance builder product for budget.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/product_for_budget",
    methods=["POST"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@use_args(
    CreateProductPostJsonSchema(),
    location="json",
    unknown=EXCLUDE,
    arg_name="json_args",
)
@obs.api_call()
def create_product(json_args: CreateProductPostJsonArgs, user: BaseUser) -> Response:
    """Create a new prevoyance builder product for budget."""
    from components.offer_builder.internal.v1.actions.prevoyance_builder_product_for_budget import (
        create_prevoyance_builder_product_for_budget,
    )
    from components.offer_builder.public.enums.builder_product_origin import (
        BuilderProductOrigin,
    )
    from components.offer_builder.public.enums.builder_product_tag import (
        BuilderProductTag,
    )
    from shared.helpers.db import current_session

    prevoyance_builder_product = create_prevoyance_builder_product_for_budget(
        account_ref=json_args.account_ref,
        prevoyance_offer_ref=json_args.prevoyance_offer_ref,
        display_name=json_args.display_name,
        builder_entities=json_args.builder_entities,
        builder_scenario_id=json_args.builder_scenario_id,
        origin=BuilderProductOrigin.offer_builder,
        tags=[BuilderProductTag.acquisition],
        creator_profile_id=user.profile_id,
    )

    current_session.commit()

    return make_json_response(
        dict(prevoyance_builder_product_id=prevoyance_builder_product.id)
    )
create_tailored_builder_product
create_tailored_builder_product(json_args, user)

Create a new tailored prevoyance builder product.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/tailored_builder_product",
    methods=["POST"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@use_args(
    CreateTailoredBuilderProductPostJsonSchema(),
    location="json",
    unknown=EXCLUDE,
    arg_name="json_args",
)
@obs.api_call()
def create_tailored_builder_product(
    json_args: CreateTailoredBuilderProductPostJsonArgs, user: BaseUser
) -> Response:
    """Create a new tailored prevoyance builder product."""
    from components.offer_builder.internal.v1.actions.prevoyance_builder_product_for_budget import (
        create_prevoyance_tailored_builder_product,
    )
    from components.offer_builder.public.enums.builder_product_origin import (
        BuilderProductOrigin,
    )
    from components.offer_builder.public.enums.builder_product_tag import (
        BuilderProductTag,
    )
    from shared.helpers.db import current_session

    prevoyance_tailored_builder_product = create_prevoyance_tailored_builder_product(
        account_ref=json_args.account_ref,
        builder_entities=json_args.builder_entities,
        origin=BuilderProductOrigin.offer_builder,
        tags=[BuilderProductTag.acquisition],
        creator_profile_id=user.profile_id,
        creator_id=user.id,  # type: ignore[arg-type]
        existing_plan_id=json_args.existing_plan_id,
        target_population=json_args.target_population or ProfessionalCategory.all,
        builder_scenario_id=json_args.builder_scenario_id,
    )

    current_session.commit()

    return make_json_response(
        dict(
            prevoyance_tailored_builder_product_id=prevoyance_tailored_builder_product.id
        )
    )
delete
delete(id)

Delete a prevoyance builder product for budget.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/product_for_budget/<id>",
    methods=["DELETE"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@obs.api_call()
def delete(id: str) -> Response:
    """Delete a prevoyance builder product for budget."""
    from components.offer_builder.internal.v1.actions.prevoyance_builder_product_for_budget import (
        delete_prevoyance_builder_product_for_budget,
    )
    from shared.helpers.db import current_session

    delete_prevoyance_builder_product_for_budget(id=id)
    current_session.commit()
    return make_json_response({"deleted": True, "id": id})
duplicate
duplicate(id, user)

Duplicate a prevoyance builder product for budget.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/product_for_budget/<id>/duplicate",
    methods=["POST"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@obs.api_call()
def duplicate(id: str, user: BaseUser) -> Response:
    """Duplicate a prevoyance builder product for budget."""
    from components.offer_builder.internal.v1.actions.prevoyance_builder_product_for_budget import (
        duplicate_prevoyance_builder_product_for_budget,
    )
    from shared.helpers.db import current_session

    new_id = duplicate_prevoyance_builder_product_for_budget(
        id=UUID(id),
        creator_profile_id=user.profile_id,
        creator_id=user.id,  # type: ignore[arg-type]
    )
    current_session.commit()
    return make_json_response({id: new_id})
get_product
get_product(id)

Get a single prevoyance builder product for budget by ID.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/product_for_budget/<id>",
    methods=["GET"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@obs.api_call()
def get_product(id: str) -> Response:
    """Get a single prevoyance builder product for budget by ID."""
    from components.offer_builder.public.v1.queries.prevoyance import (
        get_prevoyance_builder_product_for_budget_for_display,
    )

    return make_json_response(
        get_prevoyance_builder_product_for_budget_for_display(UUID(id))
    )
search
search(json_args)

Search prevoyance offers by CCN codes.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/search_offers",
    methods=["POST"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@use_args(
    SearchPostJsonSchema(), location="json", unknown=EXCLUDE, arg_name="json_args"
)
@obs.api_call()
def search(json_args: SearchPostJsonArgs) -> Response:
    """Search prevoyance offers by CCN codes."""
    from components.offer_builder.public.v1.queries.prevoyance import (
        get_prevoyance_offers_by_ccns,
    )
    from shared.helpers.response import make_json_response

    prevoyance_offers = get_prevoyance_offers_by_ccns(
        ccn_codes=set(json_args.ccn_codes)
    )

    return make_json_response(prevoyance_offers)
update
update(id, json_args)

Update a prevoyance builder product for budget.

Source code in components/offer_builder/public/v1/controllers/prevoyance.py
@PrevoyanceOfferController.action_route(
    "/product_for_budget/<id>",
    methods=["PATCH"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@use_args(
    UpdatePatchJsonSchema(), location="json", unknown=EXCLUDE, arg_name="json_args"
)
@obs.api_call()
def update(id: str, json_args: UpdatePatchJsonArgs) -> Response:
    """Update a prevoyance builder product for budget."""
    from components.offer_builder.internal.v1.actions.prevoyance_builder_product_for_budget import (
        update_prevoyance_builder_product_for_budget,
    )
    from shared.helpers.db import current_session

    update_prevoyance_builder_product_for_budget(
        id=id,
        tranche_1_participation_percent=json_args.tranche_1_participation_percent,
        tranche_2_participation_percent=json_args.tranche_2_participation_percent,
        builder_entities=json_args.builder_entities,
        builder_scenario_id=json_args.builder_scenario_id,
        competitor_product_id=json_args.competitor_product_id,
        display_name=json_args.display_name,
    )
    current_session.commit()
    return make_json_response({"updated": True, "id": id})

sales_xp_builder_product

GetProductsArgsArgs dataclass
GetProductsArgsArgs(account_id)

Query args for _get_products endpoint.

account_id class-attribute instance-attribute
account_id = field(
    metadata={
        "description": "The account id linked",
        "validate": OwnerController(NoOwner),
    }
)
GetProductsArgsSchema module-attribute
GetProductsArgsSchema = class_schema(GetProductsArgsArgs)
SalesXpBuilderProductController

Bases: BaseController

This controller serves builder products but as we already have a bunch of routes dedicated to another context, using a separate set of routes/endpoints feels simpler to understand.

endpoint_path class-attribute instance-attribute
endpoint_path = '/sales_xp/builder_product'

entities

builder_product_api

BuilderCoverageAPISchema dataclass
BuilderCoverageAPISchema(
    coverage_rules=list(),
    selected_care_types=None,
    option_number=None,
    coverage_level=None,
    past_results_correction_factor=None,
    target_loss_ratio=NOT_SET,
    membership_fee_percent_literal=NOT_SET,
    membership_fee_ratio=NOT_SET,
    alsace_moselle_price_ratio=NOT_SET,
    custom_settings=NOT_SET,
)

Bases: DataClassJsonMixin, DataClassJsonAlanMixin

__post_init__
__post_init__()
Source code in components/offer_builder/public/v1/entities/builder_product_api.py
def __post_init__(self) -> None:  # noqa: D105
    if is_set(self.membership_fee_percent_literal):
        if is_set(self.membership_fee_ratio):
            raise ValueError(
                "membership_fee_percent_literal and membership_fee_ratio cannot both be set"
            )
        membership_fee_ratio = (
            None
            if self.membership_fee_percent_literal is None
            else Decimal(self.membership_fee_percent_literal) / Decimal(100)
        )
        # NB: Must use object.__setattr__ for assignment in post_init for Frozen
        # dataclass, see doc: https://docs.python.org/3/library/dataclasses.html#frozen-instances
        object.__setattr__(self, "membership_fee_ratio", membership_fee_ratio)
        object.__setattr__(self, "membership_fee_percent_literal", NOT_SET)
alsace_moselle_price_ratio class-attribute instance-attribute
alsace_moselle_price_ratio = NOT_SET
coverage_level class-attribute instance-attribute
coverage_level = None
coverage_rules class-attribute instance-attribute
coverage_rules = field(default_factory=list)
custom_settings class-attribute instance-attribute
custom_settings = NOT_SET
membership_fee_percent_literal class-attribute instance-attribute
membership_fee_percent_literal = NOT_SET
membership_fee_ratio class-attribute instance-attribute
membership_fee_ratio = NOT_SET
option_number class-attribute instance-attribute
option_number = None
past_results_correction_factor class-attribute instance-attribute
past_results_correction_factor = None
selected_care_types class-attribute instance-attribute
selected_care_types = None
target_loss_ratio class-attribute instance-attribute
target_loss_ratio = NOT_SET
BuilderProductCoverageRuleAPISchema dataclass
BuilderProductCoverageRuleAPISchema(
    guarantee_short_code, coverage_rule_parameter_set
)

Bases: DataClassJsonMixin

coverage_rule_parameter_set instance-attribute
coverage_rule_parameter_set
guarantee_short_code instance-attribute
guarantee_short_code
BuilderProductSelectedCareTypeAPISchema dataclass
BuilderProductSelectedCareTypeAPISchema(
    guarantee_short_code, care_type_short_code
)

Bases: DataClassJsonMixin

care_type_short_code instance-attribute
care_type_short_code
guarantee_short_code instance-attribute
guarantee_short_code

builder_product_card

BuilderProductCard dataclass
BuilderProductCard(
    builder_product_ref,
    builder_product_version_ref,
    coverage_level,
    name,
    description,
    emoji,
    base_price,
    option_prices,
    plan_ref=None,
    is_renewal=False,
    is_current_plan=False,
    start_date=None,
)

Bases: DataClassJsonMixin

base_price instance-attribute
base_price
builder_product_ref instance-attribute
builder_product_ref
builder_product_version_ref instance-attribute
builder_product_version_ref
coverage_level instance-attribute
coverage_level
description instance-attribute
description
emoji instance-attribute
emoji
is_current_plan class-attribute instance-attribute
is_current_plan = False
is_renewal class-attribute instance-attribute
is_renewal = False
name instance-attribute
name
option_prices instance-attribute
option_prices
plan_ref class-attribute instance-attribute
plan_ref = None
start_date class-attribute instance-attribute
start_date = None
BuilderProductCardPrice dataclass
BuilderProductCardPrice(
    primary, partner, child, family, nth_children_free
)

Bases: DataClassJsonMixin

child instance-attribute
child
family instance-attribute
family
nth_children_free instance-attribute
nth_children_free
partner instance-attribute
partner
primary instance-attribute
primary

ccn_collective_agreement_api

CCNCollectiveAgreementAPISchema dataclass
CCNCollectiveAgreementAPISchema(
    ccn_id, collective_agreement_id=None
)

Bases: DataClassJsonMixin, DataClassJsonAlanMixin

API schema for providing CCN collective agreements to use for compliance check in a BuilderProduct.

ccn_id: The identifier of the CCN. collective_agreement_id: The identifier of the collective agreement - if not specified, selects all extended collective agreements for the CCN.

ccn_id instance-attribute
ccn_id
collective_agreement_id class-attribute instance-attribute
collective_agreement_id = None

for_display

coverage_table
BaseCoverageTableGuaranteeRow dataclass
BaseCoverageTableGuaranteeRow(
    guarantee_type,
    short_code,
    label,
    product_formula,
    care_types,
    secu_percent_reimbursement,
)

Bases: DataClassJsonMixin

care_types instance-attribute
care_types
guarantee_type instance-attribute
guarantee_type
label instance-attribute
label
product_formula instance-attribute
product_formula
secu_percent_reimbursement instance-attribute
secu_percent_reimbursement
short_code instance-attribute
short_code
BaseCoverageTableRow dataclass
BaseCoverageTableRow(section_id, short_code_prefix)

Bases: DataClassJsonMixin

section_id instance-attribute
section_id
short_code_prefix instance-attribute
short_code_prefix
CompetitorCoverageTable dataclass
CompetitorCoverageTable(rows, coverage_count)

Bases: DataClassJsonMixin

coverage_count instance-attribute
coverage_count
rows instance-attribute
rows
CompetitorCoverageTableGuaranteeRow dataclass
CompetitorCoverageTableGuaranteeRow(
    guarantee_type,
    short_code,
    label,
    product_formula,
    care_types,
    secu_percent_reimbursement,
    guarantee_values,
    mutually_exclusive_guarantees,
)

Bases: BaseCoverageTableGuaranteeRow

guarantee_values instance-attribute
guarantee_values
mutually_exclusive_guarantees instance-attribute
mutually_exclusive_guarantees
CompetitorCoverageTableGuaranteeValue dataclass
CompetitorCoverageTableGuaranteeValue(
    coverage_type,
    option_number,
    selected_mutually_exclusive_guarantee_short_code=None,
    value=None,
    selected_care_type_short_codes=None,
    coverage_identifier=None,
    is_optional=False,
    parsed_value=None,
    comment=None,
)

Bases: CoverageTableGuaranteeValue

comment class-attribute instance-attribute
comment = None
parsed_value class-attribute instance-attribute
parsed_value = None
CompetitorCoverageTableRow dataclass
CompetitorCoverageTableRow(
    section_id, short_code_prefix, guarantee_rows
)

Bases: BaseCoverageTableRow

This dataclass is a high level row in the coverage table. It aggregates multiple guarantees like OPTAM/NON_OPTAM, FOR_100_PCT_SANTE/FOR_NON_100_PCT_SANTE, and so on.

guarantee_rows instance-attribute
guarantee_rows
CompetitorCoverageTables dataclass
CompetitorCoverageTables(value_table, parsed_value_table)

Bases: DataClassJsonMixin

parsed_value_table instance-attribute
parsed_value_table
value_table instance-attribute
value_table
CoverageTable dataclass
CoverageTable(
    rows,
    coverage_count,
    template_signature_coverage_id,
    template_competitor_product_id,
)

Bases: DataClassJsonMixin

All guarantees sharing the same short_code_prefix are grouped in the same guarantee row.

That prefix is what's left when we remove the OPTAM / NON_OPTAM / ... suffixes.

coverage_count instance-attribute
coverage_count
rows instance-attribute
rows
template_competitor_product_id instance-attribute
template_competitor_product_id
template_signature_coverage_id instance-attribute
template_signature_coverage_id
CoverageTableCareType

Bases: TypedDict

label instance-attribute
label
short_code instance-attribute
short_code
CoverageTableGuaranteeRow dataclass
CoverageTableGuaranteeRow(
    guarantee_type,
    short_code,
    label,
    product_formula,
    care_types,
    secu_percent_reimbursement,
    guarantee_values,
    mutually_exclusive_guarantees,
)

Bases: BaseCoverageTableGuaranteeRow

guarantee_values instance-attribute
guarantee_values
mutually_exclusive_guarantees instance-attribute
mutually_exclusive_guarantees
CoverageTableGuaranteeValue dataclass
CoverageTableGuaranteeValue(
    coverage_type,
    option_number,
    selected_mutually_exclusive_guarantee_short_code=None,
    value=None,
    selected_care_type_short_codes=None,
    coverage_identifier=None,
    is_optional=False,
)

Bases: DataClassJsonMixin

Dump from CoverageRuleParameterSet + Information about which

Remove what's not needed for the frontend (because it's too specific to edge-cases).

coverage_identifier class-attribute instance-attribute
coverage_identifier = None
coverage_type instance-attribute
coverage_type
is_optional class-attribute instance-attribute
is_optional = False
option_number instance-attribute
option_number
selected_care_type_short_codes class-attribute instance-attribute
selected_care_type_short_codes = None
selected_mutually_exclusive_guarantee_short_code class-attribute instance-attribute
selected_mutually_exclusive_guarantee_short_code = None
value class-attribute instance-attribute
value = None
CoverageTableRow dataclass
CoverageTableRow(
    section_id, short_code_prefix, guarantee_rows
)

Bases: BaseCoverageTableRow

This dataclass is a high level row in the coverage table. It aggregates multiple guarantees like OPTAM/NON_OPTAM, FOR_100_PCT_SANTE/FOR_NON_100_CPCT_SANTE, and so on.

guarantee_rows instance-attribute
guarantee_rows
GuaranteeValueDetails dataclass
GuaranteeValueDetails(
    *,
    percent_reimbursement=None,
    percent_reimbursement_of_total_cost=None,
    max_reimbursement_per_care_in_euros=None,
    frame_amount_in_euros=None,
    limit_is_per_side=None,
    cumulative_limit_in_euros=None,
    cumulative_limit_includes_secu=None,
    count_limit=None,
    limit_window=None,
    renewal_limit_window=None,
    count_grouped_care_as_one_for_limit=None,
    reimburse_100_pct_brss=None,
    reimburse_full_cost=None,
    sum_grouped_care_amounts_for_max_reimbursement_amount=None,
    use_ccam_table_to_find_brss_when_zero_or_none=None,
    default_brss_in_euros=None,
    use_cumulative_limit_to_cover_above_brss_percent=None,
    lump_sum_in_euros=None,
    ignore_audio_accessories_in_count_limit=None,
    grouping_for_limits=None,
    percent_reimbursement_after_count_limit_reached=None,
    max_reimbursement_per_care_in_euros_after_count_limit_reached=None
)

Bases: DataClassJsonMixin

count_grouped_care_as_one_for_limit class-attribute instance-attribute
count_grouped_care_as_one_for_limit = None
count_limit class-attribute instance-attribute
count_limit = None
cumulative_limit_in_euros class-attribute instance-attribute
cumulative_limit_in_euros = None
cumulative_limit_includes_secu class-attribute instance-attribute
cumulative_limit_includes_secu = None
default_brss_in_euros class-attribute instance-attribute
default_brss_in_euros = None
frame_amount_in_euros class-attribute instance-attribute
frame_amount_in_euros = None
grouping_for_limits class-attribute instance-attribute
grouping_for_limits = None
ignore_audio_accessories_in_count_limit class-attribute instance-attribute
ignore_audio_accessories_in_count_limit = None
limit_is_per_side class-attribute instance-attribute
limit_is_per_side = None
limit_window class-attribute instance-attribute
limit_window = None
lump_sum_in_euros class-attribute instance-attribute
lump_sum_in_euros = None
max_reimbursement_per_care_in_euros class-attribute instance-attribute
max_reimbursement_per_care_in_euros = None
max_reimbursement_per_care_in_euros_after_count_limit_reached class-attribute instance-attribute
max_reimbursement_per_care_in_euros_after_count_limit_reached = (
    None
)
percent_reimbursement class-attribute instance-attribute
percent_reimbursement = None
percent_reimbursement_after_count_limit_reached class-attribute instance-attribute
percent_reimbursement_after_count_limit_reached = None
percent_reimbursement_of_total_cost class-attribute instance-attribute
percent_reimbursement_of_total_cost = None
reimburse_100_pct_brss class-attribute instance-attribute
reimburse_100_pct_brss = None
reimburse_full_cost class-attribute instance-attribute
reimburse_full_cost = None
renewal_limit_window class-attribute instance-attribute
renewal_limit_window = None
sum_grouped_care_amounts_for_max_reimbursement_amount class-attribute instance-attribute
sum_grouped_care_amounts_for_max_reimbursement_amount = None
use_ccam_table_to_find_brss_when_zero_or_none class-attribute instance-attribute
use_ccam_table_to_find_brss_when_zero_or_none = None
use_cumulative_limit_to_cover_above_brss_percent class-attribute instance-attribute
use_cumulative_limit_to_cover_above_brss_percent = None
MutuallyExclusiveGuarantee dataclass
MutuallyExclusiveGuarantee(
    guarantee_type,
    short_code,
    label,
    product_formula,
    care_types,
    secu_percent_reimbursement,
    guarantee_option_label,
)

Bases: BaseCoverageTableGuaranteeRow

guarantee_option_label instance-attribute
guarantee_option_label
prevoyance
PrevoyanceBuilderProductForBudgetForDisplay dataclass
PrevoyanceBuilderProductForBudgetForDisplay(
    account_ref,
    display_name,
    offer,
    prevoyance_builder_product,
    tranche_1_participation_percent,
    tranche_2_participation_percent,
    builder_prospect_refs,
    id,
    competitor_product_id=None,
    origin=None,
    tags=None,
    scenario_id=None,
)

Bases: DataClassJsonMixin

account_ref instance-attribute
account_ref
builder_prospect_refs instance-attribute
builder_prospect_refs
competitor_product_id class-attribute instance-attribute
competitor_product_id = None
display_name instance-attribute
display_name
id instance-attribute
id
offer instance-attribute
offer
origin class-attribute instance-attribute
origin = None
prevoyance_builder_product instance-attribute
prevoyance_builder_product
scenario_id class-attribute instance-attribute
scenario_id = None
tags class-attribute instance-attribute
tags = None
tranche_1_participation_percent instance-attribute
tranche_1_participation_percent
tranche_2_participation_percent instance-attribute
tranche_2_participation_percent
PrevoyanceBuilderProductForDisplay dataclass
PrevoyanceBuilderProductForDisplay(id)

Bases: DataClassJsonMixin

id instance-attribute
id
PrevoyanceOfferForDisplay dataclass
PrevoyanceOfferForDisplay(
    id,
    ccn_codes,
    cnp_code,
    professional_category,
    libelle,
    tranche_1_price,
    tranche_2_price,
    tranche_1_death_price,
    tranche_2_death_price,
    premium=False,
)

Bases: DataClassJsonMixin

ccn_codes instance-attribute
ccn_codes
cnp_code instance-attribute
cnp_code
id instance-attribute
id
libelle instance-attribute
libelle
premium class-attribute instance-attribute
premium = False
professional_category instance-attribute
professional_category
tranche_1_death_price instance-attribute
tranche_1_death_price
tranche_1_price instance-attribute
tranche_1_price
tranche_2_death_price instance-attribute
tranche_2_death_price
tranche_2_price instance-attribute
tranche_2_price
subscribed_product_for_display
SubscribedProductForDisplay dataclass
SubscribedProductForDisplay(
    id,
    bundle_version,
    name,
    professional_category,
    ccn_codes,
    product_line,
    is_standard_offer,
    subscription_type,
    periods,
    has_renewal_specs=False,
)

Bases: DataClassJsonMixin

bundle_version instance-attribute
bundle_version
ccn_codes instance-attribute
ccn_codes
has_renewal_specs class-attribute instance-attribute
has_renewal_specs = False
id instance-attribute
id
is_standard_offer instance-attribute
is_standard_offer
name instance-attribute
name
periods instance-attribute
periods
product_line instance-attribute
product_line
professional_category instance-attribute
professional_category
subscription_type instance-attribute
subscription_type
SubscribedProductPeriodForDisplay dataclass
SubscribedProductPeriodForDisplay(
    id, subscription_id, subscriber_id, subscriber_name
)

Bases: DataClassJsonMixin

id instance-attribute
id
subscriber_id instance-attribute
subscriber_id
subscriber_name instance-attribute
subscriber_name
subscription_id instance-attribute
subscription_id

manual_demographics

ManualDemographics dataclass
ManualDemographics(
    number_of_employees,
    number_of_covered_employees=None,
    average_age=None,
    number_of_partners=None,
    number_of_ani=None,
    number_of_option_taker=None,
    number_of_male=None,
    number_of_cadre=None,
    number_of_children=None,
    number_of_single=None,
    number_of_first_children=None,
    number_of_first_two_children=None,
    tranche_1_cadres_salary_sum=None,
    tranche_2_cadres_salary_sum=None,
    tranche_1_non_cadres_salary_sum=None,
    tranche_2_non_cadres_salary_sum=None,
    tranche_1_all_salary_sum=None,
    tranche_2_all_salary_sum=None,
    company_ref=None,
    prospect_ref=None,
    origin=ManualDemographicsOrigin.ACQUISITION,
    id=None,
)

Bases: DataClassJsonMixin

ani_ratio property
ani_ratio
average_age class-attribute instance-attribute
average_age = None
cadre_ratio property
cadre_ratio
child_ratio property
child_ratio

It used to be named children_per_primary_ratio.

Returns: Total number of children divided by the number of primaries.

company_ref class-attribute instance-attribute
company_ref = None
first_child_ratio property
first_child_ratio

Use to be primary_with_children_ratio.

Number of primaries with children divided by the number of primaries

Type Description
float | None

(equivalent to the number of first child divided by the number of primaries).

first_two_children_ratio property
first_two_children_ratio

Use to be paying_children_ratio.

Returns: Number of children being the first or the second in the policy divided by the number of primaries.

id class-attribute instance-attribute
id = None
male_ratio property
male_ratio
number_of_ani class-attribute instance-attribute
number_of_ani = None
number_of_cadre class-attribute instance-attribute
number_of_cadre = None
number_of_children class-attribute instance-attribute
number_of_children = None
number_of_covered_employees class-attribute instance-attribute
number_of_covered_employees = None
number_of_employees instance-attribute
number_of_employees
number_of_first_children class-attribute instance-attribute
number_of_first_children = None
number_of_first_two_children class-attribute instance-attribute
number_of_first_two_children = None
number_of_male class-attribute instance-attribute
number_of_male = None
number_of_option_taker class-attribute instance-attribute
number_of_option_taker = None
number_of_partners class-attribute instance-attribute
number_of_partners = None
number_of_single class-attribute instance-attribute
number_of_single = None
option_taker_ratio property
option_taker_ratio
origin class-attribute instance-attribute
origin = ACQUISITION
partner_ratio property
partner_ratio
prospect_ref class-attribute instance-attribute
prospect_ref = None
single_ratio property
single_ratio
to_global_manual_demographics
to_global_manual_demographics(nb_coverages)

Convert a v1 ManualDemographics to a global ManualDemographics entity. Temporary helper function until we move product change logic to using global manual demographics.

Source code in components/offer_builder/public/v1/entities/manual_demographics.py
def to_global_manual_demographics(
    self, nb_coverages: int
) -> GlobalManualDemographics:
    """
    Convert a v1 ManualDemographics to a global ManualDemographics entity.
    Temporary helper function until we move product change logic to using global manual demographics.
    """
    return GlobalManualDemographics(
        id=mandatory(self.id),
        prospect_ref=mandatory(self.prospect_ref),
        version=None,
        number_of_primaries=self.number_of_employees,
        number_of_covered_employees=self.number_of_covered_employees,
        average_age=self.average_age,
        male_ratio=self.male_ratio,
        target_population_ratios={
            "cadres": self.cadre_ratio,
            "non-cadres": 1 - self.cadre_ratio,
        }
        if self.cadre_ratio is not None
        else None,
        partner_ratio=self.partner_ratio,
        child_ratio=self.child_ratio,
        non_paying_ratio=self.ani_ratio,
        coverage_taker_ratios={
            cov_index: 1
            # FR specific: all employees are subscribed to base coverage
            if cov_index == 0
            else self.option_taker_ratio / (nb_coverages - 1)
            for cov_index in range(nb_coverages)
        }
        if self.option_taker_ratio is not None
        else None,
        # v1 ManualDemographics are only used in France, we can default the country until we kill it
        country=OfferBuilderSupportedCountry.fr,
        single_ratio=self.single_ratio,
        first_child_ratio=self.first_child_ratio,
        first_two_children_ratio=self.first_two_children_ratio,
        option_taker_ratio=self.option_taker_ratio,
        origin=self.origin,
    )
tranche_1_all_salary_sum class-attribute instance-attribute
tranche_1_all_salary_sum = None
tranche_1_cadres_salary_sum class-attribute instance-attribute
tranche_1_cadres_salary_sum = None
tranche_1_non_cadres_salary_sum class-attribute instance-attribute
tranche_1_non_cadres_salary_sum = None
tranche_2_all_salary_sum class-attribute instance-attribute
tranche_2_all_salary_sum = None
tranche_2_cadres_salary_sum class-attribute instance-attribute
tranche_2_cadres_salary_sum = None
tranche_2_non_cadres_salary_sum class-attribute instance-attribute
tranche_2_non_cadres_salary_sum = None

plan_payload

PlanPayload dataclass
PlanPayload(
    name,
    display_name,
    product,
    category,
    price_grid_name,
    option_price_grid_names,
    alsace_moselle_price_grid_name,
    emoji_code,
    start_date,
    bundle_version,
    assistance_type,
)
alsace_moselle_price_grid_name instance-attribute
alsace_moselle_price_grid_name
assistance_type instance-attribute
assistance_type
bundle_version instance-attribute
bundle_version
category instance-attribute
category
display_name instance-attribute
display_name
emoji_code instance-attribute
emoji_code
name instance-attribute
name
option_price_grid_names instance-attribute
option_price_grid_names
price_grid_name instance-attribute
price_grid_name
product instance-attribute
product
start_date instance-attribute
start_date

sales_xp_builder_product_api

SignatureCoverageSearchApiSchema dataclass
SignatureCoverageSearchApiSchema(
    ccn_code=None, franchise_name=None
)

Bases: DataClassJsonMixin, DataClassJsonAlanMixin

ccn_code class-attribute instance-attribute
ccn_code = None
franchise_name class-attribute instance-attribute
franchise_name = None

enums

bucket_name

BucketName

Bases: AlanBaseEnum

extra_small class-attribute instance-attribute
extra_small = 'extra_small'
from_number_of_employees staticmethod
from_number_of_employees(number_of_employees)
Source code in components/offer_builder/public/v1/enums/bucket_name.py
@staticmethod
def from_number_of_employees(number_of_employees: int) -> BucketName:  # noqa: D102
    if number_of_employees < 20:
        return BucketName.extra_small
    if number_of_employees < 50:
        return BucketName.small
    if number_of_employees < 300:
        return BucketName.medium
    if number_of_employees < 2000:
        return BucketName.large
    return BucketName.very_large
large class-attribute instance-attribute
large = 'large'
medium class-attribute instance-attribute
medium = 'medium'
small class-attribute instance-attribute
small = 'small'
very_large class-attribute instance-attribute
very_large = 'very_large'

coverage_change

COVERAGE_CHANGE_EXPLANATIONS module-attribute
COVERAGE_CHANGE_EXPLANATIONS = {
    ADD_ALAN_THERAPY_SESSION: "Two annual sessions of Alan Mind therapy sessions with a coach or psychologist will be added to the coverage.",
    ADD_MIX_GLASSES_GUARANTEES: "Introduces guarantees related to mixed types glasses, aiming to provide a comprehensive coverage including different types of optical needs.",
    ADD_CHILD_GLASSES_GUARANTEES: "Introduces guarantees for children glasses, ensuring that their unique needs are met and acknowledging the rapid changes in prescription that children may experience.",
    UPDATE_DENTAL_PROSTHESIS_COST_OF_CLAIMS: "The update involves differentiating between controlled and standard baskets (paniers) of dental prosthesis to align with regulatory requirements.",
    DEL_MEDITATION: "Removal of meditation from the list of covered guarantees.",
    ADD_MISSING_GUARANTEES: "Involves adding guarantees that were previously overlooked or became necessary due to changes in healthcare needs or regulatory adjustments.",
    ADD_PRIVATE_HOSPITAL_ROOM_LIMIT: "This change introduces a cap to private room hospital stays.",
    REMOVE_ALAN_THERAPY_SESSION: "Removes the Alan Therapy Session guarantee.",
    NON_COMPLIANT_ALTERNATIVE_MEDICINE: "Removes some alternative medicine guarantees that are now recognized as non-compliant.",
    COMPLIANCE_FIX: "Compliance fix.",
    ADD_OPTION: "Default option experiment.",
    PRIVATE_ROOM_SPLIT_DAY_NIGHT: "A day hospital stay lasts a maximum of 12 hours and does not require the same comfort and privacy needs as a full night in the hospital. A collective day room (which costs €0) may be sufficient in the case of outpatient hospitalization.",
    NON_REIMBURSED_DENTAL_PROSTHESIS_COUNT_LIMIT: "Fight against fraud: By capping the number of non-reimbursed dental prostheses to 3 per year, we can better control these atypical situations while maintaining optimal coverage for 99.96% of our members, a preventive measure that strengthens our responsible management of benefits. On average, a member needing non-reimbursed dental prostheses has 1.5 per year.",
    DENTAL_IMPLANT_COUNT_LIMIT: "By transitioning from an annual package to a reimbursement model capped at 2 or 3 implants per year, we encourage more responsible dental care consumption, knowing that a member typically undergoes 1.4 implants annually. This change will prevent situations where the entire annual package is consumed for a single implant, potentially generating savings on consumption.",
    PRIVATE_ROOM_CAP: "This change introduces a cap to private room hospital stays.",
    ALAN_MIND_MERGE: "Deprecate the 'Alan Therapy' guarantee that gave access to the in-app therapists (a remnant of Alan Mind).",
}
CoverageChangeEnum

Bases: AlanBaseEnum

ADD_ALAN_THERAPY_SESSION class-attribute instance-attribute
ADD_ALAN_THERAPY_SESSION = 'add_alan_therapy_session'
ADD_CHILD_GLASSES_GUARANTEES class-attribute instance-attribute
ADD_CHILD_GLASSES_GUARANTEES = (
    "add_child_glasses_guarantees"
)
ADD_MISSING_GUARANTEES class-attribute instance-attribute
ADD_MISSING_GUARANTEES = 'add_missing_guarantees'
ADD_MIX_GLASSES_GUARANTEES class-attribute instance-attribute
ADD_MIX_GLASSES_GUARANTEES = 'add_mix_glasses_guarantees'
ADD_OPTION class-attribute instance-attribute
ADD_OPTION = 'add_option'
ADD_PRIVATE_HOSPITAL_ROOM_LIMIT class-attribute instance-attribute
ADD_PRIVATE_HOSPITAL_ROOM_LIMIT = (
    "add_private_hospital_room_limit"
)
ALAN_MIND_MERGE class-attribute instance-attribute
ALAN_MIND_MERGE = 'alan_mind_merge'
COMPLIANCE_FIX class-attribute instance-attribute
COMPLIANCE_FIX = 'compliance_fix'
CONTACT_LENS_NETWORK class-attribute instance-attribute
CONTACT_LENS_NETWORK = 'contact_lens_network'
DEL_MEDITATION class-attribute instance-attribute
DEL_MEDITATION = 'del_meditation'
DENTAL_IMPLANT_COUNT_LIMIT class-attribute instance-attribute
DENTAL_IMPLANT_COUNT_LIMIT = 'dental_implant_count_limit'
NON_COMPLIANT_ALTERNATIVE_MEDICINE class-attribute instance-attribute
NON_COMPLIANT_ALTERNATIVE_MEDICINE = (
    "non_compliant_alternative_medicine"
)
NON_REIMBURSED_DENTAL_PROSTHESIS_COUNT_LIMIT class-attribute instance-attribute
NON_REIMBURSED_DENTAL_PROSTHESIS_COUNT_LIMIT = (
    "non_reimbursed_dental_prosthesis_count_limit"
)
PRIVATE_ROOM_CAP class-attribute instance-attribute
PRIVATE_ROOM_CAP = 'private_room_cap'
PRIVATE_ROOM_SPLIT_DAY_NIGHT class-attribute instance-attribute
PRIVATE_ROOM_SPLIT_DAY_NIGHT = (
    "private_room_split_day_night"
)
REMOVE_ALAN_THERAPY_SESSION class-attribute instance-attribute
REMOVE_ALAN_THERAPY_SESSION = (
    "remove_alan_mind_standalone_guarantee"
)
REMOVE_PRIVATE_HOSPITAL_ROOM_LIMIT class-attribute instance-attribute
REMOVE_PRIVATE_HOSPITAL_ROOM_LIMIT = (
    "remove_private_hospital_room_limit"
)
UPDATE_DENTAL_PROSTHESIS_COST_OF_CLAIMS class-attribute instance-attribute
UPDATE_DENTAL_PROSTHESIS_COST_OF_CLAIMS = (
    "update_dental_prosthesis_cost_of_claims"
)

events

ProductCreatedFromCompetitorProduct dataclass

ProductCreatedFromCompetitorProduct(
    *,
    creator_email,
    competitor_product_id,
    builder_product_id
)

Bases: WebhookMessage

Event is produced when a new product is created from a competitor product

builder_product_id instance-attribute
builder_product_id
competitor_product_id instance-attribute
competitor_product_id
creator_email instance-attribute
creator_email

helpers

builder_product_version

get_professional_category
get_professional_category(builder_product_version_ref)
Source code in components/offer_builder/public/v1/helpers/builder_product_version.py
def get_professional_category(builder_product_version_ref: str):  # type: ignore[no-untyped-def]  # noqa: D103
    builder_product_version = get_or_raise_missing_resource(
        BuilderProductVersion,
        int(builder_product_version_ref),
        options=[joinedload(BuilderProductVersion.builder_targets)],
    )
    return get_professional_category_internal(builder_product_version)

prevoyance

to_prevoyance_builder_product_for_budget_for_display
to_prevoyance_builder_product_for_budget_for_display(
    model, prevoyance_offer, prevoyance_builder_product
)
Source code in components/offer_builder/public/v1/helpers/prevoyance.py
def to_prevoyance_builder_product_for_budget_for_display(  # noqa: D103
    model: PrevoyanceBuilderProductForBudget,
    prevoyance_offer: PrevoyanceOfferForDisplay | None,
    prevoyance_builder_product: PrevoyanceBuilderProductForDisplay | None,
) -> PrevoyanceBuilderProductForBudgetForDisplay:
    return PrevoyanceBuilderProductForBudgetForDisplay(
        account_ref=model.account_ref,
        display_name=model.display_name,
        offer=prevoyance_offer,
        prevoyance_builder_product=prevoyance_builder_product,
        tranche_1_participation_percent=model.tranche_1_participation_percent,
        tranche_2_participation_percent=model.tranche_2_participation_percent,
        builder_prospect_refs=[
            prevoyance_builder_target.prospect_ref
            for prevoyance_builder_target in model.prevoyance_builder_targets
        ],
        competitor_product_id=model.competitor_product_id,
        id=model.id,
        origin=(BuilderProductOrigin(model.origin) if model.origin else None),
        tags=[BuilderProductTag(tag) for tag in model.tags] if model.tags else None,
    )
to_prevoyance_builder_product_for_display
to_prevoyance_builder_product_for_display(
    prevoyance_builder_product,
)
Source code in components/offer_builder/public/v1/helpers/prevoyance.py
def to_prevoyance_builder_product_for_display(  # noqa: D103
    prevoyance_builder_product: PrevoyanceBuilderProduct,
) -> PrevoyanceBuilderProductForDisplay:
    return PrevoyanceBuilderProductForDisplay(
        id=prevoyance_builder_product.id,  # type: ignore[arg-type]
    )
to_prevoyance_offer_for_display
to_prevoyance_offer_for_display(plan)
Source code in components/offer_builder/public/v1/helpers/prevoyance.py
def to_prevoyance_offer_for_display(  # noqa: D103
    plan: PrevoyancePlan,
) -> PrevoyanceOfferForDisplay:
    return PrevoyanceOfferForDisplay(
        ccn_codes={ccn.code for ccn in plan.ccns},
        cnp_code=plan.cnp_code,
        id=plan.id,
        premium=(
            plan.cnp_libelle.lower().find("premium") != -1
            if plan.cnp_libelle
            else False
        ),
        professional_category=ProfessionalCategory(plan.target_population),
        libelle=plan.cnp_libelle or "",
        tranche_1_price=plan.price_ta,
        tranche_2_price=plan.price_tb,
        tranche_1_death_price=plan.price_death_ta,
        tranche_2_death_price=plan.price_death_tb,
    )

queries

get_builder_product_card

get_builder_product_card
get_builder_product_card(builder_product_id)
Source code in components/offer_builder/internal/v1/queries/get_builder_product_card.py
def get_builder_product_card(
    builder_product_id: int,
) -> BuilderProductCard:
    builder_product = BuilderProductRepository().get(
        builder_product_id=builder_product_id,
    )
    return get_builder_product_card_from_builder_product(builder_product)

prevoyance

get_prevoyance_builder_product_by_id
get_prevoyance_builder_product_by_id(id)
Source code in components/offer_builder/public/v1/queries/prevoyance.py
def get_prevoyance_builder_product_by_id(  # noqa: D103
    id: str | None,
) -> PrevoyanceBuilderProductForDisplay | None:
    if id is None:
        return None

    prevoyance_builder_product = current_session.get(PrevoyanceBuilderProduct, id)

    if prevoyance_builder_product is None:
        return None

    return to_prevoyance_builder_product_for_display(prevoyance_builder_product)
get_prevoyance_builder_product_for_budget_for_display
get_prevoyance_builder_product_for_budget_for_display(id)

Get a single prevoyance builder product for budget by ID

Source code in components/offer_builder/public/v1/queries/prevoyance.py
def get_prevoyance_builder_product_for_budget_for_display(
    id: uuid.UUID,
) -> PrevoyanceBuilderProductForBudgetForDisplay:
    """
    Get a single prevoyance builder product for budget by ID
    """
    prevoyance_builder_product_for_budget = get_or_raise_missing_resource(
        PrevoyanceBuilderProductForBudget, id
    )

    return _build_prevoyance_builder_product_for_budget_for_display(
        prevoyance_builder_product_for_budget
    )
get_prevoyance_offer_by_id
get_prevoyance_offer_by_id(id)
Source code in components/offer_builder/public/v1/queries/prevoyance.py
def get_prevoyance_offer_by_id(  # noqa: D103
    id: str | None,
) -> PrevoyanceOfferForDisplay | None:
    if id is None:
        return None

    plan = current_session.get(PrevoyancePlan, id)

    if plan is None:
        return None

    return to_prevoyance_offer_for_display(plan)
get_prevoyance_offers_by_ccns
get_prevoyance_offers_by_ccns(ccn_codes)

Parameters:

Name Type Description Default
ccn_codes set[str]

a list of ccn codes.

required

Returns: a list of prevoyance offers by ccn_codes.

Source code in components/offer_builder/public/v1/queries/prevoyance.py
def get_prevoyance_offers_by_ccns(
    ccn_codes: set[str],
) -> list[PrevoyanceOfferForDisplay]:
    """
    Args:
        ccn_codes: a list of ccn codes.

    Returns: a list of prevoyance offers by ccn_codes.
    """
    plans = (
        current_session.query(PrevoyancePlan)  # noqa: ALN085
        .join(PrevoyancePlan.ccns)  # type: ignore[arg-type]
        .options(selectinload(PrevoyancePlan.ccns))  # type: ignore[arg-type]
        .filter(CCN.code.in_(list(ccn_codes)))
        .all()
    )
    return [to_prevoyance_offer_for_display(plan) for plan in plans]
list_prevoyance_builder_product_for_budget_for_display
list_prevoyance_builder_product_for_budget_for_display(
    account_ref=NOT_SET,
    builder_scenario_ids=None,
    prevoyance_builder_product_ids=None,
    origin=NOT_SET,
    tags=None,
)

Lists prevoyance builder products for budget entities

Source code in components/offer_builder/public/v1/queries/prevoyance.py
def list_prevoyance_builder_product_for_budget_for_display(
    account_ref: NotSet[str] = NOT_SET,
    builder_scenario_ids: Sequence[uuid.UUID | None] | None = None,
    prevoyance_builder_product_ids: list[uuid.UUID] | set[uuid.UUID] | None = None,
    origin: NotSet[BuilderProductOrigin | None] = NOT_SET,
    tags: list[BuilderProductTag] | None = None,
) -> Iterator[PrevoyanceBuilderProductForBudgetForDisplay]:
    """
    Lists prevoyance builder products for budget entities
    """
    prevoyance_builder_products_for_budget = list_prevoyance_builder_product_for_budget(
        account_ref=account_ref,
        builder_scenario_ids=builder_scenario_ids,
        prevoyance_builder_product_ids=prevoyance_builder_product_ids,
        origin=origin,
        tags=tags,
    )

    for prevoyance_builder_product_for_budget in prevoyance_builder_products_for_budget:
        yield _build_prevoyance_builder_product_for_budget_for_display(
            prevoyance_builder_product_for_budget
        )