pylitmus 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pylitmus/__init__.py CHANGED
@@ -26,9 +26,9 @@ from .storage import (
26
26
  RuleRepository,
27
27
  )
28
28
  from .strategies import MaxStrategy, ScoringStrategy, SumStrategy, WeightedStrategy
29
- from .types import AssessmentResult, Operator, Rule, RuleResult, Severity
29
+ from .types import AssessmentResult, DecisionTier, Operator, Rule, RuleResult, Severity
30
30
 
31
- __version__ = "1.0.0"
31
+ __version__ = "1.1.0"
32
32
 
33
33
  __all__ = [
34
34
  # Main
@@ -40,6 +40,7 @@ __all__ = [
40
40
  "Rule",
41
41
  "RuleResult",
42
42
  "AssessmentResult",
43
+ "DecisionTier",
43
44
  "Operator",
44
45
  "Severity",
45
46
  # Conditions
pylitmus/engine.py CHANGED
@@ -7,7 +7,7 @@ import time
7
7
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
8
8
 
9
9
  from .exceptions import EvaluationError
10
- from .types import AssessmentResult, Rule, RuleResult
10
+ from .types import AssessmentResult, DecisionTier, Rule, RuleResult
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from .storage.base import RuleRepository
@@ -21,24 +21,34 @@ class RuleEngine:
21
21
  Main rules engine for evaluating data against configured rules.
22
22
 
23
23
  Usage:
24
+ # Without decision tiers (returns None for decision)
24
25
  engine = RuleEngine(
25
26
  repository=DatabaseRuleRepository(db_url),
26
27
  scoring_strategy=WeightedStrategy()
27
28
  )
28
29
 
30
+ # With user-defined decision tiers
31
+ engine = RuleEngine(
32
+ repository=DatabaseRuleRepository(db_url),
33
+ scoring_strategy=WeightedStrategy(),
34
+ decision_tiers=[
35
+ DecisionTier("AUTO_APPROVE", 0, 20, "Very low risk"),
36
+ DecisionTier("APPROVE", 20, 40, "Low risk"),
37
+ DecisionTier("SOFT_REVIEW", 40, 60, "Medium risk"),
38
+ DecisionTier("HARD_REVIEW", 60, 80, "Higher risk"),
39
+ DecisionTier("FLAG", 80, 95, "High risk"),
40
+ DecisionTier("AUTO_REJECT", 95, 101, "Very high risk"),
41
+ ]
42
+ )
43
+
29
44
  result = engine.evaluate(claim, context)
30
45
  """
31
46
 
32
- DEFAULT_THRESHOLDS = {
33
- "approve": 30,
34
- "review": 70,
35
- }
36
-
37
47
  def __init__(
38
48
  self,
39
49
  repository: "RuleRepository",
40
50
  scoring_strategy: Optional["ScoringStrategy"] = None,
41
- decision_thresholds: Optional[Dict[str, int]] = None,
51
+ decision_tiers: Optional[List[DecisionTier]] = None,
42
52
  condition_builder: Optional[Any] = None,
43
53
  ):
44
54
  """
@@ -47,12 +57,13 @@ class RuleEngine:
47
57
  Args:
48
58
  repository: Rule storage backend
49
59
  scoring_strategy: Strategy for calculating scores
50
- decision_thresholds: Custom thresholds for decisions
60
+ decision_tiers: User-defined decision tiers with score ranges.
61
+ If not provided, decision will be None.
51
62
  condition_builder: Builder for creating conditions from dicts
52
63
  """
53
64
  self.repository = repository
54
65
  self._scoring_strategy = scoring_strategy
55
- self._thresholds = decision_thresholds or self.DEFAULT_THRESHOLDS.copy()
66
+ self._decision_tiers = decision_tiers
56
67
  self._condition_builder = condition_builder
57
68
  self._rules_cache: Optional[List[Rule]] = None
58
69
 
@@ -189,25 +200,25 @@ class RuleEngine:
189
200
  explanation=rule.description if triggered else "Condition not met",
190
201
  )
191
202
 
192
- def _make_decision(self, score: int) -> str:
203
+ def _make_decision(self, score: int) -> Optional[str]:
193
204
  """
194
- Make a decision based on the total score.
205
+ Make a decision based on the total score and configured decision tiers.
195
206
 
196
207
  Args:
197
208
  score: Total calculated score
198
209
 
199
210
  Returns:
200
- Decision string: 'APPROVE', 'REVIEW', or 'FLAG'
211
+ Decision string if a matching tier is found, None otherwise.
212
+ If no decision tiers are configured, returns None.
201
213
  """
202
- approve_threshold = self._thresholds.get("approve", 30)
203
- review_threshold = self._thresholds.get("review", 70)
204
-
205
- if score < approve_threshold:
206
- return "APPROVE"
207
- elif score < review_threshold:
208
- return "REVIEW"
209
- else:
210
- return "FLAG"
214
+ if not self._decision_tiers:
215
+ return None
216
+
217
+ for tier in self._decision_tiers:
218
+ if tier.matches(score):
219
+ return tier.name
220
+
221
+ return None
211
222
 
212
223
  def reload_rules(self) -> None:
213
224
  """Force reload rules from repository."""
pylitmus/factory.py CHANGED
@@ -4,7 +4,7 @@ Factory functions for creating RuleEngine instances with sensible defaults.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import TYPE_CHECKING, Dict, List, Optional, Union
7
+ from typing import TYPE_CHECKING, List, Optional, Union
8
8
 
9
9
  from .engine import RuleEngine
10
10
  from .storage import (
@@ -15,6 +15,7 @@ from .storage import (
15
15
  RuleRepository,
16
16
  )
17
17
  from .strategies import MaxStrategy, ScoringStrategy, SumStrategy, WeightedStrategy
18
+ from .types import DecisionTier
18
19
 
19
20
  if TYPE_CHECKING:
20
21
  from .types import Rule
@@ -34,7 +35,7 @@ def create_engine(
34
35
  # Scoring
35
36
  scoring_strategy: Union[str, ScoringStrategy] = "sum",
36
37
  # Decision
37
- decision_thresholds: Optional[Dict[str, int]] = None,
38
+ decision_tiers: Optional[List[DecisionTier]] = None,
38
39
  ) -> RuleEngine:
39
40
  """
40
41
  Create a configured RuleEngine instance.
@@ -52,13 +53,14 @@ def create_engine(
52
53
  cache_url: Redis URL (for 'redis' cache)
53
54
  cache_ttl: Cache TTL in seconds
54
55
  scoring_strategy: 'sum', 'weighted', 'max', or ScoringStrategy instance
55
- decision_thresholds: Custom thresholds {'approve': 30, 'review': 70}
56
+ decision_tiers: User-defined decision tiers with score ranges.
57
+ If not provided, decision will be None in results.
56
58
 
57
59
  Returns:
58
60
  Configured RuleEngine instance
59
61
 
60
62
  Examples:
61
- # Simple in-memory engine
63
+ # Simple in-memory engine (no decision tiers - decision will be None)
62
64
  engine = create_engine()
63
65
 
64
66
  # In-memory with predefined rules
@@ -78,10 +80,17 @@ def create_engine(
78
80
  rules_file='./rules/fraud_rules.yaml'
79
81
  )
80
82
 
81
- # With weighted scoring
83
+ # With user-defined decision tiers
82
84
  engine = create_engine(
83
85
  scoring_strategy='weighted',
84
- decision_thresholds={'approve': 25, 'review': 60}
86
+ decision_tiers=[
87
+ DecisionTier("AUTO_APPROVE", 0, 20, "Very low risk"),
88
+ DecisionTier("APPROVE", 20, 40, "Low risk"),
89
+ DecisionTier("SOFT_REVIEW", 40, 60, "Medium risk"),
90
+ DecisionTier("HARD_REVIEW", 60, 80, "Higher risk"),
91
+ DecisionTier("FLAG", 80, 95, "High risk"),
92
+ DecisionTier("AUTO_REJECT", 95, 101, "Very high risk"),
93
+ ]
85
94
  )
86
95
  """
87
96
 
@@ -112,7 +121,7 @@ def create_engine(
112
121
  return RuleEngine(
113
122
  repository=repo,
114
123
  scoring_strategy=strategy,
115
- decision_thresholds=decision_thresholds,
124
+ decision_tiers=decision_tiers,
116
125
  )
117
126
 
118
127
 
pylitmus/types.py CHANGED
@@ -3,13 +3,14 @@ Core type definitions for the CMAP Rules Engine.
3
3
  """
4
4
 
5
5
  from dataclasses import dataclass, field
6
- from typing import Any, Dict, List, Optional
7
- from enum import Enum
8
6
  from datetime import datetime
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional
9
9
 
10
10
 
11
11
  class Operator(str, Enum):
12
12
  """Supported condition operators."""
13
+
13
14
  EQUALS = "equals"
14
15
  NOT_EQUALS = "not_equals"
15
16
  GREATER_THAN = "greater_than"
@@ -32,6 +33,7 @@ class Operator(str, Enum):
32
33
 
33
34
  class Severity(str, Enum):
34
35
  """Rule severity levels."""
36
+
35
37
  LOW = "LOW"
36
38
  MEDIUM = "MEDIUM"
37
39
  HIGH = "HIGH"
@@ -41,6 +43,7 @@ class Severity(str, Enum):
41
43
  @dataclass
42
44
  class Rule:
43
45
  """A rule definition."""
46
+
44
47
  code: str
45
48
  name: str
46
49
  description: str
@@ -73,6 +76,7 @@ class Rule:
73
76
  @dataclass
74
77
  class RuleResult:
75
78
  """Result of evaluating a single rule."""
79
+
76
80
  rule_code: str
77
81
  rule_name: str
78
82
  triggered: bool
@@ -82,11 +86,33 @@ class RuleResult:
82
86
  explanation: str
83
87
 
84
88
 
89
+ @dataclass
90
+ class DecisionTier:
91
+ """
92
+ A decision tier definition with score range.
93
+
94
+ Example:
95
+ DecisionTier(name="APPROVE", min_score=0, max_score=30)
96
+ DecisionTier(name="REVIEW", min_score=30, max_score=70)
97
+ DecisionTier(name="FLAG", min_score=70, max_score=100)
98
+ """
99
+
100
+ name: str
101
+ min_score: int
102
+ max_score: int
103
+ description: Optional[str] = None
104
+
105
+ def matches(self, score: int) -> bool:
106
+ """Check if score falls within this tier's range (min inclusive, max exclusive)."""
107
+ return self.min_score <= score < self.max_score
108
+
109
+
85
110
  @dataclass
86
111
  class AssessmentResult:
87
112
  """Complete assessment result."""
113
+
88
114
  total_score: int
89
- decision: str
115
+ decision: Optional[str]
90
116
  triggered_rules: List[RuleResult] = field(default_factory=list)
91
117
  all_rules_evaluated: int = 0
92
118
  processing_time_ms: float = 0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylitmus
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A high-performance rules engine for Python - evaluate data against configurable rules and get clear verdicts
5
5
  Project-URL: Homepage, https://github.com/yourorg/pylitmus
6
6
  Project-URL: Documentation, https://pylitmus.readthedocs.io/
@@ -1,8 +1,8 @@
1
- pylitmus/__init__.py,sha256=MivCPQfBek8sZneeSOBIoYA575Se4pUlunP9uBvS_s0,1843
2
- pylitmus/engine.py,sha256=j-5w4AhH3FVaUnvTnJQLGREWfV8fFjaGifTOIkDI2YE,7371
1
+ pylitmus/__init__.py,sha256=xFp0yLha5ZAdH0UePFfg6GM-vSixD_1XfVOK8xVCcQ0,1877
2
+ pylitmus/engine.py,sha256=MAk3dHE0d7FRpzc2u4BttXSvgD9mEOsNR_ZRhBIUIVo,8045
3
3
  pylitmus/exceptions.py,sha256=c5AcD1LXAH52NDWTVp3xHp4vXaHwQW75n6JuaJ4D0Dk,653
4
- pylitmus/factory.py,sha256=BrhFtm0iuMg7uFlaNb5QnFTwPGkyozeVRNnEAhDWqjw,5143
5
- pylitmus/types.py,sha256=bqU4dsBfzlcBnbiLjD90zQ8LW9nJZqdj9VDcZYcYvI8,2274
4
+ pylitmus/factory.py,sha256=ZurhJZw9QJTEU5UT3x731J2cynCSHVcBtNRXAfMMpCI,5660
5
+ pylitmus/types.py,sha256=d0BgF5K7EpGSvpeH8B5sveqqDIPKhyZ1DYrt5gTGnf0,2875
6
6
  pylitmus/conditions/__init__.py,sha256=jGfOpD4cLBk2lpbnfPyNtPFbUAs0lkMO9AJ5FO4rHgc,303
7
7
  pylitmus/conditions/base.py,sha256=OaNyyf4eCy2kUYE2xEw4b2XdTzeHlF3r0XB18CSEshg,1261
8
8
  pylitmus/conditions/builder.py,sha256=kymvWoMNH_ss1B1u7hs49InNjW8WWS8kyp5-rCdVk_c,2924
@@ -33,7 +33,7 @@ pylitmus/strategies/base.py,sha256=Fq9JgUVdbuPlwEk8pdSyiLZ-RBRm_3DnJkWAWCtGmRk,5
33
33
  pylitmus/strategies/max.py,sha256=BUjZ9P3DUrDShcvy7_0pVo3Pa9lSLJI5cX9bRELUAlU,553
34
34
  pylitmus/strategies/sum.py,sha256=kA0MHiYSpLa-lYR0nMmTGGKMMKm58jrqkuC9a5sTy0U,831
35
35
  pylitmus/strategies/weighted.py,sha256=GqTt8yqS3m2GYgX9nElhjk7p8ah6lAFrtpZiCDHTSQg,1019
36
- pylitmus-1.0.0.dist-info/METADATA,sha256=el-ecW2t9Lqg9vMFppw7xD5m7guNpULSW6PAKj2axP0,11220
37
- pylitmus-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
38
- pylitmus-1.0.0.dist-info/licenses/LICENSE,sha256=BtvJ2KboQyfSvjSh9-nxRiVFK4VjxFWXe5x4sFpe7Xw,1066
39
- pylitmus-1.0.0.dist-info/RECORD,,
36
+ pylitmus-1.1.0.dist-info/METADATA,sha256=5aU1HJQeDpvkwjWXNq7baJAPsZq1twuSwBn4A2MyxKQ,11220
37
+ pylitmus-1.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
38
+ pylitmus-1.1.0.dist-info/licenses/LICENSE,sha256=BtvJ2KboQyfSvjSh9-nxRiVFK4VjxFWXe5x4sFpe7Xw,1066
39
+ pylitmus-1.1.0.dist-info/RECORD,,