Skip to content

Commit c6e4717

Browse files
committed
Adds campaign_evaluator and tests
1 parent a45b390 commit c6e4717

2 files changed

Lines changed: 157 additions & 0 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from itertools import groupby
2+
from operator import attrgetter
3+
from typing import Collection, Iterator
4+
5+
from wireup import service
6+
7+
from eligibility_signposting_api.model import rules, eligibility_status
8+
9+
10+
@service
11+
class CampaignEvaluator:
12+
"""Filters and groups campaign configurations."""
13+
14+
def get_active_campaigns(self, campaign_configs: Collection[rules.CampaignConfig]) -> list[rules.CampaignConfig]:
15+
return [cc for cc in campaign_configs if cc.campaign_live]
16+
17+
def campaigns_grouped_by_condition_name(
18+
self, campaign_configs: Collection[rules.CampaignConfig], conditions: list[str], category: str
19+
) -> Iterator[tuple[eligibility_status.ConditionName, list[rules.CampaignConfig]]]:
20+
mapping = {
21+
"ALL": {"V", "S"},
22+
"VACCINATIONS": {"V"},
23+
"SCREENING": {"S"},
24+
}
25+
26+
allowed_types = mapping.get(category, set())
27+
28+
filter_all_conditions = "ALL" in conditions
29+
30+
active_campaigns = self.get_active_campaigns(campaign_configs)
31+
32+
for condition_name, campaign_group in groupby(
33+
sorted(active_campaigns, key=attrgetter("target")),
34+
key=attrgetter("target"),
35+
):
36+
campaigns = list(campaign_group)
37+
if campaigns and campaigns[0].type in allowed_types and (
38+
filter_all_conditions or str(condition_name) in conditions):
39+
yield condition_name, campaigns
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import datetime
2+
3+
import pytest
4+
from hamcrest import assert_that, is_
5+
6+
from eligibility_signposting_api.model.rules import CampaignID
7+
from eligibility_signposting_api.services.campaign_evaluator import CampaignEvaluator
8+
from tests.fixtures.builders.model import rule
9+
10+
11+
@pytest.fixture
12+
def campaign_evaluator():
13+
return CampaignEvaluator()
14+
15+
16+
@pytest.mark.parametrize(
17+
("campaign_target", "campaign_type", "conditions_filter", "category_filter", "expected_result"),
18+
[
19+
("RSV", "V", ["RSV"], "VACCINATIONS", [("RSV", "V")]),
20+
("RSV", "V", ["COVID"], "VACCINATIONS", []),
21+
("RSV", "S", ["RSV"], "ALL", [("RSV", "S")]),
22+
("RSV", "S", ["ALL"], "ALL", [("RSV", "S")]),
23+
("RSV", "S", ["RSV"], "VACCINATIONS", []),
24+
("RSV", "V", ["RSV"], "ALL", [("RSV", "V")]),
25+
("FLU", "V", ["COVID", "RSV"], "ALL", []),
26+
("FLU", "S", ["ALL"], "ALL", [("FLU", "S")]),
27+
("COVID", "V", ["UNKNOWN"], "VACCINATIONS", []),
28+
("FLU", "V", ["COVID", "FLU"], "VACCINATIONS", [("FLU", "V")]),
29+
],
30+
)
31+
def test_campaigns_grouped_by_condition_name_filters_correctly(
32+
campaign_evaluator, campaign_target, campaign_type, conditions_filter, category_filter, expected_result
33+
):
34+
campaign = rule.CampaignConfigFactory.build(target=campaign_target, type=campaign_type)
35+
36+
result = campaign_evaluator.campaigns_grouped_by_condition_name([campaign], conditions_filter, category_filter)
37+
assert_that([(str(name), group[0].type) for name, group in result], is_(expected_result))
38+
39+
40+
def test_campaigns_grouped_by_condition_name_with_no_campaigns(campaign_evaluator):
41+
42+
result = campaign_evaluator.campaigns_grouped_by_condition_name([], ["RSV"], "VACCINATIONS")
43+
assert_that(list(result), is_([]))
44+
45+
46+
def test_campaigns_grouped_by_condition_name_with_no_active_campaigns(campaign_evaluator):
47+
campaign = rule.CampaignConfigFactory.build(target="RSV", type="V",
48+
start_date=datetime.date(2025, 4, 20),
49+
end_date=datetime.date(2025, 4, 21))
50+
51+
result = campaign_evaluator.campaigns_grouped_by_condition_name([campaign], ["RSV"], "VACCINATIONS")
52+
assert_that(list(result), is_([]))
53+
54+
55+
@pytest.mark.parametrize(
56+
("category_filter", "campaign_type", "expected_count"),
57+
[
58+
("SCREENING", "S", 1),
59+
("SCREENING", "V", 0),
60+
("INVALID_CATEGORY", "S", 0),
61+
],
62+
)
63+
def test_campaigns_grouped_by_condition_name_with_various_categories(
64+
campaign_evaluator, category_filter, campaign_type, expected_count
65+
):
66+
campaign = rule.CampaignConfigFactory.build(target="COVID", type=campaign_type)
67+
result = list(campaign_evaluator.campaigns_grouped_by_condition_name([campaign], ["COVID"], category_filter))
68+
assert_that(len(result), is_(expected_count))
69+
if expected_count > 0:
70+
assert_that(str(result[0][0]), is_("COVID"))
71+
72+
73+
def test_campaigns_grouped_by_condition_name_with_empty_conditions_filter(campaign_evaluator):
74+
campaign = rule.CampaignConfigFactory.build(target="RSV", type="V")
75+
result = campaign_evaluator.campaigns_grouped_by_condition_name([campaign], [], "VACCINATIONS")
76+
assert_that(list(result), is_([]))
77+
78+
79+
def test_campaigns_grouped_by_condition_name_groups_multiple_campaigns_for_same_target(campaign_evaluator):
80+
campaign1 = rule.CampaignConfigFactory.build(target="COVID", type="V", id="C1")
81+
campaign2 = rule.CampaignConfigFactory.build(target="COVID", type="V", id="C2")
82+
campaign3 = rule.CampaignConfigFactory.build(target="FLU", type="V", id="F1")
83+
inactive_campaign = rule.CampaignConfigFactory.build(target="COVID", type="V", id="C3",
84+
start_date=datetime.date(2025, 4, 20),
85+
end_date=datetime.date(2025, 4, 21))
86+
87+
all_campaigns = [campaign1, campaign2, campaign3, inactive_campaign]
88+
result = list(campaign_evaluator.campaigns_grouped_by_condition_name(all_campaigns, ["COVID", "FLU"], "VACCINATIONS"))
89+
90+
assert_that(len(result), is_(2))
91+
92+
result_dict = {str(name): campaigns for name, campaigns in result}
93+
assert_that("COVID" in result_dict)
94+
assert_that("FLU" in result_dict)
95+
96+
assert_that(len(result_dict["COVID"]), is_(2))
97+
assert_that({c.id for c in result_dict["COVID"]}, is_({CampaignID("C1"), CampaignID("C2")}))
98+
99+
assert_that(len(result_dict["FLU"]), is_(1))
100+
assert_that(result_dict["FLU"][0].id, is_(CampaignID("F1")))
101+
102+
103+
def test_campaign_grouping_is_affected_by_order_for_mixed_types(campaign_evaluator):
104+
campaign_v = rule.CampaignConfigFactory.build(target="RSV", type="V")
105+
campaign_s = rule.CampaignConfigFactory.build(target="RSV", type="S")
106+
107+
108+
109+
110+
evaluator_s_first = campaign_evaluator
111+
result_s_first = list(evaluator_s_first.campaigns_grouped_by_condition_name([campaign_s, campaign_v], ["RSV"], "VACCINATIONS"))
112+
assert_that(result_s_first, is_([]))
113+
114+
115+
evaluator_v_first = campaign_evaluator
116+
result_v_first = list(evaluator_v_first.campaigns_grouped_by_condition_name([campaign_v, campaign_s], ["RSV"], "VACCINATIONS"))
117+
assert_that(len(result_v_first), is_(1))
118+
assert_that(len(result_v_first[0][1]), is_(2)) # Both V and S campaigns are in the group

0 commit comments

Comments
 (0)