Api reference
components.premium.public.api ¶
PremiumAggregationService ¶
A service used to interact with subscription fees (AKA premiums in the context of insurance).
Fees are created to represent the consumption of Alan products. Many services throughout the stack need to consume fees to function: - invoicing aggregates fees into invoices - payroll reports the structure of the fees to give clarity to admins - data monitors general financial performance levels
This service is meant to become the one-stop-shop for all these consumers.
Additionally, the way we manage fees is evolving. Previously, each country & product had it's own engine for computing fees, and format to store them. We are moving toward a global solution: same engine and same format across all countries and products. However this is a still a work in progress, and this service also aims to protect consumers from having to deal with different formats. All functions are meant to function the same no matter which engine was used to compute the fees and how they were stored.
get_premium_entries_for_invoice
staticmethod
¶
Retrieve premium entries for a given invoice. If invoice has no components, then it retrieves legacy premium entries, and transforms them to PremiumEntry objects with components.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
invoice_id
|
UUID
|
The ID of the invoice to retrieve premium entries for. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of PremiumEntry objects for the invoice. |
Source code in components/premium/public/api.py
get_premiums_by_payroll_csv_id
staticmethod
¶
Retrieve all premium entries stamped with a given payroll CSV ID.
Tries the global repository first. If no entries are found there, falls back to the legacy repository.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
SQLAlchemy session injected by @transactional decorator. |
required |
payroll_csv_id
|
UUID
|
The payroll CSV ID to filter on. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of PremiumEntry objects linked to the payroll CSV. |
Source code in components/premium/public/api.py
get_premiums_for_local_payroll
staticmethod
¶
Retrieve all premium entries to be included in a local payroll CSV where
- payroll_csv_id IS NULL (global repo) or pay_csv_id IS NULL (legacy repo)
- period_start <= target_month
In practice the aggregation service is supposed to shield consumers from the 'source' of premiums. However in practice this function does not require that complexity, considering this is a temporary bridge between local & global callers know what they need.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
SQLAlchemy session injected by @transactional decorator. |
required |
enrollment_ids
|
list[UUID]
|
UUIDs of enrollments to retrieve entries for. |
required |
target_month
|
date
|
Only entries with period_start <= last day of this month are returned. |
required |
force_premium_source
|
Literal['legacy', 'global']
|
Which SOT to use when fetching premiums |
required |
Source code in components/premium/public/api.py
get_premiums_for_payroll
staticmethod
¶
get_premiums_for_payroll(
session,
*,
enrollment_ids,
target_period,
created_at_lower_bound,
created_at_upper_bound
)
Retrieve all entries where
- at least some part of the total amount is meant to be billed to the company.
- entries for the current period (targeted by
target_period) - regularisation entries for previous periods created within the
specified
created_at_lower_bound/created_at_upper_boundwindow
To shield callers from the details of how premiums are stored, this function reads from the global repository first and falls back to the legacy repository only when the global repository returns nothing. As soon as the global repository has any entry, its result is returned as-is and the legacy repository is not queried.
Considering this function is meant for the usage of payroll batch jobs, it has been designed with a certain amount of care when it comes to performance: - the read happens on a read-replica - we guarantee a set number of queries no matter how many enrollment IDs are passed (no N+1) - the process of reading 1,000 entries consumes around 19 MB of memory at peak - once all transient objects have been released by the garbage collector and only the dataclass instances remain, 1,000 entries weigh around 7 MB - you can find methodology and instructions to reproduce the measurements in the component's README
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
SQLAlchemy session injected by @transactional decorator. |
required |
enrollment_ids
|
list[int | UUID]
|
IDs of enrollments to retrieve entries for. |
required |
target_period
|
date
|
First day of the period being processed. |
required |
created_at_lower_bound
|
date
|
Lower bound (exclusive) on |
required |
created_at_upper_bound
|
date
|
Upper bound (inclusive) on |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of complete PremiumEntry objects. |
Source code in components/premium/public/api.py
get_uninvoiced_premiums
staticmethod
¶
get_uninvoiced_premiums(
*,
policy: Policy,
up_to: date,
force_premium_source: Literal["legacy", "global"],
debtor: InsuranceDebtor | None = None
) -> list[PremiumEntry]
get_uninvoiced_premiums(
*,
contract: Contract,
up_to: date,
force_premium_source: Literal["legacy", "global"],
debtor: InsuranceDebtor | None = None
) -> list[PremiumEntry]
get_uninvoiced_premiums(
*,
policy=None,
contract=None,
enrollment_ids=None,
enrollment_group_ids=None,
up_to,
force_premium_source,
debtor=None
)
Retrieve premium entries with uninvoiced components, checking specifically if the company or primary part is invoiced.
Returns complete premium entries where at least one component has not been invoiced yet. The debtor is inferred from the contract type: - Policy: primary policy holder - Contract → company - you can override this mechanism by passing a debtor type directly
When passing enrollment_ids or enrollment_group_ids, debtor is required.
Exposing force_premium_source partially defeats the purpose of a strangler pattern —
the caller should not need to know which engine produced the premiums.
However, auto-selecting the source is not yet safe: some enrollments have uninvoiced premiums in engine A but no entries in engine B (due to unrelated data issues). Merging both sources transparently would risk double-invoicing or including stale entries.
Until the data is cleaned up, having the caller declare the source explicitly is the safest option. The parameter is intentionally easy to remove once the migration is complete.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy
|
Policy | None
|
Policy to retrieve premiums for (mutually exclusive with contract/enrollment_ids/enrollment_group_ids). |
None
|
contract
|
Contract | None
|
Contract to retrieve premiums for (mutually exclusive with policy/enrollment_ids/enrollment_group_ids). |
None
|
enrollment_ids
|
list[int | UUID] | None
|
Legacy enrollment IDs. Requires force_premium_source="legacy" and debtor. |
None
|
enrollment_group_ids
|
list[UUID] | None
|
Core Stack enrollment group IDs. Requires force_premium_source="global" and debtor. |
None
|
up_to
|
date
|
Only returns entries where period_end <= up_to. |
required |
force_premium_source
|
Literal['legacy', 'global']
|
Which repository to read from ("legacy" or "global"). |
required |
debtor
|
InsuranceDebtor | None
|
Override for debtor resolution. When None, inferred from contract type. |
None
|
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of complete PremiumEntry objects with uninvoiced components. |
Source code in components/premium/public/api.py
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | |
mark_entries_for_payroll
staticmethod
¶
Mark premium entries as included in a payroll CSV or finiquito.
Exactly one of payroll_csv_id or finiquito_id must be provided.
A finiquito is a Spain-specific pay_csv generated to recap the premiums
of an employee when they leave the company. The finiquito_id path is
Spain-specific - Belgium repositories will raise NotImplementedError.
Tries the global repository first. If no entries are found there, falls back to the legacy repository. (Spain legacy written by caller so this is just mirror with no legacy fallback.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
Database session. |
required |
premium_entries
|
list[PremiumEntry]
|
Premium entries to mark. |
required |
payroll_csv_id
|
UUID | None
|
The payroll CSV ID to set on matching entries. |
None
|
finiquito_id
|
UUID | None
|
The finiquito ID to set on matching entries (Spain only). |
None
|
Source code in components/premium/public/api.py
mark_premiums_as_invoiced
staticmethod
¶
Mark premium entries as invoiced by setting invoice_id on matching components. (Right now, Spain only writes to global mirror with no legacy fallback.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
Database session. |
required |
premium_entries
|
list[PremiumEntry]
|
List of premium entries to mark as invoiced. |
required |
invoice_id
|
UUID
|
The invoice ID to set on matching components. |
required |
debtor
|
InsuranceDebtor
|
The billed entity whose components should be marked as invoiced. |
required |
Source code in components/premium/public/api.py
PremiumComputationService ¶
Compute premiums.
Not responsible for storing the computed premiums
compute_insurance_premiums
staticmethod
¶
compute_insurance_premiums(
session,
*,
policy,
contract,
start_month,
end_month,
engine_parameters=None
)
Compute how much money is owed to Alan and persist the results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
SQLAlchemy session injected by @transactional decorator |
required |
policy
|
Policy
|
The insurance policy containing enrollments |
required |
contract
|
Contract
|
The contract defining pricing terms |
required |
start_month
|
Month
|
First month to calculate premiums for |
required |
end_month
|
Month
|
Last month to include in premium calculations |
required |
engine_parameters
|
EngineParameters | None
|
Optional engine configuration. If not provided, uses defaults for current application |
None
|
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
All fresh entries returned by the computation. |
Source code in components/premium/public/api.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
compute_insurance_premiums_for_core_stack
staticmethod
¶
compute_insurance_premiums_for_core_stack(
session,
enrollment_group_id,
start_month,
end_month,
engine_parameters=None,
)
Core Stack premium computations, ES only for now.
Source code in components/premium/public/api.py
components.premium.public.commands ¶
components.premium.public.cost_types ¶
Insurance cost types for the premium component.
This module contains premium's own definitions of insurance domain types, decoupling it from price_provider component implementations. Changes to price_provider's internal pricing logic should not require changes to premium's domain model.
Currency and rounding strategy are intentionally NOT duplicated - import them
from components.core_price.public.primitives.
InsuranceBeneficiary ¶
Bases: AlanBaseEnum
Represents the different types of insurance beneficiaries.
from_enrollment_type
classmethod
¶
Convert EnrollmentType to InsuranceBeneficiary.
Source code in components/premium/public/cost_types.py
to_enrollment_type ¶
Convert InsuranceBeneficiary to EnrollmentType.
Source code in components/premium/public/cost_types.py
InsuranceCollectionMethod ¶
Bases: AlanBaseEnum
Methods for collecting an employee's participation.
direct_billing
class-attribute
instance-attribute
¶
The amount is paid directly by the employee or their partner, using the payment method of their choice.
flexben_fund
class-attribute
instance-attribute
¶
The amount is paid from the employee's flexben fund.
payroll
class-attribute
instance-attribute
¶
The amount is paid by the company but automatically deducted from the employee's payslip.
InsuranceContribution ¶
Bases: AlanBaseEnum
Represents how an amount is used, what the money is going towards.
InsuranceCost
dataclass
¶
Represents the entire cost of an insurance service.
No safeties on cost components
There are no checks verifying there are no duplicates in components. The caller is responsible for ensuring the components represent a valid cost breakdown.
Source code in components/premium/public/cost_types.py
__setattr__ ¶
Setting attributes once the dataclass is initialized is forbidden. Reproducing the behavior of frozen=True.
Source code in components/premium/public/cost_types.py
from_components
staticmethod
¶
Construct an InsuranceCost from several cost components.
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
if the debtor is not implemented yet |
ValueError
|
if the currency used by all the components is not the same |
Source code in components/premium/public/cost_types.py
merge
staticmethod
¶
Merge several costs into the same cost objects.
When we evaluate the costs of coverage modules individually, merging them is useful to get the total aggregated costs.
Source code in components/premium/public/cost_types.py
taxed_amount_billed_to_company
instance-attribute
¶
taxed_amount_billed_to_primary
instance-attribute
¶
untaxed_amount_billed_to_company
instance-attribute
¶
untaxed_amount_billed_to_primary
instance-attribute
¶
InsuranceCostComponent
dataclass
¶
InsuranceCostComponent(
*,
service_type,
module_id=None,
contribution_type,
beneficiary_type,
debtor,
collection_method=None,
enrollment_id=None,
enrollment_group_id=None,
member_id=None,
currency,
amount,
periodicity="monthly"
)
Represents part of a cost of insurance service.
__post_init__ ¶
Validate collection_method when debtor != company, and identity shape consistency (legacy XOR Core Stack, all-None also valid).
Source code in components/premium/public/cost_types.py
amount
instance-attribute
¶
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
¶
Who's coverage does this cost correspond to ?
collection_method
class-attribute
instance-attribute
¶
If an employee is responsible for paying this cost, how is the payment collected ?
compare_component_lists
staticmethod
¶
Compare two lists of insurance cost components for equality.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
list[InsuranceCostComponent]
|
First list of components to compare |
required |
right
|
list[InsuranceCostComponent]
|
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/premium/public/cost_types.py
contribution_type
instance-attribute
¶
What part of the service is paid for by this cost?
enrollment_group_id
class-attribute
instance-attribute
¶
Core Stack enrollment group id.
enrollment_id
class-attribute
instance-attribute
¶
Legacy country-specific enrollment id (e.g., EsEnrollment.id).
group_by_beneficiary_id
staticmethod
¶
Groups insurance cost components by enrollment ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
components
|
list[InsuranceCostComponent]
|
List of InsuranceCostComponent to group |
required |
Returns:
| Type | Description |
|---|---|
dict[int | UUID, list[InsuranceCostComponent]]
|
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/premium/public/cost_types.py
member_id
class-attribute
instance-attribute
¶
Core Stack member id (stringified user_id, per Core Stack convention). Set together with enrollment_group_id.
module_id
class-attribute
instance-attribute
¶
Contract module the cost belongs to.
service_type
instance-attribute
¶
Tag identifying what this cost is for. ES values describe bundle
elements (pharmacy, optical, ...); FR/BE/CA values describe
coverage roles (base, option, ...).
InsuranceDebtor ¶
Bases: AlanBaseEnum
Represents an entity paying for an insurance service.
primary covers both the primary member and partner — pricing always
bills the primary, never the partner directly.
InsuranceService ¶
Bases: AlanBaseEnum
Tag identifying what a cost is for, on a fee component.
extended
class-attribute
instance-attribute
¶
Base + option rolled into the same coverage module.
hospitalization
class-attribute
instance-attribute
¶
Neutral label for single-coverage plans (no base/extended distinction).
preexisting_conditions
class-attribute
instance-attribute
¶
premium_reimbursement
class-attribute
instance-attribute
¶
components.premium.public.dependencies ¶
COMPONENT_NAME
module-attribute
¶
Canonical name of the premium component.
PremiumDependency ¶
Bases: ABC
Represents all the dependency required by components/premium to function as
expected.
get_flexben_timeline_for_all_enrollments_of_the_policy
abstractmethod
¶
For each enrollment in the policy, return a list of periods where a flexben choice is enabled.
Considering flexben is a feature local to Belgium, we expect components/be to
be the only caller to actually inject this dependency.
Other components are expected to inject a function returning an empty dict.
Example
{ enrollment_id_1: [ ValidityPeriod(date(2024, 1, 1), date(2024, 6, 30)), ], enrollment_id_2: [ ValidityPeriod(date(2024, 3, 1), date(2024, 12, 31)), ], }
Source code in components/premium/public/dependencies.py
get_legacy_premium_entry_repository ¶
Instantiate layer to retrieve premium entries based on legacy ones
get_premium_repository
abstractmethod
¶
to_member_spec ¶
Convert a BeneficiarySpec to a MemberSpec for core_price pricing.
Default implementation delegates to CorePriceAdapter. Override in country-specific subclasses when the default conversion is not suitable (e.g. CA, which uses CaMemberSpec instead of CorePriceBeneficiarySpec).
Source code in components/premium/public/dependencies.py
get_app_dependency ¶
Function used to fetch the dependencies from the flask app.
Source code in components/premium/public/dependencies.py
set_app_dependency ¶
Function used to actually inject the dependency class in the component.
Source code in components/premium/public/dependencies.py
components.premium.public.entities ¶
AgeStrategy ¶
Bases: AlanBaseEnum
Strategy for handling age calculation in insurance premium calculations.
The effective birthday or age we use for pricing a beneficiary may differ from their actual birthday or age.
KEEP_EXACT_BIRTHDAY
class-attribute
instance-attribute
¶
Use the exact birthday for age calculation.
Example: If a member turns 23 on March 15, 2023, their age changes exactly on March 15, 2023.
MOVE_BIRTHDAY_TO_FIRST_DAY_OF_MONTH
class-attribute
instance-attribute
¶
Move birthday to the first day of the birth month for age calculation.
Example: If a member turns 23 on March 15, 2023, they will be priced as 23 years old starting March 1, 2023 for the entire month.
MOVE_BIRTHDAY_TO_JAN_OF_NEXT_YEAR
class-attribute
instance-attribute
¶
Move birthday to January 1st of the year following the birth year.
Example: If a member turns 23 on March 15, 2023, they will be priced as 22 years old for the entirety of 2023, and 23 years old starting January 1, 2024.
BeneficiarySpec
dataclass
¶
BeneficiarySpec(
*,
enrollment_id,
beneficiary_type,
beneficiary_age=None,
beneficiary_date_of_birth=None,
coverage_module_name=None,
has_active_flexben_choice=False,
contract_version_id=None,
province_code=None
)
Specification for a beneficiary used in premium calculations.
Contains all the information needed to compute insurance costs for a specific beneficiary within a stable timeline period. Age information is provided either as a computed age or birth date.
Design Decision: Single Module for Billing Domain
This component represents the billing domain, not the pricing domain. For billing purposes, we only need to know which single module to bill for a beneficiary, not all modules they might be subscribed to.
- Currently, only Belgium uses this component, and BE supports exactly one module per beneficiary
- Other countries (FR, ES, CA) can iterate on this design when they adopt the premium component
- The adapter converts from price_provider's
moduleslist by takingmodules[0]
If multiple modules per beneficiary are needed in the future, this design can be extended. For now, YAGNI (You Aren't Gonna Need It) applies.
Attributes:
| Name | Type | Description |
|---|---|---|
enrollment_id |
int | UUID | None
|
Unique identifier for the enrollment this beneficiary belongs to |
beneficiary_type |
InsuranceBeneficiary
|
Type of insurance beneficiary (employee, spouse, child, etc.) |
beneficiary_age |
int | None
|
Computed age at the time of the period (may be None if birth_date used) |
beneficiary_date_of_birth |
date | None
|
Actual birth date (may be None if age is pre-computed) |
coverage_module_name |
str | None
|
Specific coverage module for this beneficiary (optional) |
contract_version_id |
UUID | None
|
Contract version for legacy pricing function |
province_code |
str | None
|
ISO 3166-2 subdivision without country prefix (e.g. "ON", "QC"). Used by products where tax rates or pricing depend on province of residence. |
__post_init__ ¶
Source code in components/premium/internal/domain/insurance_entities.py
CoreStackIdentity ¶
CostPeriod
dataclass
¶
Bases: GenericPeriodWithStartReason
Represents a timeline period with computed premium cost.
Final output of the premium calculation pipeline, containing the actual cost for the stable period. Each period guarantees the cost remains constant throughout its validity period.
Attributes:
| Name | Type | Description |
|---|---|---|
cost |
Cost
|
The computed insurance cost for this period, calculated based on the stable beneficiary composition and contract terms. |
EngineParameters
dataclass
¶
EngineParameters(
*,
age_strategy,
rounding_strategy,
prorata_strategy,
default_child_age,
default_adult_age
)
Parameters dictating how the engine behaves and which rules it follows.
Example
- which effective birth date to use
- which prorata formula to use
- which rounding strategy to use
default
staticmethod
¶
Get default engine parameters for a given application. Application is used as a proxy for country.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
The application name to get default parameters for |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the application is not yet supported |
Source code in components/premium/internal/domain/engine_parameters.py
default_adult_age
instance-attribute
¶
Optional strategy for normalizing ages into specific buckets for pricing. When None, ages are used directly from beneficiary birthdates or defaults. When specified, the strategy determines how to group beneficiaries by age.
FeeComponent
dataclass
¶
Bases: CostComponent
A fee component represents one part of a fee that we intend to bill to a customer.
__eq__ ¶
Source code in components/premium/internal/domain/fee.py
__hash__ ¶
Source code in components/premium/internal/domain/fee.py
amount_before_prorata
instance-attribute
¶
Amount for full period coverage without prorata applied. Expressed in the minor unit of the currency. This is the full monthly amount that would be charged for complete period coverage.
as_tuple ¶
Return (debtor, contribution_type, amount)
id
class-attribute
instance-attribute
¶
Unique identifier of the entry
inverted ¶
Returns a new FeeComponent offsetting the current one.
Source code in components/premium/internal/domain/fee.py
invoice_id
instance-attribute
¶
If the component is included in an invoice, it is represented here.
LegacyBelgianNormalization
dataclass
¶
Groups beneficiaries into specific age buckets: - Children are treated as adults if they are 25 or above - Young adults (18-25) are priced as 25 or above if they are the primary policy holder or their partner - No birthdate available: default to age 25 - Anyone not falling in one of the buckets above: use their actual age
normalize_age ¶
Source code in components/premium/internal/domain/age_normalization.py
ParticipantIdentity
module-attribute
¶
A PremiumEntry's identity is one of two shapes: - CoreStackIdentity: the full (group, member, module) triple - UUID: a legacy country-specific enrollment id The discriminated union enforces "exactly one shape" statically. Readers isinstance/match-discriminate before accessing the parts.
Policy ¶
PremiumEntry
dataclass
¶
PremiumEntry(
*,
id=uuid4(),
participant_identity,
components,
period_start,
period_end,
version=1,
cancelled_by_entry_id=None,
cancelled_entry_id=None,
payroll_csv_id=None,
finiquito_id=None,
original_ids=()
)
A subscription fee specific to insurance subscriptions.
__eq__ ¶
Source code in components/premium/internal/domain/fee.py
__hash__ ¶
__post_init__ ¶
cancelled ¶
Returns two new entries: one marking this entry as cancelled, and one offsetting it.
Returns:
| Type | Description |
|---|---|
tuple[PremiumEntry, PremiumEntry | None]
|
tuple[PremiumEntry, PremiumEntry]: - First entry: Same as self but marked as cancelled - Second entry: New entry to offset self. |
Source code in components/premium/internal/domain/fee.py
cancelled_by_entry_id
class-attribute
instance-attribute
¶
components_billed_to_debtor ¶
Returns components that are actually billed to the specified debtor.
This method accounts for collection methods: - If debtor is company: includes company components AND primary components with (payroll or flexben_fund) - If debtor is primary: only includes primary components with direct_billing
Source code in components/premium/internal/domain/fee.py
components_for_debtor ¶
The debtor is not necessarily the entity being billed
If a component's debtor is the primary but the collection method is payroll, the company gets billed.
If your purpose is to list components that should be invoiced to a given debtor,
use components_billed_to_debtor().
Source code in components/premium/internal/domain/fee.py
enrollment_id
property
¶
Legacy enrollment id when the PE has a legacy-shape identity, else
None. Convenience for legacy-only consumers — Core-Stack-aware code
should pattern-match on participant_identity directly.
finiquito_id
class-attribute
instance-attribute
¶
Spain-specific. A finiquito is a pay_csv generated to recap the premiums of an employee when they leave the company. In order to maintain compatibility between Spain payroll and Global premiums we need to maintain this property. However it's not required by the Global payroll system, therefore this property is temporary and will be dropped as soon as global payroll is online.
Always null in Belgium.
is_uninvoiced ¶
Returns True if none of the relevant components have been invoiced.
If debtor is None, checks all components. If debtor is provided, checks only components billed to that specific debtor.
Source code in components/premium/internal/domain/fee.py
original_ids
class-attribute
instance-attribute
¶
Provenance of this PremiumEntry: the legacy country-specific premium-entry rows
(e.g. EsPremiumEntry.ids) that produced this entry via the legacy-to-global
mapper. Empty for PEs that did not come from a legacy mapper (e.g. fresh PEs
produced by compute_subscription_fees from a pricing breakdown).
Used by callers that need to write something back to the legacy side after they
finished working with the global-shape PremiumEntry (notably the M3 invoice
generator, which builds an EsInvoice from PE and then needs the legacy
EsPremiumEntry rows to set invoice.premium_entries via the SQLAlchemy
backref).
Migration-transient: removed at M5 when legacy storage goes away.
participant_identity
instance-attribute
¶
The PE's billable subject. CoreStackIdentity (group, member, module) for Core-Stack-native PEs, or a legacy country-specific enrollment id.
payroll_csv_id
class-attribute
instance-attribute
¶
Belgium & Spain's local payroll systems requires to keep track of which premiums are included in which file.
In order to maintain compatibility between Belgium payroll and Global premiums we need to maintain this property. However it's not required by the Global payroll system, therefore this property is temporary and will be dropped as soon as global payroll is online.
pretax_amount_billed_to_debtor ¶
Returns the total pretax amount (membership fees + costs) billed to the specified debtor.
This includes components with contribution_type of: - InsuranceContribution.membership_fee: Base membership fees - InsuranceContribution.cost: Insurance coverage costs
This method accounts for collection methods: - If debtor is company: includes company components AND primary components with (payroll or flexben_fund) - If debtor is primary: only includes primary components with direct_billing
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
debtor
|
InsuranceDebtor
|
The entity to which costs are billed |
required |
Returns:
| Type | Description |
|---|---|
int
|
Total pretax amount in minor currency units |
Source code in components/premium/internal/domain/fee.py
prorata_ratio ¶
Returns the prorata ratio for this premium entry.
The ratio is calculated as: - 1.0 (or -1.0 for negative days) if the premium covers the full month - num_days / 30 otherwise (using 30-day convention, rounded to 2 decimals)
Uses arithmetic rounding (ROUND_HALF_UP) for consistency with invoicing display.
Returns:
| Type | Description |
|---|---|
float
|
Prorata ratio as a float. Positive for regular premiums, negative for credits/reversals. |
Source code in components/premium/internal/domain/fee.py
taxes_billed_to_debtor ¶
Returns the total tax amount billed to the specified debtor.
This includes components with contribution_type of: - InsuranceContribution.taxes: Tax components
This method accounts for collection methods: - If debtor is company: includes company components AND primary components with (payroll or flexben_fund) - If debtor is primary: only includes primary components with direct_billing
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
debtor
|
InsuranceDebtor
|
The entity to which taxes are billed |
required |
Returns:
| Type | Description |
|---|---|
int
|
Total tax amount in minor currency units |
Source code in components/premium/internal/domain/fee.py
total_amount ¶
total_amount_billed_to_debtor ¶
Returns the total amount for components actually billed to the specified debtor.
This method accounts for collection methods: - If debtor is company: includes company components AND primary components with (payroll or flexben_fund) - If debtor is primary: only includes primary components with direct_billing
Source code in components/premium/internal/domain/fee.py
total_amount_for_debtor ¶
The debtor is not necessarily the entity being billed
If a component's debtor is the primary but the collection method is payroll, the company gets billed.
If your purpose is to compute the total amount that should be invoiced to a given debtor,
use total_amount_billed_to_debtor().
Source code in components/premium/internal/domain/fee.py
total_billed_to_debtor_without_prorata ¶
Returns the total amount before prorata for components actually billed to the specified debtor.
This method accounts for collection methods: - If debtor is company: includes company components AND primary components with (payroll or flexben_fund) - If debtor is primary: only includes primary components with direct_billing
Source code in components/premium/internal/domain/fee.py
total_for_debtor_without_prorata ¶
The debtor is not necessarily the entity being billed
If a component's debtor is the primary but the collection method is payroll, the company gets billed.
If your purpose is to compute the total amount without prorata that should be invoiced to a given debtor,
use total_billed_to_debtor_without_prorata().
Source code in components/premium/internal/domain/fee.py
with_components_marked_as_invoiced ¶
Returns a new PremiumEntry with components for the specified debtor marked as invoiced.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
invoice_id
|
UUID
|
The invoice ID to set on matching components. |
required |
debtor
|
InsuranceDebtor
|
The billed entity whose components should be marked as invoiced. |
required |
Returns:
| Type | Description |
|---|---|
PremiumEntry
|
New PremiumEntry with updated components. |
Source code in components/premium/internal/domain/fee.py
with_finiquito_id ¶
Returns a new PremiumEntry with finiquito_id set. Spain-specific.
Source code in components/premium/internal/domain/fee.py
with_payroll_csv_id ¶
Returns a new PremiumEntry with payroll_csv_id set.
Source code in components/premium/internal/domain/fee.py
with_version ¶
Returns a new PremiumEntry with a different version.
Source code in components/premium/internal/domain/fee.py
PremiumEntryRepository ¶
Bases: Protocol
Repository protocol for persisting and retrieving premium entries.
get_all_entries ¶
Retrieve all premium entries for specified enrollments with an optional period.
Returns all stored premium entries including both cancelled and active entries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enrollment_ids
|
list[int | UUID] | None
|
IDs (int or UUID) of enrollments to retrieve entries for. Some implementations may only support UUID IDs and will raise an error if int IDs are provided. Mutually exclusive with participants. |
None
|
participants
|
list[CoreStackIdentity] | None
|
Core Stack identities. Mutually exclusive with enrollment_ids. Some implementations may only support this form. |
None
|
period_start
|
Month | date | None
|
Optional lower bound for the entry period. If provided, only returns entries where entry.period_start == period_start. If Month is passed, use the first day of the month. If None, there is no filter on the start date of the period. |
required |
period_end
|
Month | date | None
|
Optional upper bound for the entry period. If provided, only returns entries where entry.period_end == period_end. If Month is passed, use the last day of the month. If None, there is no filter on the end date of the period. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of PremiumEntry objects matching the filtering criteria. |
list[PremiumEntry]
|
If both date parameters are None, returns all entries for the enrollments. |
Source code in components/premium/internal/domain/repository.py
get_all_uninvoiced_entries_up_to ¶
Retrieve premium entries that have uninvoiced components for a specific debtor.
An entry is included if it has at least one component where the debtor has not been invoiced (invoice_id IS NULL). The returned entries are complete - they contain all components regardless of their invoice status or debtor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enrollment_ids
|
list[int | UUID] | None
|
IDs (int or UUID) of enrollments to retrieve entries for. Some implementations may only support UUID IDs and will raise an error if int IDs are provided. Mutually exclusive with participants. |
None
|
participants
|
list[CoreStackIdentity] | None
|
Core Stack identities. Mutually exclusive with enrollment_ids. Some implementations may only support this form. |
None
|
debtor
|
InsuranceDebtor
|
The billed entity (primary, company, etc.) to check for uninvoiced components. |
required |
up_to
|
date
|
Only returns entries where period_end <= up_to. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of complete PremiumEntry objects. |
Source code in components/premium/internal/domain/repository.py
get_entries_by_payroll_csv_id ¶
Retrieve all premium entries stamped with a given payroll CSV ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payroll_csv_id
|
UUID
|
The payroll CSV ID to filter on. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of PremiumEntry objects linked to the payroll CSV. |
Source code in components/premium/internal/domain/repository.py
get_entries_for_local_payroll ¶
Retrieve all entries where payroll_csv_id IS NULL and period_start <= target_month.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enrollment_ids
|
list[UUID] | None
|
IDs of enrollments to retrieve entries for. Mutually exclusive with participants. |
None
|
participants
|
list[CoreStackIdentity] | None
|
Core Stack identities. Mutually exclusive with enrollment_ids. Some implementations may only support this form. |
None
|
target_month
|
date
|
Only entries with period_start on or before this date are returned. |
required |
Source code in components/premium/internal/domain/repository.py
get_entries_for_payroll ¶
get_entries_for_payroll(
*,
enrollment_ids=None,
participants=None,
target_period,
created_at_lower_bound,
created_at_upper_bound
)
Retrieve premium entries relevant for a payroll run.
- entries for the current period (identified by target_period) and create before
created_atupper bound - regularisation entries for previous periods created between created at lower and upper bound
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enrollment_ids
|
list[int | UUID] | None
|
IDs (int or UUID) of enrollments to retrieve entries for. Mutually exclusive with participants. |
None
|
participants
|
list[CoreStackIdentity] | None
|
Core Stack identities. Mutually exclusive with enrollment_ids. Some implementations may only support this form. |
None
|
target_period
|
date
|
First day of the period being processed. |
required |
created_at_lower_bound
|
date
|
Lower bound (exclusive) on |
required |
created_at_upper_bound
|
date
|
Upper bound (inclusive) on |
required |
Source code in components/premium/internal/domain/repository.py
get_latest_entries ¶
Retrieve the latest premium entries for specified enrollments for all periods contained within lower_bound and upper_bound.
Lower bound and upper bound may cover more than one period
The caller is free to pass any range they would like. We return one premium entry, per enrollment, per period in the range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enrollment_ids
|
list[int | UUID] | None
|
IDs (int or UUID) of enrollments to retrieve entries for. Some implementations may only support UUID IDs and will raise an error if int IDs are provided. Mutually exclusive with participants. |
None
|
participants
|
list[CoreStackIdentity] | None
|
Core Stack identities. Mutually exclusive with enrollment_ids. Some implementations may only support this form. |
None
|
lower_bound
|
Month | date
|
Range start. If Month is passed, use the first day of the month. |
required |
upper_bound
|
Month | date
|
Range end. If Month is passed, use the last day of the month. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
One premium entry per enrollment per period in the range. |
list[PremiumEntry]
|
For each period, the premium returned is the latest as defined by the |
list[PremiumEntry]
|
version. |
Source code in components/premium/internal/domain/repository.py
get_premium_entries_for_invoice ¶
Retrieve premium entries for a specific invoice. If invoice has no components, then it returns an empty list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
invoice_id
|
HybridId
|
The ID of the invoice to retrieve premium entries for. |
required |
Returns:
| Type | Description |
|---|---|
list[PremiumEntry]
|
List of PremiumEntry objects for the invoice. |
Source code in components/premium/internal/domain/repository.py
insert ¶
Insert new premium entries into the database.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entries
|
list[PremiumEntry]
|
List of PremiumEntry objects to insert. Each entry's components will be added to the database. |
required |
Source code in components/premium/internal/domain/repository.py
update ¶
Persist mutable fields of existing premium entries.
Only the following fields are updated — all others are immutable after insert:
- cancelled_entry_id
- cancelled_by_entry_id
- invoice_id
- payroll_csv_id
- finiquito_id (Spain only — always null in Belgium)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entries
|
list[PremiumEntry]
|
List of PremiumEntry objects to update. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Number of rows updated. Returns > 0 if at least one component was updated. |
Source code in components/premium/internal/domain/repository.py
ProrataStrategy ¶
Bases: AlanBaseEnum
Strategy for applying prorata to cost components.
When multiple cost components need prorating (e.g., base cost, membership fee, taxes), different strategies can be used that may produce slightly different results due to rounding.
THIRTY_DAY_PRORATA
class-attribute
instance-attribute
¶
If the service covers the whole month, bill the full amount for 1 month. A month is a full month if the number of days covered equals the number of days in the month. If the service does not cover the whole month, prorate on the base of 30 days.
Formula: monthly_price * days_covered / 30
This prorata is applied to all the components of the subscription fee and may carry rounding errors, meaning the total of components may exceed the initial total.
THIRTY_DAY_PRORATA_WITH_LARGEST_REMAINDER_DISTRIBUTION_ACROSS_FEE_COMPONENTS
class-attribute
instance-attribute
¶
THIRTY_DAY_PRORATA_WITH_LARGEST_REMAINDER_DISTRIBUTION_ACROSS_FEE_COMPONENTS = "30_day_prorata_with_largest_remainder_distribution_across_fee_components"
Similar to THIRTY_DAY_PRORATA except rounding errors are avoided by using the largest remainder algorithm to distribute the last few cents across the component.
There is a risk of losing accuracy, for example attributing more to the membership fee than we should. But we have the guarantee the total of the components will never exceed the initial total and the inaccuracies should never exceed 1 cent.
components.premium.public.models ¶
helper ¶
prefix_table_args ¶
Return a copy of a __table_args__ tuple with all constraint/index names prefixed.
SQLAlchemy tracks named constraints and indexes in a global MetaData registry keyed
by name alone, not by schema. When two concrete subclasses of the same abstract model
share a __tablename__ and are loaded into the same process, SQLAlchemy raises
ArgumentError on duplicate constraint names. Prefixing each name (e.g. "es_")
makes them unique across subclasses.
Each Index/UniqueConstraint/CheckConstraint is reconstructed (not shallow-copied).
A shallow copy would keep a reference to the same internal name object, so mutating
the name to add the prefix would silently mutate the original constraint on the abstract
model — making the prefix appear to work while actually renaming the source, not creating
a distinct object.
Additionally, copy.copy on an Index retains the parent abstract
model's table binding, which causes SQLAlchemy to raise:
"Index X is against table T, and cannot be associated with table T" when attaching it to the concrete subclass's Table. Plain dicts and unnamed entries pass through unchanged.
Only Index, UniqueConstraint, and CheckConstraint entries are prefixed.
Any other SchemaItem subtype is passed through as-is without modification.
Source code in components/premium/public/models/helper.py
premium_component ¶
PremiumComponentModel ¶
Bases: BaseModel
Abstract base for per-country, per-product premium component tables. Each product is expected to have a subclass in their own schema.
See components/premium/README.md for how to add a new country table.
__table_args__
class-attribute
instance-attribute
¶
__table_args__ = (
UniqueConstraint(
"enrollment_id",
"enrollment_group_id",
"member_id",
"premium_entry_id",
"coverage_type",
"module_id",
"beneficiary_type",
"debtor_type",
"period_start",
"period_end",
"contribution_type",
"version",
name="one_component_across_all_dimensions_per_version",
postgresql_nulls_not_distinct=True,
),
CheckConstraint(
"(enrollment_id IS NOT NULL)::int + ((enrollment_group_id IS NOT NULL AND member_id IS NOT NULL AND module_id IS NOT NULL))::int = 1",
name="exactly_one_identity_shape",
),
Index(
"ix_enrollment_id_version",
"enrollment_id",
"version",
),
Index(
"ix_enrollment_group_member_version",
"enrollment_group_id",
"member_id",
"version",
),
Index(
"ix_period_start_end", "period_start", "period_end"
),
)
amount
class-attribute
instance-attribute
¶
Amount for num_days of coverage from period_start to period_end. Expressed in the minor unit of the currency.
amount_before_prorata
class-attribute
instance-attribute
¶
Amount for full coverage from period_start to period_end. Expressed in the minor unit of the currency. This is the amount that would be billed if we decided not to apply prorata.
beneficiary_type
class-attribute
instance-attribute
¶
beneficiary_type = mapped_column(
Enum(
InsuranceBeneficiary,
create_type=False,
values_callable=lambda x: [(value) for e in x],
),
nullable=False,
)
billed_to_debtor_filter
classmethod
¶
Returns a SQLAlchemy filter expression equivalent to PremiumEntry._is_component_billed_to_debtor.
These two must always express the same logic. Update both when the billing rules change.
Source code in components/premium/public/models/premium_component.py
cancelled_by_entry_id
class-attribute
instance-attribute
¶
cancelled_entry_id
class-attribute
instance-attribute
¶
collection_method
class-attribute
instance-attribute
¶
collection_method = mapped_column(
Enum(
InsuranceCollectionMethod,
create_type=False,
values_callable=lambda x: [(value) for e in x],
nullable=True,
)
)
contribution_type
class-attribute
instance-attribute
¶
contribution_type = mapped_column(
Enum(
InsuranceContribution,
create_type=False,
values_callable=lambda x: [(value) for e in x],
),
nullable=False,
)
coverage_type
class-attribute
instance-attribute
¶
coverage_type = mapped_column(
Enum(
InsuranceService,
create_type=False,
values_callable=lambda x: [(value) for e in x],
),
nullable=False,
)
currency
class-attribute
instance-attribute
¶
ISO4217 compliant alpha code of the currency.
debtor_type
class-attribute
instance-attribute
¶
debtor_type = mapped_column(
Enum(
InsuranceDebtor,
name="insurancedebtor",
create_type=False,
values_callable=lambda x: [(value) for e in x],
),
nullable=False,
)
enrollment_group_id
class-attribute
instance-attribute
¶
enrollment_id
class-attribute
instance-attribute
¶
Identifier of the participant, for legacy subscriptions.
finiquito_id
class-attribute
instance-attribute
¶
Spain-specific. A finiquito is a pay_csv generated to recap the premiums of an employee when they leave the company. In order to maintain compatibility between Spain payroll and Global premiums we need to maintain this property. However it's not required by the Global payroll system, therefore this property is temporary and will be dropped as soon as global payroll is online.
Always null in Belgium.
invoice_id
class-attribute
instance-attribute
¶
module_id
class-attribute
instance-attribute
¶
num_days
class-attribute
instance-attribute
¶
Number of days this fee covers within the billing period.
payroll_csv_id
class-attribute
instance-attribute
¶
Belgium & Spain's local payroll systems requires to keep track of which premiums are included in which file.
In order to maintain compatibility between Belgium payroll and Global premiums we need to maintain this property. However it's not required by the Global payroll system, therefore this property is temporary and will be dropped as soon as global payroll is online.