Skip to content

Api reference

components.core_enrollment.public.commands

components.core_enrollment.public.entities

Read-only entities exposed by the core_enrollment public API.

EnrollmentGroupView

Bases: BaseModel

Aggregate-shaped read view for an EnrollmentGroup.

contract_id instance-attribute

contract_id

id instance-attribute

id

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

periods instance-attribute

periods

primary_member_id instance-attribute

primary_member_id

revision_number instance-attribute

revision_number

EnrollmentPeriodView

Bases: BaseModel

Aggregate-rooted enrollment period view.

end_date instance-attribute

end_date

member_id instance-attribute

member_id

membership_type instance-attribute

membership_type

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

module_id instance-attribute

module_id

start_date instance-attribute

start_date

ReadChangeRequest

Bases: BaseModel

A change request that produced or modified enrollment periods.

description instance-attribute

description

id instance-attribute

id

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

requested_at instance-attribute

requested_at

requester instance-attribute

requester

requester_comment class-attribute instance-attribute

requester_comment = None

requester_type instance-attribute

requester_type

scoped_changes instance-attribute

scoped_changes

ReadChangeRequestTimelineSegment

Bases: BaseModel

A time segment attributed to a change request for a member + service type.

change_request_id instance-attribute

change_request_id

end_date instance-attribute

end_date

member_id instance-attribute

member_id

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

service_type instance-attribute

service_type

start_date instance-attribute

start_date

ReadDiffPeriod

Bases: BaseModel

A period in a revision diff, with an optional reason for the change.

end_date instance-attribute

end_date

member_id instance-attribute

member_id

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

module_name instance-attribute

module_name

reason class-attribute instance-attribute

reason = ''

start_date instance-attribute

start_date

ReadEnrollmentGroup

Bases: BaseModel

Flat read-only view of an enrollment group, for API consumption.

change_request_timeline class-attribute instance-attribute

change_request_timeline = Field(default_factory=list)

change_requests class-attribute instance-attribute

change_requests = Field(default_factory=list)

company_id class-attribute instance-attribute

company_id = None

company_name class-attribute instance-attribute

company_name = None

contract_id instance-attribute

contract_id

contract_name class-attribute instance-attribute

contract_name = None

id instance-attribute

id

is_migrated instance-attribute

is_migrated

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

overrides class-attribute instance-attribute

overrides = Field(default_factory=list)

periods instance-attribute

periods

primary_member_id instance-attribute

primary_member_id

revision_number instance-attribute

revision_number

ReadEnrollmentPeriod

Bases: BaseModel

A single enrollment period with its module info resolved.

change_request_id class-attribute instance-attribute

change_request_id = None

date_interval property

date_interval

Return the matching DateInterval from the start and end dates.

end_date instance-attribute

end_date

member_id instance-attribute

member_id

membership_type instance-attribute

membership_type

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

module instance-attribute

module

revision instance-attribute

revision

start_date instance-attribute

start_date

timeline_effective_value property

timeline_effective_value

EffectiveValue representation for use with EffectiveValueContainer.

violation class-attribute instance-attribute

violation = None

ReadModule

Bases: BaseModel

Module info enriched with name and service type.

id instance-attribute

id

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

name instance-attribute

name

service_type instance-attribute

service_type

ReadOverride

Bases: BaseModel

A rule override for a member within an enrollment group.

description instance-attribute

description

end_date instance-attribute

end_date

member_id instance-attribute

member_id

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

rules instance-attribute

rules

start_date instance-attribute

start_date

ReadRevisionCause

Bases: BaseModel

What triggered this revision.

change_request_id class-attribute instance-attribute

change_request_id = None

description instance-attribute

description

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

requester_type class-attribute instance-attribute

requester_type = None

timestamp instance-attribute

timestamp

trigger_type class-attribute instance-attribute

trigger_type = None

type instance-attribute

type

ReadRevisionDiff

Bases: BaseModel

Diff between two revisions of an enrollment group.

added instance-attribute

added

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

periods_from instance-attribute

periods_from

periods_to instance-attribute

periods_to

removed instance-attribute

removed

triggers instance-attribute

triggers

ReadRevisionSummary

Bases: BaseModel

One revision in the enrollment group history, with its cause.

cause instance-attribute

cause

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

number instance-attribute

number

timestamp instance-attribute

timestamp

ReadScopedChange

Bases: BaseModel

One scoped change within a change request.

end_date instance-attribute

end_date

members instance-attribute

members

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

module_names instance-attribute

module_names

service_type instance-attribute

service_type

switch_date instance-attribute

switch_date

ReadScopedChangeMember

Bases: BaseModel

A member referenced in a scoped change, with their membership type.

member_id instance-attribute

member_id

membership_type instance-attribute

membership_type

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

components.core_enrollment.public.errors

CoreEnrollmentError

Bases: Exception

Base class for all CoreEnrollment errors.

components.core_enrollment.public.events_pipeline_events

Integration events published to the events pipeline.

Re-exports from stdlib (events_pipeline_common) so backend callers can continue importing from the public component path.

ChangeRequestApproved

Bases: EventSchema

Published when a change request transitions to terminal approved status.

contract_id instance-attribute

contract_id

engine_run_id instance-attribute

engine_run_id

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

ChangeRequestFailed

Bases: EventSchema

Published when a change request transitions to terminal failed status (engine error).

contract_id instance-attribute

contract_id

engine_run_id instance-attribute

engine_run_id

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

ChangeRequestRejected

Bases: EventSchema

Published when a change request transitions to terminal rejected status.

contract_id instance-attribute

contract_id

engine_run_id instance-attribute

engine_run_id

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

ENROLLMENT_CHANGES_STREAM_NAME module-attribute

ENROLLMENT_CHANGES_STREAM_NAME = ENROLLMENT_CHANGES

EnrollmentGroupChanged

Bases: EventSchema

Published when an enrollment group revision is created.

contract_id instance-attribute

contract_id

enrollment_group_id instance-attribute

enrollment_group_id

primary_member_id instance-attribute

primary_member_id

revision_number instance-attribute

revision_number

RecomputeRequestCreated

Bases: EventSchema

Published when a recompute request is created (pending processing).

contract_id instance-attribute

contract_id

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

RecomputeRequestFailed

Bases: EventSchema

Published when a recompute request transitions to failed status.

attempt_count instance-attribute

attempt_count

contract_id instance-attribute

contract_id

engine_run_id instance-attribute

engine_run_id

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

RecomputeRequestIgnored

Bases: EventSchema

Published when a recompute request reaches terminal ignored status.

contract_id instance-attribute

contract_id

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

RecomputeRequestRetried

Bases: EventSchema

Published when an operator manually retries a failed recompute request.

comment instance-attribute

comment

contract_id instance-attribute

contract_id

operator_identifier instance-attribute

operator_identifier

primary_member_id instance-attribute

primary_member_id

request_id instance-attribute

request_id

RecomputeRequestSucceeded

Bases: EventSchema

Published when a recompute request reaches terminal succeeded status.

contract_id instance-attribute

contract_id

engine_run_id instance-attribute

engine_run_id

primary_member_id instance-attribute

primary_member_id

produced_revision_id class-attribute instance-attribute

produced_revision_id = None

request_id instance-attribute

request_id

components.core_enrollment.public.fixtures

marmot_prototype_data

Synthetic enrollment data for the Marmot frontend prototype.

Contains hardcoded enrollment groups used by the synthetic=True code path in enrollment_group.py. These definitions only depend on core_enrollment.public.entities dataclasses — no ES-specific imports.

STATIC_ENROLLMENT_GROUPS module-attribute

STATIC_ENROLLMENT_GROUPS = [_GROUP_1, _GROUP_2, _GROUP_3]

personalize_synthetic_groups

personalize_synthetic_groups(member_id)

Replace the default viewed-member ID with the real queried member_id.

Source code in components/core_enrollment/public/fixtures/marmot_prototype_data.py
def personalize_synthetic_groups(
    member_id: str,
) -> list[ReadEnrollmentGroup]:
    """Replace the default viewed-member ID with the real queried member_id."""
    if member_id == _VIEWED_MEMBER:
        return STATIC_ENROLLMENT_GROUPS

    result: list[ReadEnrollmentGroup] = []
    for group in STATIC_ENROLLMENT_GROUPS:
        s = _sub
        vm = _VIEWED_MEMBER
        result.append(
            ReadEnrollmentGroup(
                id=group.id,
                contract_id=group.contract_id,
                primary_member_id=s(group.primary_member_id, vm, member_id),
                periods=[
                    ReadEnrollmentPeriod(
                        member_id=s(p.member_id, vm, member_id),
                        membership_type=p.membership_type,
                        module=p.module,
                        start_date=p.start_date,
                        end_date=p.end_date,
                        revision=p.revision,
                        violation=p.violation,
                        change_request_id=p.change_request_id,
                    )
                    for p in group.periods
                ],
                revision_number=group.revision_number,
                is_migrated=group.is_migrated,
                contract_name=group.contract_name,
                company_name=group.company_name,
                overrides=[
                    ReadOverride(
                        member_id=s(o.member_id, vm, member_id),
                        start_date=o.start_date,
                        end_date=o.end_date,
                        description=o.description,
                        rules=o.rules,
                    )
                    for o in group.overrides
                ],
                change_requests=[
                    ReadChangeRequest(
                        id=cr.id,
                        description=cr.description,
                        requester=s(cr.requester, vm, member_id),
                        requester_type=cr.requester_type,
                        requested_at=cr.requested_at,
                        requester_comment=cr.requester_comment,
                        scoped_changes=[
                            ReadScopedChange(
                                service_type=sc.service_type,
                                module_names=sc.module_names,
                                members=[
                                    ReadScopedChangeMember(
                                        member_id=s(m.member_id, vm, member_id),
                                        membership_type=m.membership_type,
                                    )
                                    for m in sc.members
                                ],
                                switch_date=sc.switch_date,
                                end_date=sc.end_date,
                            )
                            for sc in cr.scoped_changes
                        ],
                    )
                    for cr in group.change_requests
                ],
                change_request_timeline=[
                    ReadChangeRequestTimelineSegment(
                        change_request_id=seg.change_request_id,
                        member_id=s(seg.member_id, vm, member_id),
                        service_type=seg.service_type,
                        start_date=seg.start_date,
                        end_date=seg.end_date,
                    )
                    for seg in group.change_request_timeline
                ],
            )
        )
    return result

stub_revisions

Stub revision and diff data for the Marmot frontend prototype.

Used by get_revisions and get_revision_diff until real revision storage is wired up.

STUB_PERIODS module-attribute

STUB_PERIODS = {
    1: [
        _period(
            "Marie",
            "health_base",
            date(2024, 1, 15),
            date(2025, 6, 30),
        )
    ],
    3: [
        _period(
            "Marie",
            "health_base",
            date(2024, 1, 15),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "health_base",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "prevoyance_opt_1",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
    ],
    5: [
        _period(
            "Marie",
            "health_base",
            date(2024, 1, 15),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "prevoyance_opt_1",
            date(2024, 3, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "health_base",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "prevoyance_opt_1",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Lea",
            "health_base",
            date(2024, 4, 10),
            date(2025, 6, 30),
        ),
    ],
    8: [
        _period(
            "Marie",
            "health_base",
            date(2024, 1, 15),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "prevoyance_opt_1",
            date(2024, 3, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "flex_add_on",
            date(2024, 5, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "health_base",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "prevoyance_opt_1",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Lea",
            "health_base",
            date(2024, 4, 10),
            date(2025, 6, 30),
        ),
    ],
    10: [
        _period(
            "Marie",
            "health_base",
            date(2024, 1, 15),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "prevoyance_opt_1",
            date(2024, 3, 1),
            date(2024, 10, 31),
        ),
        _period(
            "Marie",
            "prevoyance_opt_1",
            date(2025, 3, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "flex_add_on",
            date(2024, 5, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "health_base",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "prevoyance_opt_1",
            date(2024, 2, 1),
            date(2024, 11, 30),
        ),
        _period(
            "Lea",
            "health_base",
            date(2024, 4, 10),
            date(2025, 6, 30),
        ),
    ],
    12: [
        _period(
            "Marie",
            "health_base",
            date(2024, 1, 15),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "prevoyance_opt_1",
            date(2024, 3, 1),
            date(2024, 10, 31),
        ),
        _period(
            "Marie",
            "prevoyance_opt_1",
            date(2025, 3, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Marie",
            "flex_add_on",
            date(2024, 5, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "health_base",
            date(2024, 2, 1),
            date(2025, 6, 30),
        ),
        _period(
            "Jean",
            "prevoyance_opt_1",
            date(2024, 2, 1),
            date(2024, 11, 30),
        ),
        _period(
            "Lea",
            "health_base",
            date(2024, 4, 10),
            date(2025, 6, 30),
        ),
    ],
}

STUB_REASONS module-attribute

STUB_REASONS = {
    "Marie|prevoyance_opt_1|removed|2024-03-01|2025-06-30": "period split",
    "Marie|prevoyance_opt_1|added|2024-03-01|2024-10-31": "split: contract amendment",
    "Marie|prevoyance_opt_1|added|2025-03-01|2025-06-30": "new: contract update",
    "Marie|flex_add_on|added|2024-05-01|2025-06-30": "new module + override",
    "Jean|prevoyance_opt_1|removed|2024-02-01|2025-06-30": "dependent rule: primary gap",
    "Jean|prevoyance_opt_1|added|2024-02-01|2024-11-30": "trimmed: follows primary",
}

STUB_REVISIONS module-attribute

STUB_REVISIONS = [
    _rev(
        1,
        "2024-01-15 09:00",
        _cause(
            "change_request",
            "ChangeRequest desc",
            "2024-01-15 09:00",
            change_request_id="a3f8c1",
            requester_type="member",
        ),
    ),
    _rev(
        2,
        "2024-01-15 09:01",
        _cause(
            "recompute",
            "Contract update desc",
            "2024-01-15 09:01",
            trigger_type="contract_update",
        ),
    ),
    _rev(
        3,
        "2024-02-01 14:20",
        _cause(
            "change_request",
            "ChangeRequest desc",
            "2024-02-01 14:20",
            change_request_id="b7d2e5",
            requester_type="member",
        ),
    ),
    _rev(
        4,
        "2024-02-01 14:21",
        _cause(
            "recompute",
            "Profile update desc",
            "2024-02-01 14:21",
            trigger_type="profile_update",
        ),
    ),
    _rev(
        5,
        "2024-04-10 11:30",
        _cause(
            "change_request",
            "ChangeRequest desc",
            "2024-04-10 11:30",
            change_request_id="c4e9f3",
            requester_type="member",
        ),
    ),
    _rev(
        6,
        "2024-06-15 16:00",
        _cause(
            "change_request",
            "ChangeRequest desc",
            "2024-06-15 16:00",
            change_request_id="d1a6b8",
            requester_type="admin",
        ),
    ),
    _rev(
        7,
        "2024-06-15 16:01",
        _cause(
            "recompute",
            "Contract update desc",
            "2024-06-15 16:01",
            trigger_type="contract_update",
        ),
    ),
    _rev(
        8,
        "2024-11-05 10:45",
        _cause(
            "change_request",
            "ChangeRequest desc",
            "2024-11-05 10:45",
            change_request_id="e8c3d7",
            requester_type="alaner",
        ),
    ),
    _rev(
        9,
        "2025-01-20 02:00",
        _cause(
            "recompute",
            "Contract update desc",
            "2025-01-20 02:00",
            trigger_type="contract_update",
        ),
    ),
    _rev(
        10,
        "2025-02-14 09:32",
        _cause(
            "change_request",
            "ChangeRequest desc",
            "2025-02-14 09:32",
            change_request_id="f5b2a9",
            requester_type="admin",
        ),
    ),
    _rev(
        11,
        "2025-03-15 08:50",
        _cause(
            "recompute",
            "Contract update desc",
            "2025-03-15 08:50",
            trigger_type="contract_update",
        ),
    ),
    _rev(
        12,
        "2025-04-01 11:20",
        _cause(
            "recompute",
            "Recompute run desc",
            "2025-04-01 11:20",
            trigger_type="recompute_run",
        ),
    ),
]

compute_stub_diff

compute_stub_diff(from_rev, to_rev)

Compute a diff between two stub revision snapshots.

Source code in components/core_enrollment/public/fixtures/stub_revisions.py
def compute_stub_diff(from_rev: int, to_rev: int) -> ReadRevisionDiff:
    """Compute a diff between two stub revision snapshots."""
    periods_from = stub_periods_at(from_rev)
    periods_to = stub_periods_at(to_rev)

    from_set = {
        (p.member_id, p.module_name, p.start_date, p.end_date) for p in periods_from
    }
    to_set = {
        (p.member_id, p.module_name, p.start_date, p.end_date) for p in periods_to
    }

    removed = [
        ReadDiffPeriod(
            member_id=p.member_id,
            module_name=p.module_name,
            start_date=p.start_date,
            end_date=p.end_date,
            reason=stub_reason(p.member_id, p.module_name, "removed", p),
        )
        for p in periods_from
        if (p.member_id, p.module_name, p.start_date, p.end_date) not in to_set
    ]
    added = [
        ReadDiffPeriod(
            member_id=p.member_id,
            module_name=p.module_name,
            start_date=p.start_date,
            end_date=p.end_date,
            reason=stub_reason(p.member_id, p.module_name, "added", p),
        )
        for p in periods_to
        if (p.member_id, p.module_name, p.start_date, p.end_date) not in from_set
    ]

    triggers = [r for r in STUB_REVISIONS if from_rev < r.number <= to_rev]

    return ReadRevisionDiff(
        removed=removed,
        added=added,
        periods_from=periods_from,
        periods_to=periods_to,
        triggers=triggers,
    )

stub_periods_at

stub_periods_at(rev)

Return stub periods at a given revision, filling gaps.

Source code in components/core_enrollment/public/fixtures/stub_revisions.py
def stub_periods_at(rev: int) -> list[ReadDiffPeriod]:
    """Return stub periods at a given revision, filling gaps."""
    if rev <= 0:
        return []
    keys = sorted(k for k in STUB_PERIODS if k <= rev)
    return STUB_PERIODS[keys[-1]] if keys else []

stub_reason

stub_reason(member, mod, action, p)

Look up stub reason for a diff period.

Source code in components/core_enrollment/public/fixtures/stub_revisions.py
def stub_reason(member: str, mod: str, action: str, p: ReadDiffPeriod) -> str:
    """Look up stub reason for a diff period."""
    key = f"{member}|{mod}|{action}|{p.start_date.isoformat()}|{p.end_date.isoformat()}"
    return STUB_REASONS.get(key, "")

components.core_enrollment.public.python_api

change_request

ChangeRequestService

ChangeRequestService(
    uow_propagation,
    input_directory=None,
    contract_adapter=None,
)

Interact with Enrollment Group Change Requests

Source code in components/core_enrollment/public/python_api/change_request.py
def __init__(
    self,
    uow_propagation: Propagation,
    input_directory: "InputDirectory | None" = None,
    contract_adapter: "AbstractContractAdapter | None" = None,
) -> None:
    from components.core_enrollment.internal.infrastructure.change_request_repository import (
        SQLAlchemyEnrollmentGroupChangeRequestRepository,
    )
    from components.core_enrollment.internal.infrastructure.engine_run_repository import (
        SQLAlchemyEngineRunRepository,
    )
    from components.core_enrollment.internal.infrastructure.recompute_request_repository import (
        SQLAlchemyEnrollmentGroupRecomputeRequestRepository,
    )
    from components.core_enrollment.internal.infrastructure.repository import (
        SQLAlchemyEnrollmentGroupRepository,
    )

    # NOTE: Could take a UOW factory directly in argument, or also take the respository factories
    self._unit_of_work_factory: UnitOfWorkFactory = lambda: TransactionUnitOfWork(
        propagation=uow_propagation,
        enrollment_group_repository_factory=SQLAlchemyEnrollmentGroupRepository,
        enrollment_group_recompute_request_repository_factory=SQLAlchemyEnrollmentGroupRecomputeRequestRepository,
        change_request_repository_factory=SQLAlchemyEnrollmentGroupChangeRequestRepository,
        engine_run_repository_factory=SQLAlchemyEngineRunRepository,
    )
    self._contract_adapter = contract_adapter or _get_default_contract_adapter()
    self._input_directory = input_directory or _get_default_input_directory()
create_and_process
create_and_process(
    enrollment_group_id,
    contract_id,
    primary_member_id,
    scoped_changes,
    requested_by_global_user_id=UNKNOWN_USER_ID,
    requester_type=RequesterType.unknown,
    requested_at=NOT_SET,
    last_seen_revision_number=NOT_SET,
    change_request_id=None,
)

Create a new enrollment group change request then process it through the engine in a SYNCHRONOUS way.

Each ScopedEnrollmentGroupChangeRequest carries its own switch_date / end_date.

Source code in components/core_enrollment/public/python_api/change_request.py
@obs.api_call()
def create_and_process(
    self,
    enrollment_group_id: UUID | None,
    contract_id: ContractId | None,
    primary_member_id: PrimaryId | None,
    scoped_changes: list[ScopedEnrollmentGroupChangeRequest],
    requested_by_global_user_id: str = UNKNOWN_USER_ID,
    requester_type: RequesterType = RequesterType.unknown,
    requested_at: NotSet[datetime] = NOT_SET,
    last_seen_revision_number: NotSet[int | None] = NOT_SET,
    change_request_id: UUID | None = None,
) -> "EnrollmentGroupChangeRequest":
    """
    Create a new enrollment group change request then process it through the engine in a SYNCHRONOUS way.

    Each `ScopedEnrollmentGroupChangeRequest` carries its own
    `switch_date` / `end_date`.
    """
    with self._unit_of_work_factory() as uow:
        change_request = EnrollmentGroupChangeRequest(
            id=change_request_id or uuid4(),
            last_seen_revision_number=last_seen_revision_number,
            # Target enrollment group
            enrollment_group_id=enrollment_group_id,
            contract_id=contract_id if enrollment_group_id is None else None,
            primary_member_id=(
                primary_member_id if enrollment_group_id is None else None
            ),
            # Actual changes to make (validity dates ride along per scope)
            scoped_changes=scoped_changes,
            # Requester information, leaving the requester_comment out of the picture for now
            requested_by_global_user_id=requested_by_global_user_id,
            requester_type=requester_type,
            requested_at=requested_at if is_set(requested_at) else utcnow(),
            # We didn't run the engine at that point
            status=EnrollmentGroupChangeRequestStatus.pending,
            latest_engine_run_id=None,
        )
        uow.change_request_repository.save(change_request)

    from components.core_enrollment.internal.engine.enrollment_change_requests import (
        handle_enrollment_change_request,
    )

    # NOTE: What if there are other pending change requests?
    # Should the engine process all pending change requests, not only the one we just created?

    return handle_enrollment_change_request(
        request=change_request,
        contract_adapter=self._contract_adapter,
        input_directory=self._input_directory,
        unit_of_work_factory=self._unit_of_work_factory,
        now=datetime.now(UTC),
    )

EnrollmentGroupChangeRequestStatus

Bases: StrEnum

approved class-attribute instance-attribute
approved = auto()
failed class-attribute instance-attribute
failed = auto()
is_approved staticmethod
is_approved(status)
Source code in components/core_enrollment/internal/domain/entities.py
@staticmethod
def is_approved(status: EnrollmentGroupChangeRequestStatus) -> bool:
    return status == EnrollmentGroupChangeRequestStatus.approved
pending class-attribute instance-attribute
pending = auto()
rejected class-attribute instance-attribute
rejected = auto()

MemberWithMembershipType

Bases: BaseModel

enrollment_type property
enrollment_type

Get an EnrollmentType from this, which isn't exactly the same as the membership_type.

The difference is that MembershipType includes pet, or ascendent, while EnrollmentType is a bit more legacy.

member_id instance-attribute
member_id
membership_type instance-attribute
membership_type
model_config class-attribute instance-attribute
model_config = ConfigDict(frozen=True, extra='forbid')

RequesterType

Bases: StrEnum

admin class-attribute instance-attribute
admin = auto()
both class-attribute instance-attribute
both = auto()
member class-attribute instance-attribute
member = auto()
migration_bot class-attribute instance-attribute
migration_bot = auto()
operator class-attribute instance-attribute
operator = auto()
unknown class-attribute instance-attribute
unknown = auto()

ScopedEnrollmentGroupChangeRequest

Bases: BaseModel

end_date class-attribute instance-attribute
end_date = None
members_with_membership_types instance-attribute
members_with_membership_types
module_ids instance-attribute
module_ids
service_type instance-attribute
service_type
switch_date instance-attribute
switch_date

enrollment_group

Public read API for enrollment groups.

Supports three code paths controlled by SourceOfTruth: - force_legacy: reads only from country-specific legacy repositories - force_core_stack: reads only from the core_enrollment EnrollmentGroup repository - follow_migration: delegates to per-country feature flags; when legacy flag is on, merges results from both legacy and core-stack repos (legacy wins on ID conflict)

SourceOfTruth

Bases: StrEnum

Which repository backs enrollment-group reads.

follow_migration class-attribute instance-attribute
follow_migration = auto()
force_core_stack class-attribute instance-attribute
force_core_stack = auto()
force_legacy class-attribute instance-attribute
force_legacy = auto()

filter_periods_in_range

filter_periods_in_range(periods, from_date, to_date)

Return periods overlapping [from_date, to_date].

A period overlaps if start_date <= to_date and (end_date is None or end_date >= from_date). Inclusive on both ends so a snapshot lookup (from_date == to_date) matches a period active on that day, including single-day periods.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
def filter_periods_in_range(
    periods: Sequence[ReadEnrollmentPeriod],
    from_date: date,
    to_date: date,
) -> list[ReadEnrollmentPeriod]:
    """Return periods overlapping [from_date, to_date].

    A period overlaps if `start_date <= to_date` and (`end_date is None` or
    `end_date >= from_date`). Inclusive on both ends so a snapshot lookup
    (`from_date == to_date`) matches a period active on that day, including
    single-day periods.
    """
    return [
        period
        for period in periods
        if period.start_date <= to_date
        and (period.end_date is None or period.end_date >= from_date)
    ]

get_contract_and_primary_ids_by_enrollment_group_id

get_contract_and_primary_ids_by_enrollment_group_id(
    enrollment_group_id,
)

Return (new_contract_id, primary_user_id) for a given enrollment_group_id, or None.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_contract_and_primary_ids_by_enrollment_group_id(
    enrollment_group_id: UUID,
) -> tuple[ContractId, PrimaryId] | None:
    """Return (new_contract_id, primary_user_id) for a given enrollment_group_id, or None."""
    return _get_pair_from_enrollment_group_id(enrollment_group_id)

get_enrollment_group_by_contract_and_primary

get_enrollment_group_by_contract_and_primary(
    contract_id,
    primary_id,
    *,
    contract_adapter=None,
    source_of_truth=SourceOfTruth.follow_migration
)

Get a single enrollment group by contract_id + primary_id pair.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_group_by_contract_and_primary(
    contract_id: ContractId,
    primary_id: str,
    *,
    contract_adapter: AbstractContractAdapter | None = None,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> ReadEnrollmentGroup | None:
    """Get a single enrollment group by contract_id + primary_id pair."""
    contract_adapter = _ensure_shared_contract_adapter(contract_adapter)
    with transaction() as session:
        repos = _get_repos_for_read(
            session=session,
            source_of_truth=source_of_truth,
            contract_adapter=contract_adapter,
        )
        snapshot = _first_non_none(
            repos,
            lambda r: r.get_by_contract_and_primary(contract_id, primary_id),
        )
        if snapshot is None:
            return None
        return _convert_snapshot_to_read_entity(
            snapshot=snapshot,
            contract_adapter=contract_adapter,
        )

get_enrollment_group_by_id

get_enrollment_group_by_id(
    enrollment_group_id,
    *,
    contract_adapter=None,
    source_of_truth=SourceOfTruth.follow_migration
)

Get a single enrollment group by ID.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_group_by_id(
    enrollment_group_id: UUID,
    *,
    contract_adapter: AbstractContractAdapter | None = None,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> ReadEnrollmentGroup | None:
    """Get a single enrollment group by ID."""
    contract_adapter = _ensure_shared_contract_adapter(contract_adapter)
    with transaction() as session:
        repos = _get_repos_for_read(
            session=session,
            source_of_truth=source_of_truth,
            contract_adapter=contract_adapter,
        )
        snapshot = _first_non_none(repos, lambda repo: repo.get(enrollment_group_id))
        if snapshot is None:
            return None
        return _convert_snapshot_to_read_entity(
            snapshot=snapshot,
            contract_adapter=contract_adapter,
        )

get_enrollment_group_view_by_contract_and_primary

get_enrollment_group_view_by_contract_and_primary(
    contract_id,
    primary_id,
    *,
    source_of_truth=SourceOfTruth.follow_migration
)

Get a single enrollment group by (contract_id, primary_id).

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_group_view_by_contract_and_primary(
    contract_id: ContractId,
    primary_id: str,
    *,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> EnrollmentGroupView | None:
    """Get a single enrollment group by (contract_id, primary_id)."""
    with transaction() as session:
        sources = _get_enrollment_group_query_services_for(session, source_of_truth)
        return _first_view(
            sources,
            lambda src: src.get_by_contract_and_primary(contract_id, primary_id),
        )

get_enrollment_group_view_by_id

get_enrollment_group_view_by_id(
    enrollment_group_id,
    *,
    source_of_truth=SourceOfTruth.follow_migration
)

Get a single enrollment group as an identity-only view.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_group_view_by_id(
    enrollment_group_id: UUID,
    *,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> EnrollmentGroupView | None:
    """Get a single enrollment group as an identity-only view."""
    with transaction() as session:
        sources = _get_enrollment_group_query_services_for(session, source_of_truth)
        return _first_view(sources, lambda src: src.get_by_id(enrollment_group_id))

get_enrollment_groups_by_contract

get_enrollment_groups_by_contract(
    contract_id,
    *,
    contract_adapter=None,
    source_of_truth=SourceOfTruth.follow_migration
)

List enrollment groups on a contract.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_groups_by_contract(
    contract_id: ContractId,
    *,
    contract_adapter: AbstractContractAdapter | None = None,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> list[ReadEnrollmentGroup]:
    """List enrollment groups on a contract."""
    contract_adapter = _ensure_shared_contract_adapter(contract_adapter)
    with transaction() as session:
        repos = _get_repos_for_read(
            session=session,
            source_of_truth=source_of_truth,
            contract_adapter=contract_adapter,
        )
        snapshots = _merge_snapshots(
            repos, lambda r: r.list_by_contract_id(contract_id)
        )
        return [
            _convert_snapshot_to_read_entity(
                snapshot=snapshot,
                contract_adapter=contract_adapter,
            )
            for snapshot in snapshots
        ]

get_enrollment_groups_by_member

get_enrollment_groups_by_member(
    member_id,
    *,
    contract_adapter=None,
    source_of_truth=SourceOfTruth.follow_migration
)

List enrollment groups containing a member.

Takes an optional contract_adapter in order to benefit from caching, when reading multiple enrollment groups from the same contract (which should happen quite often).

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_groups_by_member(
    member_id: str,
    *,
    contract_adapter: AbstractContractAdapter | None = None,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> list[ReadEnrollmentGroup]:
    """
    List enrollment groups containing a member.

    Takes an optional `contract_adapter` in order to benefit from caching, when reading
    multiple enrollment groups from the same contract (which should happen quite often).
    """
    contract_adapter = _ensure_shared_contract_adapter(contract_adapter)
    with transaction() as session:
        repos = _get_repos_for_read(
            session=session,
            source_of_truth=source_of_truth,
            contract_adapter=contract_adapter,
        )
        snapshots = _merge_snapshots(repos, lambda r: r.list_by_member_id(member_id))
        return [
            _convert_snapshot_to_read_entity(
                snapshot=snapshot,
                contract_adapter=contract_adapter,
            )
            for snapshot in snapshots
        ]

get_enrollment_groups_by_primary

get_enrollment_groups_by_primary(
    primary_id,
    *,
    contract_adapter=None,
    source_of_truth=SourceOfTruth.follow_migration
)

List enrollment groups where user is primary.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_enrollment_groups_by_primary(
    primary_id: str,
    *,
    contract_adapter: AbstractContractAdapter | None = None,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> list[ReadEnrollmentGroup]:
    """List enrollment groups where user is primary."""
    contract_adapter = _ensure_shared_contract_adapter(contract_adapter)
    with transaction() as session:
        repos = _get_repos_for_read(
            session=session,
            source_of_truth=source_of_truth,
            contract_adapter=contract_adapter,
        )
        snapshots = _merge_snapshots(repos, lambda r: r.list_by_primary_id(primary_id))
        return [
            _convert_snapshot_to_read_entity(
                snapshot=snapshot,
                contract_adapter=contract_adapter,
            )
            for snapshot in snapshots
        ]

get_or_create_enrollment_group_id

get_or_create_enrollment_group_id(
    new_contract_id, primary_user_id
)

Return a stable enrollment_group_id for the given (new_contract_id, primary_user_id) pair.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_or_create_enrollment_group_id(
    new_contract_id: ContractId,
    primary_user_id: str,
) -> UUID:
    """Return a stable enrollment_group_id for the given (new_contract_id, primary_user_id) pair."""
    return _get_or_create(new_contract_id, primary_user_id)

get_revision_diff

get_revision_diff(
    *, enrollment_group_id, from_revision, to_revision
)

Get the diff between two revisions of an enrollment group.

Stub: returns synthetic data for now.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_revision_diff(
    *,
    enrollment_group_id: UUID,  # noqa: ARG001 (will be used when wired to repository)
    from_revision: int,
    to_revision: int,
) -> ReadRevisionDiff:
    """Get the diff between two revisions of an enrollment group.

    Stub: returns synthetic data for now.
    """
    if from_revision < 0 or to_revision < from_revision:
        raise BaseErrorCode.invalid_arguments(
            description="from_revision must be >= 0 and <= to_revision"
        )

    return compute_stub_diff(from_revision, to_revision)

get_revisions

get_revisions(*, enrollment_group_id)

List all revisions for an enrollment group.

Stub: returns synthetic data for now.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def get_revisions(
    *,
    enrollment_group_id: UUID,  # noqa: ARG001 (will be used when wired to repository)
) -> list[ReadRevisionSummary]:
    """List all revisions for an enrollment group.

    Stub: returns synthetic data for now.
    """
    return list(STUB_REVISIONS)

list_enrollment_group_views_by_contract

list_enrollment_group_views_by_contract(
    contract_id,
    *,
    source_of_truth=SourceOfTruth.follow_migration
)

List enrollment group views on a contract.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def list_enrollment_group_views_by_contract(
    contract_id: ContractId,
    *,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> list[EnrollmentGroupView]:
    """List enrollment group views on a contract."""
    with transaction() as session:
        sources = _get_enrollment_group_query_services_for(session, source_of_truth)
        return _merge_views_by_id(
            sources, lambda src: src.list_by_contract(contract_id)
        )

list_enrollment_group_views_by_member

list_enrollment_group_views_by_member(
    member_id,
    *,
    source_of_truth=SourceOfTruth.follow_migration
)

List enrollment group views containing periods for the given member.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def list_enrollment_group_views_by_member(
    member_id: str,
    *,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> list[EnrollmentGroupView]:
    """List enrollment group views containing periods for the given member."""
    with transaction() as session:
        sources = _get_enrollment_group_query_services_for(session, source_of_truth)
        return _merge_views_by_id(sources, lambda src: src.list_by_member(member_id))

list_enrollment_group_views_by_primary

list_enrollment_group_views_by_primary(
    primary_id,
    *,
    source_of_truth=SourceOfTruth.follow_migration
)

List enrollment group views where the given member is primary.

Source code in components/core_enrollment/public/python_api/enrollment_group.py
@obs.api_call()
def list_enrollment_group_views_by_primary(
    primary_id: str,
    *,
    source_of_truth: SourceOfTruth = SourceOfTruth.follow_migration,
) -> list[EnrollmentGroupView]:
    """List enrollment group views where the given member is primary."""
    with transaction() as session:
        sources = _get_enrollment_group_query_services_for(session, source_of_truth)
        return _merge_views_by_id(sources, lambda src: src.list_by_primary(primary_id))

components.core_enrollment.public.queue

Public queue name constant for core_enrollment.

CORE_ENROLLMENT_QUEUE module-attribute

CORE_ENROLLMENT_QUEUE = 'core_enrollment'