Skip to content

Commit b66a8dc

Browse files
committed
applying to filter rules and adding test
1 parent 4fb4711 commit b66a8dc

2 files changed

Lines changed: 70 additions & 39 deletions

File tree

src/eligibility_signposting_api/services/processors/rule_processor.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ def is_eligible(
5050
) -> bool:
5151
is_eligible = True
5252
priority_getter = attrgetter("priority")
53-
sorted_rules_by_priority = sorted(self.get_exclusion_rules(cohort, filter_rules), key=priority_getter)
53+
sorted_rules_by_priority = sorted(filter_rules, key=priority_getter)
5454

5555
for _, rule_group in groupby(sorted_rules_by_priority, key=priority_getter):
56-
status, group_exclusion_reasons, _ = self.evaluate_rules_priority_group(person, rule_group)
56+
group_rules = list(rule_group)
57+
if self._should_skip_rule_group(cohort, group_rules):
58+
continue
59+
status, group_exclusion_reasons, _ = self.evaluate_rules_priority_group(person, iter(group_rules))
5760
if status.is_exclusion:
5861
if cohort.cohort_label is not None:
5962
cohort_results[cohort.cohort_label] = CohortGroupResult(
@@ -65,6 +68,7 @@ def is_eligible(
6568
)
6669
is_eligible = False
6770
break
71+
6872
return is_eligible
6973

7074
def is_actionable(
@@ -85,9 +89,7 @@ def is_actionable(
8589
if self._should_skip_rule_group(cohort, group_rules):
8690
continue
8791

88-
status, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(
89-
person, iter(group_rules)
90-
)
92+
status, group_exclusion_reasons, rule_stop = self.evaluate_rules_priority_group(person, iter(group_rules))
9193
if status.is_exclusion:
9294
is_actionable = False
9395
suppression_reasons.extend(group_exclusion_reasons)
@@ -112,9 +114,7 @@ def is_actionable(
112114
@staticmethod
113115
def _should_skip_rule_group(cohort: IterationCohort, group_rules: list[IterationRule]) -> bool:
114116
cohort_specific_rules = [rule for rule in group_rules if rule.cohort_label is not None]
115-
matching_specific_rules = [
116-
rule for rule in cohort_specific_rules if rule.cohort_label == cohort.cohort_label
117-
]
117+
matching_specific_rules = [rule for rule in cohort_specific_rules if rule.cohort_label == cohort.cohort_label]
118118
return bool(cohort_specific_rules and not matching_specific_rules)
119119

120120
def evaluate_rules_priority_group(

tests/unit/services/processors/test_rule_processor.py

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def test_evaluate_rules_priority_group_with_rule_stop(mock_rule_calculator_class
145145

146146

147147
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
148-
def test_general_rule_should_not_evaluate_in_isolation_without_matching_specific_rule(
148+
def test_general_suppression_rule_should_not_evaluate_in_isolation_without_matching_specific_rule(
149149
mock_evaluate_rules_priority_group,
150150
rule_processor,
151151
):
@@ -176,9 +176,38 @@ def test_general_rule_should_not_evaluate_in_isolation_without_matching_specific
176176

177177

178178
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
179-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
179+
def test_general_filter_rule_should_not_evaluate_in_isolation_without_matching_specific_rule(
180+
mock_evaluate_rules_priority_group,
181+
rule_processor,
182+
):
183+
# Person is in COHORT_B
184+
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_B", positive_description="Eligible")
185+
cohort_results = {}
186+
187+
# Rule 1: Non-matching rule cohort-specific to COHORT_A — should not be evaluated
188+
rule_specific = rule_builder.IterationRuleFactory.build(
189+
priority=510, type=RuleType.filter, cohort_label="COHORT_A", name="SPECIFIC_RULE"
190+
)
191+
192+
# Rule 2: Matching general rule of the same priority as cohort-specific rule
193+
# - should also not be evaluated
194+
rule_general = rule_builder.IterationRuleFactory.build(
195+
priority=510, type=RuleType.filter, cohort_label=None, name="GENERAL_RULE"
196+
)
197+
198+
filter_rules = [rule_specific, rule_general]
199+
200+
# Act
201+
rule_processor.is_eligible(MOCK_PERSON_DATA, cohort, cohort_results, filter_rules)
202+
203+
# None of the rules should be evaluated
204+
mock_evaluate_rules_priority_group.assert_not_called()
205+
206+
207+
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
208+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
180209
def test_is_eligible_by_filter_rules_eligible(
181-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
210+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
182211
):
183212
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A")
184213
cohort_results = {}
@@ -191,14 +220,14 @@ def test_is_eligible_by_filter_rules_eligible(
191220

192221
assert_that(is_eligible, is_(True))
193222
assert_that(cohort_results, is_({}))
194-
mock_get_exclusion_rules.assert_called_once_with(cohort, filter_rules)
223+
mock_should_skip_rule_group.assert_called_once_with(cohort, filter_rules)
195224
mock_evaluate_rules_priority_group.assert_called_once()
196225

197226

198227
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
199-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
228+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
200229
def test_is_eligible_by_filter_rules_not_eligible(
201-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
230+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
202231
):
203232
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A", negative_description="Not Eligible")
204233
cohort_results = {}
@@ -215,14 +244,14 @@ def test_is_eligible_by_filter_rules_not_eligible(
215244
assert_that(cohort_results["COHORT_A"].status, is_(Status.not_eligible))
216245
assert_that(cohort_results["COHORT_A"].description, is_("Not Eligible"))
217246
assert_that(cohort_results["COHORT_A"].audit_rules, is_([mock_reason]))
218-
mock_get_exclusion_rules.assert_called_once_with(cohort, filter_rules)
247+
mock_should_skip_rule_group.assert_called_once_with(cohort, filter_rules)
219248
mock_evaluate_rules_priority_group.assert_called_once()
220249

221250

222251
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
223-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
252+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
224253
def test_evaluate_suppression_rules_actionable(
225-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
254+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
226255
):
227256
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A", positive_description="Actionable")
228257
cohort_results = {}
@@ -238,14 +267,14 @@ def test_evaluate_suppression_rules_actionable(
238267
assert_that(cohort_results["COHORT_A"].description, is_("Actionable"))
239268
assert_that(cohort_results["COHORT_A"].reasons, is_([]))
240269
assert_that(cohort_results["COHORT_A"].audit_rules, is_([]))
241-
mock_get_exclusion_rules.assert_called_once_with(cohort, suppression_rules)
270+
mock_should_skip_rule_group.assert_called_once_with(cohort, suppression_rules)
242271
mock_evaluate_rules_priority_group.assert_called_once()
243272

244273

245274
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
246-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
275+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
247276
def test_evaluate_suppression_rules_not_actionable(
248-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
277+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
249278
):
250279
cohort = rule_builder.IterationCohortFactory.build(
251280
cohort_label="COHORT_A", positive_description="Positive Description"
@@ -264,14 +293,14 @@ def test_evaluate_suppression_rules_not_actionable(
264293
assert_that(cohort_results["COHORT_A"].description, is_("Positive Description"))
265294
assert_that(cohort_results["COHORT_A"].reasons, is_([mock_reason]))
266295
assert_that(cohort_results["COHORT_A"].audit_rules, is_([mock_reason]))
267-
mock_get_exclusion_rules.assert_called_once_with(cohort, suppression_rules)
296+
mock_should_skip_rule_group.assert_called_once_with(cohort, suppression_rules)
268297
mock_evaluate_rules_priority_group.assert_called_once()
269298

270299

271300
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
272-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
301+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
273302
def test_evaluate_suppression_rules_stops_on_rule_stop(
274-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
303+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
275304
):
276305
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A")
277306
cohort_results = {}
@@ -296,13 +325,13 @@ def test_evaluate_suppression_rules_stops_on_rule_stop(
296325
assert_that(cohort_results["COHORT_A"].reasons, is_([mock_reason_p1]))
297326
assert_that(cohort_results["COHORT_A"].audit_rules, is_([mock_reason_p1]))
298327
assert_that(mock_evaluate_rules_priority_group.call_count, is_(1))
299-
mock_get_exclusion_rules.assert_called_once_with(cohort, [suppression_rule_p1])
328+
mock_should_skip_rule_group.assert_called_once_with(cohort, [suppression_rule_p1])
300329

301330

302331
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
303-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
332+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
304333
def test_evaluate_suppression_rules_does_not_stop_on_rule_stop_when_status_is_actionable(
305-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
334+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
306335
):
307336
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A")
308337
cohort_results = {}
@@ -328,7 +357,7 @@ def test_evaluate_suppression_rules_does_not_stop_on_rule_stop_when_status_is_ac
328357
assert_that(cohort_results["COHORT_A"].audit_rules, is_([mock_reason_p2]))
329358

330359
assert_that(mock_evaluate_rules_priority_group.call_count, is_(2))
331-
assert_that(mock_get_exclusion_rules.call_count, is_(2))
360+
assert_that(mock_should_skip_rule_group.call_count, is_(2))
332361

333362

334363
def test_is_base_eligible(mock_person_data_reader):
@@ -395,8 +424,8 @@ def test_rules_get_group_by_types_of_rules(rule_processor):
395424

396425

397426
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
398-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
399-
def test_is_eligible_by_filter_rules(mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor):
427+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
428+
def test_is_eligible_by_filter_rules(mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor):
400429
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A")
401430
cohort_results = {}
402431
filter_rule = rule_builder.IterationRuleFactory.build(priority=1, type=RuleType.filter)
@@ -408,13 +437,15 @@ def test_is_eligible_by_filter_rules(mock_get_exclusion_rules, mock_evaluate_rul
408437

409438
assert_that(is_eligible, is_(True))
410439
assert_that(cohort_results, is_({}))
411-
mock_get_exclusion_rules.assert_called_once_with(cohort, filter_rules)
440+
mock_should_skip_rule_group.assert_called_once_with(cohort, filter_rules)
412441
mock_evaluate_rules_priority_group.assert_called_once()
413442

414443

415444
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
416-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
417-
def test_is_not_eligible_by_filter_rules(mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor):
445+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
446+
def test_is_not_eligible_by_filter_rules(
447+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
448+
):
418449
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A", negative_description="Not Eligible")
419450
cohort_results = {}
420451
filter_rule = rule_builder.IterationRuleFactory.build(priority=1, type=RuleType.filter, name="F1")
@@ -440,14 +471,14 @@ def mock_evaluate_side_effect(person, rules_group): # noqa: ARG001
440471
assert_that(cohort_results["COHORT_A"].status, is_(Status.not_eligible))
441472
assert_that(cohort_results["COHORT_A"].description, is_("Not Eligible"))
442473
assert_that(cohort_results["COHORT_A"].audit_rules, is_([mock_reason]))
443-
mock_get_exclusion_rules.assert_called_once_with(cohort, filter_rules)
474+
mock_should_skip_rule_group.assert_called_once_with(cohort, filter_rules)
444475
mock_evaluate_rules_priority_group.assert_called_once()
445476

446477

447478
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
448-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
479+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
449480
def test_is_actionable_by_suppression_rules(
450-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
481+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
451482
):
452483
cohort = rule_builder.IterationCohortFactory.build(cohort_label="COHORT_A", positive_description="Actionable")
453484
cohort_results = {}
@@ -463,14 +494,14 @@ def test_is_actionable_by_suppression_rules(
463494
assert_that(cohort_results["COHORT_A"].description, is_("Actionable"))
464495
assert_that(cohort_results["COHORT_A"].reasons, is_(empty()))
465496
assert_that(cohort_results["COHORT_A"].audit_rules, is_(empty()))
466-
mock_get_exclusion_rules.assert_called_once_with(cohort, suppression_rules)
497+
mock_should_skip_rule_group.assert_called_once_with(cohort, suppression_rules)
467498
mock_evaluate_rules_priority_group.assert_called_once()
468499

469500

470501
@patch.object(RuleProcessor, "evaluate_rules_priority_group")
471-
@patch.object(RuleProcessor, "get_exclusion_rules", side_effect=lambda cohort, rules_to_filter: rules_to_filter) # noqa: ARG005
502+
@patch.object(RuleProcessor, "_should_skip_rule_group", return_value=False)
472503
def test_is_not_actionable_by_suppression_rules(
473-
mock_get_exclusion_rules, mock_evaluate_rules_priority_group, rule_processor
504+
mock_should_skip_rule_group, mock_evaluate_rules_priority_group, rule_processor
474505
):
475506
cohort = rule_builder.IterationCohortFactory.build(
476507
cohort_label="COHORT_A", positive_description="Positive Description"
@@ -499,7 +530,7 @@ def mock_evaluate_side_effect(person, rules_group): # noqa: ARG001
499530
assert_that(cohort_results["COHORT_A"].description, is_("Positive Description"))
500531
assert_that(cohort_results["COHORT_A"].reasons, is_([mock_reason]))
501532
assert_that(cohort_results["COHORT_A"].audit_rules, is_([mock_reason]))
502-
mock_get_exclusion_rules.assert_called_once_with(cohort, suppression_rules)
533+
mock_should_skip_rule_group.assert_called_once_with(cohort, suppression_rules)
503534
mock_evaluate_rules_priority_group.assert_called_once()
504535

505536

0 commit comments

Comments
 (0)