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
20 changes: 14 additions & 6 deletions src/eligibility_signposting_api/audit/audit_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def append_audit_condition(
redirect_rule_details: tuple[RulePriority | None, RuleName | None],
) -> None:
audit_eligibility_cohorts, audit_eligibility_cohort_groups = [], []
audit_filter_rule, audit_suitability_rule, audit_redirect_rule = None, None, None
audit_filter_rule, audit_suitability_rule = None, None
best_active_iteration = best_results[0]
best_candidate = best_results[1]
best_cohort_results = best_results[2]
Expand All @@ -88,10 +88,7 @@ def append_audit_condition(
audit_filter_rule = AuditContext.create_audit_filter_rule(best_candidate, result)
audit_suitability_rule = AuditContext.create_audit_suitability_rule(best_candidate, result)

if best_candidate and best_candidate.status and best_candidate.status.name == Status.actionable.name:
audit_redirect_rule = AuditRedirectRule(
rule_priority=str(redirect_rule_details[0]), rule_name=redirect_rule_details[1]
)
audit_redirect_rule = AuditContext.get_audit_redirect_rule(best_candidate, redirect_rule_details)

audit_actions = AuditContext.create_audit_actions(suggested_actions)

Expand All @@ -102,7 +99,7 @@ def append_audit_condition(
iteration_version=best_active_iteration.version if best_active_iteration else None,
condition_name=condition_name,
status=best_candidate.status.name if best_candidate and best_candidate.status else None,
status_text=best_candidate.status.name if best_candidate and best_candidate.status else None,
status_text=best_candidate.status.get_status_text(condition_name) if best_candidate else None,
eligibility_cohorts=audit_eligibility_cohorts,
eligibility_cohort_groups=audit_eligibility_cohort_groups,
filter_rules=audit_filter_rule,
Expand All @@ -113,6 +110,17 @@ def append_audit_condition(

g.audit_log.response.condition.append(audit_condition)

@staticmethod
def get_audit_redirect_rule(
best_candidate: IterationResult | None, redirect_rule_details: tuple[RulePriority | None, RuleName | None]
) -> AuditRedirectRule | None:
audit_redirect_rule = None
if best_candidate and best_candidate.status and best_candidate.status.name == Status.actionable.name:
audit_redirect_rule = AuditRedirectRule(
rule_priority=str(redirect_rule_details[0]), rule_name=redirect_rule_details[1]
)
return audit_redirect_rule

@staticmethod
def add_response_details(response_id: UUID, last_updated: datetime) -> None:
g.audit_log.response.response_id = response_id
Expand Down
11 changes: 11 additions & 0 deletions src/eligibility_signposting_api/model/eligibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
UrlLink = NewType("UrlLink", HttpUrl)
UrlLabel = NewType("UrlLabel", str)

StatusText = NewType("StatusText", str)


class RuleType(StrEnum):
filter = "F"
Expand Down Expand Up @@ -65,6 +67,14 @@ def best(*statuses: Status) -> Status:
"""
return max(statuses)

def get_status_text(self, condition_name: ConditionName) -> StatusText:
status_to_text_mapping = {
self.not_eligible: lambda: StatusText("We do not believe you can have it"),
self.not_actionable: lambda: StatusText(f"You should have the {condition_name} vaccine"),
self.actionable: lambda: StatusText(f"You should have the {condition_name} vaccine"),
}
return status_to_text_mapping.get(self, lambda: StatusText("Unknown status provided"))()


@dataclass
class Reason:
Expand All @@ -90,6 +100,7 @@ class Condition:
condition_name: ConditionName
status: Status
cohort_results: list[CohortGroupResult]
status_text: StatusText
actions: list[SuggestedAction] | None = None


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
if TYPE_CHECKING:
from eligibility_signposting_api.model.rules import (
ActionsMapper,
CampaignConfig,
CampaignID,
CampaignVersion,
Iteration,
Expand Down Expand Up @@ -67,9 +68,14 @@ def campaigns_grouped_by_condition_name(
) -> Iterator[tuple[eligibility.ConditionName, list[rules.CampaignConfig]]]:
"""Generator that yields campaign groups filtered by condition names and campaign category."""

allowed_types = (
{"V", "S"} if category == "ALL" else {category[0]} if category in {"VACCINATIONS", "SCREENING"} else set()
)
mapping = {
"ALL": {"V", "S"},
"VACCINATIONS": {"V"},
"SCREENING": {"S"},
}

allowed_types = mapping.get(category, set())

filter_all_conditions = "ALL" in conditions

for condition_name, campaign_group in groupby(
Expand Down Expand Up @@ -159,23 +165,7 @@ def evaluate_eligibility(
best_campaign_version: CampaignVersion | None
best_cohort_results: dict[str, CohortGroupResult] | None

iteration_results: dict[
str, tuple[Iteration, IterationResult, CampaignID, CampaignVersion, dict[str, CohortGroupResult]]
] = {}

for cc in campaign_group:
active_iteration = cc.current_iteration
cohort_results: dict[str, CohortGroupResult] = self.get_cohort_results(active_iteration)

# Determine Result between cohorts - get the best
status, best_cohorts = self.get_the_best_cohort_memberships(cohort_results)
iteration_results[active_iteration.name] = (
active_iteration,
IterationResult(status, best_cohorts, actions),
cc.id,
cc.version,
cohort_results,
)
iteration_results = self.get_iteration_results(actions, campaign_group)

# Determine results between iterations - get the best
if iteration_results:
Expand Down Expand Up @@ -229,6 +219,27 @@ def evaluate_eligibility(
final_result = self.build_condition_results(condition_results)
return eligibility.EligibilityStatus(conditions=final_result)

def get_iteration_results(
self, actions: list[SuggestedAction] | None, campaign_group: list[CampaignConfig]
) -> dict[str, tuple[Iteration, IterationResult, CampaignID, CampaignVersion, dict[str, CohortGroupResult]]]:
iteration_results: dict[
str, tuple[Iteration, IterationResult, CampaignID, CampaignVersion, dict[str, CohortGroupResult]]
] = {}
for cc in campaign_group:
active_iteration = cc.current_iteration
cohort_results: dict[str, CohortGroupResult] = self.get_cohort_results(active_iteration)

# Determine Result between cohorts - get the best
status, best_cohorts = self.get_the_best_cohort_memberships(cohort_results)
iteration_results[active_iteration.name] = (
active_iteration,
IterationResult(status, best_cohorts, actions),
cc.id,
cc.version,
cohort_results,
)
return iteration_results

def handle_redirect_rules(
self, best_active_iteration: Iteration
) -> tuple[list[SuggestedAction] | None, RulePriority | None, RuleName | None]:
Expand Down Expand Up @@ -311,6 +322,7 @@ def build_condition_results(condition_results: dict[ConditionName, IterationResu
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
Expand All @@ -326,9 +338,7 @@ def is_eligible_by_filter_rules(
sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, filter_rules), key=priority_getter)

for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
status, group_inclusion_reasons, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(
rule_group
)
status, group_exclusion_reasons, _ = self.evaluate_rules_priority_group(rule_group)
if status.is_exclusion:
if cohort.cohort_label is not None:
cohort_results[cohort.cohort_label] = CohortGroupResult(
Expand All @@ -355,9 +365,7 @@ def evaluate_suppression_rules(
sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, suppression_rules), key=priority_getter)

for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
status, group_inclusion_reasons, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(
rule_group
)
status, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(rule_group)
if status.is_exclusion:
is_actionable = False
suppression_reasons.extend(group_exclusion_reasons)
Expand All @@ -381,9 +389,9 @@ def evaluate_suppression_rules(

def evaluate_rules_priority_group(
self, rules_group: Iterator[rules.IterationRule]
) -> tuple[eligibility.Status, list[eligibility.Reason], list[eligibility.Reason], bool]:
) -> tuple[eligibility.Status, list[eligibility.Reason], bool]:
is_rule_stop = False
inclusion_reasons, exclusion_reasons = [], []
exclusion_reasons = []
best_status = eligibility.Status.not_eligible

for rule in rules_group:
Expand All @@ -395,9 +403,8 @@ def evaluate_rules_priority_group(
exclusion_reasons.append(reason)
else:
best_status = eligibility.Status.actionable
inclusion_reasons.append(reason)

return best_status, inclusion_reasons, exclusion_reasons, is_rule_stop
return best_status, exclusion_reasons, is_rule_stop

@staticmethod
def get_actions_from_comms(action_mapper: ActionsMapper, comms: str) -> list[SuggestedAction] | None:
Expand Down
2 changes: 1 addition & 1 deletion src/eligibility_signposting_api/views/eligibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibi
suggestions = ProcessedSuggestion( # pyright: ignore[reportCallIssue]
condition=eligibility.ConditionName(condition.condition_name), # pyright: ignore[reportCallIssue]
status=STATUS_MAPPING[condition.status],
statusText=eligibility.StatusText(f"{condition.status}"), # pyright: ignore[reportCallIssue]
statusText=eligibility.StatusText(condition.status_text), # pyright: ignore[reportCallIssue]
eligibilityCohorts=build_eligibility_cohorts(condition), # pyright: ignore[reportCallIssue]
suitabilityRules=build_suitability_results(condition), # pyright: ignore[reportCallIssue]
actions=build_actions(condition),
Expand Down
24 changes: 12 additions & 12 deletions tests/integration/in_process/test_eligibility_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def test_not_base_eligible(
],
"actions": [],
"suitabilityRules": [],
"statusText": "Status.not_eligible",
"statusText": "We do not believe you can have it",
}
]
),
Expand Down Expand Up @@ -128,7 +128,7 @@ def test_not_eligible_by_rule(
],
"actions": [],
"suitabilityRules": [],
"statusText": "Status.not_eligible",
"statusText": "We do not believe you can have it",
}
]
),
Expand Down Expand Up @@ -177,7 +177,7 @@ def test_not_actionable(
"ruleType": "S",
}
],
"statusText": "Status.not_actionable",
"statusText": "You should have the RSV vaccine",
}
]
),
Expand Down Expand Up @@ -228,7 +228,7 @@ def test_actionable(
}
],
"suitabilityRules": [],
"statusText": "Status.actionable",
"statusText": "You should have the RSV vaccine",
}
]
),
Expand Down Expand Up @@ -273,7 +273,7 @@ def test_not_eligible_by_rule_when_only_magic_cohort_is_present(
],
"actions": [],
"suitabilityRules": [],
"statusText": "Status.not_eligible",
"statusText": "We do not believe you can have it",
}
]
),
Expand Down Expand Up @@ -322,7 +322,7 @@ def test_not_actionable_when_only_magic_cohort_is_present(
"ruleType": "S",
}
],
"statusText": "Status.not_actionable",
"statusText": "You should have the COVID vaccine",
}
]
),
Expand Down Expand Up @@ -373,7 +373,7 @@ def test_actionable_when_only_magic_cohort_is_present(
}
],
"suitabilityRules": [],
"statusText": "Status.actionable",
"statusText": "You should have the COVID vaccine",
}
]
),
Expand Down Expand Up @@ -412,7 +412,7 @@ def test_not_base_eligible(
"eligibilityCohorts": [],
"actions": [],
"suitabilityRules": [],
"statusText": "Status.not_eligible",
"statusText": "We do not believe you can have it",
}
]
),
Expand Down Expand Up @@ -449,7 +449,7 @@ def test_not_eligible_by_rule(
"eligibilityCohorts": [],
"actions": [],
"suitabilityRules": [],
"statusText": "Status.not_eligible",
"statusText": "We do not believe you can have it",
}
]
),
Expand Down Expand Up @@ -492,7 +492,7 @@ def test_not_actionable(
"ruleType": "S",
}
],
"statusText": "Status.not_actionable",
"statusText": "You should have the FLU vaccine",
}
]
),
Expand Down Expand Up @@ -537,7 +537,7 @@ def test_actionable(
}
],
"suitabilityRules": [],
"statusText": "Status.actionable",
"statusText": "You should have the FLU vaccine",
}
]
),
Expand Down Expand Up @@ -573,7 +573,7 @@ def test_actionable_no_actions(
"status": "Actionable",
"eligibilityCohorts": [],
"suitabilityRules": [],
"statusText": "Status.actionable",
"statusText": "You should have the FLU vaccine",
}
]
),
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/lambda/test_app_running_as_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def test_given_nhs_number_in_path_matches_with_nhs_number_in_headers_and_check_i
"iterationVersion": campaign_config.iterations[0].version,
"conditionName": campaign_config.target,
"status": "not_actionable",
"statusText": "not_actionable",
"statusText": f"You should have the {campaign_config.target} vaccine",
"eligibilityCohorts": [{"cohortCode": "cohort1", "cohortStatus": "not_actionable"}],
"eligibilityCohortGroups": [
{
Expand Down Expand Up @@ -461,7 +461,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
"iterationVersion": rsv_campaign.iterations[0].version,
"conditionName": rsv_campaign.target,
"status": "not_eligible",
"statusText": "not_eligible",
"statusText": "We do not believe you can have it",
"eligibilityCohorts": [{"cohortCode": "cohort_label1", "cohortStatus": "not_eligible"}],
"eligibilityCohortGroups": [
{
Expand All @@ -482,7 +482,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
"iterationVersion": covid_campaign.iterations[0].version,
"conditionName": covid_campaign.target,
"status": "not_actionable",
"statusText": "not_actionable",
"statusText": f"You should have the {covid_campaign.target} vaccine",
"eligibilityCohorts": [{"cohortCode": "cohort_label2", "cohortStatus": "not_actionable"}],
"eligibilityCohortGroups": [
{
Expand All @@ -507,7 +507,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
"iterationVersion": flu_campaign.iterations[0].version,
"conditionName": flu_campaign.target,
"status": "actionable",
"statusText": "actionable",
"statusText": f"You should have the {flu_campaign.target} vaccine",
"eligibilityCohorts": [{"cohortCode": "cohort_label3", "cohortStatus": "actionable"}],
"eligibilityCohortGroups": [
{
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/audit/test_audit_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def test_append_audit_condition_adds_condition_to_audit_log_on_g(app):
assert cond.iteration_id == iteration.id
assert cond.iteration_version == iteration.version
assert cond.status == best_results[1].status.name
assert cond.status_text == best_results[1].status.name
assert cond.status_text == "You should have the Condition1 vaccine"
assert cond.actions == expected_audit_action
assert cond.action_rule.rule_priority == "1"
assert cond.action_rule.rule_name == "RedirectRuleName1"
Expand Down
Loading