| 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 |
|
|
|
|