Skip to content

Commit 93e7725

Browse files
Suitability results deduplication + eligibility cohorts build fix (#131)
* Response - add only cohort matching best-status * Suitability rules deduplication
1 parent 21c9eba commit 93e7725

File tree

4 files changed

+133
-22
lines changed

4 files changed

+133
-22
lines changed

src/eligibility_signposting_api/views/eligibility.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,26 +69,14 @@ def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibi
6969
)
7070

7171

72-
def build_suitability_results(condition: Condition) -> list[eligibility.SuitabilityRule]:
73-
return [
74-
eligibility.SuitabilityRule(
75-
ruleType=eligibility.RuleType(reason.rule_type.value),
76-
ruleCode=eligibility.RuleCode(reason.rule_name),
77-
ruleText=eligibility.RuleText(reason.rule_result),
78-
)
79-
for cohort_result in condition.cohort_results
80-
for reason in cohort_result.reasons
81-
if condition.status == Status.not_actionable
82-
]
83-
84-
8572
def build_eligibility_cohorts(condition: Condition) -> list[eligibility.EligibilityCohort]:
8673
"""Group Iteration cohorts and make only one entry per cohort group"""
8774

8875
grouped_cohort_results = defaultdict(list)
8976

9077
for cohort_result in condition.cohort_results:
91-
grouped_cohort_results[cohort_result.cohort_code].append(cohort_result)
78+
if condition.status == cohort_result.status:
79+
grouped_cohort_results[cohort_result.cohort_code].append(cohort_result)
9280

9381
return [
9482
eligibility.EligibilityCohort(
@@ -99,3 +87,26 @@ def build_eligibility_cohorts(condition: Condition) -> list[eligibility.Eligibil
9987
for cohort_group_code, cohort_group in grouped_cohort_results.items()
10088
if cohort_group
10189
]
90+
91+
92+
def build_suitability_results(condition: Condition) -> list[eligibility.SuitabilityRule]:
93+
if not condition.status.not_actionable:
94+
return []
95+
96+
unique_rule_codes = set()
97+
suitability_results = []
98+
99+
for cohort_result in condition.cohort_results:
100+
if cohort_result.status.not_actionable:
101+
for reason in cohort_result.reasons:
102+
if reason.rule_name not in unique_rule_codes:
103+
unique_rule_codes.add(reason.rule_name)
104+
suitability_results.append(
105+
eligibility.SuitabilityRule(
106+
ruleType=eligibility.RuleType(reason.rule_type.value),
107+
ruleCode=eligibility.RuleCode(reason.rule_name),
108+
ruleText=eligibility.RuleText(reason.rule_result),
109+
)
110+
)
111+
112+
return suitability_results

tests/fixtures/builders/model/eligibility.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from polyfactory import Use
55
from polyfactory.factories import DataclassFactory
66

7-
from eligibility_signposting_api.model.eligibility import Condition, EligibilityStatus
7+
from eligibility_signposting_api.model.eligibility import CohortResult, Condition, EligibilityStatus
88

99

1010
class ConditionFactory(DataclassFactory[Condition]): ...
@@ -14,5 +14,8 @@ class EligibilityStatusFactory(DataclassFactory[EligibilityStatus]):
1414
condition = Use(ConditionFactory.batch, size=2)
1515

1616

17+
class CohortResultFactory(DataclassFactory[CohortResult]): ...
18+
19+
1720
def random_str(length: int) -> str:
1821
return "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))

tests/fixtures/matchers/eligibility.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from hamcrest.core.matcher import Matcher
22

33
from eligibility_signposting_api.model.eligibility import CohortResult, Condition, EligibilityStatus, Reason
4-
from eligibility_signposting_api.model.rules import IterationCohort
4+
from eligibility_signposting_api.views.response_model.eligibility import EligibilityCohort, SuitabilityRule
55

66
from .meta import BaseAutoMatcher
77

@@ -18,7 +18,10 @@ class CohortResultMatcher(BaseAutoMatcher[CohortResult]): ...
1818
class ReasonMatcher(BaseAutoMatcher[Reason]): ...
1919

2020

21-
class IterationCohortMatcher(BaseAutoMatcher[IterationCohort]): ...
21+
class EligibilityCohortMatcher(BaseAutoMatcher[EligibilityCohort]): ...
22+
23+
24+
class SuitabilityRuleMatcher(BaseAutoMatcher[SuitabilityRule]): ...
2225

2326

2427
def is_eligibility_status() -> Matcher[EligibilityStatus]:
@@ -37,5 +40,9 @@ def is_reason() -> Matcher[Reason]:
3740
return ReasonMatcher()
3841

3942

40-
def is_iteration_cohort() -> Matcher[IterationCohort]:
41-
return IterationCohortMatcher()
43+
def is_eligibility_cohort() -> Matcher[EligibilityCohort]:
44+
return EligibilityCohortMatcher()
45+
46+
47+
def is_suitability_rule() -> Matcher[SuitabilityRule]:
48+
return SuitabilityRuleMatcher()

tests/unit/views/test_eligibility.py

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,26 @@
55
from brunns.matchers.werkzeug import is_werkzeug_response as is_response
66
from flask import Flask
77
from flask.testing import FlaskClient
8-
from hamcrest import assert_that, contains_exactly, has_entries, has_key
8+
from hamcrest import assert_that, contains_exactly, has_entries, has_key, has_length
99
from wireup.integration.flask import get_app_container
1010

11-
from eligibility_signposting_api.model.eligibility import EligibilityStatus, NHSNumber
11+
from eligibility_signposting_api.model.eligibility import (
12+
Condition,
13+
EligibilityStatus,
14+
NHSNumber,
15+
Reason,
16+
RuleName,
17+
RuleResult,
18+
RuleType,
19+
Status,
20+
)
1221
from eligibility_signposting_api.services import EligibilityService, UnknownPersonError
13-
from tests.fixtures.builders.model.eligibility import EligibilityStatusFactory
22+
from eligibility_signposting_api.views.eligibility import (
23+
build_eligibility_cohorts,
24+
build_suitability_results,
25+
)
26+
from tests.fixtures.builders.model.eligibility import CohortResultFactory, ConditionFactory, EligibilityStatusFactory
27+
from tests.fixtures.matchers.eligibility import is_eligibility_cohort, is_suitability_rule
1428

1529
logger = logging.getLogger(__name__)
1630

@@ -95,3 +109,79 @@ def test_unexpected_error(app: Flask, client: FlaskClient):
95109
)
96110
),
97111
)
112+
113+
114+
def test_build_eligibility_cohorts_results_consider_only_cohorts_with_best_status():
115+
condition: Condition = ConditionFactory.build(
116+
status=Status.not_actionable,
117+
cohort_results=[
118+
CohortResultFactory.build(
119+
cohort_code="cohort_group1",
120+
status=Status.not_actionable,
121+
),
122+
CohortResultFactory.build(
123+
cohort_code="cohort_group2",
124+
status=Status.not_eligible,
125+
),
126+
],
127+
)
128+
129+
results = build_eligibility_cohorts(condition)
130+
131+
assert_that(
132+
results,
133+
contains_exactly(is_eligibility_cohort().with_cohort_code("cohort_group1").and_cohort_status("NotActionable")),
134+
)
135+
136+
137+
def test_build_suitability_results_with_deduplication():
138+
condition: Condition = ConditionFactory.build(
139+
status=Status.not_actionable,
140+
cohort_results=[
141+
CohortResultFactory.build(
142+
cohort_code="cohort_group1",
143+
status=Status.not_actionable,
144+
reasons=[
145+
Reason(
146+
rule_type=RuleType.suppression,
147+
rule_name=RuleName("Exclude too young less than 75"),
148+
rule_result=RuleResult("Age < 75"),
149+
),
150+
Reason(
151+
rule_type=RuleType.suppression,
152+
rule_name=RuleName("Exclude more than 100"),
153+
rule_result=RuleResult("Age > 100"),
154+
),
155+
],
156+
),
157+
CohortResultFactory.build(
158+
cohort_code="cohort_group2",
159+
status=Status.not_actionable,
160+
reasons=[
161+
Reason(
162+
rule_type=RuleType.suppression,
163+
rule_name=RuleName("Exclude too young less than 75"),
164+
rule_result=RuleResult("Age < 75"),
165+
)
166+
],
167+
),
168+
],
169+
)
170+
171+
results = build_suitability_results(condition)
172+
173+
assert_that(
174+
results,
175+
contains_exactly(
176+
is_suitability_rule().with_rule_code("Exclude too young less than 75").and_rule_text("Age < 75"),
177+
is_suitability_rule().with_rule_code("Exclude more than 100").and_rule_text("Age > 100"),
178+
),
179+
)
180+
181+
182+
def test_no_suitability_rules_for_actionable():
183+
condition = ConditionFactory.build(status=Status.actionable, cohort_results=[])
184+
185+
results = build_suitability_results(condition)
186+
187+
assert_that(results, has_length(0))

0 commit comments

Comments
 (0)