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 +3 -2
- pylitmus/engine.py +32 -21
- pylitmus/factory.py +16 -7
- pylitmus/types.py +29 -3
- {pylitmus-1.0.0.dist-info → pylitmus-1.1.0.dist-info}/METADATA +1 -1
- {pylitmus-1.0.0.dist-info → pylitmus-1.1.0.dist-info}/RECORD +8 -8
- {pylitmus-1.0.0.dist-info → pylitmus-1.1.0.dist-info}/WHEEL +0 -0
- {pylitmus-1.0.0.dist-info → pylitmus-1.1.0.dist-info}/licenses/LICENSE +0 -0
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
211
|
+
Decision string if a matching tier is found, None otherwise.
|
|
212
|
+
If no decision tiers are configured, returns None.
|
|
201
213
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
83
|
+
# With user-defined decision tiers
|
|
82
84
|
engine = create_engine(
|
|
83
85
|
scoring_strategy='weighted',
|
|
84
|
-
|
|
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
|
-
|
|
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.
|
|
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=
|
|
2
|
-
pylitmus/engine.py,sha256=
|
|
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=
|
|
5
|
-
pylitmus/types.py,sha256=
|
|
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.
|
|
37
|
-
pylitmus-1.
|
|
38
|
-
pylitmus-1.
|
|
39
|
-
pylitmus-1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|