Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/eligibility_signposting_api/audit/audit_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def append_audit_condition(
condition_name=condition_name,
status=best_candidate.status.name if best_candidate and best_candidate.status else None,
status_text=best_candidate.status_text if best_candidate else None,
status_text_override=action_detail.status_text_override, # NEW
eligibility_cohorts=audit_eligibility_cohorts,
eligibility_cohort_groups=audit_eligibility_cohort_groups,
filter_rules=audit_filter_rule,
Expand Down
1 change: 1 addition & 0 deletions src/eligibility_signposting_api/audit/audit_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class AuditCondition(CamelCaseBaseModel):
condition_name: str | None = None
status: str | None = None
status_text: str | None = None
status_text_override: str | None = None # NEW — records override when applied
eligibility_cohorts: list[AuditEligibilityCohorts] | None = None
eligibility_cohort_groups: list[AuditEligibilityCohortGroups] | None = None
filter_rules: list[AuditFilterRule] | None = None
Expand Down
2 changes: 2 additions & 0 deletions src/eligibility_signposting_api/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
CONSUMER_MAPPING_FILE_NAME = "consumer_mapping_config.json"

CACHE_TTL_SECONDS = int(os.getenv("CONFIG_CACHE_TTL_SECONDS", "1800"))

STATUS_TEXT_OVERRIDE_ACTION_TYPE = "norender_StatusTextOverride"
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class MatchedActionDetail:
rule_name: campaign_config.RuleName | None = None
rule_priority: campaign_config.RulePriority | None = None
actions: list[SuggestedAction] | None = None
status_text_override: StatusText | None = None


@dataclass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def get_eligibility_status(
include_actions_flag=include_actions_flag,
)

# Apply status text override if the matched actions contained one
if matched_action_detail.status_text_override:
iteration_result_summary.iteration_result.status_text = matched_action_detail.status_text_override

iteration_result_summary = TokenProcessor.find_and_replace_tokens(self.person, iteration_result_summary)
matched_action_detail = TokenProcessor.find_and_replace_tokens(self.person, matched_action_detail)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from itertools import groupby
from operator import attrgetter

from eligibility_signposting_api.config.constants import STATUS_TEXT_OVERRIDE_ACTION_TYPE
from eligibility_signposting_api.model.campaign_config import (
ActionsMapper,
Iteration,
Expand All @@ -16,7 +17,7 @@
RuleType,
SuggestedAction,
UrlLabel,
UrlLink,
UrlLink, StatusText,
)
from eligibility_signposting_api.model.person import Person
from eligibility_signposting_api.services.calculators.rule_calculator import RuleCalculator
Expand Down Expand Up @@ -65,7 +66,31 @@ def _handle(self, person: Person, best_active_iteration: Iteration, rule_type: R
matched_action_rule_name = rule_group_list[0].name
break

return MatchedActionDetail(matched_action_rule_name, matched_action_rule_priority, actions)
#return MatchedActionDetail(matched_action_rule_name, matched_action_rule_priority, actions)
actions, status_text_override = self._extract_status_text_override(actions)
return MatchedActionDetail(matched_action_rule_name, matched_action_rule_priority, actions, status_text_override)

@staticmethod
def _extract_status_text_override(
actions: list[SuggestedAction] | None,
) -> tuple[list[SuggestedAction] | None, StatusText | None]:
"""Extract and remove any status text override action from the actions list.

Returns the filtered actions list and the override text (if found).
"""
if not actions:
return actions, None

override_text: StatusText | None = None
filtered: list[SuggestedAction] = []

for action in actions:
if action.action_type == STATUS_TEXT_OVERRIDE_ACTION_TYPE:
override_text = StatusText(action.action_description) if action.action_description else None
else:
filtered.append(action)

return filtered or None, override_text

@staticmethod
def _get_action_rules_components(
Expand Down
12 changes: 12 additions & 0 deletions tests/fixtures/builders/model/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,15 @@ class ICBNonActionableActionRuleFactory(IterationRuleFactory):
attribute_name = RuleAttributeName("ICB")
comparator = RuleComparator("QE1")
comms_routing = CommsRouting("ActionCode1")

class ClinicalRiskRedirectRuleFactory(IterationRuleFactory):
type = RuleType.redirect
name = RuleName("Health reason redirect with override")
code = None
description = RuleDescription("Redirect for clinical risk individuals")
priority = RulePriority(10)
operator = RuleOperator.is_not_null
attribute_level = RuleAttributeLevel.PERSON
attribute_name = RuleAttributeName("CLINICAL_RISK_GROUP")
comparator = RuleComparator("")
comms_routing = CommsRouting("STATUS_TEXT_OVERRIDE")
95 changes: 94 additions & 1 deletion tests/unit/services/calculators/test_eligibility_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
RuleComparator,
RuleName,
RuleOperator,
RuleType,
RuleType, CommsRouting,
)
from eligibility_signposting_api.model.eligibility_status import (
ActionCode,
Expand Down Expand Up @@ -2170,3 +2170,96 @@ def test_build_condition_results_single_cohort(self, reason_2, expected_reasons)

assert_that(len(result.cohort_results), is_(1))
assert_that(result.cohort_results[0].reasons, contains_inanyorder(*expected_reasons))


def test_configureable_status_text(faker: Faker):
# Given
nhs_number = NHSNumber(faker.nhs_number())
date_of_birth = DateOfBirth(faker.date_of_birth(minimum_age=85, maximum_age=85))

person_rows = person_rows_builder(
nhs_number,
date_of_birth=date_of_birth,
cohorts=["rsv_cohort_1"],
icb="QE1",
)

available_action = AvailableAction(
ExternalRoutingCode="StatusTextOverride",
ActionType="norender_StatusTextOverride",
ActionDescription="You maybe eligible for an RSV vaccine.",
)

campaign_configs = [
rule_builder.CampaignConfigFactory.build(
target="RSV",
iterations=[
rule_builder.IterationFactory.build(
#default_comms_routing="TOKEN_TEST",
#default_not_actionable_routing="TOKEN_TEST",
#default_not_eligible_routing="TOKEN_TEST",
iteration_cohorts=[
rule_builder.IterationCohortFactory.build(
cohort_label="rsv_cohort_1", cohort_group="rsv_cohort_group", priority=0
),
],
iteration_rules=[
rule_builder.PersonAgeSuppressionRuleFactory.build(
type=RuleType.filter,
name=RuleName("NotEligible Reason 1"),
description=RuleText("NotEligible Description 1"),
priority=RulePriority("100"),
operator=RuleOperator.year_lte,
attribute_level=RuleAttributeLevel.PERSON,
attribute_name=RuleAttributeName("DATE_OF_BIRTH"),
comparator=RuleComparator("-90"),
),

# rule_builder.ClinicalRiskRedirectRuleFactory.build(
# comms_routing=CommsRouting("STATUS_TEXT_OVERRIDE")
# ),

rule_builder.ICBRedirectRuleFactory.build(
comms_routing=CommsRouting("STATUS_TEXT_OVERRIDE")
),


rule_builder.PersonAgeSuppressionRuleFactory.build(
type=RuleType.suppression,
name=RuleName("NotActionable Reason 1"),
description=RuleText("NotActionable Description 1"),
priority=RulePriority("110"),
operator=RuleOperator.year_lte,
attribute_level=RuleAttributeLevel.PERSON,
attribute_name=RuleAttributeName("DATE_OF_BIRTH"),
comparator=RuleComparator("-90"),
),
],
status_text=campaign_config.StatusText(
NotEligible="You are not eligible to take RSV vaccines.",
NotActionable="You have taken RSV vaccine in the last 90 days",
Actionable="You can take RSV vaccine.",
),
actions_mapper=rule_builder.ActionsMapperFactory.build(root={"STATUS_TEXT_OVERRIDE": available_action}),
)
],
)
]

calculator = EligibilityCalculator(person_rows, campaign_configs)

# When
actual = calculator.get_eligibility_status("Y", ["ALL"], "ALL")

assert_that(
actual,
is_eligibility_status().with_conditions(
has_item(
is_condition()
.with_condition_name(ConditionName("RSV"))
.and_status(Status.actionable)
#.and_status_text(StatusText("You can take RSV vaccine."))
.and_status_text(StatusText("You maybe eligible for an RSV vaccine."))
)
),
)