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
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ def is_eligible(
) -> bool:
is_eligible = True
priority_getter = attrgetter("priority")
sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, filter_rules), key=priority_getter)
sorted_rules_by_priority = sorted(filter_rules, key=priority_getter)

for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
status, group_exclusion_reasons, _ = self.evaluate_rules_priority_group(person, rule_group)
group_rules = list(rule_group)
if self._should_skip_rule_group(cohort, group_rules):
continue
status, group_exclusion_reasons, _ = self.evaluate_rules_priority_group(person, iter(group_rules))
if status.is_exclusion:
if cohort.cohort_label is not None:
cohort_results[cohort.cohort_label] = CohortGroupResult(
Expand All @@ -65,6 +68,7 @@ def is_eligible(
)
is_eligible = False
break

return is_eligible

def is_actionable(
Expand All @@ -78,10 +82,14 @@ def is_actionable(
priority_getter = attrgetter("priority")
suppression_reasons = []

sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, suppression_rules), key=priority_getter)
sorted_rules_by_priority = sorted(suppression_rules, key=priority_getter)

for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
status, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(person, rule_group)
group_rules = list(rule_group)
if self._should_skip_rule_group(cohort, group_rules):
continue

status, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(person, iter(group_rules))
if status.is_exclusion:
is_actionable = False
suppression_reasons.extend(group_exclusion_reasons)
Expand All @@ -103,6 +111,12 @@ def is_actionable(
suppression_reasons,
)

@staticmethod
def _should_skip_rule_group(cohort: IterationCohort, group_rules: list[IterationRule]) -> bool:
cohort_specific_rules = [rule for rule in group_rules if rule.cohort_label is not None]
matching_specific_rules = [rule for rule in cohort_specific_rules if rule.cohort_label == cohort.cohort_label]
return bool(cohort_specific_rules and not matching_specific_rules)

def evaluate_rules_priority_group(
self, person: Person, rules_group: Iterator[IterationRule]
) -> tuple[eligibility_status.Status, list[eligibility_status.Reason], bool]:
Expand Down
37 changes: 37 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,43 @@ def campaign_config(s3_client: BaseClient, rules_bucket: BucketName) -> Generato
s3_client.delete_object(Bucket=rules_bucket, Key=f"{campaign.name}.json")


@pytest.fixture(scope="class")
def campaign_config_with_and_rule(s3_client: BaseClient, rules_bucket: BucketName) -> Generator[CampaignConfig]:
campaign: CampaignConfig = rule.CampaignConfigFactory.build(
target="RSV",
iterations=[
rule.IterationFactory.build(
iteration_rules=[
rule.PostcodeSuppressionRuleFactory.build(
cohort_label="cohort2",
),
rule.PersonAgeSuppressionRuleFactory.build(),
],
iteration_cohorts=[
rule.IterationCohortFactory.build(
cohort_label="cohort1",
cohort_group="cohort_group1",
positive_description="positive_description",
negative_description="negative_description",
),
rule.IterationCohortFactory.build(
cohort_label="cohort2",
cohort_group="cohort_group2",
positive_description="positive_description",
negative_description="negative_description",
),
],
)
],
)
campaign_data = {"CampaignConfig": campaign.model_dump(by_alias=True)}
s3_client.put_object(
Bucket=rules_bucket, Key=f"{campaign.name}.json", Body=json.dumps(campaign_data), ContentType="application/json"
)
yield campaign
s3_client.delete_object(Bucket=rules_bucket, Key=f"{campaign.name}.json")


@pytest.fixture(scope="class")
def multiple_campaign_configs(s3_client: BaseClient, rules_bucket: BucketName) -> Generator[list[CampaignConfig]]:
"""Create and upload multiple campaign configs to S3, then clean up after tests."""
Expand Down
51 changes: 51 additions & 0 deletions tests/integration/in_process/test_eligibility_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,57 @@ def test_actionable(
),
)

def test_actionable_with_and_rule(
self,
client: FlaskClient,
persisted_person: NHSNumber,
campaign_config_with_and_rule: CampaignConfig, # noqa: ARG002
):
# Given

# When
response = client.get(f"/patient-check/{persisted_person}?includeActions=Y")

# Then
assert_that(
response,
is_response()
.with_status_code(HTTPStatus.OK)
.and_text(
is_json_that(
has_entry(
"processedSuggestions",
equal_to(
[
{
"condition": "RSV",
"status": "Actionable",
"eligibilityCohorts": [
{
"cohortCode": "cohort_group1",
"cohortStatus": "Actionable",
"cohortText": "positive_description",
}
],
"actions": [
{
"actionCode": "action_code",
"actionType": "defaultcomms",
"description": "",
"urlLabel": "",
"urlLink": "",
}
],
"suitabilityRules": [],
"statusText": "You should have the RSV vaccine",
}
]
),
)
)
),
)


class TestMagicCohortResponse:
def test_not_eligible_by_rule_when_only_magic_cohort_is_present(
Expand Down
Loading