11from __future__ import annotations
22
3- from _operator import attrgetter
3+ from _operator import add , attrgetter
44from collections import defaultdict
55from collections .abc import Collection , Iterator , Mapping
66from dataclasses import dataclass , field
7- from functools import cached_property
7+ from functools import cached_property , reduce
88from itertools import groupby
99from typing import Any
1010
11- from localstack .services .stepfunctions .asl .component .state .state_execution .state_map import iteration
1211from wireup import service
1312
1413from eligibility_signposting_api .model import eligibility , rules
15- from eligibility_signposting_api .model .eligibility import Status
14+ from eligibility_signposting_api .model .eligibility import CohortStatus , Condition , ConditionName , Status
1615from eligibility_signposting_api .services .calculators .rule_calculator import RuleCalculator
1716
1817Row = Collection [Mapping [str , Any ]]
@@ -73,78 +72,82 @@ def evaluate_eligibility_back_up(self) -> eligibility.EligibilityStatus:
7372 from collections import defaultdict
7473
7574 # Assuming cohort_results contains tuples of (IterationCohort, Status, list[Reason])
76- def get_best_cohort (self ,
77- cohort_results : dict [
78- str , tuple [rules .IterationCohort , eligibility .Status , list [eligibility .Reason ]]]) -> tuple [
79- rules .IterationCohort , eligibility .Status ,
80- list [
81- eligibility .Reason ]] | None :
82-
83-
84- if not cohort_results :
85- return None
86-
75+ def get_best_cohort (self , cohort_results : dict [str , CohortStatus ]) -> tuple [Status , list [CohortStatus ]]:
8776 # Find the best status across cohorts
88- best_status = eligibility .Status .best (* [result [ 1 ] for result in cohort_results .values ()])
77+ best_status = eligibility .Status .best (* [result . status for result in cohort_results .values ()])
8978
9079 # Filter cohorts that match the best status
91- best_cohorts = [result for result in cohort_results .values () if result [1 ] == best_status ]
92-
93- # Pick the cohort with the highest priority
94- #TODO return the appropriate result
95- # Actionable,Actionable, not-actionable, not-actionable, not eligible,not eligible
96- return max (best_cohorts , key = lambda cohort : cohort [0 ].priority ) if best_cohorts else None
80+ best_cohorts = [result for result in cohort_results .values () if result .status == best_status ]
81+ return best_status , best_cohorts
9782
98- def evaluate_eligibility (self ):
83+ def evaluate_eligibility (self ) -> eligibility . EligibilityStatus :
9984 """Iterates over campaign groups, evaluates eligibility, and returns a consolidated status."""
10085 priority_getter = attrgetter ("priority" )
101- results : dict [
102- str , tuple [rules .IterationCohort , eligibility .Status , list [eligibility .Reason ]]] = defaultdict ()
86+ results : dict [ConditionName , tuple [Status , list [CohortStatus ]]] = defaultdict ()
10387 for condition_name , campaign_group in self .campaigns_grouped_by_condition_name :
104- iteration_results : dict [
105- str , tuple [rules .IterationCohort , eligibility .Status , list [eligibility .Reason ]]] = defaultdict ()
88+ iteration_results : dict [str , tuple [Status , list [CohortStatus ]]] = defaultdict ()
10689 for active_iteration in [cc .current_iteration for cc in campaign_group ]:
107- cohort_results : dict [
108- str , tuple [rules .IterationCohort , eligibility .Status , list [eligibility .Reason ]]] = defaultdict ()
109- rules_by_type = {rule_type : tuple (
110- rule for rule in active_iteration .iteration_rules if attrgetter ("type" )(rule ) == rule_type )
111- for rule_type in {"F" , "R" , "S" }}
90+ cohort_results : dict [str , CohortStatus ] = defaultdict ()
91+
92+ # Get the rules for this iteration
93+ rules_filter , rules_suppression , rules_redirect = {
94+ rule_type : tuple (
95+ rule for rule in active_iteration .iteration_rules if attrgetter ("type" )(rule ) == rule_type
96+ )
97+ for rule_type in (rules .RuleType .filter , rules .RuleType .suppression , rules .RuleType .redirect )
98+ }.values ()
11299
113100 for cohort in sorted (active_iteration .iteration_cohorts , key = priority_getter ):
114101 # Check base Eligibility
115102 if cohort .cohort_label in self .person_cohorts :
116103 # Base eligible
117104 # Check Eligibility - F - Rules
118105 eli_flag : bool = True
119- for _ , rule_group in groupby (sorted (rules_by_type ["F" ], key = priority_getter ),
120- key = priority_getter ):
106+ for _ , rule_group in groupby (sorted (rules_filter , key = priority_getter ), key = priority_getter ):
121107 # iter F rules by priority and grouping
122108 # find first exclusion - throws
123109 status , group_actionable , group_exclusions = self .evaluate_rules_priority_group (rule_group )
124110 if status .is_exclusion :
125- cohort_results [cohort .cohort_label ] = (cohort , status , group_exclusions )
111+ cohort_results [cohort .cohort_label ] = CohortStatus (cohort , status , group_exclusions )
126112 eli_flag = False
127113 break
128- if not eli_flag : continue
129114
130- for _ , rule_group in groupby (sorted (rules_by_type ["S" ], key = priority_getter ),
131- key = priority_getter ):
132- status , group_actionable , group_exclusions = self .evaluate_rules_priority_group (
133- rule_group )
134- if status .is_exclusion :
135- cohort_results [cohort .cohort_label ] = (cohort , status , group_exclusions )
136- break
115+ if eli_flag :
116+ for _ , rule_group in groupby (
117+ sorted (rules_suppression , key = priority_getter ), key = priority_getter
118+ ):
119+ status , group_actionable , group_exclusions = self .evaluate_rules_priority_group (
120+ rule_group
121+ )
122+ if status .is_exclusion :
123+ cohort_results [cohort .cohort_label ] = CohortStatus (cohort , status , group_exclusions )
124+ break
125+ # No exclusions - actionable
126+ cohort_results [cohort .cohort_label ] = CohortStatus (cohort , Status .actionable , [])
137127 else :
138128 # Not base eligibility
139- cohort_results [cohort .cohort_label ] = (cohort , eligibility .Status .not_eligible , [])
140-
141- # Determine Result between cohorts
142- iteration_results [active_iteration .name ] = self .get_best_cohort (cohort_results )
143- # Determine results between iterations
144- results [condition_name ] = self .get_best_cohort (iteration_results )
129+ cohort_results [cohort .cohort_label ] = CohortStatus (cohort , eligibility .Status .not_eligible , [])
130+
131+ # Determine Result between cohorts - get the best
132+ iteration_results [active_iteration .name ] = self .get_best_cohort (cohort_results ) # multiple
133+ # Determine results between iterations - get the best
134+ best_so_far : Status = eligibility .Status .not_eligible
135+ for iteration_result in iteration_results .values ():
136+ best_so_far = eligibility .Status .best (best_so_far , iteration_result [0 ])
137+ for iteration_result in iteration_results .values ():
138+ if iteration_result [0 ] is best_so_far :
139+ results [condition_name ] = iteration_result
140+ break # if the status is the same, we can break out of the loop - we are picking the first one
145141 # Consolidate all the results and return
146-
147- return eligibility .EligibilityStatus (conditions = list (self .results ))
142+ final_result = [
143+ Condition (
144+ condition_name = condition_name ,
145+ status = status ,
146+ reasons = reduce (add , [cohort .reasons for cohort in cohort_status_list ], []),
147+ )
148+ for condition_name , (status , cohort_status_list ) in results .items ()
149+ ]
150+ return eligibility .EligibilityStatus (conditions = final_result )
148151
149152 def get_the_base_eligible_campaigns (self , campaign_group : list [rules .CampaignConfig ]) -> list [rules .CampaignConfig ]:
150153 """Return campaigns for which the person is base eligible via cohorts."""
@@ -213,7 +216,7 @@ def evaluate_priority_group(
213216 ir
214217 for ir in iteration_rule_group
215218 if ir .type in (rules .RuleType .filter , rules .RuleType .suppression )
216- and (ir .cohort_label is None or (ir .cohort_label in self .person_cohorts ))
219+ and (ir .cohort_label is None or (ir .cohort_label in self .person_cohorts ))
217220 ]
218221
219222 best_status = eligibility .Status .not_eligible if exclude_capable_rules else eligibility .Status .actionable
@@ -233,17 +236,16 @@ def evaluate_priority_group(
233236 is_rule_stop = any (rule .rule_stop for rule in exclude_capable_rules )
234237 return worst_group_status , actionable_reasons , exclusion_reasons , is_rule_stop
235238
236- def evaluate_rules_priority_group ( # TODO refractor
237- self ,
238- iteration_rule_group : Iterator [rules .IterationRule ]
239+ def evaluate_rules_priority_group (
240+ self , iteration_rule_group : Iterator [rules .IterationRule ]
239241 ) -> tuple [eligibility .Status , list [eligibility .Reason ], list [eligibility .Reason ]]:
240242 status = Status .not_eligible
241243 exclusion_reasons , actionable_reasons = [], []
242244 exclude_capable_rules = [
243245 ir
244246 for ir in iteration_rule_group
245247 if ir .type in (rules .RuleType .filter , rules .RuleType .suppression )
246- and (ir .cohort_label is None or (ir .cohort_label in self .person_cohorts ))
248+ and (ir .cohort_label is None or (ir .cohort_label in self .person_cohorts ))
247249 ]
248250
249251 for rule in exclude_capable_rules :
0 commit comments