Skip to content

Commit d62bca8

Browse files
ELI-615 | wip
1 parent 039d79c commit d62bca8

3 files changed

Lines changed: 52 additions & 38 deletions

File tree

src/eligibility_signposting_api/services/calculators/eligibility_calculator.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,18 @@ def get_the_best_cohort_memberships(
8181

8282
return best_status, best_cohorts
8383

84-
def get_eligibility_status(self, include_actions: str, conditions: list[str], requested_category: str) -> EligibilityStatus:
84+
def get_eligibility_status(
85+
self, include_actions: str, conditions: list[str], requested_category: str
86+
) -> EligibilityStatus:
8587
include_actions_flag = include_actions.upper() == "Y"
8688
condition_results: dict[ConditionName, IterationResult] = {}
8789
final_result = []
8890

8991
requested_grouped_campaigns = self.campaign_evaluator.get_campaign_with_latest_active_iteration_per_target(
9092
self.campaign_configs, conditions, requested_category
9193
)
92-
for condition_name, campaign_group in requested_grouped_campaigns:
93-
best_iteration_result = self.get_best_iteration_result(campaign_group)
94+
for condition_name, campaign in requested_grouped_campaigns:
95+
best_iteration_result = self.get_best_iteration_result(campaign)
9496

9597
if best_iteration_result is None:
9698
continue
@@ -123,23 +125,8 @@ def get_eligibility_status(self, include_actions: str, conditions: list[str], re
123125
# Consolidate all the results and return
124126
return eligibility_status.EligibilityStatus(conditions=final_result)
125127

126-
def get_best_iteration_result(self, campaign_group: list[CampaignConfig]) -> BestIterationResult | None:
127-
iteration_results = self.get_iteration_results(campaign_group)
128128

129-
if not iteration_results:
130-
return None
131-
132-
(_best_iteration_name, best_iteration_result) = max(
133-
iteration_results.items(),
134-
key=lambda item: next(iter(item[1].cohort_results.values())).status.value
135-
# Below handles the case where there are no cohort results
136-
if item[1].cohort_results
137-
else -1,
138-
)
139-
140-
return best_iteration_result
141-
142-
def get_iteration_results(self, campaign_group: list[CampaignConfig]) -> dict[IterationName, BestIterationResult]:
129+
def get_iteration_results(self, campaign_group: CampaignConfig) -> dict[IterationName, BestIterationResult]:
143130
iteration_results: dict[IterationName, BestIterationResult] = {}
144131

145132
for cc in campaign_group:

src/eligibility_signposting_api/services/processors/campaign_evaluator.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,35 @@ class CampaignEvaluator:
1515
def get_active_campaigns(self, campaign_configs: Collection[CampaignConfig]) -> list[CampaignConfig]:
1616
return [cc for cc in campaign_configs if cc.campaign_live]
1717

18-
def get_latest_campaign(self, campaign_group: list[CampaignConfig]):
19-
if not campaign_group:
20-
return None
18+
def get_campaign_with_latest_iteration(self, active_campaigns: list[CampaignConfig]) -> CampaignConfig:
19+
20+
"""
21+
Returns the campaign with the latest active iteration date.
2122
22-
latest_date = max(c.start_date for c in campaign_group)
23+
1. Collect all campaigns with an active iteration.
24+
2. Sort by iteration date (descending).
25+
3. Extract the lead campaign, throwing an error if a tie for the latest date exists.
26+
"""
27+
28+
if not active_campaigns:
29+
return None
2330

24-
latest = [c for c in campaign_group if c.start_date == latest_date]
31+
valid_items = [
32+
(cc.current_iteration.iteration_date, cc)
33+
for cc in active_campaigns if cc.current_iteration
34+
]
2535

26-
if len(latest) == 1:
27-
return latest[0]
36+
if not valid_items:
37+
latest_date, latest_campaign = None, None
38+
else:
39+
max_date = max(item[0] for item in valid_items)
40+
cc_with_max_iteration_date = [item for item in valid_items if item[0] == max_date]
41+
if len(cc_with_max_iteration_date) > 1:
42+
raise ValueError(f"Ambiguous result: {len(cc_with_max_iteration_date)} campaigns found for date {max_date}")
2843

29-
if len(latest) > 1:
30-
raise ValueError(
31-
f"Multiple campaigns share the latest start_date: {latest_date}") # TODO handle it in FHIR format
44+
latest_date, latest_campaign = cc_with_max_iteration_date[0]
3245

33-
return None
46+
return latest_campaign
3447

3548
def get_campaign_with_latest_active_iteration_per_target(
3649
self, campaign_configs: Collection[CampaignConfig], conditions: list[str], requested_category: str
@@ -52,6 +65,6 @@ def get_campaign_with_latest_active_iteration_per_target(
5265
sorted(active_campaigns, key=attrgetter("target")),
5366
key=attrgetter("target"),
5467
):
55-
campaigns = [c for c in allowed_campaigns if filter_all_conditions or str(condition_name) in conditions]
68+
filtered_campaigns = [c for c in allowed_campaigns if filter_all_conditions or str(condition_name) in conditions]
5669

57-
yield condition_name, self.get_latest_campaign(campaigns)
70+
yield condition_name, self.get_campaign_with_latest_iteration(filtered_campaigns)

tests/unit/services/processors/test_campaign_evaluator.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ def test_campaigns_grouped_by_condition_name_filters_correctly( # noqa: PLR0913
3333
):
3434
campaign = rule.CampaignConfigFactory.build(target=campaign_target, type=campaign_type)
3535

36-
result = campaign_evaluator.get_campaign_with_latest_active_iteration_per_target([campaign], conditions_filter, category_filter)
36+
result = campaign_evaluator.get_campaign_with_latest_active_iteration_per_target(
37+
[campaign], conditions_filter, category_filter
38+
)
3739
assert_that([(str(name), group[0].type) for name, group in result], is_(expected_result))
3840

3941

@@ -47,7 +49,9 @@ def test_campaigns_grouped_by_condition_name_with_no_active_campaigns(campaign_e
4749
target="RSV", type="V", start_date=datetime.date(2025, 4, 20), end_date=datetime.date(2025, 4, 21)
4850
)
4951

50-
result = campaign_evaluator.get_campaign_with_latest_active_iteration_per_target([campaign], ["RSV"], "VACCINATIONS")
52+
result = campaign_evaluator.get_campaign_with_latest_active_iteration_per_target(
53+
[campaign], ["RSV"], "VACCINATIONS"
54+
)
5155
assert_that(list(result), is_([]))
5256

5357

@@ -63,7 +67,9 @@ def test_campaigns_grouped_by_condition_name_with_various_categories(
6367
campaign_evaluator, category_filter, campaign_type, expected_count
6468
):
6569
campaign = rule.CampaignConfigFactory.build(target="COVID", type=campaign_type)
66-
result = list(campaign_evaluator.get_campaign_with_latest_active_iteration_per_target([campaign], ["COVID"], category_filter))
70+
result = list(
71+
campaign_evaluator.get_campaign_with_latest_active_iteration_per_target([campaign], ["COVID"], category_filter)
72+
)
6773
assert_that(len(result), is_(expected_count))
6874
if expected_count > 0:
6975
assert_that(str(result[0][0]), is_("COVID"))
@@ -84,7 +90,11 @@ def test_campaigns_grouped_by_condition_name_groups_multiple_campaigns_for_same_
8490
)
8591

8692
all_campaigns = [campaign1, campaign2, campaign3, inactive_campaign]
87-
result = list(campaign_evaluator.get_campaign_with_latest_active_iteration_per_target(all_campaigns, ["COVID", "FLU"], "VACCINATIONS"))
93+
result = list(
94+
campaign_evaluator.get_campaign_with_latest_active_iteration_per_target(
95+
all_campaigns, ["COVID", "FLU"], "VACCINATIONS"
96+
)
97+
)
8898

8999
assert_that(len(result), is_(2))
90100

@@ -105,13 +115,17 @@ def test_campaign_grouping_is_affected_by_order_for_mixed_types(campaign_evaluat
105115

106116
evaluator_s_first = campaign_evaluator
107117
result_s_first = list(
108-
evaluator_s_first.get_campaign_with_latest_active_iteration_per_target([campaign_s, campaign_v], ["RSV"], "VACCINATIONS")
118+
evaluator_s_first.get_campaign_with_latest_active_iteration_per_target(
119+
[campaign_s, campaign_v], ["RSV"], "VACCINATIONS"
120+
)
109121
)
110122
assert_that(result_s_first, is_([]))
111123

112124
evaluator_v_first = campaign_evaluator
113125
result_v_first = list(
114-
evaluator_v_first.get_campaign_with_latest_active_iteration_per_target([campaign_v, campaign_s], ["RSV"], "VACCINATIONS")
126+
evaluator_v_first.get_campaign_with_latest_active_iteration_per_target(
127+
[campaign_v, campaign_s], ["RSV"], "VACCINATIONS"
128+
)
115129
)
116130
assert_that(len(result_v_first), is_(1))
117131
assert_that(len(result_v_first[0][1]), is_(2))

0 commit comments

Comments
 (0)