Skip to content

Commit 6ea9223

Browse files
authored
ELI-294: Personalised/customised status text (#234)
* ELI-294: Personalised/customised status text * ELI-294: Personalised/customised status text * ELI-294: Adds unit tests for Status Enum * ELI-294: Fix sonar issues
1 parent 542431d commit 6ea9223

8 files changed

Lines changed: 120 additions & 54 deletions

File tree

src/eligibility_signposting_api/audit/audit_context.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def append_audit_condition(
6666
redirect_rule_details: tuple[RulePriority | None, RuleName | None],
6767
) -> None:
6868
audit_eligibility_cohorts, audit_eligibility_cohort_groups = [], []
69-
audit_filter_rule, audit_suitability_rule, audit_redirect_rule = None, None, None
69+
audit_filter_rule, audit_suitability_rule = None, None
7070
best_active_iteration = best_results[0]
7171
best_candidate = best_results[1]
7272
best_cohort_results = best_results[2]
@@ -88,10 +88,7 @@ def append_audit_condition(
8888
audit_filter_rule = AuditContext.create_audit_filter_rule(best_candidate, result)
8989
audit_suitability_rule = AuditContext.create_audit_suitability_rule(best_candidate, result)
9090

91-
if best_candidate and best_candidate.status and best_candidate.status.name == Status.actionable.name:
92-
audit_redirect_rule = AuditRedirectRule(
93-
rule_priority=str(redirect_rule_details[0]), rule_name=redirect_rule_details[1]
94-
)
91+
audit_redirect_rule = AuditContext.get_audit_redirect_rule(best_candidate, redirect_rule_details)
9592

9693
audit_actions = AuditContext.create_audit_actions(suggested_actions)
9794

@@ -102,7 +99,7 @@ def append_audit_condition(
10299
iteration_version=best_active_iteration.version if best_active_iteration else None,
103100
condition_name=condition_name,
104101
status=best_candidate.status.name if best_candidate and best_candidate.status else None,
105-
status_text=best_candidate.status.name if best_candidate and best_candidate.status else None,
102+
status_text=best_candidate.status.get_status_text(condition_name) if best_candidate else None,
106103
eligibility_cohorts=audit_eligibility_cohorts,
107104
eligibility_cohort_groups=audit_eligibility_cohort_groups,
108105
filter_rules=audit_filter_rule,
@@ -113,6 +110,17 @@ def append_audit_condition(
113110

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

113+
@staticmethod
114+
def get_audit_redirect_rule(
115+
best_candidate: IterationResult | None, redirect_rule_details: tuple[RulePriority | None, RuleName | None]
116+
) -> AuditRedirectRule | None:
117+
audit_redirect_rule = None
118+
if best_candidate and best_candidate.status and best_candidate.status.name == Status.actionable.name:
119+
audit_redirect_rule = AuditRedirectRule(
120+
rule_priority=str(redirect_rule_details[0]), rule_name=redirect_rule_details[1]
121+
)
122+
return audit_redirect_rule
123+
116124
@staticmethod
117125
def add_response_details(response_id: UUID, last_updated: datetime) -> None:
118126
g.audit_log.response.response_id = response_id

src/eligibility_signposting_api/model/eligibility.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
UrlLink = NewType("UrlLink", HttpUrl)
2525
UrlLabel = NewType("UrlLabel", str)
2626

27+
StatusText = NewType("StatusText", str)
28+
2729

2830
class RuleType(StrEnum):
2931
filter = "F"
@@ -65,6 +67,14 @@ def best(*statuses: Status) -> Status:
6567
"""
6668
return max(statuses)
6769

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

6979
@dataclass
7080
class Reason:
@@ -90,6 +100,7 @@ class Condition:
90100
condition_name: ConditionName
91101
status: Status
92102
cohort_results: list[CohortGroupResult]
103+
status_text: StatusText
93104
actions: list[SuggestedAction] | None = None
94105

95106

src/eligibility_signposting_api/services/calculators/eligibility_calculator.py

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
if TYPE_CHECKING:
1313
from eligibility_signposting_api.model.rules import (
1414
ActionsMapper,
15+
CampaignConfig,
1516
CampaignID,
1617
CampaignVersion,
1718
Iteration,
@@ -67,9 +68,14 @@ def campaigns_grouped_by_condition_name(
6768
) -> Iterator[tuple[eligibility.ConditionName, list[rules.CampaignConfig]]]:
6869
"""Generator that yields campaign groups filtered by condition names and campaign category."""
6970

70-
allowed_types = (
71-
{"V", "S"} if category == "ALL" else {category[0]} if category in {"VACCINATIONS", "SCREENING"} else set()
72-
)
71+
mapping = {
72+
"ALL": {"V", "S"},
73+
"VACCINATIONS": {"V"},
74+
"SCREENING": {"S"},
75+
}
76+
77+
allowed_types = mapping.get(category, set())
78+
7379
filter_all_conditions = "ALL" in conditions
7480

7581
for condition_name, campaign_group in groupby(
@@ -159,23 +165,7 @@ def evaluate_eligibility(
159165
best_campaign_version: CampaignVersion | None
160166
best_cohort_results: dict[str, CohortGroupResult] | None
161167

162-
iteration_results: dict[
163-
str, tuple[Iteration, IterationResult, CampaignID, CampaignVersion, dict[str, CohortGroupResult]]
164-
] = {}
165-
166-
for cc in campaign_group:
167-
active_iteration = cc.current_iteration
168-
cohort_results: dict[str, CohortGroupResult] = self.get_cohort_results(active_iteration)
169-
170-
# Determine Result between cohorts - get the best
171-
status, best_cohorts = self.get_the_best_cohort_memberships(cohort_results)
172-
iteration_results[active_iteration.name] = (
173-
active_iteration,
174-
IterationResult(status, best_cohorts, actions),
175-
cc.id,
176-
cc.version,
177-
cohort_results,
178-
)
168+
iteration_results = self.get_iteration_results(actions, campaign_group)
179169

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

222+
def get_iteration_results(
223+
self, actions: list[SuggestedAction] | None, campaign_group: list[CampaignConfig]
224+
) -> dict[str, tuple[Iteration, IterationResult, CampaignID, CampaignVersion, dict[str, CohortGroupResult]]]:
225+
iteration_results: dict[
226+
str, tuple[Iteration, IterationResult, CampaignID, CampaignVersion, dict[str, CohortGroupResult]]
227+
] = {}
228+
for cc in campaign_group:
229+
active_iteration = cc.current_iteration
230+
cohort_results: dict[str, CohortGroupResult] = self.get_cohort_results(active_iteration)
231+
232+
# Determine Result between cohorts - get the best
233+
status, best_cohorts = self.get_the_best_cohort_memberships(cohort_results)
234+
iteration_results[active_iteration.name] = (
235+
active_iteration,
236+
IterationResult(status, best_cohorts, actions),
237+
cc.id,
238+
cc.version,
239+
cohort_results,
240+
)
241+
return iteration_results
242+
232243
def handle_redirect_rules(
233244
self, best_active_iteration: Iteration
234245
) -> tuple[list[SuggestedAction] | None, RulePriority | None, RuleName | None]:
@@ -311,6 +322,7 @@ def build_condition_results(condition_results: dict[ConditionName, IterationResu
311322
status=active_iteration_result.status,
312323
cohort_results=list(deduplicated_cohort_results),
313324
actions=condition_results[condition_name].actions,
325+
status_text=active_iteration_result.status.get_status_text(condition_name),
314326
)
315327
)
316328
return conditions
@@ -326,9 +338,7 @@ def is_eligible_by_filter_rules(
326338
sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, filter_rules), key=priority_getter)
327339

328340
for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
329-
status, group_inclusion_reasons, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(
330-
rule_group
331-
)
341+
status, group_exclusion_reasons, _ = self.evaluate_rules_priority_group(rule_group)
332342
if status.is_exclusion:
333343
if cohort.cohort_label is not None:
334344
cohort_results[cohort.cohort_label] = CohortGroupResult(
@@ -355,9 +365,7 @@ def evaluate_suppression_rules(
355365
sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, suppression_rules), key=priority_getter)
356366

357367
for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
358-
status, group_inclusion_reasons, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(
359-
rule_group
360-
)
368+
status, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(rule_group)
361369
if status.is_exclusion:
362370
is_actionable = False
363371
suppression_reasons.extend(group_exclusion_reasons)
@@ -381,9 +389,9 @@ def evaluate_suppression_rules(
381389

382390
def evaluate_rules_priority_group(
383391
self, rules_group: Iterator[rules.IterationRule]
384-
) -> tuple[eligibility.Status, list[eligibility.Reason], list[eligibility.Reason], bool]:
392+
) -> tuple[eligibility.Status, list[eligibility.Reason], bool]:
385393
is_rule_stop = False
386-
inclusion_reasons, exclusion_reasons = [], []
394+
exclusion_reasons = []
387395
best_status = eligibility.Status.not_eligible
388396

389397
for rule in rules_group:
@@ -395,9 +403,8 @@ def evaluate_rules_priority_group(
395403
exclusion_reasons.append(reason)
396404
else:
397405
best_status = eligibility.Status.actionable
398-
inclusion_reasons.append(reason)
399406

400-
return best_status, inclusion_reasons, exclusion_reasons, is_rule_stop
407+
return best_status, exclusion_reasons, is_rule_stop
401408

402409
@staticmethod
403410
def get_actions_from_comms(action_mapper: ActionsMapper, comms: str) -> list[SuggestedAction] | None:

src/eligibility_signposting_api/views/eligibility.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibi
113113
suggestions = ProcessedSuggestion( # pyright: ignore[reportCallIssue]
114114
condition=eligibility.ConditionName(condition.condition_name), # pyright: ignore[reportCallIssue]
115115
status=STATUS_MAPPING[condition.status],
116-
statusText=eligibility.StatusText(f"{condition.status}"), # pyright: ignore[reportCallIssue]
116+
statusText=eligibility.StatusText(condition.status_text), # pyright: ignore[reportCallIssue]
117117
eligibilityCohorts=build_eligibility_cohorts(condition), # pyright: ignore[reportCallIssue]
118118
suitabilityRules=build_suitability_results(condition), # pyright: ignore[reportCallIssue]
119119
actions=build_actions(condition),

tests/integration/in_process/test_eligibility_endpoint.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test_not_base_eligible(
8585
],
8686
"actions": [],
8787
"suitabilityRules": [],
88-
"statusText": "Status.not_eligible",
88+
"statusText": "We do not believe you can have it",
8989
}
9090
]
9191
),
@@ -128,7 +128,7 @@ def test_not_eligible_by_rule(
128128
],
129129
"actions": [],
130130
"suitabilityRules": [],
131-
"statusText": "Status.not_eligible",
131+
"statusText": "We do not believe you can have it",
132132
}
133133
]
134134
),
@@ -177,7 +177,7 @@ def test_not_actionable(
177177
"ruleType": "S",
178178
}
179179
],
180-
"statusText": "Status.not_actionable",
180+
"statusText": "You should have the RSV vaccine",
181181
}
182182
]
183183
),
@@ -228,7 +228,7 @@ def test_actionable(
228228
}
229229
],
230230
"suitabilityRules": [],
231-
"statusText": "Status.actionable",
231+
"statusText": "You should have the RSV vaccine",
232232
}
233233
]
234234
),
@@ -273,7 +273,7 @@ def test_not_eligible_by_rule_when_only_magic_cohort_is_present(
273273
],
274274
"actions": [],
275275
"suitabilityRules": [],
276-
"statusText": "Status.not_eligible",
276+
"statusText": "We do not believe you can have it",
277277
}
278278
]
279279
),
@@ -322,7 +322,7 @@ def test_not_actionable_when_only_magic_cohort_is_present(
322322
"ruleType": "S",
323323
}
324324
],
325-
"statusText": "Status.not_actionable",
325+
"statusText": "You should have the COVID vaccine",
326326
}
327327
]
328328
),
@@ -373,7 +373,7 @@ def test_actionable_when_only_magic_cohort_is_present(
373373
}
374374
],
375375
"suitabilityRules": [],
376-
"statusText": "Status.actionable",
376+
"statusText": "You should have the COVID vaccine",
377377
}
378378
]
379379
),
@@ -412,7 +412,7 @@ def test_not_base_eligible(
412412
"eligibilityCohorts": [],
413413
"actions": [],
414414
"suitabilityRules": [],
415-
"statusText": "Status.not_eligible",
415+
"statusText": "We do not believe you can have it",
416416
}
417417
]
418418
),
@@ -449,7 +449,7 @@ def test_not_eligible_by_rule(
449449
"eligibilityCohorts": [],
450450
"actions": [],
451451
"suitabilityRules": [],
452-
"statusText": "Status.not_eligible",
452+
"statusText": "We do not believe you can have it",
453453
}
454454
]
455455
),
@@ -492,7 +492,7 @@ def test_not_actionable(
492492
"ruleType": "S",
493493
}
494494
],
495-
"statusText": "Status.not_actionable",
495+
"statusText": "You should have the FLU vaccine",
496496
}
497497
]
498498
),
@@ -537,7 +537,7 @@ def test_actionable(
537537
}
538538
],
539539
"suitabilityRules": [],
540-
"statusText": "Status.actionable",
540+
"statusText": "You should have the FLU vaccine",
541541
}
542542
]
543543
),
@@ -573,7 +573,7 @@ def test_actionable_no_actions(
573573
"status": "Actionable",
574574
"eligibilityCohorts": [],
575575
"suitabilityRules": [],
576-
"statusText": "Status.actionable",
576+
"statusText": "You should have the FLU vaccine",
577577
}
578578
]
579579
),

tests/integration/lambda/test_app_running_as_lambda.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def test_given_nhs_number_in_path_matches_with_nhs_number_in_headers_and_check_i
235235
"iterationVersion": campaign_config.iterations[0].version,
236236
"conditionName": campaign_config.target,
237237
"status": "not_actionable",
238-
"statusText": "not_actionable",
238+
"statusText": f"You should have the {campaign_config.target} vaccine",
239239
"eligibilityCohorts": [{"cohortCode": "cohort1", "cohortStatus": "not_actionable"}],
240240
"eligibilityCohortGroups": [
241241
{
@@ -461,7 +461,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
461461
"iterationVersion": rsv_campaign.iterations[0].version,
462462
"conditionName": rsv_campaign.target,
463463
"status": "not_eligible",
464-
"statusText": "not_eligible",
464+
"statusText": "We do not believe you can have it",
465465
"eligibilityCohorts": [{"cohortCode": "cohort_label1", "cohortStatus": "not_eligible"}],
466466
"eligibilityCohortGroups": [
467467
{
@@ -482,7 +482,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
482482
"iterationVersion": covid_campaign.iterations[0].version,
483483
"conditionName": covid_campaign.target,
484484
"status": "not_actionable",
485-
"statusText": "not_actionable",
485+
"statusText": f"You should have the {covid_campaign.target} vaccine",
486486
"eligibilityCohorts": [{"cohortCode": "cohort_label2", "cohortStatus": "not_actionable"}],
487487
"eligibilityCohortGroups": [
488488
{
@@ -507,7 +507,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
507507
"iterationVersion": flu_campaign.iterations[0].version,
508508
"conditionName": flu_campaign.target,
509509
"status": "actionable",
510-
"statusText": "actionable",
510+
"statusText": f"You should have the {flu_campaign.target} vaccine",
511511
"eligibilityCohorts": [{"cohortCode": "cohort_label3", "cohortStatus": "actionable"}],
512512
"eligibilityCohortGroups": [
513513
{

tests/unit/audit/test_audit_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def test_append_audit_condition_adds_condition_to_audit_log_on_g(app):
149149
assert cond.iteration_id == iteration.id
150150
assert cond.iteration_version == iteration.version
151151
assert cond.status == best_results[1].status.name
152-
assert cond.status_text == best_results[1].status.name
152+
assert cond.status_text == "You should have the Condition1 vaccine"
153153
assert cond.actions == expected_audit_action
154154
assert cond.action_rule.rule_priority == "1"
155155
assert cond.action_rule.rule_name == "RedirectRuleName1"

0 commit comments

Comments
 (0)