Skip to content

Api reference

components.offer_builder.public.api

OfferBuilderCommand module-attribute

OfferBuilderCommand = CreateOfferFromBuilderProductCommand

OfferBuilderCreationCommand module-attribute

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

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_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
    ]

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_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,
) -> None:
    """
    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_proposal_readiness

validate_proposal_readiness(builder_product_id)

Checks that the builder product is ready to be used to create a proposal. 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_proposal_readiness(
    builder_product_id: int,
) -> OfferBuilderErrorCode | None:
    """Checks that the builder product is ready to be used to create a proposal. If not
    an error is returned.

    Currrently checks that:
    * product is priced
    * product doesn't have any blockers
    """
    from components.offer_builder.internal.presenters.builder_product_validation_presenter import (
        BuilderProductValidationLevel,
    )
    from components.offer_builder.internal.presenters.queries.builder_coverage_validations import (
        get_builder_coverage_validations,
    )
    from components.offer_builder.internal.presenters.queries.builder_product_issues import (
        get_builder_product_validations,
    )

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

    if not builder_product.is_priced:
        return OfferBuilderErrorCode.builder_product_is_not_ready_for_proposal(
            builder_product_id=builder_product_id,
            description="BuilderProduct is not priced yet",
        )

    validations = get_builder_product_validations(
        builder_product_id
    ) + get_builder_coverage_validations(builder_product_id)
    blockers = [
        validation
        for validation in validations
        if validation.level == BuilderProductValidationLevel.blocker
    ]

    if blockers:
        return OfferBuilderErrorCode.builder_product_is_not_ready_for_proposal(
            builder_product_id=builder_product_id,
            description="BuilderProduct blockers must be resolved first",
            blockers=blockers,
        )

    return None

components.offer_builder.public.commands

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
)

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
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.fr.subcomponents.offer_builder.internal.enums.fr_target_population import (  # noqa: ALN039, ALN043
        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.fr.subcomponents.offer_builder.internal.enums.fr_target_population import (  # noqa: ALN039, ALN043
        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
        )
    elif child_ratio is not None:
        # FIRST_CHILD_RATIO is the estimated ratio of first children among all children
        first_child_ratio = child_ratio * FIRST_CHILD_RATIO

    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
        )
    elif child_ratio is not None:
        # FIRST_TWO_CHILDREN_RATIO is the estimated ratio of first two children among all children
        first_two_children_ratio = child_ratio * FIRST_TWO_CHILDREN_RATIO

    return ManualDemographics(
        prospect_ref=self.prospect_ref,
        version=None,
        number_of_primaries=number_of_primaries,
        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

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

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

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.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.
    """
    pass

filter_builder_product_versions abstractmethod

filter_builder_product_versions(
    builder_product_version_ids, has_plan
)

Filters builder product version IDs based on the presence of associated plans.

Parameters:

Name Type Description Default
builder_product_version_ids list[int]

A list of builder product version IDs to filter.

required
has_plan bool

A flag indicating whether to return IDs with associated plans (True) or without (False).

required

Returns:

Type Description
list[int]

A list of filtered builder product version IDs.

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def filter_builder_product_versions(
    self,
    builder_product_version_ids: list[int],
    has_plan: bool,
) -> list[int]:
    """
    Filters builder product version IDs based on the presence of associated plans.

    Args:
        builder_product_version_ids: A list of builder product version IDs to filter.
        has_plan: A flag indicating whether to return IDs with associated plans (True) or without (False).

    Returns:
        A list of filtered builder product version IDs.
    """
    pass

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,
)

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
) -> list[BuilderProductCoverageValidation]:
    """Returns coverage constraint violations for all coverages of a given builder product"""
    pass

get_builder_product_coverage_infos

get_builder_product_coverage_infos(builder_product)

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
) -> list[BuilderProductCoverageValidation]:
    """Returns coverage infos for all coverages of a given builder product"""
    return []

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_competitor_products abstractmethod

get_competitor_products(
    competitor_product_ids=None, account_ref=None
)

Get the competitor product filtered on the given parameters.

Source code in components/offer_builder/public/dependencies.py
@abstractmethod
def get_competitor_products(
    self,
    competitor_product_ids: list[int] | None = None,
    account_ref: UUID | None = None,
) -> list[CompetitorProduct]:
    """Get the competitor product filtered on the given parameters."""
    pass

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"""
    pass

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."""
    pass

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.
    """
    pass

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.
    """
    pass

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

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_deleted

on_builder_scenario_deleted(builder_scenario_id)

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_id UUID

The identifier of the builder scenario that was deleted.

required
Source code in components/offer_builder/public/dependencies.py
def on_builder_scenario_deleted(
    self,
    builder_scenario_id: UUID,  # noqa: ARG002
) -> 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_id: The identifier of the builder scenario that was deleted.
    """
    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

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"""
    pass

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
)

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,
    )
id instance-attribute
id
template_name instance-attribute
template_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_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_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,
    number_of_primaries,
    average_age,
    male_ratio,
    target_population_ratios,
    partner_ratio,
    child_ratio,
    non_paying_ratio,
    coverage_taker_ratios,
    country,
    single_ratio,
    first_child_ratio,
    first_two_children_ratio,
    option_taker_ratio
)

Bases: DataClassJsonMixin

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 instance-attribute
average_age
child_ratio instance-attribute
child_ratio
country instance-attribute
country
coverage_taker_ratios instance-attribute
coverage_taker_ratios
first_child_ratio instance-attribute
first_child_ratio
first_two_children_ratio instance-attribute
first_two_children_ratio
id class-attribute instance-attribute
id = field(default_factory=uuid4)
male_ratio instance-attribute
male_ratio
non_paying_ratio instance-attribute
non_paying_ratio
number_of_primaries instance-attribute
number_of_primaries
option_taker_ratio instance-attribute
option_taker_ratio
partner_ratio instance-attribute
partner_ratio
prospect_ref instance-attribute
prospect_ref
single_ratio instance-attribute
single_ratio
target_population_ratios instance-attribute
target_population_ratios
version instance-attribute
version

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

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
    """
    from components.offer_builder.subcomponents.pricer_v1.protected.round_price import (
        round_price,
    )

    return self.__class__(
        primary_cents=round_price(self.primary_cents, precision_cents),
        partner_cents=round_price(self.partner_cents, precision_cents),
        child_cents=round_price(self.child_cents, precision_cents),
        family_cents=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,
        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

assert_deep_almost_equal
assert_deep_almost_equal(
    expected, actual, *args, tolerance=0, **kwargs
)
Source code in components/offer_builder/subcomponents/pricer/internal/e2e/snapshot.py
def assert_deep_almost_equal(  # type: ignore[no-untyped-def]
    self, expected, actual, *args, tolerance: int = 0, **kwargs
):
    is_root = "__trace" not in kwargs
    trace = kwargs.pop("__trace", "ROOT")
    try:
        if isinstance(expected, float | int):
            delta = tolerance * expected + kwargs.pop("delta", 0)
            # used in end-to-end tests, do not migrate to pytest
            self.assertAlmostEqual(  # type: ignore[call-overload]  # noqa: PT009
                expected,
                actual,
                *args,
                delta=delta,
                **kwargs,
                msg=f"expected={expected}, actual={actual}",
            )
        elif isinstance(expected, list | tuple):
            assert len(expected) == len(actual)
            for index, (v1, v2) in enumerate(zip(expected, actual)):
                self.assert_deep_almost_equal(
                    v1,
                    v2,
                    *args,
                    tolerance=tolerance,
                    __trace=repr(index),
                    **kwargs,
                )
        elif isinstance(expected, dict):
            self.assertEqual(set(expected), set(actual))  # noqa: PT009
            for key in expected:
                self.assert_deep_almost_equal(
                    expected[key],
                    actual[key],
                    *args,
                    tolerance=tolerance,
                    __trace=repr(key),
                    **kwargs,
                )
        else:
            self.assertEqual(  # noqa: PT009
                expected, actual, msg=f"expected={expected}, actual={actual}"
            )
    except AssertionError as exc:
        exc.__dict__.setdefault("traces", []).append(trace)
        if is_root:
            trace = " -> ".join(reversed(exc.traces))  # type: ignore[attr-defined]
            exc.args = exc.args + (f"TRACE: {trace}",)
        raise
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)

    """
    # 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]
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)
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

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,
    is_proposal_ready,
    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. is_proposal_ready: Whether or not the product is considered to be complete and usable in a contract proposal. In the case of health products, True iff the product is priced. 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"""
    from components.offer_builder.public.api import validate_proposal_readiness

    return ProductSummary(
        id=mandatory(builder_product.id),
        display_name=builder_product.display_name,
        product_type=HEALTH_PRODUCT_TYPE,
        is_proposal_ready=validate_proposal_readiness(mandatory(builder_product.id))
        is None,
        scenario_id=builder_product.builder_scenario_id,
        prospect_refs=builder_product.prospect_refs,
    )
id instance-attribute
id
is_proposal_ready instance-attribute
is_proposal_ready
product_type instance-attribute
product_type
prospect_refs instance-attribute
prospect_refs
scenario_id instance-attribute
scenario_id

components.offer_builder.public.enums

base_premiumness

BasePremiumness

Bases: AlanBaseEnum

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("_")

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'
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,
    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,
    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)
    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

CategoryRef module-attribute

CategoryRef = str

GuaranteeRef module-attribute

GuaranteeRef = str

PriceTarget module-attribute

PriceTarget = str | None

TargetPopulation module-attribute

TargetPopulation = str | None

components.offer_builder.public.v1

actions

builder_product

delete_builder_products
delete_builder_products(
    builder_product_ids=None,
    account_refs=None,
    origin=None,
    delete_empty_scenarios=False,
    any_version_has_plan=None,
)
Source code in components/offer_builder/internal/v1/actions/builder_product.py
def delete_builder_products(
    builder_product_ids: list[int] | None = None,
    account_refs: list[str] | None = None,
    origin: BuilderProductOrigin | None = None,
    delete_empty_scenarios: bool = False,
    any_version_has_plan: bool | None = None,
) -> None:
    builder_products = list_builder_products(
        builder_product_ids=builder_product_ids,
        origin=origin,
        any_version_has_plan=any_version_has_plan,
        account_refs=account_refs,
    )
    builder_product_ids = [builder_product.id for builder_product in builder_products]

    if not builder_product_ids:
        return
    rows = current_session.scalars(
        select(BuilderProductVersion.id).filter(
            BuilderProductVersion.builder_product_id.in_(builder_product_ids)
        )
    ).all()
    if rows:
        builder_product_version_ids = [id_ for id_ in rows]
        delete_plans_from_builder_product_versions(builder_product_version_ids)
        delete_instant_quotes_for_product_versions(
            product_version_refs=[str(id_) for id_ in builder_product_version_ids]
        )

    # TODO Move the ondelete CASCADE to the FK, and then we'll be able to do a query.delete() directly.
    # Currently, we need to load the builder_product in session and then delete them
    # because the ondelete CASCADE is defined in the relationship and NOT in the ForeignKey:
    # - relationship stuff are handled by the ORM in python :-(
    # - ForeignKey stuff are handled by the DB in SQL
    for builder_product in current_session.scalars(
        select(BuilderProduct).where(BuilderProduct.id.in_(builder_product_ids))
    ).all():
        current_session.delete(builder_product)

    if delete_empty_scenarios:
        builder_scenario_ids = list(
            set(compact(bp.builder_scenario_id for bp in builder_products))
        )
        builder_scenarios = list_builder_scenarios(
            builder_scenario_ids=builder_scenario_ids,
            custom_filter=partial(filter_empty_builder_scenarios, is_empty=True),
        )
        for scenario in builder_scenarios:
            delete_builder_scenario(scenario.id)

    current_logger.info(
        f"Deleted {len(builder_product_ids)} builder products",
        builder_product_ids=builder_product_ids,
    )

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

builder_product

create_prevoyance_builder_product_from_contracts

create_prevoyance_builder_product_from_contracts(
    account_ref,
    contract_version_ids,
    origin,
    tags,
    prevoyance_offer_ref=None,
    builder_scenario_id=None,
    creator_profile_id=None,
)
Source code in components/offer_builder/internal/v1/actions/prevoyance_builder_product_for_budget.py
def create_prevoyance_builder_product_from_contracts(
    account_ref: str,
    contract_version_ids: list[str],
    origin: BuilderProductOrigin,
    tags: list[BuilderProductTag],
    prevoyance_offer_ref: str | None = None,
    builder_scenario_id: uuid.UUID | None = None,
    creator_profile_id: uuid.UUID | None = None,
) -> PrevoyanceBuilderProductForBudgetForDisplay:
    prevoyance_contract_infos = [
        get_prevoyance_contract_info_from_period_ref(id) for id in contract_version_ids
    ]

    companies = list(
        {
            prevoyance_contract_info.prevoyance_contract.company_contractee
            for prevoyance_contract_info in prevoyance_contract_infos
        }
    )
    current_offer_ref = str(
        one(
            {
                prevoyance_contract_info.prevoyance_contract_version.prevoyance_plan_id
                for prevoyance_contract_info in prevoyance_contract_infos
            }
        )
    )

    participations = most_common(
        [
            (
                float(info.prevoyance_contract_version.participation.ta or 100),
                float(info.prevoyance_contract_version.participation.tb_tc or 100),
            )
            for info in prevoyance_contract_infos
        ]
    ) or [100, 100]

    return create_prevoyance_builder_product_for_budget(
        account_ref=account_ref,
        prevoyance_offer_ref=prevoyance_offer_ref or current_offer_ref,
        display_name=f"♻️ ISO contract {current_offer_ref}",
        builder_entities=[
            BuilderEntityAPISchema(company_ref=str(company.id)) for company in companies
        ],
        builder_scenario_id=builder_scenario_id,
        origin=origin,
        tags=tags,
        tranche_1_participation_percent=participations[0],
        tranche_2_participation_percent=participations[1],
        creator_profile_id=creator_profile_id,
    )

controllers

builder_product

BuilderProductController

Bases: BaseController

endpoint_path class-attribute instance-attribute
endpoint_path = '/builder_product'
get
get(id=None, params=None)
Source code in components/offer_builder/public/v1/controllers/builder_product.py
@view_method(
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@request_argument(
    "account_id",
    type=str,
    help="Filter by account_id (exclusive with search)",
    owner_controller=AccountController,
)
@request_argument(
    "page",
    type=int,
    help="Page number (not index) to return (see per_page)",
    required=False,
)
@request_argument(
    "per_page",
    type=int,
    help="Size of the page to return (see page)",
    required=False,
)
@request_argument(
    "origin_acquisition",
    type=inputs.boolean,
    help="Search for builder product origin.acquisition only",
    required=False,
)
def get(  # noqa: D102
    self, id: int | None = None, params: dict[str, Any] | None = None
) -> Response:
    from components.offer_builder.internal.v1.queries.get_builder_product_for_display import (
        get_builder_product_for_display,
    )
    from components.offer_builder.internal.v1.queries.list_builder_product import (
        list_builder_product,
    )

    if id is not None:
        if params and "account_id" in params:
            return abort(
                400, "account_id can't be sent when querying a specific product"
            )
        builder_product_for_display = get_builder_product_for_display(id)
        return make_json_response(builder_product_for_display)

    return make_json_response(list_builder_product(**(params or {})))
version
version(id, version)
Source code in components/offer_builder/public/v1/controllers/builder_product.py
@BuilderProductController.action_route(
    "/<int:id>/version/<int:version>",
    methods=["GET"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@obs.api_call()
def version(id, version):  # type: ignore[no-untyped-def]  # noqa: D103
    from components.offer_builder.internal.v1.queries.get_builder_product_version_for_display import (
        get_builder_product_version_for_display,
    )

    (builder_product_version_id,) = first_or_404(  # type: ignore[type-var]
        current_session.query(BuilderProductVersion.id).filter_by(  # noqa: ALN085
            builder_product_id=id,
            version=version,
        )
    )
    builder_product_version_for_display = get_builder_product_version_for_display(
        builder_product_version_id
    )

    return make_json_response(builder_product_version_for_display.to_dict())

manual_demographics

ManualDemographicsController

Bases: BaseController

endpoint_path class-attribute instance-attribute
endpoint_path = '/manual_demographics'
post
post(params)
Source code in components/offer_builder/public/v1/controllers/manual_demographics.py
@view_method(
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@request_argument("prospect_ref", type=str, required=False)
@request_argument("company_ref", type=str, required=False)
@request_argument("number_of_employees", type=int, required=True)
@request_argument("number_of_covered_employees", type=int, required=False)
@request_argument("average_age", type=float, required=False)
@request_argument("number_of_ani", type=int, required=False)
@request_argument("number_of_option_taker", type=int, required=False)
@request_argument("number_of_male", type=int, required=False)
@request_argument("number_of_cadre", type=int, required=False)
@request_argument("number_of_children", type=int, required=False)
@request_argument("number_of_single", type=int, required=False)
@request_argument("number_of_partners", type=int, required=False)
def post(self, params: dict):  # type: ignore[type-arg,no-untyped-def]  # noqa: D102
    from components.offer_builder.internal.v1.actions.manual_demographics import (
        create_manual_demographics,
    )
    from components.offer_builder.public.v1.entities.manual_demographics import (
        ManualDemographics,
    )
    from shared.helpers.response import make_json_response

    manual_demographics_entity = ManualDemographics.from_dict(params)  # type: ignore[no-untyped-call]
    manual_demographics = create_manual_demographics(manual_demographics_entity)
    current_session.commit()
    return make_json_response(manual_demographics)
get_current
get_current(params)
Source code in components/offer_builder/public/v1/controllers/manual_demographics.py
@ManualDemographicsController.action_route(
    "/current",
    methods=["GET"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@request_argument("prospect_ref", type=str, required=False)
@request_argument("company_ref", type=str, required=False)
@request_argument("account_ref", type=str, required=False)
@obs.api_call()
def get_current(params: dict):  # type: ignore[type-arg,no-untyped-def]  # noqa: D103
    from components.offer_builder.internal.v1.queries.manual_demographics import (
        get_current_manual_demographics_for_display,
        list_current_account_manual_demographics_for_display,
    )
    from shared.helpers.response import make_json_response

    account_ref = params.get("account_ref")

    manual_demographics = (
        list_current_account_manual_demographics_for_display(account_ref)
        if account_ref
        else get_current_manual_demographics_for_display(
            prospect_ref=params.get("prospect_ref"),
            company_ref=params.get("company_ref"),
        )
    )

    return make_json_response(manual_demographics)
search
search(params)
Source code in components/offer_builder/public/v1/controllers/manual_demographics.py
@ManualDemographicsController.action_route(
    "/search",
    methods=["POST"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@request_argument("prospect_refs", type=list[str], required=True, location="json")
@obs.api_call()
def search(params: dict):  # type: ignore[type-arg,no-untyped-def]  # noqa: D103
    from components.offer_builder.internal.v1.queries.manual_demographics import (
        list_current_manual_demographics_for_display,
    )
    from shared.helpers.response import make_json_response

    return make_json_response(
        list_current_manual_demographics_for_display(
            prospect_refs=params["prospect_refs"],
        )
    )

prevoyance

PrevoyanceOfferController

Bases: BaseController

endpoint_path class-attribute instance-attribute
endpoint_path = '/sales_xp/prevoyance'
create_product
create_product(params, 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}
    ),
)
@request_argument(
    "builder_entities",
    type=BuilderEntityAPISchema.from_list_dict,
    required=True,
    location="json",
    help="The entities we want to create the prev product for",
    owner_controller=NoOwner,
)
@request_argument(
    "prevoyance_offer_ref",
    type=str,
    required=True,
    location="json",
    owner_controller=NoOwner,
)
@request_argument(
    "account_ref", type=str, required=True, location="json", owner_controller=NoOwner
)
@request_argument(
    "display_name", type=str, required=True, location="json", owner_controller=NoOwner
)
@request_argument(
    "builder_scenario_id",
    type=uuid.UUID,
    required=False,
    location="json",
    help="The builder scenario in which the builder product should be created",
    owner_controller=NoOwner,
)
@obs.api_call()
def create_product(params: dict[str, Any], 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=params["account_ref"],
        prevoyance_offer_ref=params["prevoyance_offer_ref"],
        display_name=params["display_name"],
        builder_entities=params["builder_entities"],
        builder_scenario_id=params.get("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(params, 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}
    ),
)
@request_argument(
    "builder_entities",
    type=BuilderEntityAPISchema.from_list_dict,
    required=True,
    location="json",
    help="The entities we want to create the prev product for",
    owner_controller=NoOwner,
)
@request_argument(
    "account_ref", type=str, required=True, location="json", owner_controller=NoOwner
)
@request_argument(
    "builder_scenario_id",
    type=uuid.UUID,
    required=False,
    location="json",
    help="The builder scenario in which the builder product should be created",
    owner_controller=NoOwner,
)
@request_argument(
    "existing_plan_id",
    type=int,
    required=False,
    location="json",
    help="A PrevoyancePlan id to use a a template and to copy guarantees to the builder product",
    owner_controller=NoOwner,
)
@request_argument(
    "target_population",
    type=ProfessionalCategory,
    required=False,
    location="json",
    help="The target population for template",
    owner_controller=NoOwner,
)
@obs.api_call()
def create_tailored_builder_product(params: dict[str, Any], 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=params["account_ref"],
        builder_entities=params["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=params.get("existing_plan_id"),
        target_population=params["target_population"],
        builder_scenario_id=params.get("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.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.UUID(id))
    )
search
search(params)

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}
    ),
)
@request_argument("ccn_codes", type=set[str], required=True, location="json")
@obs.api_call()
def search(params: dict[str, Any]) -> 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=params["ccn_codes"])

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

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}
    ),
)
@request_argument(
    "tranche_1_participation_percent", type=float, required=False, location="json"
)
@request_argument(
    "tranche_2_participation_percent", type=float, required=False, location="json"
)
@request_argument(
    "builder_entities",
    type=BuilderEntityAPISchema.from_list_dict,
    required=False,
    location="json",
    help="The entities we want to create the prev product for",
    owner_controller=NoOwner,
)
@request_argument(
    "builder_scenario_id",
    type=uuid.UUID,
    required=False,
    location="json",
    help="The builder scenario in which the builder product should be created",
    owner_controller=NoOwner,
)
@request_argument(
    "competitor_product_id",
    type=int,
    required=False,
    location="json",
    help="The competitor product id to attach to the prevoyance product",
    owner_controller=NoOwner,
)
@request_argument(
    "display_name",
    type=str,
    required=False,
    location="json",
    help="The display name of the prevoyance product",
    owner_controller=NoOwner,
)
@obs.api_call()
def update(id: str, params: dict[str, Any]) -> 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=params.get("tranche_1_participation_percent"),
        tranche_2_participation_percent=params.get("tranche_2_participation_percent"),
        builder_entities=params.get("builder_entities"),
        builder_scenario_id=params.get("builder_scenario_id", NOT_SET),
        competitor_product_id=params.get("competitor_product_id", NOT_SET),
        display_name=params.get("display_name", NOT_SET),
    )
    current_session.commit()
    return make_json_response({"updated": True, "id": id})

sales_xp_builder_product

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'
create_from_competitor_product_id
create_from_competitor_product_id(
    params, user, profile_service
)

Create new product from a competitor product

Source code in components/offer_builder/public/v1/controllers/sales_xp_builder_product.py
@SalesXpBuilderProductController.action_route(
    "/create_from_competitor_product",
    methods=["POST"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@request_argument(
    "competitor_product_id",
    type=int,
    required=True,
    location="json",
    help="The competitor product to use to create the builder product",
    owner_controller=NoOwner,
)
@request_argument(
    "builder_entities",
    type=BuilderEntityAPISchema.from_list_dict,
    required=True,
    location="json",
    help="The entities we want to create the builder product for",
    owner_controller=NoOwner,
)
@request_argument(
    "builder_scenario_id",
    type=UUID,
    required=False,
    location="json",
    help="The builder scenario in which the builder product should be created",
    owner_controller=NoOwner,
)
@inject_profile_service
@obs.api_call()
def create_from_competitor_product_id(
    params: dict[str, Any], user: BaseUser, profile_service: ProfileService
) -> flask.Response:
    """Create new product from a competitor product"""
    from components.offer_builder.internal.v1.actions.builder_product import (
        create_builder_product_from_competitor_product,
    )

    builder_product = create_builder_product_from_competitor_product(
        competitor_product_id=params["competitor_product_id"],
        builder_entities=params["builder_entities"],
        builder_scenario_id=params.get("builder_scenario_id"),
        creator_profile_id=user.profile_id,
    )
    current_session.commit()

    creator_email: str | None = None

    if builder_product.creator_profile_id:
        profile = profile_service.get_profile(builder_product.creator_profile_id)

        if profile:
            creator_email = profile.email

    message_broker = get_message_broker()
    message_broker.publish(
        ProductCreatedFromCompetitorProduct(
            creator_email=creator_email,
            competitor_product_id=params["competitor_product_id"],
            builder_product_id=builder_product.id,
        )
    )

    return make_json_response({"builder_product_id": builder_product.id})
get_builder_product_summaries
get_builder_product_summaries(params)
Source code in components/offer_builder/public/v1/controllers/sales_xp_builder_product.py
@SalesXpBuilderProductController.action_route(
    "/summary",
    methods=["GET"],
    auth_strategy=AuthorizationStrategies.alaner_admin(
        permitted_for={EmployeePermission.use_offer_builder}
    ),
)
@request_argument(
    "account_id",
    type=str,
    required=True,
    help="Get the summary of the builder products for a given account",
    owner_controller=NoOwner,
)
@request_argument(
    "builder_scenario_id",
    type=str,
    required=False,
    help="Filter the builder products by scenario",
    owner_controller=NoOwner,
)
@request_argument(
    "without_scenario",
    type=inputs.boolean,
    help="If true, only return the builder products that are not associated to any scenario",
    owner_controller=NoOwner,
)
@obs.api_call()
def get_builder_product_summaries(params: dict) -> Response:  # type: ignore[type-arg] # noqa: D103
    from components.offer_builder.public.v1.queries.get_builder_products_summaries import (
        get_builder_product_summaries,
    )

    return make_json_response(
        get_builder_product_summaries(
            account_id=params["account_id"],
            builder_scenario_id=(
                params.get("builder_scenario_id", NOT_SET)
                if not params.get("without_scenario", False)
                else None
            ),
        ),
    )

create_plan_from_builder_product_version

create_plan_from_builder_product_version

create_plan_from_builder_product_version(
    builder_product_version_id,
    professional_category,
    standard_plan_payload=None,
    include_alsace_moselle_prices=False,
    has_assistance=True,
    has_aan_contribution=None,
    plan_context=None,
    bypass_blockers=False,
    allow_non_responsible_base_with_options=None,
)

Create a plan from a builder product version.

Also sets the builder_product_version as frozen.

Source code in components/offer_builder/public/v1/create_plan_from_builder_product_version.py
@offer_builder_tracer_wrap()
def create_plan_from_builder_product_version(
    builder_product_version_id: int,
    professional_category: ProfessionalCategory,
    standard_plan_payload: PlanPayload | None = None,
    include_alsace_moselle_prices: bool = False,
    has_assistance: bool = True,
    has_aan_contribution: bool | None = None,
    plan_context: dict[str, Any] | None = None,
    bypass_blockers: bool = False,
    allow_non_responsible_base_with_options: bool | None = None,
) -> Plan:
    """
    Create a plan from a builder product version.

    Also sets the builder_product_version as frozen.
    """
    if not bypass_blockers:
        _check_builder_product_version_blocker_issues(builder_product_version_id)

    builder_product_version = _load_builder_product_version_for_plan_creation(
        builder_product_version_id
    )
    builder_product_version.is_frozen = True

    sorted_builder_option_coverages = builder_product_version.option_coverages
    number_of_options = len(sorted_builder_option_coverages)
    has_option = number_of_options > 0

    if allow_non_responsible_base_with_options is None and g:
        current_user = getattr(g, "current_user", None)
        if current_user:
            allow_non_responsible_base_with_options = is_feature_enabled_for_user_id(
                "offer_allow_non_responsible_base_with_options", current_user.id
            )

    if (
        not allow_non_responsible_base_with_options
        and builder_product_version.base_coverage
        and not builder_product_version.base_coverage.is_responsible
        and has_option
    ):
        # TODO Remove this error once the contracting side is ready
        raise ErrorCode.builder_wrong_non_responsible_setup(
            builder_product_version_id=builder_product_version_id
        )

    product_short_name = _get_builder_product_short_name(
        builder_product_version.builder_product
    )
    plan_payload = standard_plan_payload or _get_plan_payload_for_tailor_made(
        product_short_name=product_short_name,
        builder_product_version=builder_product_version,
        target_population=professional_category,
        plan_includes_alsace_moselle_prices=include_alsace_moselle_prices,
    )

    plan_type = get_plan_type(
        has_standard_payload=standard_plan_payload is not None,
    )

    # Base coverage
    base_coverage = mandatory(builder_product_version.base_coverage)
    base_health_coverage = get_or_create_coverage_from_builder_coverage(
        builder_coverage=base_coverage, plan_name=plan_payload.name
    )
    base_price_structure = base_coverage.pricing_configuration.tailored_price_structure
    base_price_grid = _get_price_grid_by_name_or_create_one(
        price_grid_name=plan_payload.price_grid_name,
        builder_pricing=mandatory(base_coverage.pricing),
        plan_name=plan_payload.name,
        price_structure=base_price_structure,
        is_for_alsace_moselle=False,
    )
    if include_alsace_moselle_prices:
        alsace_moselle_price_grid = _get_price_grid_by_name_or_create_one(
            price_grid_name=plan_payload.alsace_moselle_price_grid_name,
            builder_pricing=mandatory(base_coverage.pricing),
            plan_name=plan_payload.name,
            price_structure=base_price_structure,
            is_for_alsace_moselle=True,
        )
    else:
        alsace_moselle_price_grid = None

    # Option coverages
    option_health_coverages: list[HealthCoverage | None] = [None] * number_of_options
    has_responsible_options: list[bool] = [False] * number_of_options
    option_price_grids: list[PriceGrid | None] = [None] * number_of_options
    for i, coverage in enumerate(sorted_builder_option_coverages):
        option_health_coverages[i] = get_or_create_coverage_from_builder_coverage(
            builder_coverage=coverage, plan_name=plan_payload.name
        )
        has_responsible_options[i] = mandatory(
            option_health_coverages[i]
        ).is_responsible
        price_grid_name = None
        try:
            price_grid_name = plan_payload.option_price_grid_names[i]
        except IndexError:
            pass

        option_price_structure = coverage.pricing_configuration.tailored_price_structure
        option_price_grids[i] = _get_price_grid_by_name_or_create_one(
            price_grid_name=price_grid_name,
            builder_pricing=mandatory(coverage.pricing),
            plan_name=plan_payload.name,
            price_structure=option_price_structure,
            is_for_alsace_moselle=False,
        )

    document_version = get_latest_plan_document_version(
        base_is_non_responsible=not mandatory(base_coverage.is_responsible),
        has_option=has_option,
    )

    has_action_sociale = True
    assistance_type = AssistanceType.company if has_assistance else None
    assistance_type = (
        standard_plan_payload.assistance_type
        if standard_plan_payload
        else assistance_type
    )
    has_aan_contribution = _get_default_aan_contribution_or_raise(
        has_aan_contribution=has_aan_contribution, plan_type=plan_type
    )
    price_metadata = create_price_metadata(
        has_action_sociale=has_action_sociale,
        assistance_type=assistance_type,
        has_aan_contribution=has_aan_contribution,
    )
    plan = Plan(
        name=plan_payload.name,
        display_name=plan_payload.display_name,
        category=PlanCategory.built,
        product=plan_payload.product,
        emoji_code=plan_payload.emoji_code,
        document_version=document_version,
        type=plan_type,
        target_population=professional_category,
        short_name=plan_payload.product,
        start_date=plan_payload.start_date,
        bundle_version=plan_payload.bundle_version,
        builder_product_version_id=builder_product_version.id,
        has_action_sociale=has_action_sociale,
        has_madelin=False,
        has_aan_contribution=has_aan_contribution,
        visibility=PlanVisibility.start_date,
        segment=PlanSegment.company,
        context=plan_context,
        price_metadata=price_metadata,
        assistance_type=assistance_type,
    )
    current_session.add(plan)
    builder_product_version.plans.append(plan)
    current_session.add(
        HealthCoverageModule(
            index=0,
            plan=plan,
            health_coverage=base_health_coverage,
            price_grid=base_price_grid,
            alsace_moselle_price_grid=alsace_moselle_price_grid,
        )
    )
    for i, (health_coverage, price_grid) in enumerate(
        zip(option_health_coverages, option_price_grids)
    ):
        if health_coverage and price_grid:
            current_session.add(
                HealthCoverageModule(
                    index=i + 1,
                    plan=plan,
                    health_coverage=health_coverage,
                    price_grid=price_grid,
                )
            )
    current_session.flush()
    return plan

create_price_metadata

create_price_metadata(
    has_action_sociale,
    assistance_type,
    has_aan_contribution,
)
Source code in components/offer_builder/public/v1/create_plan_from_builder_product_version.py
def create_price_metadata(  # noqa: D103
    has_action_sociale: bool,
    assistance_type: AssistanceType | None,
    has_aan_contribution: bool,
) -> PriceMetadata:
    from components.fr.subcomponents.offer_catalog.protected.constants import (  # noqa: ALN039, ALN043 # refactoring ongoing, this function will eventually be deleted
        COTISATION_AAN_PRIMARY_IN_EUROS,
    )

    has_assistance = assistance_type is not None
    price_metadata = PriceMetadata(
        tsa_base_rate=TSA_BASE_RATE,
        tsa_surco_rate=TSA_NON_RESPONSIBLE_RATE,
        assistance_child_in_euros=ASSISTANCE_CHILD_MONTHLY_CENTS / 100
        if has_assistance
        else 0,
        assistance_primary_in_euros=ASSISTANCE_ADULT_MONTHLY_CENTS / 100
        if has_assistance
        else 0,
        assistance_partner_in_euros=ASSISTANCE_ADULT_MONTHLY_CENTS / 100
        if has_assistance
        else 0,
        cotisation_aan_primary_in_euros=COTISATION_AAN_PRIMARY_IN_EUROS
        if has_aan_contribution
        else 0,
        forfait_medecin_rate=FORFAIT_MEDECIN_RATE,
        action_sociale_rate=ACTION_SOCIALE_RATE if has_action_sociale else 0,
        civil_servants_social_funds_name=None,
        civil_servants_agents_social_fund_rate=0,
        civil_servants_retirees_social_fund_rate=0,
    )
    current_session.add(price_metadata)
    return price_metadata

get_or_create_plan_from_builder_product_version

get_or_create_plan_from_builder_product_version(
    builder_product_version_id,
    professional_category,
    include_alsace_moselle_prices=False,
)

Get the existing plan for the given parameters If it doesn't exist, it creates a new plan

Source code in components/offer_builder/public/v1/create_plan_from_builder_product_version.py
def get_or_create_plan_from_builder_product_version(
    builder_product_version_id: int,
    professional_category: ProfessionalCategory,
    include_alsace_moselle_prices: bool = False,
) -> Plan:
    """
    Get the existing plan for the given parameters
    If it doesn't exist, it creates a new plan
    """
    plans: list[Plan] = (
        current_session.query(Plan)  # noqa: ALN085
        .filter(
            Plan.builder_product_version_id == builder_product_version_id,
            Plan.target_population == professional_category,
        )
        .order_by(Plan.created_at.desc())
        .all()
    )
    if len(plans) >= 2:
        # We are not supposed to have multiple plans for the same parameters
        current_logger.warning(
            "Multiple plans found for the following parameters",
            builder_product_version_id=builder_product_version_id,
            target_population=professional_category,
            output_plan_bundle_versions=[plan.bundle_version for plan in plans],
        )
    if plans:
        return plans[0]

    return create_plan_from_builder_product_version(
        builder_product_version_id=builder_product_version_id,
        professional_category=professional_category,
        include_alsace_moselle_prices=include_alsace_moselle_prices,
    )

get_plan_type

get_plan_type(has_standard_payload)
Source code in components/offer_builder/public/v1/create_plan_from_builder_product_version.py
def get_plan_type(  # noqa: D103
    has_standard_payload: bool,
) -> PlanType:
    if has_standard_payload:
        return PlanType.standard
    return PlanType.tailored

entities

builder_product

BuilderProduct dataclass
BuilderProduct(id, builder_scenario_id)

Bases: DataClassJsonMixin

builder_scenario_id instance-attribute
builder_scenario_id
from_model classmethod
from_model(model)
Source code in components/offer_builder/public/v1/entities/builder_product.py
@classmethod
def from_model(cls, model: BuilderProductSQLA) -> BuilderProduct:  # noqa: D102
    return cls(id=model.id, builder_scenario_id=model.builder_scenario_id)
id instance-attribute
id

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
BuilderProductAPISchema dataclass
BuilderProductAPISchema(
    base_coverage=None,
    option_coverages=list(),
    company_demographics_id=None,
    display_name=None,
    salesforce_opportunity_id=None,
    gsheet=None,
    price_structure=None,
    price_structure_coefficients=None,
    builder_targets=None,
    builder_entities=None,
    ccn_collective_agreement_ids=NOT_SET,
    template_id=NOT_SET,
    enable_hospital_cap_cost_of_care_reduction=None,
    competitor_product_id=NOT_SET,
    invalidate_pricing_first=None,
    return_current_version=None,
    return_issues=None,
    return_coverage_table=None,
    primary_participation_percent=NOT_SET,
    partner_participation_percent=NOT_SET,
    child_participation_percent=NOT_SET,
    builder_scenario_id=NOT_SET,
)

Bases: DataClassJsonMixin

base_coverage class-attribute instance-attribute
base_coverage = None
builder_entities class-attribute instance-attribute
builder_entities = None
builder_scenario_id class-attribute instance-attribute
builder_scenario_id = NOT_SET
builder_targets class-attribute instance-attribute
builder_targets = None
ccn_collective_agreement_ids class-attribute instance-attribute
ccn_collective_agreement_ids = NOT_SET
child_participation_percent class-attribute instance-attribute
child_participation_percent = NOT_SET
company_demographics_id class-attribute instance-attribute
company_demographics_id = None
competitor_product_id class-attribute instance-attribute
competitor_product_id = NOT_SET
display_name class-attribute instance-attribute
display_name = None
enable_hospital_cap_cost_of_care_reduction class-attribute instance-attribute
enable_hospital_cap_cost_of_care_reduction = None
gsheet class-attribute instance-attribute
gsheet = None
invalidate_pricing_first class-attribute instance-attribute
invalidate_pricing_first = None
option_coverages class-attribute instance-attribute
option_coverages = field(default_factory=list)
partner_participation_percent class-attribute instance-attribute
partner_participation_percent = NOT_SET
price_structure class-attribute instance-attribute
price_structure = None
price_structure_coefficients class-attribute instance-attribute
price_structure_coefficients = None
primary_participation_percent class-attribute instance-attribute
primary_participation_percent = NOT_SET
return_coverage_table class-attribute instance-attribute
return_coverage_table = None
return_current_version class-attribute instance-attribute
return_current_version = None
return_issues class-attribute instance-attribute
return_issues = None
salesforce_opportunity_id class-attribute instance-attribute
salesforce_opportunity_id = None
template_id class-attribute instance-attribute
template_id = NOT_SET
BuilderProductCoverageRuleAPISchema dataclass
BuilderProductCoverageRuleAPISchema(
    guarantee_short_code, coverage_rule_parameter_set
)

Bases: DataClassJsonMixin

coverage_rule_parameter_set instance-attribute
coverage_rule_parameter_set
from_builder_product_coverage_rule staticmethod
from_builder_product_coverage_rule(product_coverage_rule)
Source code in components/offer_builder/public/v1/entities/builder_product_api.py
@staticmethod
def from_builder_product_coverage_rule(  # noqa: D102
    product_coverage_rule: BuilderProductCoverageRule
    | CompetitorProductCoverageRule,
) -> BuilderProductCoverageRuleAPISchema:
    return BuilderProductCoverageRuleAPISchema(
        guarantee_short_code=product_coverage_rule.guarantee_short_code,
        coverage_rule_parameter_set=product_coverage_rule.coverage_rule_parameter_set,
    )
guarantee_short_code instance-attribute
guarantee_short_code
BuilderProductGSheetAPISchema dataclass
BuilderProductGSheetAPISchema(id, sheet_id)

Bases: DataClassJsonMixin

id instance-attribute
id
sheet_id instance-attribute
sheet_id
BuilderProductSelectedCareTypeAPISchema dataclass
BuilderProductSelectedCareTypeAPISchema(
    guarantee_short_code,
    care_type_short_code,
    care_type_label,
)

Bases: DataClassJsonMixin

care_type_label instance-attribute
care_type_label
care_type_short_code instance-attribute
care_type_short_code
from_builder_product_selected_care_type staticmethod
from_builder_product_selected_care_type(
    builder_product_selected_care_type,
)
Source code in components/offer_builder/public/v1/entities/builder_product_api.py
@staticmethod
def from_builder_product_selected_care_type(  # noqa: D102
    builder_product_selected_care_type: BuilderProductSelectedCareType
    | CompetitorProductSelectedCareType,
) -> BuilderProductSelectedCareTypeAPISchema:
    return BuilderProductSelectedCareTypeAPISchema(
        guarantee_short_code=builder_product_selected_care_type.guarantee_short_code,
        care_type_short_code=builder_product_selected_care_type.care_type_short_code,
        care_type_label=builder_product_selected_care_type.care_type_label,
    )
guarantee_short_code instance-attribute
guarantee_short_code
BuilderTargetAPISchema dataclass
BuilderTargetAPISchema(
    id=None,
    ape_code=None,
    ccn_code=None,
    franchise_id=None,
    postal_code=None,
    professional_category=None,
    company_creation_year=None,
    number_of_primaries=None,
    prospect_ref=None,
)

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
    self.validate()
ape_code class-attribute instance-attribute
ape_code = None
ccn_code class-attribute instance-attribute
ccn_code = None
company_creation_year class-attribute instance-attribute
company_creation_year = None
franchise_id class-attribute instance-attribute
franchise_id = None
id class-attribute instance-attribute
id = None
number_of_primaries class-attribute instance-attribute
number_of_primaries = None
postal_code class-attribute instance-attribute
postal_code = None
professional_category class-attribute instance-attribute
professional_category = None
prospect_ref class-attribute instance-attribute
prospect_ref = None
validate
validate()
Source code in components/offer_builder/public/v1/entities/builder_product_api.py
def validate(self) -> None:  # noqa: D102
    if self.id is None and (
        self.ape_code is None
        or self.ccn_code is None
        or self.postal_code is None
        or self.professional_category is None
        or self.company_creation_year is None
        or self.number_of_primaries is None
        or self.prospect_ref is None
    ):
        raise ValueError(
            "Either ID must be set or all other fields (except optional franchise_id) must be set"
        )
PriceStructureCoefficientsAPISchema dataclass
PriceStructureCoefficientsAPISchema(
    *,
    partner_coefficient,
    child_coefficient,
    family_coefficient,
    nth_children_free
)

Bases: PriceStructureCoefficients, DataClassJsonMixin, DataClassJsonAlanMixin

from_price_structure staticmethod
from_price_structure(price_structure)

Converts from PriceStructure to PriceStructureCoefficientsAPISchema

Source code in components/offer_builder/public/v1/entities/builder_product_api.py
@staticmethod
def from_price_structure(
    price_structure: PriceStructure,
) -> PriceStructureCoefficientsAPISchema:
    """Converts from PriceStructure to PriceStructureCoefficientsAPISchema"""
    return PriceStructureCoefficientsAPISchema(
        partner_coefficient=price_structure.partner_coefficient,
        child_coefficient=price_structure.child_coefficient,
        family_coefficient=price_structure.family_coefficient,
        nth_children_free=price_structure.nth_children_free,
    )
nth_children_free instance-attribute
nth_children_free

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

coverage_api

GuaranteeViolationAPISchema dataclass
GuaranteeViolationAPISchema(
    violation_code,
    violation_message,
    guarantee_short_code=None,
    guarantee_label=None,
)

Bases: DataClassJsonMixin

guarantee_label class-attribute instance-attribute
guarantee_label = None
guarantee_short_code class-attribute instance-attribute
guarantee_short_code = None
violation_code instance-attribute
violation_code
violation_message instance-attribute
violation_message

for_display

builder_coverage_for_display
BuilderCoverageForDisplay dataclass
BuilderCoverageForDisplay(
    id,
    option_number,
    coverage_level,
    coverage_rules,
    selected_care_types,
    is_responsible,
    pricing,
    price_structure,
    price_structure_coefficients,
    past_results_correction_factor,
    target_loss_ratio,
    membership_fee_ratio,
    updated_at,
)

Bases: DataClassJsonMixin

coverage_level instance-attribute
coverage_level
coverage_rules instance-attribute
coverage_rules
id instance-attribute
id
is_responsible instance-attribute
is_responsible
membership_fee_ratio instance-attribute
membership_fee_ratio
option_number instance-attribute
option_number
past_results_correction_factor instance-attribute
past_results_correction_factor
price_structure instance-attribute
price_structure
price_structure_coefficients instance-attribute
price_structure_coefficients
pricing instance-attribute
pricing
selected_care_types instance-attribute
selected_care_types
target_loss_ratio instance-attribute
target_loss_ratio
updated_at instance-attribute
updated_at
CoverageRuleForDisplay dataclass
CoverageRuleForDisplay(
    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
SelectedCareTypeForDisplay dataclass
SelectedCareTypeForDisplay(
    guarantee_short_code,
    care_type_short_code,
    care_type_label,
)

Bases: DataClassJsonMixin

care_type_label instance-attribute
care_type_label
care_type_short_code instance-attribute
care_type_short_code
guarantee_short_code instance-attribute
guarantee_short_code
builder_pricing_for_display
BuilderPricingForDisplay dataclass
BuilderPricingForDisplay(
    prices,
    labelled_price_details,
    health_premium,
    pricer_version,
    margin_target_percent,
)

Bases: BuilderPricingPresenter

Legacy presenter for a builder_pricing

margin_target_percent instance-attribute
margin_target_percent
builder_product_for_display
BuilderProductCreator dataclass
BuilderProductCreator(id, full_name)

Bases: DataClassJsonMixin

full_name instance-attribute
full_name
id instance-attribute
id
BuilderProductForDisplay dataclass
BuilderProductForDisplay(
    id,
    display_name,
    created_at,
    updated_at,
    is_template,
    is_allowed_above_business_max,
    salesforce_opportunity_id,
    gsheet,
    competitor_product_id,
    coverage_level,
    origin,
    tags,
    current_version,
    franchise_name,
)

Bases: _BuilderProductForDisplayBase

current_version instance-attribute
current_version
franchise_name instance-attribute
franchise_name
BuilderProductForDisplayShort dataclass
BuilderProductForDisplayShort(
    id,
    display_name,
    created_at,
    updated_at,
    is_template,
    is_allowed_above_business_max,
    salesforce_opportunity_id,
    gsheet,
    competitor_product_id,
    coverage_level,
    origin,
    tags,
    current_version,
)

Bases: _BuilderProductForDisplayBase

current_version instance-attribute
current_version
BuilderProductGSheet dataclass
BuilderProductGSheet(id, sheet_id)

Bases: DataClassJsonMixin

id instance-attribute
id
sheet_id instance-attribute
sheet_id
builder_product_summary_for_display
BuilderProductSummaryForDisplay dataclass
BuilderProductSummaryForDisplay(
    id,
    account_ref,
    builder_scenario_id,
    display_name,
    version_number,
    is_priced,
    coverage_level,
    competitor_product_id,
    competitor_product_template_id,
    google_spreadsheet_url,
    google_spreadsheet_export,
    last_updated_at,
    instant_quote_ids,
    targets,
    professional_category,
    origin,
    tags,
    is_using_global_guarantees,
)

Bases: DataClassJsonMixin

account_ref instance-attribute
account_ref
builder_scenario_id instance-attribute
builder_scenario_id
competitor_product_id instance-attribute
competitor_product_id
competitor_product_template_id instance-attribute
competitor_product_template_id
coverage_level instance-attribute
coverage_level
display_name instance-attribute
display_name
google_spreadsheet_export instance-attribute
google_spreadsheet_export
google_spreadsheet_url instance-attribute
google_spreadsheet_url
id instance-attribute
id
instant_quote_ids instance-attribute
instant_quote_ids
is_priced instance-attribute
is_priced
is_using_global_guarantees instance-attribute
is_using_global_guarantees
last_updated_at instance-attribute
last_updated_at
origin instance-attribute
origin
professional_category instance-attribute
professional_category
tags instance-attribute
tags
targets instance-attribute
targets
version_number instance-attribute
version_number
BuilderProductSummaryForDisplayGSheetExport dataclass
BuilderProductSummaryForDisplayGSheetExport(
    id,
    status,
    queued_at,
    processing_at,
    failed_at,
    success_at,
)

Bases: DataClassJsonMixin

failed_at instance-attribute
failed_at
id instance-attribute
id
processing_at instance-attribute
processing_at
queued_at instance-attribute
queued_at
status instance-attribute
status
success_at instance-attribute
success_at
BuilderProductSummaryForDisplayTarget dataclass
BuilderProductSummaryForDisplayTarget(prospect_ref)

Bases: DataClassJsonMixin

prospect_ref instance-attribute
prospect_ref
builder_product_version_for_display
BuilderProductVersionForDisplay dataclass
BuilderProductVersionForDisplay(
    id,
    version,
    builder_product_id,
    display_name,
    product_template_id,
    template_signature_coverage_id,
    base_coverage,
    option_coverages,
    is_latest_version,
    is_priced,
    prevoyance_1_5_rule_flag,
    google_sheet_id,
    created_at,
    collective_agreement_ids,
    ccn_collective_agreements,
    enable_hospital_cap_cost_of_care_reduction,
    actual_participation,
    minimum_participation,
    price_structure_coefficient_caps,
    builder_targets=None,
    output_metrics=None,
)

Bases: DataClassJsonMixin

actual_participation instance-attribute
actual_participation
base_coverage instance-attribute
base_coverage
builder_product_id instance-attribute
builder_product_id
builder_targets class-attribute instance-attribute
builder_targets = None
ccn_collective_agreements instance-attribute
ccn_collective_agreements
collective_agreement_ids instance-attribute
collective_agreement_ids
created_at instance-attribute
created_at
display_name instance-attribute
display_name
enable_hospital_cap_cost_of_care_reduction instance-attribute
enable_hospital_cap_cost_of_care_reduction
google_sheet_id instance-attribute
google_sheet_id
id instance-attribute
id
is_latest_version instance-attribute
is_latest_version
is_priced instance-attribute
is_priced
minimum_participation instance-attribute
minimum_participation
option_coverages instance-attribute
option_coverages
output_metrics class-attribute instance-attribute
output_metrics = None
prevoyance_1_5_rule_flag instance-attribute
prevoyance_1_5_rule_flag
price_structure_coefficient_caps instance-attribute
price_structure_coefficient_caps
product_template_id instance-attribute
product_template_id
template_signature_coverage_id instance-attribute
template_signature_coverage_id
version instance-attribute
version
BuilderProductVersionForDisplayShort dataclass
BuilderProductVersionForDisplayShort(
    id, version, is_priced
)

Bases: DataClassJsonMixin

id instance-attribute
id
is_priced instance-attribute
is_priced
version instance-attribute
version
Participation dataclass
Participation(
    primary_participation_percent,
    partner_participation_percent=0,
    child_participation_percent=0,
)

Bases: DataClassJsonMixin

child_participation_percent class-attribute instance-attribute
child_participation_percent = 0
partner_participation_percent class-attribute instance-attribute
partner_participation_percent = 0
primary_participation_percent instance-attribute
primary_participation_percent
builder_target_for_display
BuilderTargetForDisplay dataclass
BuilderTargetForDisplay(
    id,
    ape_code,
    ccn_code,
    franchise_id,
    postal_code,
    professional_category,
    company_creation_year,
    number_of_primaries,
    prospect_ref,
)

Bases: ProductTarget, DataClassJsonMixin

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
manual_demographics_for_display
ManualDemographicsForDisplay dataclass
ManualDemographicsForDisplay(
    origin,
    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,
    company_ref=None,
    prospect_ref=None,
)

Bases: DataClassJsonMixin

__post_init__
__post_init__()
Source code in components/offer_builder/public/v1/entities/for_display/manual_demographics_for_display.py
def __post_init__(self) -> None:  # noqa: D105
    object.__setattr__(self, "is_empty", _is_empty(self))
    object.__setattr__(
        self, "is_valid", validate_manual_demographics(self, do_not_raise=True)
    )
average_age class-attribute instance-attribute
average_age = None
company_ref class-attribute instance-attribute
company_ref = None
is_empty class-attribute instance-attribute
is_empty = field(init=False)
is_valid class-attribute instance-attribute
is_valid = field(init=False)
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_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
origin instance-attribute
origin
prospect_ref class-attribute instance-attribute
prospect_ref = None
validate_manual_demographics
validate_manual_demographics(
    manual_demographics, do_not_raise=False
)
Source code in components/offer_builder/public/v1/entities/for_display/manual_demographics_for_display.py
def validate_manual_demographics(  # noqa: D103
    manual_demographics: ManualDemographics
    | ManualDemographicsSQLA
    | ManualDemographicsForDisplay
    | None,
    do_not_raise: bool = False,
) -> bool:
    if (
        manual_demographics is None
    ):  # in case we have no manual demographics we consider it valid at least for now
        return True
    if (
        manual_demographics.prospect_ref is None
        and getattr(manual_demographics, "company_ref", None) is None
    ):
        if do_not_raise:
            return False
        raise ManualDemographicsError("prospect_ref or company_ref are required")

    if (  # In case we don't have number_of_employees we consider it valid at least for now
        manual_demographics.number_of_employees is None
        or manual_demographics.number_of_employees == 0
    ):
        return True

    if (mandatory(manual_demographics.number_of_employees) >= 1000) and (
        manual_demographics.average_age is None
        or manual_demographics.number_of_option_taker is None
        or manual_demographics.number_of_male is None
        or manual_demographics.number_of_cadre is None
        or manual_demographics.number_of_children is None
        or manual_demographics.number_of_single is None
    ):
        if do_not_raise:
            return False
        raise ManualDemographicsError(
            "all fields (except number_of_ani are required when number_of_employees >= 1000"
        )
    if (
        mandatory(manual_demographics.number_of_employees) >= 100
        and manual_demographics.average_age is None
    ):
        if do_not_raise:
            return False
        raise ManualDemographicsError(
            "average_age is required for number_of_employees >= 100"
        )
    return True
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,
    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_price instance-attribute
tranche_1_price
tranche_2_price instance-attribute
tranche_2_price
signature_coverage_for_display
SignatureCoverageForDisplay dataclass
SignatureCoverageForDisplay(
    id,
    ccn_code,
    franchise_id,
    franchise_name,
    coverage_level,
    builder_product_id,
    current_product_version,
)

Bases: DataClassJsonMixin

builder_product_id instance-attribute
builder_product_id
ccn_code instance-attribute
ccn_code
coverage_level instance-attribute
coverage_level
current_product_version instance-attribute
current_product_version
franchise_id instance-attribute
franchise_id
franchise_name instance-attribute
franchise_name
id instance-attribute
id
SignatureCoverageForDisplaySearchResults dataclass
SignatureCoverageForDisplaySearchResults(franchises, ccns)

Bases: DataClassJsonMixin

ccns instance-attribute
ccns
franchises instance-attribute
franchises
SignatureCoverageProductVersionForDisplayShort dataclass
SignatureCoverageProductVersionForDisplayShort(
    id, number_of_options
)

Bases: DataClassJsonMixin

id instance-attribute
id
number_of_options instance-attribute
number_of_options
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.

children_per_primary_with_children_ratio property
children_per_primary_with_children_ratio

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

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.

from_dict staticmethod
from_dict(params)
Source code in components/offer_builder/public/v1/entities/manual_demographics.py
@staticmethod
def from_dict(params):  # type: ignore[no-untyped-def,override]  # noqa: D102
    return ManualDemographics(
        number_of_employees=params.get("number_of_employees"),
        number_of_covered_employees=params.get("number_of_covered_employees"),
        average_age=params.get("average_age"),
        number_of_partners=params.get("number_of_partners"),
        number_of_ani=params.get("number_of_ani"),
        number_of_option_taker=params.get("number_of_option_taker"),
        number_of_male=params.get("number_of_male"),
        number_of_cadre=params.get("number_of_cadre"),
        number_of_children=params.get("number_of_children"),
        number_of_single=params.get("number_of_single"),
        company_ref=params.get("company_ref"),
        prospect_ref=params.get("prospect_ref"),
    )
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,
        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(0, 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,
    )
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
ManualDemographicsOrigin

Bases: AlanBaseEnum

ACQUISITION class-attribute instance-attribute
ACQUISITION = 'acquisition'
TURING class-attribute instance-attribute
TURING = 'turing'

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"
)
mandatory_changes staticmethod
mandatory_changes()
Source code in components/offer_builder/public/v1/enums/coverage_change.py
@staticmethod
def mandatory_changes() -> list[CoverageChangeEnum]:  # noqa: D102
    # ! Order is important
    # - Child glasses guarantees must be added after mix glasses guarantees
    # - UPDATE_DENTAL_PROSTHESIS_COST_OF_CLAIMS must be first because of hack in legacy product-builder
    return [
        # Was once an optional change, but now mandatory
        CoverageChangeEnum.UPDATE_DENTAL_PROSTHESIS_COST_OF_CLAIMS,
        # we add all missing guarantees (guarantees considered as mandatory and not yet included)
        CoverageChangeEnum.ADD_MISSING_GUARANTEES,
        # we still make sure it's a "bundle product" (Mind sessions, but no meditation)
        # it's only a temporary convenience, this code can be removed later
        CoverageChangeEnum.DEL_MEDITATION,
        # These are not coverage changes per se, as we're determining the mix glasses guarantees
        # from the other glasses guarantees (SIMPLE, COMPLEX, VERY_COMPLEX)
        CoverageChangeEnum.ADD_MIX_GLASSES_GUARANTEES,
        CoverageChangeEnum.ADD_CHILD_GLASSES_GUARANTEES,
    ]
with_mandatory_changes classmethod
with_mandatory_changes(coverage_changes)

Returns a list of coverage changes with the mandatory ones + the given ones.

Source code in components/offer_builder/public/v1/enums/coverage_change.py
@classmethod
def with_mandatory_changes(
    cls, coverage_changes: Iterable[CoverageChangeEnum] | None
) -> list[CoverageChangeEnum]:
    """
    Returns a list of coverage changes with the mandatory ones + the given ones.
    """
    # NB: not using a set to preserve order
    merged_changes = cls.mandatory_changes()
    if not coverage_changes:
        return merged_changes
    for change in coverage_changes:
        if change not in merged_changes:
            merged_changes.append(change)
    return merged_changes

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,
    )

queries

builder_scenario

get_builder_scenario
get_builder_scenario(id)
Source code in components/offer_builder/internal/v1/queries/v2/builder_scenario.py
def get_builder_scenario(
    id: UUID,
) -> BuilderScenarioForDisplay:
    scenario = get_or_raise_missing_resource(
        BuilderScenarioSQLA,
        id,
        options=[
            joinedload(BuilderScenarioSQLA.builder_products).options(
                joinedload(BuilderProduct.current_version)
            )
        ],
    )

    return _from_builder_scenario_model_to_display(scenario, with_output_metrics=True)

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)

get_builder_product_version_for_display

get_builder_products_summaries

get_builder_product_summaries
get_builder_product_summaries(
    account_id, builder_scenario_id=NOT_SET
)
Source code in components/offer_builder/public/v1/queries/get_builder_products_summaries.py
def get_builder_product_summaries(  # noqa: D103
    account_id: uuid.UUID,
    builder_scenario_id: NotSet[uuid.UUID | None] = NOT_SET,
) -> list[BuilderProductSummaryForDisplay]:
    from components.instant_quote.public.main import (
        get_instant_quote_ids_for_product_versions,
    )

    account_ref = str(account_id)
    query = current_session.query(BuilderProduct).filter(  # noqa: ALN085
        BuilderProduct.account_ref == account_ref
    )
    if is_set(builder_scenario_id):
        query = query.filter(BuilderProduct.builder_scenario_id == builder_scenario_id)

    builder_products: list[BuilderProduct] = query.options(
        joinedload(BuilderProduct.versions).options(
            joinedload(BuilderProductVersion.product_template).options(
                joinedload(BuilderProduct.signature_coverage).options(
                    joinedload(SignatureCoverage.franchise),
                    joinedload(SignatureCoverage.ccn),
                ),
                joinedload(BuilderProduct.builder_template),
            ),
            joinedload(BuilderProductVersion.builder_coverages).joinedload(
                BuilderCoverage.pricing
            ),
        ),
        joinedload(BuilderProduct.competitor_product_template),
    ).all()

    builder_formula_by_guarantee_short_code = (
        _get_builder_formula_by_guarantee_code_for_global_guarantee_check()
    )

    builder_product_summaries: list[BuilderProductSummaryForDisplay] = []
    for builder_product in builder_products:
        current_version = builder_product.current_version

        if current_version is None:
            continue  # we ignore products without version or without base coverage (to avoid hard failing)
        if not current_version.product_template or (
            not current_version.product_template.signature_coverage_id
            and not current_version.product_template.builder_template
        ):
            # we ignore products that are not tailored (created from legacy product builder)
            continue

        google_spreadsheet_url = None
        google_spreadsheet_export = None

        if len(current_version.gsheet_exports) > 0:
            export = current_version.gsheet_exports[-1]
            google_spreadsheet_export = BuilderProductSummaryForDisplayGSheetExport(
                id=export.id,
                status=export.status,
                queued_at=export.queued_at,
                processing_at=export.processing_at,
                failed_at=export.failed_at,
                success_at=export.success_at,
            )

        if builder_product.google_spreadsheet_id:
            google_spreadsheet_url = builder_product.google_spreadsheet_url

        display_name = get_builder_product_version_display_name(current_version.id)
        base_coverage = current_version.base_coverage

        builder_product_summaries.append(
            BuilderProductSummaryForDisplay(
                id=builder_product.id,
                account_ref=account_ref,
                builder_scenario_id=builder_product.builder_scenario_id,
                display_name=display_name,
                version_number=current_version.version,
                is_priced=current_version.is_priced,
                coverage_level=base_coverage.coverage_level if base_coverage else None,
                competitor_product_id=builder_product.competitor_product_id,
                competitor_product_template_id=builder_product.competitor_product_template_id,
                google_spreadsheet_export=google_spreadsheet_export,
                google_spreadsheet_url=google_spreadsheet_url,
                last_updated_at=current_version.last_updated_at,
                instant_quote_ids=get_instant_quote_ids_for_product_versions(
                    [str(version.id) for version in builder_product.versions]
                ),
                targets=[
                    BuilderProductSummaryForDisplayTarget(
                        prospect_ref=target.prospect_ref
                    )
                    for target in current_version.builder_targets
                ],
                professional_category=get_professional_category(current_version)
                or ProfessionalCategory.all,
                origin=BuilderProductOrigin(builder_product.origin),
                tags=[BuilderProductTag(tag) for tag in builder_product.tags],
                is_using_global_guarantees=_is_using_global_guarantees(
                    builder_product,
                    builder_formula_by_guarantee_short_code,
                ),
            ),
        )

    return builder_product_summaries

list_builder_products

list_builder_products
list_builder_products(
    builder_product_ids=None,
    prospect_refs=None,
    account_refs=None,
    origin=None,
    tags=None,
    options=None,
    any_version_has_plan=None,
)
Source code in components/offer_builder/public/v1/queries/list_builder_products.py
def list_builder_products(  # noqa: D103
    builder_product_ids: list[int] | None = None,
    prospect_refs: list[str] | None = None,
    account_refs: list[str] | None = None,
    origin: BuilderProductOrigin | None = None,
    tags: list[BuilderProductTag] | None = None,
    options: list[Load] | None = None,
    any_version_has_plan: bool | None = None,
) -> list[BuilderProduct]:
    builder_product_models = search_builder_products(
        builder_product_ids=builder_product_ids,
        prospect_refs=prospect_refs,
        account_refs=account_refs,
        origin=origin,
        tags=tags,
        options=options,
        any_version_has_plan=any_version_has_plan,
    )

    return [BuilderProduct.from_model(model) for model in builder_product_models]

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: list[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
        )