File size: 1,808 Bytes
7872230 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | from dataclasses import dataclass, field
from decimal import Decimal
from app.core.enums import RuleType
from app.models.rule import RuleMaster
from app.services.operators import evaluate_operator
@dataclass
class RuleEvaluation:
matched: list[RuleMaster] = field(default_factory=list)
skipped: list[str] = field(default_factory=list)
@property
def codes(self) -> list[str]:
return [rule.rule_code for rule in self.matched]
@property
def reason_codes(self) -> list[str]:
return [rule.reason_code for rule in self.matched]
class RuleEngine:
def evaluate(self, rules: list[RuleMaster], context: dict) -> RuleEvaluation:
result = RuleEvaluation()
for rule in sorted(rules, key=lambda item: item.priority):
try:
if evaluate_operator(context.get(rule.field_name), rule.operator, rule.field_value):
result.matched.append(rule)
else:
result.skipped.append(rule.rule_code)
except ValueError:
result.skipped.append(rule.rule_code)
return result
def score(self, rules: list[RuleMaster], context: dict) -> tuple[Decimal, dict[str, Decimal], RuleEvaluation]:
score_rules = [rule for rule in rules if rule.rule_type == RuleType.SCORE]
evaluation = self.evaluate(score_rules, context)
total = Decimal("0")
breakup: dict[str, Decimal] = {}
for rule in evaluation.matched:
value = Decimal(rule.score_value or 0)
component = rule.score_component or "other"
breakup[component] = breakup.get(component, Decimal("0")) + value
total += value
capped = max(Decimal("0"), min(Decimal("100"), total))
return capped, breakup, evaluation
|