diff --git a/src/eligibility_signposting_api/audit/audit_context.py b/src/eligibility_signposting_api/audit/audit_context.py index 48f4389bf..58bb49578 100644 --- a/src/eligibility_signposting_api/audit/audit_context.py +++ b/src/eligibility_signposting_api/audit/audit_context.py @@ -24,6 +24,7 @@ ConditionName, IterationResult, MatchedActionDetail, + Reason, Status, SuggestedAction, ) @@ -63,9 +64,9 @@ def append_audit_condition( condition_name: ConditionName, best_iteration_result: BestIterationResult, action_detail: MatchedActionDetail, + cohort_results: list[CohortGroupResult], ) -> None: audit_eligibility_cohorts, audit_eligibility_cohort_groups, audit_actions = [], [], [] - audit_filter_rule, audit_suitability_rule, audit_action_rule = None, None, None best_active_iteration = best_iteration_result.active_iteration best_candidate = best_iteration_result.iteration_result best_cohort_results = best_iteration_result.cohort_results @@ -83,9 +84,15 @@ def append_audit_condition( ) ) - if result.audit_rules and best_candidate: - audit_filter_rule = AuditContext.create_audit_filter_rule(best_candidate, result) - audit_suitability_rule = AuditContext.create_audit_suitability_rule(best_candidate, result) + filter_audit_rules, suitability_audit_rules = [], [] + for result in cohort_results: + if result.status.name == Status.not_eligible.name: + filter_audit_rules.extend(result.audit_rules) + if result.status.name == Status.not_actionable.name: + suitability_audit_rules.extend(result.audit_rules) + + audit_filter_rule = AuditContext.create_audit_filter_rule(filter_audit_rules) + audit_suitability_rule = AuditContext.create_audit_suitability_rule(suitability_audit_rules) audit_action_rule = AuditContext.add_rule_name_and_priority_to_audit(best_candidate, action_detail) @@ -153,24 +160,46 @@ def create_audit_actions(suggested_actions: list[SuggestedAction] | None) -> lis return audit_actions @staticmethod - def create_audit_suitability_rule( - best_candidate: IterationResult, result: CohortGroupResult - ) -> AuditSuitabilityRule | None: - audit_suitability_rule = None - if best_candidate.status and best_candidate.status.name == Status.not_actionable.name: - audit_suitability_rule = AuditSuitabilityRule( - rule_priority=result.audit_rules[0].rule_priority, - rule_name=result.audit_rules[0].rule_name, - rule_message=result.audit_rules[0].rule_description, + def create_audit_suitability_rule(reasons: list[Reason]) -> list[AuditSuitabilityRule] | None: + unique_reasons = AuditContext.deduplicate_reasons(reasons) + + suitability_audit = [ + AuditSuitabilityRule( + rule_priority=rule.rule_priority, + rule_name=rule.rule_name, + rule_message=rule.rule_description, ) - return audit_suitability_rule + for rule in unique_reasons + ] + + return suitability_audit if suitability_audit else None @staticmethod - def create_audit_filter_rule(best_candidate: IterationResult, result: CohortGroupResult) -> AuditFilterRule | None: - audit_filter_rule = None - if best_candidate.status and best_candidate.status.name == Status.not_eligible.name: - audit_filter_rule = AuditFilterRule( - rule_priority=result.audit_rules[0].rule_priority, - rule_name=result.audit_rules[0].rule_name, - ) - return audit_filter_rule + def create_audit_filter_rule(reasons: list[Reason]) -> list[AuditFilterRule] | None: + unique_reasons = AuditContext.deduplicate_reasons(reasons) + + filter_audit = [ + AuditFilterRule(rule_priority=rule.rule_priority, rule_name=rule.rule_name) for rule in unique_reasons + ] + + return filter_audit if len(filter_audit) > 0 else None + + @staticmethod + def deduplicate_reasons(reasons: list[Reason]) -> list[Reason]: + unique_rule_codes = set() + deduplicated_reasons = [] + + for reason in reasons: + if reason.rule_name not in unique_rule_codes and reason.rule_description: + unique_rule_codes.add(reason.rule_name) + deduplicated_reasons.append( + Reason( + reason.rule_type, + reason.rule_name, + reason.rule_priority, + reason.rule_description, + reason.matcher_matched, + ) + ) + + return deduplicated_reasons diff --git a/src/eligibility_signposting_api/audit/audit_models.py b/src/eligibility_signposting_api/audit/audit_models.py index 5ce4e598f..2f1b0ee2d 100644 --- a/src/eligibility_signposting_api/audit/audit_models.py +++ b/src/eligibility_signposting_api/audit/audit_models.py @@ -78,8 +78,8 @@ class AuditCondition(CamelCaseBaseModel): status_text: str | None = None eligibility_cohorts: list[AuditEligibilityCohorts] | None = None eligibility_cohort_groups: list[AuditEligibilityCohortGroups] | None = None - filter_rules: AuditFilterRule | None = None - suitability_rules: AuditSuitabilityRule | None = None + filter_rules: list[AuditFilterRule] | None = None + suitability_rules: list[AuditSuitabilityRule] | None = None action_rule: AuditRedirectRule | None = None actions: list[AuditAction] | None = Field(default_factory=list) diff --git a/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py b/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py index 6ea60ec55..bfbcb5a2f 100644 --- a/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py +++ b/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py @@ -76,6 +76,7 @@ def get_the_best_cohort_memberships( def get_eligibility_status(self, include_actions: str, conditions: list[str], category: str) -> EligibilityStatus: include_actions_flag = include_actions.upper() == "Y" condition_results: dict[ConditionName, IterationResult] = {} + final_result = [] requested_grouped_campaigns = self.campaign_evaluator.get_requested_grouped_campaigns( self.campaign_configs, conditions, category @@ -93,10 +94,17 @@ def get_eligibility_status(self, include_actions: str, conditions: list[str], ca condition_results[condition_name] = best_iteration_result.iteration_result condition_results[condition_name].actions = matched_action_detail.actions - AuditContext.append_audit_condition(condition_name, best_iteration_result, matched_action_detail) + condition_result = self.build_condition_results(condition_results[condition_name], condition_name) + final_result.append(condition_result) + + AuditContext.append_audit_condition( + condition_name, + best_iteration_result, + matched_action_detail, + condition_results[condition_name].cohort_results, + ) # Consolidate all the results and return - final_result = self.build_condition_results(condition_results) return eligibility_status.EligibilityStatus(conditions=final_result) def get_best_iteration_result(self, campaign_group: list[CampaignConfig]) -> BestIterationResult: @@ -133,39 +141,39 @@ def get_iteration_results(self, campaign_group: list[CampaignConfig]) -> dict[It return iteration_results @staticmethod - def build_condition_results(condition_results: dict[ConditionName, IterationResult]) -> list[Condition]: - conditions: list[Condition] = [] - # iterate over conditions - for condition_name, active_iteration_result in condition_results.items(): - grouped_cohort_results = defaultdict(list) - # iterate over cohorts and group them by status and cohort_group - for cohort_result in active_iteration_result.cohort_results: - if active_iteration_result.status == cohort_result.status: - grouped_cohort_results[cohort_result.cohort_code].append(cohort_result) - - # deduplicate grouped cohort results by cohort_code - deduplicated_cohort_results = [ - CohortGroupResult( + def build_condition_results(iteration_result: IterationResult, condition_name: ConditionName) -> Condition: + grouped_cohort_results = defaultdict(list) + + for cohort_result in iteration_result.cohort_results: + if iteration_result.status == cohort_result.status: + grouped_cohort_results[cohort_result.cohort_code].append(cohort_result) + + deduplicated_cohort_results = [] + + for group_cohort_code, group in grouped_cohort_results.items(): + if group: + unique_rule_codes = set() + deduplicated_reasons = [] + for cohort in group: + for reason in cohort.reasons: + if reason.rule_name not in unique_rule_codes and reason.rule_description: + unique_rule_codes.add(reason.rule_name) + deduplicated_reasons.append(reason) + + non_empty_description = next((c.description for c in group if c.description), group[0].description) + cohort_group_result = CohortGroupResult( cohort_code=group_cohort_code, status=group[0].status, - # Flatten all reasons from the group - reasons=[reason for cohort in group for reason in cohort.reasons], - # get the first nonempty description - description=next((c.description for c in group if c.description), group[0].description), + reasons=deduplicated_reasons, + description=non_empty_description, audit_rules=[], ) - for group_cohort_code, group in grouped_cohort_results.items() - if group - ] - - # return condition with cohort results - conditions.append( - Condition( - condition_name=condition_name, - status=active_iteration_result.status, - cohort_results=list(deduplicated_cohort_results), - actions=condition_results[condition_name].actions, - status_text=active_iteration_result.status.get_status_text(condition_name), - ) - ) - return conditions + deduplicated_cohort_results.append(cohort_group_result) + + return Condition( + condition_name=condition_name, + status=iteration_result.status, + cohort_results=list(deduplicated_cohort_results), + actions=iteration_result.actions, + status_text=iteration_result.status.get_status_text(condition_name), + ) diff --git a/src/eligibility_signposting_api/views/eligibility.py b/src/eligibility_signposting_api/views/eligibility.py index 802515347..7dee05144 100644 --- a/src/eligibility_signposting_api/views/eligibility.py +++ b/src/eligibility_signposting_api/views/eligibility.py @@ -162,20 +162,18 @@ def build_suitability_results(condition: Condition) -> list[eligibility_response if condition.status != Status.not_actionable: return [] - unique_rule_codes = set() suitability_results = [] for cohort_result in condition.cohort_results: if cohort_result.status == Status.not_actionable: - for reason in cohort_result.reasons: - if reason.rule_name not in unique_rule_codes and reason.rule_description: - unique_rule_codes.add(reason.rule_name) - suitability_results.append( - eligibility_response.SuitabilityRule( - ruleType=eligibility_response.RuleType(reason.rule_type.value), - ruleCode=eligibility_response.RuleCode(reason.rule_name), - ruleText=eligibility_response.RuleText(reason.rule_description), - ) - ) + suitability_results.extend( + eligibility_response.SuitabilityRule( + ruleType=eligibility_response.RuleType(reason.rule_type.value), + ruleCode=eligibility_response.RuleCode(reason.rule_name), + ruleText=eligibility_response.RuleText(reason.rule_description), + ) + for reason in cohort_result.reasons + if reason.rule_description + ) return suitability_results diff --git a/tests/fixtures/builders/model/eligibility.py b/tests/fixtures/builders/model/eligibility.py index 07f3c825c..894b61c47 100644 --- a/tests/fixtures/builders/model/eligibility.py +++ b/tests/fixtures/builders/model/eligibility.py @@ -5,7 +5,13 @@ from polyfactory.factories import DataclassFactory from eligibility_signposting_api.model import eligibility_status -from eligibility_signposting_api.model.eligibility_status import RuleType, UrlLink +from eligibility_signposting_api.model.eligibility_status import ( + RuleDescription, + RuleName, + RulePriority, + RuleType, + UrlLink, +) class SuggestedActionFactory(DataclassFactory[eligibility_status.SuggestedAction]): @@ -14,6 +20,10 @@ class SuggestedActionFactory(DataclassFactory[eligibility_status.SuggestedAction class ReasonFactory(DataclassFactory[eligibility_status.Reason]): rule_type = RuleType.filter + rule_name = RuleName("name") + rule_priority = RulePriority("1") + rule_description = RuleDescription("description") + matcher_matched = False class CohortResultFactory(DataclassFactory[eligibility_status.CohortGroupResult]): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 82059511c..f324245db 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,7 +17,10 @@ from yarl import URL from eligibility_signposting_api.model import eligibility_status -from eligibility_signposting_api.model.campaign_config import CampaignConfig, RuleType +from eligibility_signposting_api.model.campaign_config import ( + CampaignConfig, + RuleType, +) from eligibility_signposting_api.repos.campaign_repo import BucketName from eligibility_signposting_api.repos.person_repo import TableName from tests.fixtures.builders.model import rule @@ -376,8 +379,8 @@ def persisted_person_all_cohorts(person_table: Any, faker: Faker) -> Generator[e rows := person_rows_builder( nhs_number, date_of_birth=date_of_birth, - postcode="hp1", - cohorts=["cohort_label1", "cohort_label2", "cohort_label3"], + postcode="SW19", + cohorts=["cohort_label1", "cohort_label2", "cohort_label3", "cohort_label4"], icb="QE1", ).data ): @@ -488,8 +491,14 @@ def multiple_campaign_configs(s3_client: BaseClient, rules_bucket: BucketName) - targets = ["RSV", "COVID", "FLU"] target_rules_map = { - targets[0]: [rule.PersonAgeSuppressionRuleFactory.build(type=RuleType.filter)], - targets[1]: [rule.PersonAgeSuppressionRuleFactory.build()], + targets[0]: [ + rule.PersonAgeSuppressionRuleFactory.build(type=RuleType.filter, description="TOO YOUNG"), + rule.PostcodeSuppressionRuleFactory.build(type=RuleType.filter, priority=8, cohort_label="cohort_label4"), + ], + targets[1]: [ + rule.PersonAgeSuppressionRuleFactory.build(description="TOO YOUNG"), + rule.PostcodeSuppressionRuleFactory.build(priority=12, cohort_label="cohort_label2"), + ], targets[2]: [rule.ICBRedirectRuleFactory.build()], } @@ -507,7 +516,13 @@ def multiple_campaign_configs(s3_client: BaseClient, rules_bucket: BucketName) - cohort_group=f"cohort_group{i + 1}", positive_description=f"positive_desc_{i + 1}", negative_description=f"negative_desc_{i + 1}", - ) + ), + rule.IterationCohortFactory.build( + cohort_label="cohort_label4", + cohort_group="cohort_group4", + positive_description="positive_desc_4", + negative_description="negative_desc_4", + ), ], ) ], diff --git a/tests/integration/lambda/test_app_running_as_lambda.py b/tests/integration/lambda/test_app_running_as_lambda.py index 6894e1e53..b4cb61744 100644 --- a/tests/integration/lambda/test_app_running_as_lambda.py +++ b/tests/integration/lambda/test_app_running_as_lambda.py @@ -242,11 +242,13 @@ def test_given_nhs_number_in_path_matches_with_nhs_number_in_headers_and_check_i } ], "filterRules": None, - "suitabilityRules": { - "rulePriority": "10", - "ruleName": "Exclude too young less than 75", - "ruleMessage": "Exclude too young less than 75", - }, + "suitabilityRules": [ + { + "rulePriority": "10", + "ruleName": "Exclude too young less than 75", + "ruleMessage": "Exclude too young less than 75", + } + ], "actionRule": None, "actions": [], } @@ -459,15 +461,18 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( # "conditionName": rsv_campaign.target, "status": "not_eligible", "statusText": "We do not believe you can have it", - "eligibilityCohorts": [{"cohortCode": "cohort_label1", "cohortStatus": "not_eligible"}], + "eligibilityCohorts": [ + {"cohortCode": "cohort_label1", "cohortStatus": "not_eligible"}, + {"cohortCode": "cohort_label4", "cohortStatus": "not_eligible"}, + ], "eligibilityCohortGroups": [ - { - "cohortCode": "cohort_group1", - "cohortText": "negative_desc_1", - "cohortStatus": "not_eligible", - } + {"cohortCode": "cohort_group1", "cohortText": "negative_desc_1", "cohortStatus": "not_eligible"}, + {"cohortCode": "cohort_group4", "cohortText": "negative_desc_4", "cohortStatus": "not_eligible"}, + ], + "filterRules": [ + {"rulePriority": "10", "ruleName": "Exclude too young less than 75"}, + {"rulePriority": "8", "ruleName": "Excluded postcode In SW19"}, ], - "filterRules": {"rulePriority": "10", "ruleName": "Exclude too young less than 75"}, "suitabilityRules": None, "actionRule": None, "actions": [], @@ -480,20 +485,19 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( # "conditionName": covid_campaign.target, "status": "not_actionable", "statusText": f"You should have the {covid_campaign.target} vaccine", - "eligibilityCohorts": [{"cohortCode": "cohort_label2", "cohortStatus": "not_actionable"}], + "eligibilityCohorts": [ + {"cohortCode": "cohort_label2", "cohortStatus": "not_actionable"}, + {"cohortCode": "cohort_label4", "cohortStatus": "not_actionable"}, + ], "eligibilityCohortGroups": [ - { - "cohortCode": "cohort_group2", - "cohortText": "positive_desc_2", - "cohortStatus": "not_actionable", - } + {"cohortCode": "cohort_group2", "cohortText": "positive_desc_2", "cohortStatus": "not_actionable"}, + {"cohortCode": "cohort_group4", "cohortText": "positive_desc_4", "cohortStatus": "not_actionable"}, ], "filterRules": None, - "suitabilityRules": { - "rulePriority": "10", - "ruleName": "Exclude too young less than 75", - "ruleMessage": "Exclude too young less than 75", - }, + "suitabilityRules": [ + {"rulePriority": "10", "ruleName": "Exclude too young less than 75", "ruleMessage": "TOO YOUNG"}, + {"rulePriority": "12", "ruleName": "Excluded postcode In SW19", "ruleMessage": "In SW19"}, + ], "actionRule": None, "actions": [], }, @@ -505,13 +509,13 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( # "conditionName": flu_campaign.target, "status": "actionable", "statusText": f"You should have the {flu_campaign.target} vaccine", - "eligibilityCohorts": [{"cohortCode": "cohort_label3", "cohortStatus": "actionable"}], + "eligibilityCohorts": [ + {"cohortCode": "cohort_label3", "cohortStatus": "actionable"}, + {"cohortCode": "cohort_label4", "cohortStatus": "actionable"}, + ], "eligibilityCohortGroups": [ - { - "cohortCode": "cohort_group3", - "cohortText": "positive_desc_3", - "cohortStatus": "actionable", - } + {"cohortCode": "cohort_group3", "cohortText": "positive_desc_3", "cohortStatus": "actionable"}, + {"cohortCode": "cohort_group4", "cohortText": "positive_desc_4", "cohortStatus": "actionable"}, ], "filterRules": None, "suitabilityRules": None, diff --git a/tests/unit/audit/test_audit_context.py b/tests/unit/audit/test_audit_context.py index 40abf9c80..05d78bd8e 100644 --- a/tests/unit/audit/test_audit_context.py +++ b/tests/unit/audit/test_audit_context.py @@ -129,7 +129,9 @@ def test_append_audit_condition_adds_condition_to_audit_log_on_g(app): with app.app_context(): g.audit_log = AuditEvent() - AuditContext.append_audit_condition(condition_name, best_iteration_results, matched_action_detail) + AuditContext.append_audit_condition( + condition_name, best_iteration_results, matched_action_detail, [cohort_group_result] + ) expected_audit_action = [ AuditAction( @@ -191,3 +193,44 @@ def test_write_to_firehose_calls_audit_service_with_correct_data_from_g(app): assert g.audit_log.response.last_updated == last_updated mock_audit_service.audit.assert_called_once_with(g.audit_log.model_dump(by_alias=True)) + + +def test_no_duplicates_returns_same_list(): + reasons = [ + Reason(RuleType("F"), RuleName("code1"), RulePriority("1"), RuleDescription("desc1"), matcher_matched=True), + Reason(RuleType("S"), RuleName("code2"), RulePriority("2"), RuleDescription("desc2"), matcher_matched=False), + ] + expected = reasons + assert AuditContext.deduplicate_reasons(reasons) == expected + + +def test_duplicates_are_removed(): + reasons = [ + Reason(RuleType("F"), RuleName("code1"), RulePriority("1"), RuleDescription("desc1"), matcher_matched=True), + Reason(RuleType("S"), RuleName("code1"), RulePriority("2"), RuleDescription("desc2"), matcher_matched=False), + Reason(RuleType("R"), RuleName("code3"), RulePriority("3"), RuleDescription("desc3"), matcher_matched=True), + ] + expected = [ + Reason(RuleType("F"), RuleName("code1"), RulePriority("1"), RuleDescription("desc1"), matcher_matched=True), + Reason(RuleType("R"), RuleName("code3"), RulePriority("3"), RuleDescription("desc3"), matcher_matched=True), + ] + assert AuditContext.deduplicate_reasons(reasons) == expected + + +def test_empty_list_returns_empty_list(): + reasons = [] + expected = [] + assert AuditContext.deduplicate_reasons(reasons) == expected + + +def test_reasons_with_no_description_are_filtered_out(): + reasons = [ + Reason(RuleType("F"), RuleName("code1"), RulePriority("1"), RuleDescription("desc1"), matcher_matched=True), + Reason(RuleType("S"), RuleName("code2"), RulePriority("2"), None, matcher_matched=False), + Reason(RuleType("R"), RuleName("code3"), RulePriority("3"), RuleDescription("desc3"), matcher_matched=True), + ] + expected = [ + Reason(RuleType("F"), RuleName("code1"), RulePriority("1"), RuleDescription("desc1"), matcher_matched=True), + Reason(RuleType("R"), RuleName("code3"), RulePriority("3"), RuleDescription("desc3"), matcher_matched=True), + ] + assert AuditContext.deduplicate_reasons(reasons) == expected diff --git a/tests/unit/services/calculators/test_eligibility_calculator.py b/tests/unit/services/calculators/test_eligibility_calculator.py index b1da841ff..60f90d841 100644 --- a/tests/unit/services/calculators/test_eligibility_calculator.py +++ b/tests/unit/services/calculators/test_eligibility_calculator.py @@ -601,11 +601,6 @@ def test_cohort_group_descriptions_are_selected_based_on_priority_when_cohorts_h class TestEligibilityResultBuilder: - def test_build_condition_results_empty_input(self): - condition_results = {} - result = EligibilityCalculator.build_condition_results(condition_results) - assert_that(result, is_([])) - def test_build_condition_results_single_condition_single_cohort_actionable(self): cohort_group_results = [CohortGroupResult("COHORT_A", Status.actionable, [], "Cohort A Description", [])] suggested_actions = [ @@ -620,18 +615,15 @@ def test_build_condition_results_single_condition_single_cohort_actionable(self) ] iteration_result = IterationResult(Status.actionable, cohort_group_results, suggested_actions) - condition_results = {ConditionName("RSV"): iteration_result} - - result = EligibilityCalculator.build_condition_results(condition_results) + result = EligibilityCalculator.build_condition_results(iteration_result, ConditionName("RSV")) - assert_that(len(result), is_(1)) - assert_that(result[0].condition_name, is_(ConditionName("RSV"))) - assert_that(result[0].status, is_(Status.actionable)) - assert_that(result[0].actions, is_(suggested_actions)) - assert_that(result[0].status_text, is_(Status.actionable.get_status_text(ConditionName("RSV")))) + assert_that(result.condition_name, is_(ConditionName("RSV"))) + assert_that(result.status, is_(Status.actionable)) + assert_that(result.actions, is_(suggested_actions)) + assert_that(result.status_text, is_(Status.actionable.get_status_text(ConditionName("RSV")))) - assert_that(len(result[0].cohort_results), is_(1)) - deduplicated_cohort = result[0].cohort_results[0] + assert_that(len(result.cohort_results), is_(1)) + deduplicated_cohort = result.cohort_results[0] assert_that(deduplicated_cohort.cohort_code, is_("COHORT_A")) assert_that(deduplicated_cohort.status, is_(Status.actionable)) assert_that(deduplicated_cohort.reasons, is_([])) @@ -652,18 +644,15 @@ def test_build_condition_results_single_condition_single_cohort_not_eligible_wit ] iteration_result = IterationResult(Status.not_eligible, cohort_group_results, suggested_actions) - condition_results = {ConditionName("RSV"): iteration_result} + result = EligibilityCalculator.build_condition_results(iteration_result, ConditionName("RSV")) - result = EligibilityCalculator.build_condition_results(condition_results) + assert_that(result.condition_name, is_(ConditionName("RSV"))) + assert_that(result.status, is_(Status.not_eligible)) + assert_that(result.actions, is_(suggested_actions)) + assert_that(result.status_text, is_(Status.not_eligible.get_status_text(ConditionName("RSV")))) - assert_that(len(result), is_(1)) - assert_that(result[0].condition_name, is_(ConditionName("RSV"))) - assert_that(result[0].status, is_(Status.not_eligible)) - assert_that(result[0].actions, is_(suggested_actions)) - assert_that(result[0].status_text, is_(Status.not_eligible.get_status_text(ConditionName("RSV")))) - - assert_that(len(result[0].cohort_results), is_(1)) - deduplicated_cohort = result[0].cohort_results[0] + assert_that(len(result.cohort_results), is_(1)) + deduplicated_cohort = result.cohort_results[0] assert_that(deduplicated_cohort.cohort_code, is_("COHORT_A")) assert_that(deduplicated_cohort.status, is_(Status.not_eligible)) assert_that(deduplicated_cohort.reasons, is_([])) @@ -703,15 +692,11 @@ def test_build_condition_results_single_condition_multiple_cohorts_same_cohort_c ] iteration_result = IterationResult(Status.not_eligible, cohort_group_results, suggested_actions) - condition_results = {ConditionName("RSV"): iteration_result} - - result = EligibilityCalculator.build_condition_results(condition_results) + result = EligibilityCalculator.build_condition_results(iteration_result, ConditionName("RSV")) - assert_that(len(result), is_(1)) - condition = result[0] - assert_that(len(condition.cohort_results), is_(1)) + assert_that(len(result.cohort_results), is_(1)) - deduplicated_cohort = condition.cohort_results[0] + deduplicated_cohort = result.cohort_results[0] assert_that(deduplicated_cohort.cohort_code, is_("COHORT_A")) assert_that(deduplicated_cohort.status, is_(Status.not_eligible)) assert_that(deduplicated_cohort.reasons, contains_inanyorder(reason_1, reason_2)) @@ -749,19 +734,15 @@ def test_build_condition_results_multiple_cohorts_different_cohort_code_same_sta ] iteration_result = IterationResult(Status.not_eligible, cohort_group_results, suggested_actions) - condition_results = {ConditionName("RSV"): iteration_result} + result = EligibilityCalculator.build_condition_results(iteration_result, ConditionName("RSV")) - result = EligibilityCalculator.build_condition_results(condition_results) - - assert_that(len(result), is_(1)) - condition = result[0] - assert_that(len(condition.cohort_results), is_(2)) + assert_that(len(result.cohort_results), is_(2)) expected_deduplicated_cohorts = [ CohortGroupResult("COHORT_X", Status.not_eligible, [reason_1], "Cohort X Description", []), CohortGroupResult("COHORT_Y", Status.not_eligible, [reason_2], "Cohort Y Description", []), ] - assert_that(condition.cohort_results, contains_inanyorder(*expected_deduplicated_cohorts)) + assert_that(result.cohort_results, contains_inanyorder(*expected_deduplicated_cohorts)) def test_build_condition_results_cohorts_status_not_matching_iteration_status(self): reason_1 = Reason( @@ -785,53 +766,8 @@ def test_build_condition_results_cohorts_status_not_matching_iteration_status(se iteration_result = IterationResult(Status.not_eligible, cohort_group_results, []) - condition_results = {ConditionName("RSV"): iteration_result} - - result = EligibilityCalculator.build_condition_results(condition_results) - - assert_that(len(result), is_(1)) - condition = result[0] - assert_that(len(condition.cohort_results), is_(1)) - assert_that(condition.cohort_results[0].cohort_code, is_("COHORT_X")) - assert_that(condition.cohort_results[0].status, is_(Status.not_eligible)) - - def test_build_condition_results_multiple_conditions(self): - reason_1 = Reason( - RuleType.filter, - eligibility_status.RuleName("Filter Rule 1"), - RulePriority("1"), - RuleDescription("Filter Rule Description 2"), - matcher_matched=True, - ) - reason_2 = Reason( - RuleType.filter, - eligibility_status.RuleName("Filter Rule 2"), - RulePriority("2"), - RuleDescription("Filter Rule Description 2"), - matcher_matched=True, - ) - cohort_group_result1 = [CohortGroupResult("RSV_COHORT", Status.not_eligible, [reason_1], "RSV Desc", [])] - cohort_group_result2 = [CohortGroupResult("COVID_COHORT", Status.not_actionable, [reason_2], "Covid Desc", [])] - - iteration_result1 = IterationResult(Status.not_eligible, cohort_group_result1, []) - - iteration_result2 = IterationResult(Status.not_actionable, cohort_group_result2, []) - - condition_results = { - ConditionName("RSV"): iteration_result1, - ConditionName("COVID"): iteration_result2, - } - - result = EligibilityCalculator.build_condition_results(condition_results) - - rsv = next((c for c in result if c.condition_name == ConditionName("RSV")), None) - assert_that(rsv.status, is_(Status.not_eligible)) - assert_that(len(rsv.cohort_results), is_(1)) - assert_that(rsv.cohort_results[0].cohort_code, is_("RSV_COHORT")) - assert_that(rsv.cohort_results[0].reasons, is_([reason_1])) + result = EligibilityCalculator.build_condition_results(iteration_result, ConditionName("RSV")) - covid = next((c for c in result if c.condition_name == ConditionName("COVID")), None) - assert_that(covid.status, is_(Status.not_actionable)) - assert_that(len(covid.cohort_results), is_(1)) - assert_that(covid.cohort_results[0].cohort_code, is_("COVID_COHORT")) - assert_that(covid.cohort_results[0].reasons, is_([reason_2])) + assert_that(len(result.cohort_results), is_(1)) + assert_that(result.cohort_results[0].cohort_code, is_("COHORT_X")) + assert_that(result.cohort_results[0].status, is_(Status.not_eligible)) diff --git a/tests/unit/views/test_eligibility.py b/tests/unit/views/test_eligibility.py index 963b1a9ab..04d223236 100644 --- a/tests/unit/views/test_eligibility.py +++ b/tests/unit/views/test_eligibility.py @@ -22,11 +22,6 @@ Condition, EligibilityStatus, NHSNumber, - Reason, - RuleDescription, - RuleName, - RulePriority, - RuleType, Status, SuggestedAction, UrlLabel, @@ -45,7 +40,7 @@ ConditionFactory, EligibilityStatusFactory, ) -from tests.fixtures.matchers.eligibility import is_eligibility_cohort, is_suitability_rule +from tests.fixtures.matchers.eligibility import is_eligibility_cohort logger = logging.getLogger(__name__) @@ -253,166 +248,6 @@ def test_build_eligibility_cohorts_results_consider_only_cohorts_groups_that_has ) -def test_build_suitability_results_with_deduplication(): - condition: Condition = ConditionFactory.build( - status=Status.not_actionable, - cohort_results=[ - CohortResultFactory.build( - cohort_code="cohort_group1", - status=Status.not_actionable, - reasons=[ - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude too young less than 75"), - rule_description=RuleDescription("your age is greater than 75"), - matcher_matched=False, - rule_priority=RulePriority(1), - ), - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude too young less than 75"), - rule_description=RuleDescription("your age is greater than 75"), - matcher_matched=False, - rule_priority=RulePriority(1), - ), - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude more than 100"), - rule_description=RuleDescription("your age is greater than 100"), - matcher_matched=False, - rule_priority=RulePriority(1), - ), - ], - ), - CohortResultFactory.build( - cohort_code="cohort_group2", - status=Status.not_actionable, - reasons=[ - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude too young less than 75"), - rule_description=RuleDescription("your age is greater than 75"), - matcher_matched=False, - rule_priority=RulePriority(1), - ) - ], - ), - CohortResultFactory.build( - cohort_code="cohort_group3", - status=Status.not_eligible, - reasons=[ - Reason( - rule_type=RuleType.filter, - rule_name=RuleName("Exclude is present in sw1"), - rule_description=RuleDescription("your a member of sw1"), - matcher_matched=False, - rule_priority=RulePriority(1), - ) - ], - ), - CohortResultFactory.build( - cohort_code="cohort_group4", - description="", - status=Status.not_actionable, - reasons=[ - Reason( - rule_type=RuleType.filter, - rule_name=RuleName("Already vaccinated"), - rule_description=RuleDescription("you have already vaccinated"), - matcher_matched=False, - rule_priority=RulePriority(1), - ) - ], - ), - ], - ) - - results = build_suitability_results(condition) - - assert_that( - results, - contains_exactly( - is_suitability_rule() - .with_rule_code("Exclude too young less than 75") - .and_rule_text("your age is greater than 75"), - is_suitability_rule().with_rule_code("Exclude more than 100").and_rule_text("your age is greater than 100"), - is_suitability_rule().with_rule_code("Already vaccinated").and_rule_text("you have already vaccinated"), - ), - ) - - -def test_build_suitability_results_when_rule_text_is_empty_or_null(): - condition: Condition = ConditionFactory.build( - status=Status.not_actionable, - cohort_results=[ - CohortResultFactory.build( - cohort_code="cohort_group1", - status=Status.not_actionable, - reasons=[ - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude too young less than 75"), - rule_description=RuleDescription("your age is greater than 75"), - matcher_matched=False, - rule_priority=RulePriority(1), - ), - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude more than 100"), - rule_description=RuleDescription(""), - matcher_matched=False, - rule_priority=RulePriority(1), - ), - Reason( - rule_type=RuleType.suppression, - rule_name=RuleName("Exclude more than 100"), - matcher_matched=False, - rule_description=None, - rule_priority=RulePriority(1), - ), - ], - ), - CohortResultFactory.build( - cohort_code="cohort_group2", - status=Status.not_actionable, - reasons=[ - Reason( - rule_type=RuleType.filter, - rule_name=RuleName("Exclude is present in sw1"), - rule_description=RuleDescription(""), - matcher_matched=False, - rule_priority=RulePriority(1), - ) - ], - ), - CohortResultFactory.build( - cohort_code="cohort_group3", - status=Status.not_actionable, - reasons=[ - Reason( - rule_type=RuleType.filter, - rule_name=RuleName("Exclude is present in sw1"), - rule_description=None, - matcher_matched=False, - rule_priority=RulePriority(1), - ) - ], - ), - ], - ) - - results = build_suitability_results(condition) - - assert_that( - results, - contains_exactly( - is_suitability_rule() - .with_rule_code("Exclude too young less than 75") - .and_rule_text("your age is greater than 75") - ), - ) - - def test_no_suitability_rules_for_actionable(): condition = ConditionFactory.build(status=Status.actionable, cohort_results=[])