Skip to content

Commit 010e860

Browse files
ELI-427: Allows dynamic status text (#319)
* ELI-427: Allows dynamic status text * Fixed formatting. --------- Co-authored-by: ayeshalshukri1-nhs <[email protected]>
1 parent 3126647 commit 010e860

File tree

11 files changed

+440
-28
lines changed

11 files changed

+440
-28
lines changed

src/eligibility_signposting_api/audit/audit_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def append_audit_condition(
9999
iteration_version=best_active_iteration.version if best_active_iteration else None,
100100
condition_name=condition_name,
101101
status=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,
102+
status_text=best_candidate.status_text if best_candidate else None,
103103
eligibility_cohorts=audit_eligibility_cohorts,
104104
eligibility_cohort_groups=audit_eligibility_cohort_groups,
105105
filter_rules=audit_filter_rule,

src/eligibility_signposting_api/model/campaign_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ def get(self, key: str, default: AvailableAction | None = None) -> AvailableActi
145145
return self.root.get(key, default)
146146

147147

148+
class StatusText(BaseModel):
149+
not_eligible: str | None = Field(None, alias="NotEligible")
150+
not_actionable: str | None = Field(None, alias="NotActionable")
151+
actionable: str | None = Field(None, alias="Actionable")
152+
153+
model_config = {"populate_by_name": True, "extra": "ignore"}
154+
155+
148156
class Iteration(BaseModel):
149157
id: IterationID = Field(..., alias="ID")
150158
version: IterationVersion = Field(..., alias="Version")
@@ -160,6 +168,7 @@ class Iteration(BaseModel):
160168
iteration_cohorts: list[IterationCohort] = Field(..., alias="IterationCohorts")
161169
iteration_rules: list[IterationRule] = Field(..., alias="IterationRules")
162170
actions_mapper: ActionsMapper = Field(..., alias="ActionsMapper")
171+
status_text: StatusText | None = Field(None, alias="StatusText")
163172

164173
model_config = {"populate_by_name": True, "arbitrary_types_allowed": True, "extra": "ignore"}
165174

src/eligibility_signposting_api/model/eligibility_status.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def best(*statuses: Status) -> Status:
7373
"""
7474
return max(statuses)
7575

76-
def get_status_text(self, condition_name: ConditionName) -> StatusText:
76+
def get_default_status_text(self, condition_name: ConditionName) -> StatusText:
7777
status_to_text_mapping = {
7878
self.not_eligible: lambda: StatusText("We do not believe you can have it"),
7979
self.not_actionable: lambda: StatusText(f"You should have the {condition_name} vaccine"),
@@ -130,6 +130,7 @@ class CohortGroupResult:
130130
@dataclass
131131
class IterationResult:
132132
status: Status
133+
status_text: StatusText
133134
cohort_results: list[CohortGroupResult]
134135
actions: list[SuggestedAction] | None
135136

src/eligibility_signposting_api/services/calculators/eligibility_calculator.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from wireup import service
1010

1111
from eligibility_signposting_api.audit.audit_context import AuditContext
12-
from eligibility_signposting_api.model import eligibility_status
12+
from eligibility_signposting_api.model import campaign_config, eligibility_status
1313
from eligibility_signposting_api.model.eligibility_status import (
1414
BestIterationResult,
1515
CohortGroupResult,
@@ -19,6 +19,7 @@
1919
IterationResult,
2020
Reason,
2121
Status,
22+
StatusText,
2223
)
2324
from eligibility_signposting_api.services.processors.action_rule_handler import ActionRuleHandler
2425
from eligibility_signposting_api.services.processors.campaign_evaluator import CampaignEvaluator
@@ -153,11 +154,34 @@ def get_iteration_results(self, campaign_group: list[CampaignConfig]) -> dict[It
153154

154155
# Determine Result between cohorts - get the best
155156
status, best_cohorts = self.get_the_best_cohort_memberships(cohort_results)
157+
status_text = self.get_status_text(active_iteration.status_text, ConditionName(cc.target), status)
158+
156159
iteration_results[active_iteration.name] = BestIterationResult(
157-
IterationResult(status, best_cohorts, []), active_iteration, cc.id, cc.version, cohort_results
160+
IterationResult(status, status_text, best_cohorts, []),
161+
active_iteration,
162+
cc.id,
163+
cc.version,
164+
cohort_results,
158165
)
159166
return iteration_results
160167

168+
@staticmethod
169+
def get_status_text(
170+
status_text: campaign_config.StatusText | None, condition_name: ConditionName, status: Status
171+
) -> StatusText:
172+
if status_text is None:
173+
status_text_or_default = status.get_default_status_text(condition_name)
174+
else:
175+
status_to_text = {
176+
Status.not_eligible: status_text.not_eligible
177+
or Status.not_eligible.get_default_status_text(condition_name),
178+
Status.not_actionable: status_text.not_actionable
179+
or Status.not_actionable.get_default_status_text(condition_name),
180+
Status.actionable: status_text.actionable or Status.actionable.get_default_status_text(condition_name),
181+
}
182+
status_text_or_default = StatusText(status_to_text[status])
183+
return status_text_or_default
184+
161185
@staticmethod
162186
def build_condition(iteration_result: IterationResult, condition_name: ConditionName) -> Condition:
163187
grouped_cohort_results = defaultdict(list)
@@ -180,7 +204,7 @@ def build_condition(iteration_result: IterationResult, condition_name: Condition
180204
cohort_results=list(deduplicated_cohort_results),
181205
suitability_rules=list(overall_deduplicated_reasons_for_condition),
182206
actions=iteration_result.actions,
183-
status_text=iteration_result.status.get_status_text(condition_name),
207+
status_text=iteration_result.status_text,
184208
)
185209

186210
@staticmethod

tests/fixtures/builders/model/rule.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
RuleOperator,
2525
RulePriority,
2626
RuleType,
27+
StatusText,
2728
)
2829

2930

@@ -60,6 +61,12 @@ class ActionsMapperFactory(ModelFactory[ActionsMapper]):
6061
root = Use(lambda: {"defaultcomms": AvailableActionDetailFactory.build()})
6162

6263

64+
class StatusTextFactory(ModelFactory[StatusText]):
65+
not_eligible = "Not eligible status text"
66+
not_actionable = "Not actionable status text"
67+
actionable = "Actionable status text"
68+
69+
6370
class IterationFactory(ModelFactory[Iteration]):
6471
iteration_cohorts = Use(IterationCohortFactory.batch, size=2)
6572
iteration_rules = Use(IterationRuleFactory.batch, size=2)
@@ -80,7 +87,7 @@ class CampaignConfigFactory(RawCampaignConfigFactory):
8087
def build(cls, **kwargs) -> CampaignConfig:
8188
"""Ensure invariants are met:
8289
* no iterations with duplicate iteration dates
83-
* must have iteration active from campaign start date"""
90+
* must have iteration active from the campaign start date"""
8491
processed_kwargs = cls.process_kwargs(**kwargs)
8592
start_date: date = processed_kwargs["start_date"]
8693
iterations: list[Iteration] = processed_kwargs["iterations"]

tests/integration/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
EndDate,
2525
RuleType,
2626
StartDate,
27+
StatusText,
2728
)
2829
from eligibility_signposting_api.repos.campaign_repo import BucketName
2930
from eligibility_signposting_api.repos.person_repo import TableName
@@ -528,6 +529,7 @@ def campaign_config(s3_client: BaseClient, rules_bucket: BucketName) -> Generato
528529
negative_description="negative_description",
529530
)
530531
],
532+
status_text=None,
531533
)
532534
],
533535
)
@@ -607,6 +609,7 @@ def campaign_config_with_and_rule(s3_client: BaseClient, rules_bucket: BucketNam
607609
negative_description="negative_description",
608610
),
609611
],
612+
status_text=None,
610613
)
611614
],
612615
)
@@ -762,6 +765,11 @@ def multiple_campaign_configs(s3_client: BaseClient, rules_bucket: BucketName) -
762765
negative_description="negative_desc_4",
763766
),
764767
],
768+
status_text=StatusText(
769+
NotEligible=f"You are not eligible to take {targets[i]} vaccines.",
770+
NotActionable=f"You have taken {targets[i]} vaccine in the last 90 days",
771+
Actionable=f"You can take {targets[i]} vaccine.",
772+
),
765773
)
766774
],
767775
)
@@ -790,6 +798,7 @@ def campaign_config_with_magic_cohort(s3_client: BaseClient, rules_bucket: Bucke
790798
rule.PersonAgeSuppressionRuleFactory.build(),
791799
],
792800
iteration_cohorts=[rule.MagicCohortFactory.build(cohort_label="elid_all_people")],
801+
status_text=None,
793802
)
794803
],
795804
)
@@ -822,6 +831,7 @@ def campaign_config_with_missing_descriptions_missing_rule_text(
822831
negative_description="",
823832
)
824833
],
834+
status_text=None,
825835
)
826836
],
827837
)

tests/integration/lambda/test_app_running_as_lambda.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
460460
"iterationVersion": rsv_campaign.iterations[0].version,
461461
"conditionName": rsv_campaign.target,
462462
"status": "not_eligible",
463-
"statusText": "We do not believe you can have it",
463+
"statusText": f"You are not eligible to take {rsv_campaign.target} vaccines.",
464464
"eligibilityCohorts": [
465465
{"cohortCode": "cohort_label1", "cohortStatus": "not_eligible"},
466466
{"cohortCode": "cohort_label4", "cohortStatus": "not_eligible"},
@@ -484,7 +484,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
484484
"iterationVersion": covid_campaign.iterations[0].version,
485485
"conditionName": covid_campaign.target,
486486
"status": "not_actionable",
487-
"statusText": f"You should have the {covid_campaign.target} vaccine",
487+
"statusText": f"You have taken {covid_campaign.target} vaccine in the last 90 days",
488488
"eligibilityCohorts": [
489489
{"cohortCode": "cohort_label2", "cohortStatus": "not_actionable"},
490490
{"cohortCode": "cohort_label4", "cohortStatus": "not_actionable"},
@@ -516,7 +516,7 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
516516
"iterationVersion": flu_campaign.iterations[0].version,
517517
"conditionName": flu_campaign.target,
518518
"status": "actionable",
519-
"statusText": f"You should have the {flu_campaign.target} vaccine",
519+
"statusText": f"You can take {flu_campaign.target} vaccine.",
520520
"eligibilityCohorts": [
521521
{"cohortCode": "cohort_label3", "cohortStatus": "actionable"},
522522
{"cohortCode": "cohort_label4", "cohortStatus": "actionable"},

tests/unit/audit/test_audit_context.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
RuleName,
2727
RulePriority,
2828
Status,
29+
StatusText,
2930
SuggestedAction,
3031
UrlLabel,
3132
UrlLink,
@@ -132,7 +133,10 @@ def test_append_audit_condition_adds_condition_to_audit_log_on_g_for_actionable_
132133
reasons=audit_rules,
133134
)
134135
iteration_result = IterationResult(
135-
status=Status.actionable, cohort_results=[cohort_group_result], actions=suggested_actions
136+
status=Status.actionable,
137+
status_text=StatusText("You should have the Condition1 vaccine"),
138+
cohort_results=[cohort_group_result],
139+
actions=suggested_actions,
136140
)
137141
campaign_details = (CampaignID("CampaignID1"), CampaignVersion(123))
138142
matched_action_detail = MatchedActionDetail(
@@ -210,7 +214,12 @@ def test_should_append_audit_suppression_rules_for_actionable_status(app):
210214
audit_rules=audit_rules,
211215
reasons=audit_rules,
212216
)
213-
iteration_result = IterationResult(status=Status.actionable, cohort_results=[cohort_group_result], actions=[])
217+
iteration_result = IterationResult(
218+
status=Status.actionable,
219+
status_text=StatusText("You should have the Condition1 vaccine"),
220+
cohort_results=[cohort_group_result],
221+
actions=[],
222+
)
214223
campaign_details = (CampaignID("CampaignID1"), CampaignVersion(123))
215224

216225
best_iteration_results = BestIterationResult(
@@ -265,7 +274,12 @@ def test_should_append_audit_filter_rules_for_not_actionable_status(app):
265274
audit_rules=audit_rules,
266275
reasons=audit_rules,
267276
)
268-
iteration_result = IterationResult(status=Status.not_actionable, cohort_results=[cohort_group_result], actions=[])
277+
iteration_result = IterationResult(
278+
status=Status.not_actionable,
279+
status_text=StatusText("You should have the Condition1 vaccine"),
280+
cohort_results=[cohort_group_result],
281+
actions=[],
282+
)
269283
campaign_details = (CampaignID("CampaignID1"), CampaignVersion(123))
270284

271285
best_iteration_results = BestIterationResult(

tests/unit/model/test_status.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ def test_best_status(self):
2727
assert Status.best(Status.not_eligible) == Status.not_eligible
2828

2929
def test_get_status_text(self):
30-
assert Status.not_eligible.get_status_text(ConditionName("COVID")) == StatusText(
30+
assert Status.not_eligible.get_default_status_text(ConditionName("COVID")) == StatusText(
3131
"We do not believe you can have it"
3232
)
3333

34-
assert Status.not_actionable.get_status_text(ConditionName("FLU")) == StatusText(
34+
assert Status.not_actionable.get_default_status_text(ConditionName("FLU")) == StatusText(
3535
"You should have the FLU vaccine"
3636
)
3737

38-
assert Status.actionable.get_status_text(ConditionName("COVID")) == StatusText(
38+
assert Status.actionable.get_default_status_text(ConditionName("COVID")) == StatusText(
3939
"You should have the COVID vaccine"
4040
)
4141

0 commit comments

Comments
 (0)