Skip to content

Commit 3376775

Browse files
authored
Merge branch 'main' into feature/eja-eli-413-add-extra-tags
2 parents 14abb4a + 08a808e commit 3376775

File tree

8 files changed

+152
-49
lines changed

8 files changed

+152
-49
lines changed

.github/workflows/cicd-2-publish.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
branches:
1010
- main
1111
- hotfix/*
12+
workflow_dispatch: {}
1213

1314
concurrency:
1415
group: terraform-dev

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ localstack = "^4.8.1"
5757
pytest-docker = "^3.2.3"
5858
stamina = "^25.1.0"
5959
pytest-freezer = "^0.4.9"
60-
moto = "^5.1.9"
60+
moto = "^5.1.13"
6161
requests = "^2.32.5"
6262
jsonschema = "^4.25.1"
6363
behave = "^1.2.6"

src/eligibility_signposting_api/services/processors/rule_processor.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ class RuleProcessor:
3939

4040
def is_base_eligible(self, person: Person, cohort: IterationCohort) -> bool:
4141
if cohort.is_virtual_cohort:
42-
for row in person.data:
43-
if row.get("ATTRIBUTE_TYPE", "") == "COHORTS":
44-
row["COHORT_MEMBERSHIPS"].append({"COHORT_LABEL": cohort.cohort_label})
42+
cohorts_data = next((row for row in person.data if row.get("ATTRIBUTE_TYPE") == "COHORTS"), None)
43+
44+
if cohorts_data is None:
45+
cohorts_data = {"ATTRIBUTE_TYPE": "COHORTS", "COHORT_MEMBERSHIPS": []}
46+
person.data.append(cohorts_data)
47+
48+
cohorts_data.setdefault("COHORT_MEMBERSHIPS", []).append({"COHORT_LABEL": cohort.cohort_label})
4549

4650
person_cohorts = self.person_data_reader.get_person_cohorts(person)
4751

tests/fixtures/builders/repos/person.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def person_rows_builder( # noqa:PLR0913
1818
gender: Gender | None = ...,
1919
postcode: str | None = ...,
2020
cohorts: Sequence[str] | None = ...,
21-
vaccines: Sequence[tuple[str, date]] | None = ...,
21+
vaccines: dict[str, dict[str, str | None]] | None = ...,
2222
icb: str | None = ...,
2323
gp_practice: str | None = ...,
2424
pcn: str | None = ...,
@@ -36,8 +36,6 @@ def person_rows_builder( # noqa:PLR0913
3636
date_of_birth = date_of_birth if date_of_birth is not ... else faker.date_of_birth(minimum_age=18, maximum_age=99)
3737
gender = gender if gender is not ... else choice(get_args(Gender))
3838
postcode = postcode if postcode is not ... else faker.postcode()
39-
cohorts = cohorts if cohorts is not ... else ["cohort-a", "cohort-b"]
40-
vaccines = vaccines if vaccines is not ... else [("RSV", faker.past_date("-5y")), ("COVID", faker.past_date("-5y"))]
4139
icb = icb if icb is not ... else faker.icb()
4240
gp_practice = gp_practice if gp_practice is not ... else faker.gp_practice()
4341
pcn = pcn if pcn is not ... else faker.pcn()
@@ -64,26 +62,28 @@ def person_rows_builder( # noqa:PLR0913
6462
"MSOA": msoa,
6563
"LSOA": lsoa,
6664
},
67-
{
68-
"NHS_NUMBER": key,
69-
"ATTRIBUTE_TYPE": "COHORTS",
70-
"COHORT_MEMBERSHIPS": [
71-
{"COHORT_LABEL": cohort, "DATE_JOINED": faker.past_date().strftime("%Y%m%d")} for cohort in cohorts
72-
],
73-
},
7465
]
75-
rows.extend(
76-
{
77-
"NHS_NUMBER": key,
78-
"ATTRIBUTE_TYPE": vaccine,
79-
"LAST_SUCCESSFUL_DATE": (
80-
last_successful_date.strftime("%Y%m%d") if last_successful_date else last_successful_date
81-
),
82-
"OPTOUT": choice(["Y", "N"]),
83-
"LAST_INVITE_DATE": faker.past_date("-5y").strftime("%Y%m%d"),
84-
}
85-
for vaccine, last_successful_date in vaccines
86-
)
66+
67+
if cohorts is not ... and cohorts:
68+
rows.append(
69+
{
70+
"NHS_NUMBER": key,
71+
"ATTRIBUTE_TYPE": "COHORTS",
72+
"COHORT_MEMBERSHIPS": [
73+
{"COHORT_LABEL": cohort, "DATE_JOINED": faker.past_date().strftime("%Y%m%d")} for cohort in cohorts
74+
],
75+
}
76+
)
77+
78+
if vaccines is not ... and vaccines:
79+
rows.extend(
80+
{
81+
"NHS_NUMBER": key,
82+
"ATTRIBUTE_TYPE": vaccine_name,
83+
**details,
84+
}
85+
for vaccine_name, details in vaccines.items()
86+
)
8787

8888
shuffle(rows)
8989

tests/integration/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def person_with_all_data(person_table: Any, faker: Faker) -> Generator[eligibili
409409
gender="0",
410410
postcode="SW18",
411411
cohorts=["cohort_label1", "cohort_label2"],
412-
vaccines=[("RSV", None)],
412+
vaccines={"RSV": {"LAST_SUCCESSFUL_DATE": None}},
413413
icb="QE1",
414414
gp_practice="C81002",
415415
pcn="U78207",

tests/integration/repo/test_person_repo.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ def test_person_found(person_table: Any, persisted_person: NHSNumber):
2222
contains_inanyorder(
2323
has_entries({"NHS_NUMBER": persisted_person, "ATTRIBUTE_TYPE": "PERSON"}),
2424
has_entries({"NHS_NUMBER": persisted_person, "ATTRIBUTE_TYPE": "COHORTS"}),
25-
has_entries({"NHS_NUMBER": persisted_person, "ATTRIBUTE_TYPE": "COVID"}),
26-
has_entries({"NHS_NUMBER": persisted_person, "ATTRIBUTE_TYPE": "RSV"}),
2725
),
2826
)
2927

tests/unit/services/calculators/test_eligibility_calculator.py

Lines changed: 116 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -329,16 +329,7 @@ def test_status_on_target_based_on_last_successful_date(
329329
nhs_number = NHSNumber(faker.nhs_number())
330330

331331
target_rows = person_rows_builder(
332-
nhs_number,
333-
cohorts=["cohort1"],
334-
vaccines=[
335-
(
336-
vaccine,
337-
datetime.datetime.strptime(last_successful_date, "%Y%m%d").replace(tzinfo=datetime.UTC)
338-
if last_successful_date
339-
else None,
340-
)
341-
],
332+
nhs_number, cohorts=["cohort1"], vaccines={vaccine: {"LAST_SUCCESSFUL_DATE": last_successful_date}}
342333
)
343334

344335
campaign_configs = [
@@ -728,7 +719,7 @@ def test_cohort_group_descriptions_are_selected_based_on_priority_when_cohorts_h
728719
@freeze_time("2025-04-25")
729720
def test_no_active_iteration_returns_empty_conditions_with_single_active_campaign(faker: Faker):
730721
# Given
731-
person_rows = person_rows_builder(NHSNumber(faker.nhs_number()))
722+
person_rows = person_rows_builder(NHSNumber(faker.nhs_number()), cohorts=[])
732723
campaign_configs = [
733724
rule_builder.CampaignConfigFactory.build(
734725
target="RSV",
@@ -757,7 +748,7 @@ def test_no_active_iteration_returns_empty_conditions_with_single_active_campaig
757748
@freeze_time("2025-04-25")
758749
def test_returns_no_condition_data_for_campaign_without_active_iteration(faker: Faker, caplog):
759750
# Given
760-
person_rows = person_rows_builder(NHSNumber(faker.nhs_number()))
751+
person_rows = person_rows_builder(NHSNumber(faker.nhs_number()), cohorts=[])
761752
campaign_configs = [
762753
rule_builder.CampaignConfigFactory.build(
763754
target="RSV",
@@ -801,7 +792,7 @@ def test_returns_no_condition_data_for_campaign_without_active_iteration(faker:
801792
@freeze_time("2025-04-25")
802793
def test_no_active_campaign(faker: Faker):
803794
# Given
804-
person_rows = person_rows_builder(NHSNumber(faker.nhs_number()))
795+
person_rows = person_rows_builder(NHSNumber(faker.nhs_number()), cohorts=[])
805796
campaign_configs = [rule_builder.CampaignConfigFactory.build()]
806797
# Need to set the campaign dates to override CampaignConfigFactory.fix_iteration_date_invariants behavior
807798
campaign_configs[0].start_date = datetime.date(2025, 5, 10)
@@ -824,7 +815,7 @@ def test_eligibility_status_replaces_tokens_with_attribute_data(faker: Faker):
824815
nhs_number,
825816
date_of_birth=date_of_birth,
826817
cohorts=["cohort_1", "cohort_2", "cohort_3"],
827-
vaccines=[("RSV", datetime.date(2024, 1, 3))],
818+
vaccines={"RSV": {"LAST_SUCCESSFUL_DATE": datetime.date(2024, 1, 3).strftime("%Y%m%d")}},
828819
icb="QE1",
829820
gp_practice=None,
830821
)
@@ -1161,6 +1152,109 @@ def test_multiple_virtual_cohorts(faker: Faker):
11611152
)
11621153

11631154

1155+
@freeze_time("2025-10-02")
1156+
def test_virtual_cohorts_when_person_has_no_existing_cohorts(faker: Faker):
1157+
# Given
1158+
nhs_number = NHSNumber(faker.nhs_number())
1159+
date_of_birth = DateOfBirth(datetime.date(1980, 10, 2))
1160+
person_rows = person_rows_builder(
1161+
nhs_number,
1162+
date_of_birth=date_of_birth,
1163+
cohorts=[],
1164+
vaccines={
1165+
"RSV": {
1166+
"LAST_SUCCESSFUL_DATE": datetime.date(2025, 9, 25).strftime("%Y%m%d"),
1167+
"BOOKED_APPOINTMENT_DATE": datetime.date(2025, 10, 9).strftime("%Y%m%d"),
1168+
},
1169+
},
1170+
)
1171+
campaign_configs = [
1172+
rule_builder.CampaignConfigFactory.build(
1173+
target="RSV",
1174+
iterations=[
1175+
rule_builder.IterationFactory.build(
1176+
iteration_cohorts=[
1177+
rule_builder.IterationCohortFactory.build(
1178+
cohort_label="rsv_75to79",
1179+
cohort_group="rsv_age",
1180+
positive_description="In rsv_75to79",
1181+
negative_description="Out rsv_75to79",
1182+
priority=0,
1183+
),
1184+
rule_builder.IterationCohortFactory.build(
1185+
cohort_label="rsv_80_since_02_Sept_2024",
1186+
cohort_group="rsv_age_catchup",
1187+
positive_description="In rsv_80_since_02_Sept_2024",
1188+
negative_description="Out rsv_80_since_02_Sept_2024",
1189+
priority=10,
1190+
),
1191+
rule_builder.IterationCohortFactory.build(
1192+
cohort_label="elid_all_people",
1193+
cohort_group="magic_cohort",
1194+
positive_description="In elid_all_people",
1195+
negative_description="Out elid_all_people",
1196+
priority=20,
1197+
virtual="Y",
1198+
),
1199+
],
1200+
iteration_rules=[
1201+
rule_builder.PersonAgeSuppressionRuleFactory.build(
1202+
attribute_level=RuleAttributeLevel.TARGET,
1203+
attribute_name="LAST_SUCCESSFUL_DATE",
1204+
attribute_target="RSV",
1205+
cohort_label="elid_all_people",
1206+
comparator="-25[[NVL:18000101]]",
1207+
description="Remove anyone NOT already vaccinated within the last 25 years",
1208+
name="Remove from magic cohort unless already vaccinated or have future booking",
1209+
operator=RuleOperator.year_lte,
1210+
priority=100,
1211+
type=RuleType.filter,
1212+
),
1213+
rule_builder.PersonAgeSuppressionRuleFactory.build(
1214+
attribute_level=RuleAttributeLevel.TARGET,
1215+
attribute_name="BOOKED_APPOINTMENT_DATE",
1216+
attribute_target="RSV",
1217+
cohort_label="elid_all_people",
1218+
comparator="0[[NVL:18000101]]",
1219+
description="Remove anyone without a future booking from magic cohort",
1220+
name="Remove from magic cohort unless already vaccinated or have future booking",
1221+
operator=RuleOperator.day_lt,
1222+
priority=110,
1223+
type=RuleType.filter,
1224+
),
1225+
rule_builder.PersonAgeSuppressionRuleFactory.build(
1226+
attribute_level=RuleAttributeLevel.TARGET,
1227+
attribute_name="LAST_SUCCESSFUL_DATE",
1228+
attribute_target="RSV",
1229+
comparator="-25[[NVL:18000101]]",
1230+
description="## You've had your RSV vaccination\n\nWe believe you had your vaccination.",
1231+
name="Already Vaccinated",
1232+
operator=RuleOperator.year_gte,
1233+
priority=200,
1234+
rule_stop=True,
1235+
type=RuleType.suppression,
1236+
),
1237+
],
1238+
)
1239+
],
1240+
)
1241+
]
1242+
1243+
calculator = EligibilityCalculator(person_rows, campaign_configs)
1244+
1245+
# When
1246+
actual = calculator.get_eligibility_status("Y", ["ALL"], "ALL")
1247+
1248+
assert_that(
1249+
actual,
1250+
is_eligibility_status().with_conditions(
1251+
has_items(
1252+
is_condition().with_condition_name(ConditionName("RSV")).and_status(Status.not_actionable),
1253+
)
1254+
),
1255+
)
1256+
1257+
11641258
def test_regardless_of_final_status_audit_all_types_of_cohort_status_rules(faker: Faker):
11651259
# Given
11661260
nhs_number = NHSNumber(faker.nhs_number())
@@ -1277,7 +1371,10 @@ def test_eligibility_status_with_invalid_tokens_raises_attribute_error(faker: Fa
12771371
date_of_birth = DateOfBirth(datetime.date(2025, 5, 10))
12781372

12791373
person_rows = person_rows_builder(
1280-
nhs_number, date_of_birth=date_of_birth, cohorts=["cohort_1"], vaccines=[("RSV", datetime.date(2024, 1, 3))]
1374+
nhs_number,
1375+
date_of_birth=date_of_birth,
1376+
cohorts=["cohort_1"],
1377+
vaccines={"RSV": {"LAST_SUCCESSFUL_DATE": datetime.date(2024, 1, 3).strftime("%Y%m%d")}},
12811378
)
12821379

12831380
target_attribute_token = "LAST_SUCCESSFUL_DATE: [[TARGET.RSV.LAST_SUCCESSFUL_DATE:INVALID_DATE_FORMAT(%d %B %Y)]]" # noqa: S105
@@ -1309,7 +1406,10 @@ def test_eligibility_status_with_invalid_person_attribute_name_raises_value_erro
13091406
date_of_birth = DateOfBirth(datetime.date(2025, 5, 10))
13101407

13111408
person_rows = person_rows_builder(
1312-
nhs_number, date_of_birth=date_of_birth, cohorts=["cohort_1"], vaccines=[("RSV", datetime.date(2024, 1, 3))]
1409+
nhs_number,
1410+
date_of_birth=date_of_birth,
1411+
cohorts=["cohort_1"],
1412+
vaccines={"RSV": {"LAST_SUCCESSFUL_DATE": datetime.date(2024, 1, 3).strftime("%Y%m%d")}},
13131413
)
13141414

13151415
target_attribute_token = "LAST_SUCCESSFUL_DATE: [[TARGET.RSV.ICECREAM]]" # noqa: S105

0 commit comments

Comments
 (0)