Skip to content

Commit 0456482

Browse files
Added more operators and tests, reorganised ordering.
1 parent 118fa5b commit 0456482

File tree

3 files changed

+196
-50
lines changed

3 files changed

+196
-50
lines changed

src/eligibility_signposting_api/model/rules.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,36 +44,41 @@ class RuleOperator(str, Enum):
4444
contains = "contains"
4545
not_contains = "not_contains"
4646
starts_with = "starts_with"
47+
not_starts_with = "not_starts_with"
4748
ends_with = "ends_with"
49+
4850
is_in = "in"
4951
not_in = "not_in"
52+
53+
member_of = "MemberOf"
54+
not_member_of = "NotaMemberOf"
55+
5056
is_null = "is_null"
5157
is_not_null = "is_not_null"
58+
5259
between = "between"
5360
not_between = "not_between"
61+
5462
is_empty = "is_empty"
5563
is_not_empty = "is_not_empty"
64+
5665
is_true = "is_true"
5766
is_false = "is_false"
5867

5968
day_lte = "D<="
60-
week_lte = "W<="
61-
year_lte = "Y<="
62-
69+
day_lt = "D<"
6370
day_gte = "D>="
64-
week_gte = "W>="
65-
year_gte = "Y>="
71+
day_gt = "D>"
6672

67-
day_lt = "D<"
73+
week_lte = "W<="
6874
week_lt = "W<"
69-
year_lt = "Y<"
70-
71-
day_gt = "D>"
75+
week_gte = "W>="
7276
week_gt = "W>"
73-
year_gt = "Y>"
7477

75-
member_of = "MemberOf"
76-
not_member_of = "NotaMemberOf"
78+
year_lte = "Y<="
79+
year_lt = "Y<"
80+
year_gte = "Y>="
81+
year_gt = "Y>"
7782

7883

7984
class RuleAttributeLevel(str, Enum):

src/eligibility_signposting_api/services/eligibility_services.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,31 +102,79 @@ def evaluate_rule(iteration_rule: IterationRule, attribute_value: Any) -> bool:
102102

103103
case RuleOperator.starts_with:
104104
return str(attribute_value).startswith(iteration_rule.comparator)
105+
case RuleOperator.not_starts_with:
106+
return not str(attribute_value).startswith(iteration_rule.comparator)
107+
108+
case RuleOperator.ends_with:
109+
return str(attribute_value).endswith(iteration_rule.comparator)
105110

106111
case RuleOperator.is_in:
107112
comparators = str(iteration_rule.comparator).split(",")
108113
return str(attribute_value) in comparators
109114
case RuleOperator.not_in:
110115
comparators = str(iteration_rule.comparator).split(",")
111116
return str(attribute_value) not in comparators
117+
112118
case RuleOperator.member_of:
113119
attribute_values = str(attribute_value).split(",")
114120
return iteration_rule.comparator in attribute_values
115121
case RuleOperator.not_member_of:
116122
attribute_values = str(attribute_value).split(",")
117123
return iteration_rule.comparator not in attribute_values
124+
125+
case RuleOperator.is_null:
126+
return attribute_value in (None, "")
127+
case RuleOperator.is_not_null:
128+
return attribute_value not in (None, "")
129+
130+
case RuleOperator.between:
131+
if attribute_value in (None, ""): return False
132+
low_comparator_str, high_comparator_str = str(iteration_rule.comparator).split(",")
133+
low_comparator = min(int(low_comparator_str), int(high_comparator_str))
134+
high_comparator = max(int(low_comparator_str), int(high_comparator_str))
135+
return low_comparator <= int(attribute_value) <= high_comparator
136+
137+
case RuleOperator.not_between:
138+
if attribute_value in (None, ""): return False
139+
low_comparator_str, high_comparator_str = str(iteration_rule.comparator).split(",")
140+
low_comparator = min(int(low_comparator_str), int(high_comparator_str))
141+
high_comparator = max(int(low_comparator_str), int(high_comparator_str))
142+
return int(attribute_value) < low_comparator or int(attribute_value) > high_comparator
143+
144+
case RuleOperator.is_empty:
145+
msg = f"{iteration_rule.operator} not implemented"
146+
raise NotImplementedError(msg)
147+
148+
case RuleOperator.is_not_empty:
149+
msg = f"{iteration_rule.operator} not implemented"
150+
raise NotImplementedError(msg)
151+
152+
case RuleOperator.is_true:
153+
return attribute_value is True
154+
case RuleOperator.is_false:
155+
return attribute_value is False
156+
157+
158+
case RuleOperator.day_lte:
159+
attribute_date = datetime.strptime(str(attribute_value), '%Y%m%d') if attribute_value else None # noqa: DTZ007
160+
today_date = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
161+
cutoff = today_date + relativedelta(days=int(iteration_rule.comparator))
162+
return (attribute_date <= cutoff) if attribute_date else False
163+
164+
case RuleOperator.day_gte:
165+
attribute_date = datetime.strptime(str(attribute_value),'%Y%m%d') if attribute_value else None # noqa: DTZ007
166+
today_date = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
167+
cutoff = today_date + relativedelta(days=int(iteration_rule.comparator))
168+
return (attribute_date >= cutoff) if attribute_date else False
169+
170+
118171
case RuleOperator.year_gt:
119172
attribute_date = datetime.strptime(str(attribute_value),
120173
"%Y%m%d") if attribute_value else None # noqa: DTZ007
121174
today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
122175
cutoff = today + relativedelta(years=int(iteration_rule.comparator))
123176
return (attribute_date > cutoff) if attribute_date else False
124-
case RuleOperator.day_gte:
125-
attribute_date = datetime.strptime(str(attribute_value),
126-
'%Y%m%d') if attribute_value else None # noqa: DTZ007
127-
today_date = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
128-
cutoff = today_date + relativedelta(days=int(iteration_rule.comparator))
129-
return (attribute_date >= cutoff) if attribute_date else False
177+
130178
case _:
131179
msg = f"{iteration_rule.operator} not implemented"
132180
raise NotImplementedError(msg)

tests/unit/services/test_eligibility_services.py

Lines changed: 125 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -235,17 +235,31 @@ def test_starts_with_rule():
235235
rule = IterationRuleFactory.build(operator=RuleOperator.starts_with, comparator="YY66")
236236
assert EligibilityService.evaluate_rule(rule, "YY66")
237237
assert EligibilityService.evaluate_rule(rule, "YY66095")
238-
239238
assert not EligibilityService.evaluate_rule(rule, "BB11")
240239
assert not EligibilityService.evaluate_rule(rule, "BYY66095")
241240
assert not EligibilityService.evaluate_rule(rule, " YY66")
242-
243241
assert not EligibilityService.evaluate_rule(rule, None)
244242
assert not EligibilityService.evaluate_rule(rule, "")
245243

246244

245+
def test_not_starts_with_rule():
246+
rule = IterationRuleFactory.build(operator=RuleOperator.not_starts_with, comparator="YY66")
247+
assert not EligibilityService.evaluate_rule(rule, "YY66")
248+
assert not EligibilityService.evaluate_rule(rule, "YY66095")
249+
assert EligibilityService.evaluate_rule(rule, "BB11")
250+
assert EligibilityService.evaluate_rule(rule, "BYY66095")
251+
assert EligibilityService.evaluate_rule(rule, " YY66")
252+
assert EligibilityService.evaluate_rule(rule, None)
253+
assert EligibilityService.evaluate_rule(rule, "")
254+
255+
247256
def test_ends_with_rule():
248-
pass
257+
rule = IterationRuleFactory.build(operator=RuleOperator.ends_with, comparator="2BA")
258+
assert EligibilityService.evaluate_rule(rule, "2BA")
259+
assert EligibilityService.evaluate_rule(rule, "002BA")
260+
assert not EligibilityService.evaluate_rule(rule, None)
261+
assert not EligibilityService.evaluate_rule(rule, "")
262+
assert not EligibilityService.evaluate_rule(rule, "2BA00")
249263

250264

251265
def test_in_rule():
@@ -264,44 +278,139 @@ def test_not_in_rule():
264278
assert not EligibilityService.evaluate_rule(rule, "QH8")
265279

266280

267-
def test_is_null_rule():
268-
pass
281+
def test_member_of_rule():
282+
rule = IterationRuleFactory.build(operator=RuleOperator.member_of, comparator="cohort1")
283+
assert EligibilityService.evaluate_rule(rule, "cohort1,cohort2")
284+
assert not EligibilityService.evaluate_rule(rule, None)
285+
assert not EligibilityService.evaluate_rule(rule, "")
286+
assert not EligibilityService.evaluate_rule(rule, "cohort3")
269287

270288

271-
def test_is_not_null_rule():
272-
pass
289+
def test_not_member_of_rule():
290+
rule = IterationRuleFactory.build(operator=RuleOperator.not_member_of, comparator="cohort1")
291+
assert not EligibilityService.evaluate_rule(rule, "cohort1,cohort2")
292+
assert EligibilityService.evaluate_rule(rule, None)
293+
assert EligibilityService.evaluate_rule(rule, "")
294+
assert EligibilityService.evaluate_rule(rule, "cohort3")
273295

274296

297+
def test_is_null_rule():
298+
# Check email flag is null
299+
rule = IterationRuleFactory.build(operator=RuleOperator.is_null)
300+
assert EligibilityService.evaluate_rule(rule, "")
301+
assert EligibilityService.evaluate_rule(rule, None)
302+
assert not EligibilityService.evaluate_rule(rule, "email_flag")
303+
assert not EligibilityService.evaluate_rule(rule, 42)
304+
275305
def test_is_not_null_rule():
276-
pass
306+
# Check email flag is not null
307+
rule = IterationRuleFactory.build(operator=RuleOperator.is_not_null)
308+
assert not EligibilityService.evaluate_rule(rule, "")
309+
assert not EligibilityService.evaluate_rule(rule, None)
310+
assert EligibilityService.evaluate_rule(rule, "email_flag")
311+
assert EligibilityService.evaluate_rule(rule, 42)
277312

278313

279314
def test_between_rule():
280-
pass
315+
# check if numerical value is between two give values (inclusive)
316+
rule = IterationRuleFactory.build(operator=RuleOperator.between, comparator="1,3")
317+
assert not EligibilityService.evaluate_rule(rule, "0")
318+
assert EligibilityService.evaluate_rule(rule, "1")
319+
assert EligibilityService.evaluate_rule(rule, "2")
320+
assert EligibilityService.evaluate_rule(rule, "3")
321+
assert not EligibilityService.evaluate_rule(rule, "4")
322+
assert not EligibilityService.evaluate_rule(rule, "")
323+
assert not EligibilityService.evaluate_rule(rule, None)
324+
325+
rule = IterationRuleFactory.build(operator=RuleOperator.between, comparator="3,1")
326+
assert not EligibilityService.evaluate_rule(rule, "0")
327+
assert EligibilityService.evaluate_rule(rule, "1")
328+
assert EligibilityService.evaluate_rule(rule, "2")
329+
assert EligibilityService.evaluate_rule(rule, "3")
330+
assert not EligibilityService.evaluate_rule(rule, "4")
331+
assert not EligibilityService.evaluate_rule(rule, "")
332+
assert not EligibilityService.evaluate_rule(rule, None)
333+
334+
rule = IterationRuleFactory.build(operator=RuleOperator.between, comparator="3,3")
335+
assert not EligibilityService.evaluate_rule(rule, "2")
336+
assert EligibilityService.evaluate_rule(rule, "3")
337+
assert not EligibilityService.evaluate_rule(rule, "4")
338+
assert not EligibilityService.evaluate_rule(rule, "")
339+
assert not EligibilityService.evaluate_rule(rule, None)
340+
341+
rule = IterationRuleFactory.build(operator=RuleOperator.between, comparator="20100302,20100304")
342+
assert not EligibilityService.evaluate_rule(rule, "20100301")
343+
assert EligibilityService.evaluate_rule(rule, "20100302")
344+
assert EligibilityService.evaluate_rule(rule, "20100303")
345+
assert EligibilityService.evaluate_rule(rule, "20100304")
346+
assert not EligibilityService.evaluate_rule(rule, "20100305")
347+
assert not EligibilityService.evaluate_rule(rule, "")
348+
assert not EligibilityService.evaluate_rule(rule, None)
349+
281350

282351

283352
def test_not_between_rule():
284-
pass
353+
# check if numerical value is NOT between two give values (inclusive)
354+
rule = IterationRuleFactory.build(operator=RuleOperator.not_between, comparator="1,3")
355+
assert EligibilityService.evaluate_rule(rule, "0")
356+
assert not EligibilityService.evaluate_rule(rule, "1")
357+
assert not EligibilityService.evaluate_rule(rule, "2")
358+
assert not EligibilityService.evaluate_rule(rule, "3")
359+
assert EligibilityService.evaluate_rule(rule, "4")
360+
assert not EligibilityService.evaluate_rule(rule, "")
361+
assert not EligibilityService.evaluate_rule(rule, None)
285362

363+
rule = IterationRuleFactory.build(operator=RuleOperator.not_between, comparator="3,1")
364+
assert EligibilityService.evaluate_rule(rule, "0")
365+
assert not EligibilityService.evaluate_rule(rule, "1")
366+
assert not EligibilityService.evaluate_rule(rule, "2")
367+
assert not EligibilityService.evaluate_rule(rule, "3")
368+
assert EligibilityService.evaluate_rule(rule, "4")
369+
assert not EligibilityService.evaluate_rule(rule, "")
370+
assert not EligibilityService.evaluate_rule(rule, None)
286371

287-
def test_is_empty_rule():
288-
pass
372+
rule = IterationRuleFactory.build(operator=RuleOperator.not_between, comparator="3,3")
373+
assert EligibilityService.evaluate_rule(rule, "2")
374+
assert not EligibilityService.evaluate_rule(rule, "3")
375+
assert EligibilityService.evaluate_rule(rule, "4")
376+
assert not EligibilityService.evaluate_rule(rule, "")
377+
assert not EligibilityService.evaluate_rule(rule, None)
289378

290379

380+
def test_is_empty_rule():
381+
pass # clarify definition
382+
291383
def test_is_not_empty_rule():
292-
pass
384+
pass # clarify definition
293385

294386

295387
def test_is_true_rule():
296-
pass
388+
rule = IterationRuleFactory.build(operator=RuleOperator.is_true)
389+
assert EligibilityService.evaluate_rule(rule, True)
390+
assert not EligibilityService.evaluate_rule(rule, False)
391+
assert not EligibilityService.evaluate_rule(rule, "")
392+
assert not EligibilityService.evaluate_rule(rule, None)
393+
assert not EligibilityService.evaluate_rule(rule, "True")
297394

298395

299396
def test_is_false_rule():
300-
pass
397+
rule = IterationRuleFactory.build(operator=RuleOperator.is_false)
398+
assert EligibilityService.evaluate_rule(rule, False)
399+
assert not EligibilityService.evaluate_rule(rule, True)
400+
assert not EligibilityService.evaluate_rule(rule, "")
401+
assert not EligibilityService.evaluate_rule(rule, None)
402+
assert not EligibilityService.evaluate_rule(rule, "False")
301403

302404

303405
def test_day_lte_rule():
304-
pass
406+
today = datetime.today() # noqa: DTZ002
407+
days_offset = 2
408+
future_date = today + relativedelta(days=days_offset - 3)
409+
attribute_value = future_date.strftime("%Y%m%d")
410+
rule = IterationRuleFactory.build(operator=RuleOperator.day_lte, comparator=str(days_offset))
411+
assert EligibilityService.evaluate_rule(rule, attribute_value)
412+
assert not EligibilityService.evaluate_rule(rule, "")
413+
assert not EligibilityService.evaluate_rule(rule, None)
305414

306415

307416
def test_week_lte_rule():
@@ -406,22 +515,6 @@ def test_year_gt_rule_same_date():
406515
assert not EligibilityService.evaluate_rule(rule, None)
407516

408517

409-
def test_member_of_rule():
410-
rule = IterationRuleFactory.build(operator=RuleOperator.member_of, comparator="cohort1")
411-
assert EligibilityService.evaluate_rule(rule, "cohort1,cohort2")
412-
assert not EligibilityService.evaluate_rule(rule, None)
413-
assert not EligibilityService.evaluate_rule(rule, "")
414-
assert not EligibilityService.evaluate_rule(rule, "cohort3")
415-
416-
417-
def test_not_member_of_rule():
418-
rule = IterationRuleFactory.build(operator=RuleOperator.not_member_of, comparator="cohort1")
419-
assert not EligibilityService.evaluate_rule(rule, "cohort1,cohort2")
420-
assert EligibilityService.evaluate_rule(rule, None)
421-
assert EligibilityService.evaluate_rule(rule, "")
422-
assert EligibilityService.evaluate_rule(rule, "cohort3")
423-
424-
425518
@pytest.mark.skip(reason="Skipping this test since all the operators are implemented")
426519
def test_unimplemented_operator():
427520
rule = IterationRuleFactory.build(operator=RuleOperator.member_of, comparator="something")

0 commit comments

Comments
 (0)