Skip to content

Commit 748e4d3

Browse files
Merge pull request #85 from NHSDigital/feature/ELI-252-filter-vs-suppression-rules
ELI-252 - Implement filter and suppression
2 parents 1cc6750 + 9ef9abf commit 748e4d3

File tree

5 files changed

+117
-27
lines changed

5 files changed

+117
-27
lines changed

src/eligibility_signposting_api/model/eligibility.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from dataclasses import dataclass
22
from datetime import date
3-
from enum import Enum, auto
3+
from enum import Enum, StrEnum, auto
44
from functools import total_ordering
55
from typing import NewType, Self
66

@@ -13,7 +13,7 @@
1313
RuleResult = NewType("RuleResult", str)
1414

1515

16-
class RuleType(str, Enum):
16+
class RuleType(StrEnum):
1717
filter = "F"
1818
suppression = "S"
1919
redirect = "R"

src/eligibility_signposting_api/model/rules.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import typing
44
from datetime import UTC, date, datetime
5-
from enum import Enum
5+
from enum import StrEnum
66
from functools import cached_property
77
from operator import attrgetter
88
from typing import Literal, NewType
@@ -29,13 +29,13 @@
2929
CohortLabel = NewType("CohortLabel", str)
3030

3131

32-
class RuleType(str, Enum):
32+
class RuleType(StrEnum):
3333
filter = "F"
3434
suppression = "S"
3535
redirect = "R"
3636

3737

38-
class RuleOperator(str, Enum):
38+
class RuleOperator(StrEnum):
3939
equals = "="
4040
gt = ">"
4141
lt = "<"
@@ -73,7 +73,7 @@ class RuleOperator(str, Enum):
7373
year_gt = "Y>"
7474

7575

76-
class RuleAttributeLevel(str, Enum):
76+
class RuleAttributeLevel(StrEnum):
7777
PERSON = "PERSON"
7878
TARGET = "TARGET"
7979
COHORT = "COHORT"

src/eligibility_signposting_api/views/response_model/eligibility.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from enum import Enum
2+
from enum import StrEnum
33
from typing import NewType
44

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

1919

20-
class Status(str, Enum):
20+
class Status(StrEnum):
2121
not_eligible = "NotEligible"
2222
not_actionable = "NotActionable"
2323
actionable = "Actionable"
2424

2525

26-
class RuleType(str, Enum):
26+
class RuleType(StrEnum):
2727
filter = "F"
2828
suppression = "S"
2929
redirect = "R"

tests/fixtures/builders/model/rule.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from polyfactory.factories.pydantic_factory import ModelFactory
66

77
from eligibility_signposting_api.model import rules
8+
from eligibility_signposting_api.model.rules import RulePriority
89

910

1011
def past_date(days_behind: int = 365) -> date:
@@ -38,6 +39,7 @@ class PersonAgeSuppressionRuleFactory(IterationRuleFactory):
3839
type = rules.RuleType.suppression
3940
name = "Exclude too young less than 75"
4041
description = "Exclude too young less than 75"
42+
priority = RulePriority(10)
4143
operator = rules.RuleOperator.year_gt
4244
attribute_level = rules.RuleAttributeLevel.PERSON
4345
attribute_name = "DATE_OF_BIRTH"

tests/unit/services/test_eligibility_services.py

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from hamcrest import assert_that, empty, has_item
88

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

3030
# When
31-
actual = ps.get_eligibility_status(NHSNumber("1234567890"))
31+
actual = service.get_eligibility_status(NHSNumber("1234567890"))
3232

3333
# Then
3434
assert_that(actual, is_eligibility_status().with_conditions(empty()))
@@ -39,11 +39,11 @@ def test_eligibility_service_for_nonexistent_nhs_number():
3939
eligibility_repo = MagicMock(spec=EligibilityRepo)
4040
rules_repo = MagicMock(spec=RulesRepo)
4141
eligibility_repo.get_eligibility_data = MagicMock(side_effect=NotFoundError)
42-
ps = EligibilityService(eligibility_repo, rules_repo)
42+
service = EligibilityService(eligibility_repo, rules_repo)
4343

4444
# When
4545
with pytest.raises(UnknownPersonError):
46-
ps.get_eligibility_status(NHSNumber("1234567890"))
46+
service.get_eligibility_status(NHSNumber("1234567890"))
4747

4848

4949
def test_not_base_eligible(faker: Faker):
@@ -69,10 +69,10 @@ def test_not_base_eligible(faker: Faker):
6969
]
7070
)
7171

72-
ps = EligibilityService(eligibility_repo, rules_repo)
72+
service = EligibilityService(eligibility_repo, rules_repo)
7373

7474
# When
75-
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
75+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
7676

7777
# Then
7878
assert_that(
@@ -123,10 +123,10 @@ def test_only_live_campaigns_considered(faker: Faker):
123123
]
124124
)
125125

126-
ps = EligibilityService(eligibility_repo, rules_repo)
126+
service = EligibilityService(eligibility_repo, rules_repo)
127127

128128
# When
129-
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
129+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
130130

131131
# Then
132132
assert_that(
@@ -161,10 +161,10 @@ def test_base_eligible_and_simple_rule_includes(faker: Faker):
161161
]
162162
)
163163

164-
ps = EligibilityService(eligibility_repo, rules_repo)
164+
service = EligibilityService(eligibility_repo, rules_repo)
165165

166166
# When
167-
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
167+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
168168

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

183183
eligibility_repo = MagicMock(spec=EligibilityRepo)
184184
rules_repo = MagicMock(spec=RulesRepo)
@@ -199,10 +199,10 @@ def test_base_eligible_but_simple_rule_excludes(faker: Faker):
199199
]
200200
)
201201

202-
ps = EligibilityService(eligibility_repo, rules_repo)
202+
service = EligibilityService(eligibility_repo, rules_repo)
203203

204204
# When
205-
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
205+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
206206

207207
# Then
208208
assert_that(
@@ -252,10 +252,10 @@ def test_simple_rule_only_excludes_from_live_iteration(faker: Faker):
252252
]
253253
)
254254

255-
ps = EligibilityService(eligibility_repo, rules_repo)
255+
service = EligibilityService(eligibility_repo, rules_repo)
256256

257257
# When
258-
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
258+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
259259

260260
# Then
261261
assert_that(
@@ -287,10 +287,98 @@ def test_campaign_with_no_active_iteration_not_considered(faker: Faker):
287287
]
288288
)
289289

290-
ps = EligibilityService(eligibility_repo, rules_repo)
290+
service = EligibilityService(eligibility_repo, rules_repo)
291291

292292
# When
293-
actual = ps.get_eligibility_status(NHSNumber(nhs_number))
293+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
294294

295295
# Then
296296
assert_that(actual, is_eligibility_status().with_conditions(empty()))
297+
298+
299+
@pytest.mark.parametrize(
300+
("rule_type", "expected_status"),
301+
[
302+
(RuleType.suppression, Status.not_actionable),
303+
(RuleType.filter, Status.not_eligible),
304+
(RuleType.redirect, Status.actionable),
305+
],
306+
)
307+
def test_rule_types_cause_correct_statuses(rule_type: RuleType, expected_status: Status, faker: Faker):
308+
# Given
309+
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
310+
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))
311+
312+
eligibility_repo = MagicMock(spec=EligibilityRepo)
313+
rules_repo = MagicMock(spec=RulesRepo)
314+
eligibility_repo.get_eligibility_data = MagicMock(
315+
return_value=eligibility_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
316+
)
317+
rules_repo.get_campaign_configs = MagicMock(
318+
return_value=[
319+
rule_builder.CampaignConfigFactory.build(
320+
target="RSV",
321+
iterations=[
322+
rule_builder.IterationFactory.build(
323+
iteration_rules=[rule_builder.PersonAgeSuppressionRuleFactory.build(type=rule_type)],
324+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
325+
)
326+
],
327+
)
328+
]
329+
)
330+
331+
service = EligibilityService(eligibility_repo, rules_repo)
332+
333+
# When
334+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
335+
336+
# Then
337+
assert_that(
338+
actual,
339+
is_eligibility_status().with_conditions(
340+
has_item(is_condition().with_condition_name(ConditionName("RSV")).and_status(expected_status))
341+
),
342+
)
343+
344+
345+
def test_multiple_rule_types_cause_correct_status(faker: Faker):
346+
# Given
347+
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
348+
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))
349+
350+
eligibility_repo = MagicMock(spec=EligibilityRepo)
351+
rules_repo = MagicMock(spec=RulesRepo)
352+
eligibility_repo.get_eligibility_data = MagicMock(
353+
return_value=eligibility_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
354+
)
355+
rules_repo.get_campaign_configs = MagicMock(
356+
return_value=[
357+
rule_builder.CampaignConfigFactory.build(
358+
target="RSV",
359+
iterations=[
360+
rule_builder.IterationFactory.build(
361+
iteration_rules=[
362+
rule_builder.PersonAgeSuppressionRuleFactory.build(type=RuleType.suppression),
363+
rule_builder.PersonAgeSuppressionRuleFactory.build(type=RuleType.filter),
364+
rule_builder.PersonAgeSuppressionRuleFactory.build(type=RuleType.suppression),
365+
],
366+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
367+
)
368+
],
369+
)
370+
]
371+
)
372+
373+
service = EligibilityService(eligibility_repo, rules_repo)
374+
375+
# When
376+
actual = service.get_eligibility_status(NHSNumber(nhs_number))
377+
378+
# Then
379+
assert_that(
380+
actual,
381+
is_eligibility_status().with_conditions(
382+
has_item(is_condition().with_condition_name(ConditionName("RSV")).and_status(Status.not_eligible))
383+
),
384+
)

0 commit comments

Comments
 (0)