Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/eligibility_signposting_api/model/eligibility.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from datetime import date
from enum import Enum, auto
from enum import Enum, StrEnum, auto
from functools import total_ordering
from typing import NewType, Self

Expand All @@ -13,7 +13,7 @@
RuleResult = NewType("RuleResult", str)


class RuleType(str, Enum):
class RuleType(StrEnum):
filter = "F"
suppression = "S"
redirect = "R"
Expand Down
8 changes: 4 additions & 4 deletions src/eligibility_signposting_api/model/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import typing
from datetime import UTC, date, datetime
from enum import Enum
from enum import StrEnum
from functools import cached_property
from operator import attrgetter
from typing import Literal, NewType
Expand All @@ -29,13 +29,13 @@
CohortLabel = NewType("CohortLabel", str)


class RuleType(str, Enum):
class RuleType(StrEnum):
filter = "F"
suppression = "S"
redirect = "R"


class RuleOperator(str, Enum):
class RuleOperator(StrEnum):
equals = "="
gt = ">"
lt = "<"
Expand Down Expand Up @@ -73,7 +73,7 @@ class RuleOperator(str, Enum):
year_gt = "Y>"


class RuleAttributeLevel(str, Enum):
class RuleAttributeLevel(StrEnum):
PERSON = "PERSON"
TARGET = "TARGET"
COHORT = "COHORT"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from enum import Enum
from enum import StrEnum
from typing import NewType

from pydantic import UUID4, BaseModel, Field, HttpUrl, field_serializer
Expand All @@ -17,13 +17,13 @@
CohortText = NewType("CohortText", str)


class Status(str, Enum):
class Status(StrEnum):
not_eligible = "NotEligible"
not_actionable = "NotActionable"
actionable = "Actionable"


class RuleType(str, Enum):
class RuleType(StrEnum):
filter = "F"
suppression = "S"
redirect = "R"
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/builders/model/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from polyfactory.factories.pydantic_factory import ModelFactory

from eligibility_signposting_api.model import rules
from eligibility_signposting_api.model.rules import RulePriority


def past_date(days_behind: int = 365) -> date:
Expand Down Expand Up @@ -38,6 +39,7 @@ class PersonAgeSuppressionRuleFactory(IterationRuleFactory):
type = rules.RuleType.suppression
name = "Exclude too young less than 75"
description = "Exclude too young less than 75"
priority = RulePriority(10)
operator = rules.RuleOperator.year_gt
attribute_level = rules.RuleAttributeLevel.PERSON
attribute_name = "DATE_OF_BIRTH"
Expand Down
124 changes: 106 additions & 18 deletions tests/unit/services/test_eligibility_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from hamcrest import assert_that, empty, has_item

from eligibility_signposting_api.model.eligibility import ConditionName, DateOfBirth, NHSNumber, Status
from eligibility_signposting_api.model.rules import IterationDate
from eligibility_signposting_api.model.rules import IterationDate, RuleType
from eligibility_signposting_api.repos import EligibilityRepo, NotFoundError, RulesRepo
from eligibility_signposting_api.services import EligibilityService, UnknownPersonError
from tests.fixtures.builders.model import rule as rule_builder
Expand All @@ -25,10 +25,10 @@ def test_eligibility_service_returns_from_repo():
eligibility_repo = MagicMock(spec=EligibilityRepo)
rules_repo = MagicMock(spec=RulesRepo)
eligibility_repo.get_eligibility = MagicMock(return_value=[])
ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber("1234567890"))
actual = service.get_eligibility_status(NHSNumber("1234567890"))

# Then
assert_that(actual, is_eligibility_status().with_conditions(empty()))
Expand All @@ -39,11 +39,11 @@ def test_eligibility_service_for_nonexistent_nhs_number():
eligibility_repo = MagicMock(spec=EligibilityRepo)
rules_repo = MagicMock(spec=RulesRepo)
eligibility_repo.get_eligibility_data = MagicMock(side_effect=NotFoundError)
ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
with pytest.raises(UnknownPersonError):
ps.get_eligibility_status(NHSNumber("1234567890"))
service.get_eligibility_status(NHSNumber("1234567890"))


def test_not_base_eligible(faker: Faker):
Expand All @@ -69,10 +69,10 @@ def test_not_base_eligible(faker: Faker):
]
)

ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
Expand Down Expand Up @@ -123,10 +123,10 @@ def test_only_live_campaigns_considered(faker: Faker):
]
)

ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
Expand Down Expand Up @@ -161,10 +161,10 @@ def test_base_eligible_and_simple_rule_includes(faker: Faker):
]
)

ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
Expand All @@ -178,7 +178,7 @@ def test_base_eligible_and_simple_rule_includes(faker: Faker):
def test_base_eligible_but_simple_rule_excludes(faker: Faker):
# Given
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
date_of_birth = DateOfBirth(faker.date_of_birth(maximum_age=74))
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))

eligibility_repo = MagicMock(spec=EligibilityRepo)
rules_repo = MagicMock(spec=RulesRepo)
Expand All @@ -199,10 +199,10 @@ def test_base_eligible_but_simple_rule_excludes(faker: Faker):
]
)

ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
Expand Down Expand Up @@ -252,10 +252,10 @@ def test_simple_rule_only_excludes_from_live_iteration(faker: Faker):
]
)

ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
Expand Down Expand Up @@ -287,10 +287,98 @@ def test_campaign_with_no_active_iteration_not_considered(faker: Faker):
]
)

ps = EligibilityService(eligibility_repo, rules_repo)
service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(actual, is_eligibility_status().with_conditions(empty()))


@pytest.mark.parametrize(
("rule_type", "expected_status"),
[
(RuleType.suppression, Status.not_actionable),
(RuleType.filter, Status.not_eligible),
(RuleType.redirect, Status.actionable),
],
)
def test_rule_types_cause_correct_statuses(rule_type: RuleType, expected_status: Status, faker: Faker):
# Given
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))

eligibility_repo = MagicMock(spec=EligibilityRepo)
rules_repo = MagicMock(spec=RulesRepo)
eligibility_repo.get_eligibility_data = MagicMock(
return_value=eligibility_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
)
rules_repo.get_campaign_configs = MagicMock(
return_value=[
rule_builder.CampaignConfigFactory.build(
target="RSV",
iterations=[
rule_builder.IterationFactory.build(
iteration_rules=[rule_builder.PersonAgeSuppressionRuleFactory.build(type=rule_type)],
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
)
],
)
]
)

service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
actual,
is_eligibility_status().with_conditions(
has_item(is_condition().with_condition_name(ConditionName("RSV")).and_status(expected_status))
),
)


def test_multiple_rule_types_cause_correct_status(faker: Faker):
# Given
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))

eligibility_repo = MagicMock(spec=EligibilityRepo)
rules_repo = MagicMock(spec=RulesRepo)
eligibility_repo.get_eligibility_data = MagicMock(
return_value=eligibility_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
)
rules_repo.get_campaign_configs = MagicMock(
return_value=[
rule_builder.CampaignConfigFactory.build(
target="RSV",
iterations=[
rule_builder.IterationFactory.build(
iteration_rules=[
rule_builder.PersonAgeSuppressionRuleFactory.build(type=RuleType.suppression),
rule_builder.PersonAgeSuppressionRuleFactory.build(type=RuleType.filter),
rule_builder.PersonAgeSuppressionRuleFactory.build(type=RuleType.suppression),
],
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
)
],
)
]
)

service = EligibilityService(eligibility_repo, rules_repo)

# When
actual = service.get_eligibility_status(NHSNumber(nhs_number))

# Then
assert_that(
actual,
is_eligibility_status().with_conditions(
has_item(is_condition().with_condition_name(ConditionName("RSV")).and_status(Status.not_eligible))
),
)
Loading