Skip to content

Commit 888ff49

Browse files
Merge pull request #93 from NHSDigital/test/adw-eli-25-additional-icb-null-test
Test/adw eli 25 additional icb null test
2 parents b9b05b6 + 19a2997 commit 888ff49

File tree

7 files changed

+167
-34
lines changed

7 files changed

+167
-34
lines changed

tests/conftest.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import string
2+
from random import choice, randint
3+
14
import pytest
25
from faker import Faker
6+
from faker.providers import BaseProvider
37
from flask import Flask
48
from flask.testing import FlaskClient
59

@@ -18,4 +22,41 @@ def client(app) -> FlaskClient:
1822

1923
@pytest.fixture(scope="session")
2024
def faker() -> Faker:
21-
return Faker("en_UK")
25+
faker = Faker("en_UK")
26+
faker.add_provider(PersonDetailProvider)
27+
return faker
28+
29+
30+
class PersonDetailProvider(BaseProvider):
31+
def nhs_number(self) -> str:
32+
return f"5{randint(1, 999999999):09}"
33+
34+
def icb(self) -> str | None:
35+
if randint(0, 3):
36+
return f"{choice(string.ascii_uppercase)}{choice(string.ascii_uppercase)}{choice(string.digits)}"
37+
return None
38+
39+
def gp_practice(self) -> str | None:
40+
if randint(0, 3):
41+
return f"{choice(string.ascii_uppercase)}{randint(1, 99999):05}"
42+
return None
43+
44+
def pcn(self) -> str | None:
45+
if randint(0, 3):
46+
return f"{choice(string.ascii_uppercase)}{randint(1, 99999):05}"
47+
return None
48+
49+
def comissioning_region(self) -> str | None:
50+
if randint(0, 3):
51+
return f"{choice(string.ascii_uppercase)}{randint(1, 99):02}"
52+
return None
53+
54+
def msoa(self) -> str | None:
55+
if randint(0, 3):
56+
return f"{choice(string.ascii_uppercase)}{randint(1, 99999999):08}"
57+
return None
58+
59+
def lsoa(self) -> str | None:
60+
if randint(0, 3):
61+
return f"{choice(string.ascii_uppercase)}{randint(1, 99999999):08}"
62+
return None

tests/fixtures/builders/model/rule.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,14 @@ class PostcodeSuppressionRuleFactory(IterationRuleFactory):
5454
attribute_level = rules.RuleAttributeLevel.PERSON
5555
attribute_name = rules.RuleAttributeName("POSTCODE")
5656
comparator = rules.RuleComparator("SW19")
57+
58+
59+
class ICBSuppressionRuleFactory(IterationRuleFactory):
60+
type = rules.RuleType.filter
61+
name = rules.RuleName("Not in QE1")
62+
description = rules.RuleDescription("Not in QE1")
63+
priority = rules.RulePriority(10)
64+
operator = rules.RuleOperator.ne
65+
attribute_level = rules.RuleAttributeLevel.PERSON
66+
attribute_name = rules.RuleAttributeName("ICB")
67+
comparator = rules.RuleComparator("QE1")
Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,67 @@
1-
import random
21
from collections.abc import Sequence
3-
from typing import Any
2+
from datetime import date
3+
from random import choice, shuffle
4+
from typing import Any, Literal, get_args
45

56
from faker import Faker
67

7-
from eligibility_signposting_api.model import eligibility
8+
from tests.conftest import PersonDetailProvider
89

10+
Gender = Literal["0", "1", "2", "9"] # 0 - Not known, 1- Male, 2 - Female, 9 - Not specified. I know, right?
911

10-
def person_rows_builder(
11-
nhs_number: eligibility.NHSNumber,
12+
13+
def person_rows_builder( # noqa:PLR0913
14+
nhs_number: str,
1215
*,
13-
date_of_birth: eligibility.DateOfBirth | None = None,
14-
postcode: eligibility.Postcode | None = None,
15-
cohorts: Sequence[str] | None = None,
16-
vaccines: Sequence[str] | None = None,
16+
date_of_birth: date | None = ...,
17+
gender: Gender | None = ...,
18+
postcode: str | None = ...,
19+
cohorts: Sequence[str] | None = ...,
20+
vaccines: Sequence[tuple[str, date]] | None = ...,
21+
icb: str | None = ...,
22+
gp_practice: str | None = ...,
23+
pcn: str | None = ...,
24+
comissioning_region: str | None = ...,
25+
thirteen_q: bool | None = ...,
26+
care_home: bool | None = ...,
27+
de: bool | None = ...,
28+
msoa: str | None = ...,
29+
lsoa: str | None = ...,
1730
) -> list[dict[str, Any]]:
1831
faker = Faker("en_UK")
32+
faker.add_provider(PersonDetailProvider)
1933

2034
key = f"PERSON#{nhs_number}"
21-
date_of_birth = date_of_birth or eligibility.DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=99))
22-
postcode = postcode or eligibility.Postcode(faker.postcode())
23-
cohorts = cohorts if cohorts is not None else ["cohort-a", "cohort-b"]
24-
vaccines = vaccines if vaccines is not None else ["RSV", "COVID"]
35+
date_of_birth = date_of_birth if date_of_birth is not ... else faker.date_of_birth(minimum_age=18, maximum_age=99)
36+
gender = gender if gender is not ... else choice(get_args(Gender))
37+
postcode = postcode if postcode is not ... else faker.postcode()
38+
cohorts = cohorts if cohorts is not ... else ["cohort-a", "cohort-b"]
39+
vaccines = vaccines if vaccines is not ... else [("RSV", faker.past_date("-5y")), ("COVID", faker.past_date("-5y"))]
40+
icb = icb if icb is not ... else faker.icb()
41+
gp_practice = gp_practice if gp_practice is not ... else faker.gp_practice()
42+
pcn = pcn if pcn is not ... else faker.pcn()
43+
comissioning_region = comissioning_region if comissioning_region is not ... else faker.comissioning_region()
44+
thirteen_q = thirteen_q if thirteen_q is not ... else faker.boolean()
45+
care_home = care_home if care_home is not ... else faker.boolean()
46+
de = de if de is not ... else faker.boolean()
47+
msoa = msoa if msoa is not ... else faker.msoa()
48+
lsoa = lsoa if lsoa is not ... else faker.lsoa()
2549
rows: list[dict[str, Any]] = [
2650
{
2751
"NHS_NUMBER": key,
2852
"ATTRIBUTE_TYPE": "PERSON",
2953
"DATE_OF_BIRTH": date_of_birth.strftime("%Y%m%d"),
54+
"GENDER": gender,
3055
"POSTCODE": postcode,
56+
"ICB": icb,
57+
"GP_PRACTICE": gp_practice,
58+
"PCN": pcn,
59+
"COMISSIONING_REGION": comissioning_region,
60+
"13Q_FLAG": "Y" if thirteen_q else "N",
61+
"CARE_HOME_FLAG": "Y" if care_home else "N",
62+
"DE_FLAG": "Y" if de else "N",
63+
"MSOA": msoa,
64+
"LSOA": lsoa,
3165
},
3266
{
3367
"NHS_NUMBER": key,
@@ -45,11 +79,12 @@ def person_rows_builder(
4579
{
4680
"NHS_NUMBER": key,
4781
"ATTRIBUTE_TYPE": vaccine,
48-
"LAST_SUCCESSFUL_DATE": faker.past_date().strftime("%Y%m%d"),
49-
"OPTOUT": random.choice(["Y", "N"]),
50-
"LAST_INVITE_DATE": faker.past_date().strftime("%Y%m%d"),
82+
"LAST_SUCCESSFUL_DATE": last_successful_date.strftime("%Y%m%d"),
83+
"OPTOUT": choice(["Y", "N"]),
84+
"LAST_INVITE_DATE": faker.past_date("-5y").strftime("%Y%m%d"),
5185
}
52-
for vaccine in vaccines
86+
for vaccine, last_successful_date in vaccines
5387
)
5488

89+
shuffle(rows)
5590
return rows

tests/integration/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def person_table(dynamodb_resource: ServiceResource) -> Generator[Any]:
215215

216216
@pytest.fixture
217217
def persisted_person(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]:
218-
nhs_number = eligibility.NHSNumber(f"5{faker.random_int(max=999999999):09d}")
218+
nhs_number = eligibility.NHSNumber(faker.nhs_number())
219219
date_of_birth = eligibility.DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=65))
220220

221221
for row in (rows := person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])):
@@ -229,7 +229,7 @@ def persisted_person(person_table: Any, faker: Faker) -> Generator[eligibility.N
229229

230230
@pytest.fixture
231231
def persisted_77yo_person(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]:
232-
nhs_number = eligibility.NHSNumber(f"5{faker.random_int(max=999999999):09d}")
232+
nhs_number = eligibility.NHSNumber(faker.nhs_number())
233233
date_of_birth = eligibility.DateOfBirth(faker.date_of_birth(minimum_age=77, maximum_age=77))
234234

235235
for row in (rows := person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1", "cohort2"])):

tests/integration/lambda/test_app_running_as_lambda.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def test_install_and_call_flask_lambda_with_unknown_nhs_number(
9494
):
9595
"""Given lambda installed into localstack, run it via http, with a nonexistent NHS number specified"""
9696
# Given
97-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
97+
nhs_number = NHSNumber(faker.nhs_number())
9898

9999
# When
100100
response = httpx.get(str(flask_function_url / "eligibility" / nhs_number))

tests/integration/repo/test_person_repo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_person_found(person_table: Any, persisted_person: NHSNumber):
3030

3131
def test_person_not_found(person_table: Any, faker: Faker):
3232
# Given
33-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
33+
nhs_number = NHSNumber(faker.nhs_number())
3434
repo = PersonRepo(person_table)
3535

3636
# When, Then

tests/unit/services/calculators/test_eligibility_calculator.py

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
def test_not_base_eligible(faker: Faker):
1717
# Given
18-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
18+
nhs_number = NHSNumber(faker.nhs_number())
1919

2020
person_rows = person_rows_builder(nhs_number, cohorts=["cohort1"])
2121
campaign_configs = [
@@ -48,7 +48,7 @@ def test_not_base_eligible(faker: Faker):
4848
@freeze_time("2025-04-25")
4949
def test_only_live_campaigns_considered(faker: Faker):
5050
# Given
51-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
51+
nhs_number = NHSNumber(faker.nhs_number())
5252

5353
person_rows = person_rows_builder(nhs_number, cohorts=["cohort1"])
5454
campaign_configs = [
@@ -95,7 +95,7 @@ def test_only_live_campaigns_considered(faker: Faker):
9595

9696
def test_base_eligible_and_simple_rule_includes(faker: Faker):
9797
# Given
98-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
98+
nhs_number = NHSNumber(faker.nhs_number())
9999
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=76, maximum_age=79))
100100

101101
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -127,7 +127,7 @@ def test_base_eligible_and_simple_rule_includes(faker: Faker):
127127

128128
def test_base_eligible_but_simple_rule_excludes(faker: Faker):
129129
# Given
130-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
130+
nhs_number = NHSNumber(faker.nhs_number())
131131
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))
132132

133133
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -160,7 +160,7 @@ def test_base_eligible_but_simple_rule_excludes(faker: Faker):
160160
@freeze_time("2025-04-25")
161161
def test_simple_rule_only_excludes_from_live_iteration(faker: Faker):
162162
# Given
163-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
163+
nhs_number = NHSNumber(faker.nhs_number())
164164
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=66, maximum_age=74))
165165

166166
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -207,7 +207,7 @@ def test_simple_rule_only_excludes_from_live_iteration(faker: Faker):
207207
@freeze_time("2025-04-25")
208208
def test_campaign_with_no_active_iteration_not_considered(faker: Faker):
209209
# Given
210-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
210+
nhs_number = NHSNumber(faker.nhs_number())
211211

212212
person_rows = person_rows_builder(nhs_number)
213213
campaign_configs = [
@@ -240,7 +240,7 @@ def test_campaign_with_no_active_iteration_not_considered(faker: Faker):
240240
)
241241
def test_rule_types_cause_correct_statuses(rule_type: rules_model.RuleType, expected_status: Status, faker: Faker):
242242
# Given
243-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
243+
nhs_number = NHSNumber(faker.nhs_number())
244244
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=74))
245245

246246
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -272,7 +272,7 @@ def test_rule_types_cause_correct_statuses(rule_type: rules_model.RuleType, expe
272272

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

278278
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -369,7 +369,7 @@ def test_rules_with_same_priority_must_all_match_to_exclude(
369369
faker: Faker,
370370
):
371371
# Given
372-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
372+
nhs_number = NHSNumber(faker.nhs_number())
373373
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=66, maximum_age=74))
374374

375375
person_rows = person_rows_builder(
@@ -404,7 +404,7 @@ def test_rules_with_same_priority_must_all_match_to_exclude(
404404

405405
def test_multiple_conditions_where_both_are_actionable(faker: Faker):
406406
# Given
407-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
407+
nhs_number = NHSNumber(faker.nhs_number())
408408
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=76, maximum_age=78))
409409

410410
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -448,7 +448,7 @@ def test_multiple_conditions_where_both_are_actionable(faker: Faker):
448448

449449
def test_multiple_conditions_where_all_give_unique_statuses(faker: Faker):
450450
# Given
451-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
451+
nhs_number = NHSNumber(faker.nhs_number())
452452
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=76, maximum_age=78))
453453

454454
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -551,7 +551,7 @@ def test_multiple_campaigns_for_single_condition(
551551
test_comment: str, campaign1: rules_model.CampaignConfig, campaign2: rules_model.CampaignConfig, faker: Faker
552552
):
553553
# Given
554-
nhs_number = NHSNumber(f"5{faker.random_int(max=999999999):09d}")
554+
nhs_number = NHSNumber(faker.nhs_number())
555555
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=76, maximum_age=78))
556556

557557
person_rows = person_rows_builder(nhs_number, date_of_birth=date_of_birth, cohorts=["cohort1"])
@@ -570,3 +570,49 @@ def test_multiple_campaigns_for_single_condition(
570570
),
571571
test_comment,
572572
)
573+
574+
575+
@pytest.mark.parametrize(
576+
("icb", "rule_type", "expected_status"),
577+
[
578+
("QE1", rules_model.RuleType.suppression, Status.actionable),
579+
("QWU", rules_model.RuleType.suppression, Status.not_actionable),
580+
("", rules_model.RuleType.suppression, Status.not_actionable),
581+
(None, rules_model.RuleType.suppression, Status.not_actionable),
582+
("QE1", rules_model.RuleType.filter, Status.actionable),
583+
("QWU", rules_model.RuleType.filter, Status.not_eligible),
584+
("", rules_model.RuleType.filter, Status.not_eligible),
585+
(None, rules_model.RuleType.filter, Status.not_eligible),
586+
],
587+
)
588+
def test_base_eligible_and_icb_example(
589+
icb: str | None, rule_type: rules_model.RuleType, expected_status: Status, faker: Faker
590+
):
591+
# Given
592+
nhs_number = NHSNumber(faker.nhs_number())
593+
594+
person_rows = person_rows_builder(nhs_number, cohorts=["cohort1"], icb=icb)
595+
campaign_configs = [
596+
rule_builder.CampaignConfigFactory.build(
597+
target="RSV",
598+
iterations=[
599+
rule_builder.IterationFactory.build(
600+
iteration_rules=[rule_builder.ICBSuppressionRuleFactory.build(type=rule_type)],
601+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
602+
)
603+
],
604+
)
605+
]
606+
607+
calculator = EligibilityCalculator(person_rows, campaign_configs)
608+
609+
# When
610+
actual = calculator.evaluate_eligibility()
611+
612+
# Then
613+
assert_that(
614+
actual,
615+
is_eligibility_status().with_conditions(
616+
has_item(is_condition().with_condition_name(ConditionName("RSV")).and_status(expected_status))
617+
),
618+
)

0 commit comments

Comments
 (0)