Skip to content

Api reference

components.core_price.public.api

PricingService

Service for computing price breakdowns.

get_breakdown staticmethod

get_breakdown(
    *,
    on_date: date,
    pricing_source: PricingSource,
    contract_identifier: None = None,
    rounding_strategy: RoundingStrategy = RoundingStrategy.ARITHMETIC
) -> PriceBreakdown
get_breakdown(
    *,
    on_date: date,
    pricing_source: None = None,
    contract_identifier: ContractIdentifier,
    rounding_strategy: RoundingStrategy = RoundingStrategy.ARITHMETIC
) -> PriceBreakdown
get_breakdown(
    *,
    on_date: date,
    member_specs: Sequence[MemberSpec],
    pricing_source: PricingSource,
    rounding_strategy: RoundingStrategy = RoundingStrategy.ARITHMETIC
) -> PriceBreakdown
get_breakdown(
    *,
    on_date: date,
    additional_member_specs: Sequence[MemberSpec],
    pricing_source: PricingSource,
    rounding_strategy: RoundingStrategy = RoundingStrategy.ARITHMETIC
) -> PriceBreakdown
get_breakdown(
    *,
    on_date,
    pricing_source=None,
    contract_identifier=None,
    member_specs=None,
    additional_member_specs=None,
    rounding_strategy=RoundingStrategy.ARITHMETIC
)

Compute a price breakdown.

This price breakdown is evaluated based on
  • all coverage modules active on the given date
  • all beneficiaries active on the given date
  • all discounts active on the given date

When evaluating prices for a policy, if several coverage modules are active, the breakdown contains the aggregated price components of all the coverage modules.

Parameters:

Name Type Description Default
on_date date

The reference date for coverage and affiliation data.

required
pricing_source PricingSource | None

Abstract identifier for pricing resolution (policy_id, enrollment_id, etc.). Must be provided if contract_identifier is None.

None
contract_identifier ContractIdentifier | None

The contract identifier to compute prices for. Must be provided if pricing_source is None. Currently not implemented.

None
member_specs Sequence[MemberSpec] | None

Optional list of enrollment specs to use instead of the source's current beneficiaries. When provided with pricing_source, uses these beneficiaries with the source's pricing function.

None
additional_member_specs Sequence[MemberSpec] | None

Optional list of specs to append to the source's existing beneficiaries. Resolves current members from pricing_source, then appends these specs before computing. Useful for simulating adding a new dependent to an existing policy.

None
rounding_strategy RoundingStrategy

Strategy for rounding monetary amounts when splitting prices between entities. Defaults to arithmetic rounding.

ARITHMETIC

Returns:

Type Description
PriceBreakdown

A PriceBreakdown containing detailed price breakdown for 30 days of coverage.

Raises:

Type Description
ValueError

If neither pricing_source nor contract_identifier is provided.

ValueError

If both member_specs and additional_member_specs are provided.

ValueError

If no price breakdown is found for the source on this date.

NotImplementedError

If contract_identifier is used (not yet supported).

Examples:

# Use the beneficiary information corresponding to the state of the source
# at the date of on_date.
PricingService.get_breakdown(
    pricing_source=PolicyPricingSource(policy_id), on_date=on_date
)

# Use an arbitrary list of beneficiaries.
PricingService.get_breakdown(
    member_specs=[
        MemberSpec(...),
        MemberSpec(...),
    ],
    pricing_source=PolicyPricingSource(policy_id),
    on_date=on_date,
)

# Simulate adding a new member to an existing policy.
PricingService.get_breakdown(
    additional_member_specs=[MemberSpec(...)],
    pricing_source=PolicyPricingSource(policy_id),
    on_date=on_date,
)
Source code in components/core_price/public/api.py
@staticmethod
def get_breakdown(
    *,
    on_date: date,
    pricing_source: PricingSource | None = None,
    contract_identifier: "ContractIdentifier | None" = None,
    member_specs: Sequence[MemberSpec] | None = None,
    additional_member_specs: Sequence[MemberSpec] | None = None,
    rounding_strategy: RoundingStrategy = RoundingStrategy.ARITHMETIC,
) -> "PriceBreakdown":
    """Compute a price breakdown.

    This price breakdown is evaluated based on:
        - all coverage modules active on the given date
        - all beneficiaries active on the given date
        - all discounts active on the given date

    When evaluating prices for a policy, if several coverage modules are active,
    the breakdown contains the aggregated price components of all the coverage
    modules.

    Args:
        on_date: The reference date for coverage and affiliation data.
        pricing_source: Abstract identifier for pricing resolution (policy_id,
            enrollment_id, etc.). Must be provided if contract_identifier is None.
        contract_identifier: The contract identifier to compute prices for.
            Must be provided if pricing_source is None. Currently not implemented.
        member_specs: Optional list of enrollment specs to use instead of
            the source's current beneficiaries. When provided with pricing_source,
            uses these beneficiaries with the source's pricing function.
        additional_member_specs: Optional list of specs to append to the source's
            existing beneficiaries. Resolves current members from pricing_source,
            then appends these specs before computing. Useful for simulating adding
            a new dependent to an existing policy.
        rounding_strategy: Strategy for rounding monetary amounts when splitting
            prices between entities. Defaults to arithmetic rounding.

    Returns:
        A PriceBreakdown containing detailed price breakdown for 30 days of coverage.

    Raises:
        ValueError: If neither pricing_source nor contract_identifier is provided.
        ValueError: If both member_specs and additional_member_specs are provided.
        ValueError: If no price breakdown is found for the source on this date.
        NotImplementedError: If contract_identifier is used (not yet supported).

    Examples:
        ```python
        # Use the beneficiary information corresponding to the state of the source
        # at the date of on_date.
        PricingService.get_breakdown(
            pricing_source=PolicyPricingSource(policy_id), on_date=on_date
        )

        # Use an arbitrary list of beneficiaries.
        PricingService.get_breakdown(
            member_specs=[
                MemberSpec(...),
                MemberSpec(...),
            ],
            pricing_source=PolicyPricingSource(policy_id),
            on_date=on_date,
        )

        # Simulate adding a new member to an existing policy.
        PricingService.get_breakdown(
            additional_member_specs=[MemberSpec(...)],
            pricing_source=PolicyPricingSource(policy_id),
            on_date=on_date,
        )
        ```
    """
    if member_specs and additional_member_specs:
        raise ValueError(
            "Cannot provide both member_specs and additional_member_specs"
        )

    price_breakdown = None

    if member_specs and pricing_source:
        from components.core_price.public.dependencies import get_app_dependency

        deps = get_app_dependency()
        price_breakdown = deps.get_price_breakdown(
            pricing_context=PricingContext(
                on_date=on_date,
                member_specs=member_specs,
                rounding_strategy=rounding_strategy,
            ),
        )

    elif additional_member_specs and pricing_source:
        from components.core_price.public.dependencies import get_app_dependency

        deps = get_app_dependency()
        pricing_context = deps.resolve_pricing_context(
            pricing_source=pricing_source,
            on_date=on_date,
        )

        # We are leaking some complexity here, we allow merging member specs inside our API layer
        # if we have more complexity related to this merge, we could remove it entirely from the responsibility
        # of the core_price: It's the consumer that will deal with merging/enriching specs
        combined_specs = list(pricing_context.member_specs) + list(
            additional_member_specs
        )
        price_breakdown = deps.get_price_breakdown(
            pricing_context=PricingContext(
                on_date=on_date,
                member_specs=combined_specs,
                rounding_strategy=rounding_strategy,
            ),
        )

    elif pricing_source:
        from components.core_price.public.dependencies import get_app_dependency

        deps = get_app_dependency()
        pricing_context = deps.resolve_pricing_context(
            pricing_source=pricing_source,
            on_date=on_date,
        )
        price_breakdown = deps.get_price_breakdown(
            pricing_context=pricing_context,
        )

    elif contract_identifier:
        raise NotImplementedError(
            "get_breakdown by contract_identifier not implemented yet"
        )
    else:
        raise ValueError(
            "Must provide exactly one of pricing_source or contract_identifier"
        )

    if price_breakdown is None:
        raise ValueError("No price breakdown found")

    return price_breakdown

components.core_price.public.dependencies

COMPONENT_NAME module-attribute

COMPONENT_NAME = 'core_price'

Canonical name of the core price component.

CorePriceDependency

Bases: ABC

Dependencies injected into the core price component.

Every component depending on the core price component is responsible for injecting its own implementation of these dependencies.

get_price_breakdown abstractmethod

get_price_breakdown(*, pricing_context)

Compute price breakdown.

Implementation handles module -> PricingFunction resolution internally using pricing_context.pricing_source.

Parameters:

Name Type Description Default
pricing_context PricingContext

Inputs required to compute prices, including pricing_source, date, beneficiaries, and rounding strategy.

required

Returns:

Name Type Description
PriceBreakdown PriceBreakdown

A price breakdown with price components.

Source code in components/core_price/public/dependencies.py
@abstractmethod
def get_price_breakdown(
    self,
    *,
    pricing_context: PricingContext,
) -> PriceBreakdown:
    """Compute price breakdown.

    Implementation handles module -> PricingFunction resolution internally
    using pricing_context.pricing_source.

    Args:
        pricing_context: Inputs required to compute prices, including
            pricing_source, date, beneficiaries, and rounding strategy.

    Returns:
        PriceBreakdown: A price breakdown with price components.
    """

resolve_pricing_context abstractmethod

resolve_pricing_context(*, pricing_source, on_date)

Resolve a pricing context from a pricing source.

Parameters:

Name Type Description Default
pricing_source PricingSource

Abstract identifier (policy_id, enrollment_id, etc.)

required
on_date date

The date for which to resolve the context.

required

Returns:

Type Description
PricingContext

A complete PricingContext with source, date, enrollment specs,

PricingContext

and implementation-specific rounding strategy.

Source code in components/core_price/public/dependencies.py
@abstractmethod
def resolve_pricing_context(
    self,
    *,
    pricing_source: PricingSource,
    on_date: date,
) -> PricingContext:
    """Resolve a pricing context from a pricing source.

    Args:
        pricing_source: Abstract identifier (policy_id, enrollment_id, etc.)
        on_date: The date for which to resolve the context.

    Returns:
        A complete PricingContext with source, date, enrollment specs,
        and implementation-specific rounding strategy.
    """

get_app_dependency

get_app_dependency()

Function used to fetch the dependencies from the flask app.

Source code in components/core_price/public/dependencies.py
def get_app_dependency() -> CorePriceDependency:
    """Function used to fetch the dependencies from the flask app."""
    from flask import current_app

    return cast("CustomFlask", current_app).get_component_dependency(COMPONENT_NAME)  # type: ignore[no-any-return]

set_app_dependency

set_app_dependency(dependency)

Function used to actually inject the dependency class in the component.

Source code in components/core_price/public/dependencies.py
def set_app_dependency(dependency: CorePriceDependency) -> None:
    """Function used to actually inject the dependency class in the component."""
    from flask import current_app

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

components.core_price.public.entities

Pricing API domain entities.

These types define the pricing-domain shapes used at component boundaries. Consumers should prefer local domain equivalents and map at adapters. For shared technical primitives (currency, rounding strategy), import from components.core_price.public.primitives.

Note: Entities are defined in internal and re-exported here for external consumers.

CollectionMethod

Bases: AlanBaseEnum

Methods for collecting a member's contribution.

direct_billing class-attribute instance-attribute

direct_billing = 'direct_billing'

The amount is paid directly by the employee or their partner, using the payment method of their choice.

flexben_fund class-attribute instance-attribute

flexben_fund = 'flexben_fund'

The amount is paid from the employee's flexben fund.

payroll class-attribute instance-attribute

payroll = 'payroll'

The amount is paid by the company but automatically deducted from the employee's payslip.

ContributionType

Bases: AlanBaseEnum

Represents how an amount is used, what the money is going towards.

discount class-attribute instance-attribute

discount = 'discount'

membership_fee class-attribute instance-attribute

membership_fee = 'membership_fee'

pure_cost class-attribute instance-attribute

pure_cost = 'pure_cost'

social_fund class-attribute instance-attribute

social_fund = 'social_fund'

taxes class-attribute instance-attribute

taxes = 'taxes'

Debtor

Bases: AlanBaseEnum

Represents an entity paying for a price component.

company class-attribute instance-attribute

company = 'company'

partner class-attribute instance-attribute

partner = 'partner'

primary class-attribute instance-attribute

primary = 'primary'

MemberSpec

Bases: Protocol

Represents a member enrollment used for pricing.

contract_version_id property

contract_version_id

ID of the contract version for legacy pricing function, it will be deprecated once we can rely only on module ids.

enrollment_id property

enrollment_id

Does this spec corresponding to a member enrolled on a policy ?

member_age property

member_age

The age of the member, if available.

member_date_of_birth property

member_date_of_birth

The date of birth of the member, if available

member_type property

member_type

The type of member (primary, partner, child).

modules property

modules

List of enrolled modules to price for this beneficiary.

MemberType

Bases: AlanBaseEnum

Represents the different member types.

children class-attribute instance-attribute

children = 'children'

from_enrollment_type classmethod

from_enrollment_type(enrollment_type)

Convert EnrollmentType to member type.

Source code in components/core_price/internal/entities.py
@classmethod
def from_enrollment_type(cls, enrollment_type: EnrollmentType) -> "MemberType":
    """Convert EnrollmentType to member type."""
    if enrollment_type == EnrollmentType.primary:
        return cls.primary
    elif enrollment_type == EnrollmentType.partner:
        return cls.partner
    elif enrollment_type == EnrollmentType.child:
        return cls.children
    else:
        raise ValueError(f"Unknown enrollment type: {enrollment_type}")

partner class-attribute instance-attribute

partner = 'partner'

primary class-attribute instance-attribute

primary = 'primary'

to_enrollment_type

to_enrollment_type()

Convert member type to EnrollmentType.

Source code in components/core_price/internal/entities.py
def to_enrollment_type(self) -> EnrollmentType:
    """Convert member type to EnrollmentType."""
    if self == MemberType.primary:
        return EnrollmentType.primary
    elif self == MemberType.partner:
        return EnrollmentType.partner
    elif self == MemberType.children:
        return EnrollmentType.child
    else:
        raise ValueError(f"Unknown member type: {self}")

Module

Bases: AlanBaseEnum

Represents a coverage module.

base class-attribute instance-attribute

base = 'base'

extended class-attribute instance-attribute

extended = 'extended'

Base + option rolled into the same coverage module.

hospitalization class-attribute instance-attribute

hospitalization = 'hospitalization'

Neutral label for single-coverage plans (no base/extended distinction).

option class-attribute instance-attribute

option = 'option'

PolicyPricingSource dataclass

PolicyPricingSource(policy_id)

Resolve pricing from a policy_id directly.

policy_id instance-attribute

policy_id

PriceBreakdown dataclass

PriceBreakdown(*, price_components)

Represents the full price breakdown.

No safeties on price components

There are no checks verifying there are no duplicates in components. The caller is responsible for ensuring the components represent a valid price breakdown.

Source code in components/core_price/internal/entities.py
def __init__(self, *, price_components: list[PriceComponent]):
    self.price_components = price_components
    self.currency = price_components[0].currency if price_components else None

    # Compute some intermediary values frequently used by consumers, in O(n) time
    untaxed_amount_billed_to_primary = 0
    untaxed_amount_billed_to_company = 0

    taxes_billed_to_company = 0
    taxes_billed_to_primary = 0

    for component in price_components:
        if component.currency != self.currency:
            raise ValueError("Cannot mix different currencies.")

        match component.debtor:
            case Debtor.company:
                if component.contribution_type == ContributionType.taxes:
                    taxes_billed_to_company += component.amount
                else:
                    untaxed_amount_billed_to_company += component.amount
            case Debtor.primary:
                if component.contribution_type == ContributionType.taxes:
                    taxes_billed_to_primary += component.amount
                else:
                    untaxed_amount_billed_to_primary += component.amount
            case debtor:
                raise NotImplementedError(f"Unimplement billed entity: {debtor}")

    taxed_amount_billed_to_primary = (
        untaxed_amount_billed_to_primary + taxes_billed_to_primary
    )
    taxed_amount_billed_to_company = (
        untaxed_amount_billed_to_company + taxes_billed_to_company
    )

    self.taxed_amount_billed_to_primary = taxed_amount_billed_to_primary
    self.untaxed_amount_billed_to_primary = untaxed_amount_billed_to_primary
    self.untaxed_amount_billed_to_company = untaxed_amount_billed_to_company
    self.taxed_amount_billed_to_company = taxed_amount_billed_to_company

    # Once initialised, the dataclass is frozen.
    # Enforced via custom __setattr__ implementation.
    self._initialized = True

__setattr__

__setattr__(name, value)

Setting attributes once the dataclass is initialized is forbidden. Reproducing the behavior of frozen=True.

Source code in components/core_price/internal/entities.py
def __setattr__(self, name: str, value: object) -> None:
    """
    Setting attributes once the dataclass is initialized is forbidden.
    Reproducing the behavior of frozen=True.
    """
    if hasattr(self, "_initialized") and self._initialized:
        raise AttributeError(
            f"Cannot modify {name} after initialization. PriceBreakdown is immutable."
        )
    super().__setattr__(name, value)

currency instance-attribute

currency = currency if price_components else None

for_member

for_member(member_id)

Return a new PriceBreakdown containing only components for the given member.

Parameters:

Name Type Description Default
member_id int | UUID | None

The enrollment_id identifying the member. Use None for simulated (not-yet-enrolled) members.

required
Source code in components/core_price/internal/entities.py
def for_member(self, member_id: int | UUID | None) -> "PriceBreakdown":
    """Return a new PriceBreakdown containing only components for the given member.

    Args:
        member_id: The enrollment_id identifying the member. Use None for
            simulated (not-yet-enrolled) members.
    """
    filtered = [c for c in self.price_components if c.enrollment_id == member_id]
    return PriceBreakdown.from_components(filtered)

from_components staticmethod

from_components(price_components)

Construct a PriceBreakdown from several price components.

Raises:

Type Description
NotImplementedError

if the billed entity is not implemented yet

ValueError

if the currency used by all the components is not the same

Source code in components/core_price/internal/entities.py
@staticmethod
def from_components(
    price_components: list[PriceComponent],
) -> "PriceBreakdown":
    """
    Construct a PriceBreakdown from several price components.

    Raises:
        NotImplementedError: if the billed entity is not implemented yet
        ValueError: if the currency used by all the components is not the same
    """
    return PriceBreakdown(price_components=price_components)

merge staticmethod

merge(price_breakdowns)

Merge several price breakdowns into a single one.

When we evaluate the prices of coverage modules individually, merging them is useful to get the total aggregated prices.

Source code in components/core_price/internal/entities.py
@staticmethod
def merge(
    price_breakdowns: list["PriceBreakdown"],
) -> "PriceBreakdown":
    """
    Merge several price breakdowns into a single one.

    When we evaluate the prices of coverage modules individually, merging them is
    useful to get the total aggregated prices.
    """
    if len(price_breakdowns) == 1:
        return price_breakdowns[0]

    all_components: list[PriceComponent] = []
    for breakdown in price_breakdowns:
        all_components.extend(breakdown.price_components)
    return PriceBreakdown.from_components(all_components)

price_components instance-attribute

price_components = price_components

taxed_amount_billed_to_company instance-attribute

taxed_amount_billed_to_company = (
    taxed_amount_billed_to_company
)

taxed_amount_billed_to_primary instance-attribute

taxed_amount_billed_to_primary = (
    taxed_amount_billed_to_primary
)

untaxed_amount_billed_to_company instance-attribute

untaxed_amount_billed_to_company = (
    untaxed_amount_billed_to_company
)

untaxed_amount_billed_to_primary instance-attribute

untaxed_amount_billed_to_primary = (
    untaxed_amount_billed_to_primary
)

PriceComponent dataclass

PriceComponent(
    *,
    service_type,
    contribution_type,
    beneficiary_type,
    debtor,
    collection_method=None,
    enrollment_id=None,
    currency,
    amount,
    periodicity="monthly"
)

Represents part of a price.

__post_init__

__post_init__()

Validate that collection method is set when billed entity is not company.

Source code in components/core_price/internal/entities.py
def __post_init__(self) -> None:
    """Validate that collection method is set when billed entity is not company."""
    if self.debtor != Debtor.company and self.collection_method is None:
        raise ValueError(
            f"Collection method must be set when billed entity is {self.debtor}"
        )

amount instance-attribute

amount

The amount for an entire period of service.

Can be negative, for example if the amount corresponds to a discount. Must be expressed in the minor unit of the chosen currency.

beneficiary_type instance-attribute

beneficiary_type

Which member does this price correspond to?

collection_method class-attribute instance-attribute

collection_method = None

If a member is responsible for paying, how is the payment collected?

compare_component_lists staticmethod

compare_component_lists(left, right)

Compare two lists of price components for equality.

Parameters:

Name Type Description Default
left list[PriceComponent]

First list of components to compare

required
right list[PriceComponent]

Second list of components to compare

required

Returns:

Type Description
bool

True if both lists contain the same components in any order, False otherwise

Source code in components/core_price/internal/entities.py
@staticmethod
def compare_component_lists(
    left: list["PriceComponent"], right: list["PriceComponent"]
) -> bool:
    """
    Compare two lists of price components for equality.

    Args:
        left: First list of components to compare
        right: Second list of components to compare

    Returns:
        True if both lists contain the same components in any order, False otherwise
    """
    return set(left) == set(right)

contribution_type instance-attribute

contribution_type

What part of the price does this component correspond to?

currency instance-attribute

currency

debtor instance-attribute

debtor

Who is responsible for paying this price?

enrollment_id class-attribute instance-attribute

enrollment_id = None

An optional enrollment ID, in case the component corresponds to an enrolled member. It would typically be set when computing the prices for an existing policy. It would be None when a member projects the price of adding their partner to their policy.

group_by_beneficiary_id staticmethod

group_by_beneficiary_id(components)

Groups price components by enrollment ID.

Parameters:

Name Type Description Default
components list[PriceComponent]

List of PriceComponent to group

required

Returns:

Type Description
dict[int | UUID, list[PriceComponent]]

Dictionary mapping enrollment ID (int or UUID) to list of components

Raises:

Type Description
ValueError

If any component does not have an enrollment ID

Source code in components/core_price/internal/entities.py
@staticmethod
def group_by_beneficiary_id(
    components: list["PriceComponent"],
) -> dict[int | UUID, list["PriceComponent"]]:
    """
    Groups price components by enrollment ID.

    Args:
        components: List of PriceComponent to group

    Returns:
        Dictionary mapping enrollment ID (int or UUID) to list of components

    Raises:
        ValueError: If any component does not have an enrollment ID
    """
    grouped: dict[int | UUID, list[PriceComponent]] = {}

    for component in components:
        if component.enrollment_id is None:
            raise ValueError("All components must have an enrollment ID")

        enrollment_id = component.enrollment_id
        if enrollment_id not in grouped:
            grouped[enrollment_id] = []
        grouped[enrollment_id].append(component)

    return grouped

periodicity class-attribute instance-attribute

periodicity = 'monthly'

service_type instance-attribute

service_type

What module does this price correspond to ?

PricingContext dataclass

PricingContext(
    *,
    on_date,
    member_specs,
    rounding_strategy=RoundingStrategy.ARITHMETIC
)

Shared inputs required to compute a price breakdown.

member_specs instance-attribute

member_specs

on_date instance-attribute

on_date

primary_spec property

primary_spec

Return the primary member spec.

Uses first instead of one because the BE coverage upgrade flow can inject a simulated primary spec (with enrollment_id=None) via additional_member_specs, resulting in 2 primaries in the list. Real specs always come first (api.py merges policy specs before additional ones). TODO @cfollet: revert to one once upgrade flow uses dedicated pricing endpoint.

rounding_strategy class-attribute instance-attribute

rounding_strategy = ARITHMETIC

PricingFunction

Bases: Protocol

Encapsulates price rules. Local implementations are responsible for bringing their own pricing function implementation and resolving pricing sources internally.

get_price_breakdown

get_price_breakdown(pricing_context)

Compute monthly prices for beneficiaries.

Parameters:

Name Type Description Default
pricing_context PricingContext

Inputs required to compute prices, including date, beneficiaries, and rounding strategy.

required

Returns:

Type Description
PriceBreakdown

Price breakdown.

Source code in components/core_price/internal/entities.py
def get_price_breakdown(self, pricing_context: PricingContext) -> "PriceBreakdown":
    """Compute monthly prices for beneficiaries.

    Args:
        pricing_context: Inputs required to compute prices, including date,
            beneficiaries, and rounding strategy.

    Returns:
        Price breakdown.
    """
    ...

PricingSource module-attribute

PricingSource = UserPricingSource | PolicyPricingSource

Abstract identifier for pricing resolution.

UserPricingSource dataclass

UserPricingSource(user_id)

Resolve pricing from a user_id (looks up active enrollment → policy).

user_id instance-attribute

user_id

components.core_price.public.primitives

Shared primitives for pricing.

These are stable, cross-component technical types (currency, rounding strategy, etc.). It is OK to import these broadly.

Currencies

Common currencies supported by this component.

EUR class-attribute instance-attribute

EUR = Currency(
    alphabetic_code="EUR",
    numeric_code=978,
    minor_unit=2,
    symbol="€",
    locale_formats={
        "fr": LocaleFormat(
            decimal_separator=",",
            thousands_separator="\u202f",
            display_format="{amount}\xa0{symbol}",
            negative_sign_position="before_number",
        ),
        "nl": LocaleFormat(
            decimal_separator=",",
            thousands_separator=".",
            display_format="{symbol}\xa0{amount}",
            negative_sign_position="after_symbol",
        ),
        "en": LocaleFormat(
            decimal_separator=".",
            thousands_separator=",",
            display_format="{symbol}{amount}",
            negative_sign_position="before_symbol",
        ),
    },
)

from_alphabetic_code classmethod

from_alphabetic_code(code)

Get a Currency instance by its ISO4217 alphabetic code.

Parameters:

Name Type Description Default
code str

The ISO4217 alphabetic code (e.g., "EUR")

required

Returns:

Type Description
Currency

The Currency instance matching the code

Raises:

Type Description
ValueError

If the currency code is not supported

Examples:

>>> currency = Currencies.from_alphabetic_code("EUR")
>>> currency.alphabetic_code
'EUR'
Source code in components/core_price/public/primitives.py
@classmethod
def from_alphabetic_code(cls, code: str) -> Currency:
    """
    Get a Currency instance by its ISO4217 alphabetic code.

    Args:
        code: The ISO4217 alphabetic code (e.g., "EUR")

    Returns:
        The Currency instance matching the code

    Raises:
        ValueError: If the currency code is not supported

    Examples:
        >>> currency = Currencies.from_alphabetic_code("EUR")
        >>> currency.alphabetic_code
        'EUR'
    """
    for attr_name in dir(cls):
        if attr_name.startswith("_"):
            continue
        attr_value = getattr(cls, attr_name)
        if isinstance(attr_value, Currency) and (
            attr_value.alphabetic_code == code
        ):
            return attr_value

    supported_codes = [
        getattr(cls, attr).alphabetic_code
        for attr in dir(cls)
        if not attr.startswith("_") and isinstance(getattr(cls, attr), Currency)
    ]
    raise ValueError(
        f"Unsupported currency code: {code}. "
        f"Supported codes: {', '.join(supported_codes)}"
    )

Currency dataclass

Currency(
    *,
    alphabetic_code,
    numeric_code,
    minor_unit,
    symbol,
    locale_formats
)

Represents a currency. Attempting to comply with ISO4217.

__hash__

__hash__()

Hash the currency using its alphabetic code.

The alphabetic code (e.g., "EUR", "USD") uniquely identifies each currency according to ISO4217 standards, making it the ideal hash key.

Source code in components/core_price/public/primitives.py
def __hash__(self) -> int:
    """
    Hash the currency using its alphabetic code.

    The alphabetic code (e.g., "EUR", "USD") uniquely identifies each
    currency according to ISO4217 standards, making it the ideal hash key.
    """
    return hash(self.alphabetic_code)

alphabetic_code instance-attribute

alphabetic_code

ISO4217 compliant alpha code of the currency.

format_amount

format_amount(amount)

Format an amount in minor units as a string with currency code.

Examples:

formatted = Currencies.EUR.format_amount(1032)
print(formatted) # "10.32 EUR"
Source code in components/core_price/public/primitives.py
def format_amount(self, amount: int) -> str:
    """Format an amount in minor units as a string with currency code.

    Examples:
        ```python
        formatted = Currencies.EUR.format_amount(1032)
        print(formatted) # "10.32 EUR"
        ```
    """
    decimal_amount = self.from_minor_unit(amount)
    return f"{decimal_amount} {self.alphabetic_code}"

format_amount_with_symbol

format_amount_with_symbol(amount, locale)

Format an amount in minor units with currency symbol for display.

Trailing zeros after the decimal point are removed unless needed. The formatting respects the currency's ISO 4217 minor unit specification. Negative amounts are formatted according to locale-specific conventions.

Parameters:

Name Type Description Default
amount int

The amount in minor units (e.g., cents for EUR)

required
locale str

The locale to use for formatting ("fr", "nl", or "en")

required

Returns:

Type Description
str

Formatted string with locale-specific symbol position and

str

separators

Examples:

# French locale: symbol after with space, comma as decimal, thin
# space as thousands
formatted = Currencies.EUR.format_amount_with_symbol(12020, "fr")
print(formatted)  # "120,20 €"

formatted = Currencies.EUR.format_amount_with_symbol(10000, "fr")
print(formatted)  # "100 €"

formatted = Currencies.EUR.format_amount_with_symbol(-12020, "fr")
print(formatted)  # "-120,20 €"

# English locale: symbol before no space, dot as decimal, comma as
# thousands
formatted = Currencies.EUR.format_amount_with_symbol(12020, "en")
print(formatted)  # "€120.20"

formatted = Currencies.EUR.format_amount_with_symbol(-12020, "en")
print(formatted)  # "-€120.20"

# Dutch locale: symbol before with space, comma as decimal, dot as
# thousands
formatted = Currencies.EUR.format_amount_with_symbol(12020, "nl")
print(formatted)  # "€ 120,20"

formatted = Currencies.EUR.format_amount_with_symbol(-12020, "nl")
print(formatted)  # "€ -120,20"

formatted = Currencies.EUR.format_amount_with_symbol(
    12345678900, "fr"
)
print(formatted)  # "123 456 789 €"
Source code in components/core_price/public/primitives.py
def format_amount_with_symbol(self, amount: int, locale: str) -> str:
    """Format an amount in minor units with currency symbol for display.

    Trailing zeros after the decimal point are removed unless needed.
    The formatting respects the currency's ISO 4217 minor unit
    specification. Negative amounts are formatted according to
    locale-specific conventions.

    Args:
        amount: The amount in minor units (e.g., cents for EUR)
        locale: The locale to use for formatting ("fr", "nl", or "en")

    Returns:
        Formatted string with locale-specific symbol position and
        separators

    Examples:
        ```python
        # French locale: symbol after with space, comma as decimal, thin
        # space as thousands
        formatted = Currencies.EUR.format_amount_with_symbol(12020, "fr")
        print(formatted)  # "120,20 €"

        formatted = Currencies.EUR.format_amount_with_symbol(10000, "fr")
        print(formatted)  # "100 €"

        formatted = Currencies.EUR.format_amount_with_symbol(-12020, "fr")
        print(formatted)  # "-120,20 €"

        # English locale: symbol before no space, dot as decimal, comma as
        # thousands
        formatted = Currencies.EUR.format_amount_with_symbol(12020, "en")
        print(formatted)  # "€120.20"

        formatted = Currencies.EUR.format_amount_with_symbol(-12020, "en")
        print(formatted)  # "-€120.20"

        # Dutch locale: symbol before with space, comma as decimal, dot as
        # thousands
        formatted = Currencies.EUR.format_amount_with_symbol(12020, "nl")
        print(formatted)  # "€ 120,20"

        formatted = Currencies.EUR.format_amount_with_symbol(-12020, "nl")
        print(formatted)  # "€ -120,20"

        formatted = Currencies.EUR.format_amount_with_symbol(
            12345678900, "fr"
        )
        print(formatted)  # "123 456 789 €"
        ```
    """
    locale_format = self.locale_formats[locale]
    is_negative = amount < 0
    currency_amount = self.from_minor_unit(abs(amount))

    if int(currency_amount) == currency_amount:
        formatted_string = f"{int(currency_amount):,}"
    else:
        formatted_string = f"{currency_amount:,.{self.minor_unit}f}"

    # Use a temporary placeholder to avoid conflicts when thousands and
    # decimal separators are swapped (e.g., Dutch uses "." for thousands
    # and "," for decimals)
    number_part = (
        formatted_string.replace(",", "{{THOUSANDS}}")
        .replace(".", "{{DECIMAL}}")
        .replace("{{THOUSANDS}}", locale_format.thousands_separator)
        .replace("{{DECIMAL}}", locale_format.decimal_separator)
    )

    formatted_result = locale_format.display_format.format(
        amount=number_part, symbol=self.symbol
    )

    if is_negative:
        if locale_format.negative_sign_position == "before_symbol":
            formatted_result = f"-{formatted_result}"
        elif locale_format.negative_sign_position == "after_symbol":
            # Replace "symbol " with "symbol -" to avoid extra space
            formatted_result = formatted_result.replace(
                f"{self.symbol}\u00a0", f"{self.symbol} -", 1
            )
        elif locale_format.negative_sign_position == "before_number":
            formatted_result = formatted_result.replace(
                number_part, f"-{number_part}", 1
            )

    return formatted_result

from_minor_unit

from_minor_unit(amount)

Convert from integer representation of the currency to a decimal representation.

Examples:

decimal_representation = Currencies.EUR.from_minor_unit(1032)
print(decimal_representation) # 10.32
Source code in components/core_price/public/primitives.py
def from_minor_unit(self, amount: int) -> Decimal:
    """Convert from integer representation of the currency to a decimal
    representation.

    Examples:
        ```python
        decimal_representation = Currencies.EUR.from_minor_unit(1032)
        print(decimal_representation) # 10.32
        ```
    """
    return Decimal(amount) / Decimal(10**self.minor_unit)

locale_formats instance-attribute

locale_formats

Locale-specific formatting configuration for displaying amounts.

Examples:

  • "fr": LocaleFormat(decimal=",", thousands=" ", format="{amount} {symbol}") produces "123 456,78 €"
  • "nl": LocaleFormat(decimal=",", thousands=".", format="{symbol} {amount}") produces "€ 123.456,78"
  • "en": LocaleFormat(decimal=".", thousands=",", format="{symbol}{amount}") produces "€123,456.78"

minor_unit instance-attribute

minor_unit

The number of digits after the decimal separator to represent as the minor unit. Helps us represent amounts as integers instead of floats, for example representing euros as cents.

Certain currenceis are naturally representable as ints and have 0 digits after the decimal place.

Examples: Japanese yen, South Korean won

numeric_code instance-attribute

numeric_code

ISO4217 compliant code of the currency.

symbol instance-attribute

symbol

The currency symbol used for display (e.g., "€", "$", "£").

to_minor_unit

to_minor_unit(amount)

Convert from floating point representation of the currency to an integer representation.

Useful to avoid precision errors of large floats.

Examples:

integer_representation = Currencies.EUR.to_minor_unit(10.32)
print(integer_representation) # 1032 cents
Source code in components/core_price/public/primitives.py
def to_minor_unit(self, amount: Decimal) -> int:
    """Convert from floating point representation of the currency to an
    integer representation.

    Useful to avoid precision errors of large floats.

    Examples:
        ```python
        integer_representation = Currencies.EUR.to_minor_unit(10.32)
        print(integer_representation) # 1032 cents
        ```
    """
    return int(amount * (10**self.minor_unit))

LocaleFormat dataclass

LocaleFormat(
    *,
    decimal_separator,
    thousands_separator,
    display_format,
    negative_sign_position
)

Configuration for locale-specific number and currency formatting.

decimal_separator instance-attribute

decimal_separator

Character used as decimal separator (e.g., "." or ",")

display_format instance-attribute

display_format

Template for displaying amount with symbol. Should contain {amount} and {symbol} placeholders. Example: "{amount} {symbol}" or "{symbol}{amount}"

negative_sign_position instance-attribute

negative_sign_position

Where to place the minus sign for negative amounts.

Examples:

  • "before_symbol": -€120.20 (English)
  • "after_symbol": € -120,20 (Dutch)
  • "before_number": -120,20 € (French)

thousands_separator instance-attribute

thousands_separator

Character used as thousands separator (e.g., ",", ".", or " ")

RoundingStrategy

Bases: AlanBaseEnum

Strategy for rounding monetary amounts.

ARITHMETIC class-attribute instance-attribute

ARITHMETIC = 'arithmetic'

Round half up (0.5 -> 1). Traditional rounding method.

BANKERS class-attribute instance-attribute

BANKERS = 'bankers'

Round half to even (0.5 -> 0, 1.5 -> 2). Reduces systematic bias.