pylitmus 1.0.0__tar.gz → 1.1.0__tar.gz

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.
Files changed (58) hide show
  1. {pylitmus-1.0.0 → pylitmus-1.1.0}/PKG-INFO +1 -1
  2. {pylitmus-1.0.0 → pylitmus-1.1.0}/pyproject.toml +1 -1
  3. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/__init__.py +3 -2
  4. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/engine.py +32 -21
  5. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/factory.py +16 -7
  6. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/types.py +29 -3
  7. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase1_core.py +50 -21
  8. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase2_conditions.py +4 -4
  9. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase3_evaluators.py +5 -5
  10. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase4_strategies.py +10 -10
  11. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase5_storage.py +4 -4
  12. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase6_patterns.py +7 -7
  13. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase7_flask.py +25 -25
  14. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/test_phase8_factory.py +50 -14
  15. pylitmus-1.1.0/tests/test_phase9_decision_tiers.py +566 -0
  16. {pylitmus-1.0.0 → pylitmus-1.1.0}/CHANGELOG.md +0 -0
  17. {pylitmus-1.0.0 → pylitmus-1.1.0}/LICENSE +0 -0
  18. {pylitmus-1.0.0 → pylitmus-1.1.0}/README.md +0 -0
  19. {pylitmus-1.0.0 → pylitmus-1.1.0}/docs/api-reference.md +0 -0
  20. {pylitmus-1.0.0 → pylitmus-1.1.0}/docs/flask-integration.md +0 -0
  21. {pylitmus-1.0.0 → pylitmus-1.1.0}/docs/quickstart.md +0 -0
  22. {pylitmus-1.0.0 → pylitmus-1.1.0}/docs/rules-format.md +0 -0
  23. {pylitmus-1.0.0 → pylitmus-1.1.0}/examples/basic_usage.py +0 -0
  24. {pylitmus-1.0.0 → pylitmus-1.1.0}/examples/flask_app/app.py +0 -0
  25. {pylitmus-1.0.0 → pylitmus-1.1.0}/examples/flask_app/requirements.txt +0 -0
  26. {pylitmus-1.0.0 → pylitmus-1.1.0}/examples/flask_app/rules.yaml +0 -0
  27. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/conditions/__init__.py +0 -0
  28. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/conditions/base.py +0 -0
  29. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/conditions/builder.py +0 -0
  30. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/conditions/composite.py +0 -0
  31. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/conditions/simple.py +0 -0
  32. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/evaluators/__init__.py +0 -0
  33. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/evaluators/base.py +0 -0
  34. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/evaluators/factory.py +0 -0
  35. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/exceptions.py +0 -0
  36. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/integrations/__init__.py +0 -0
  37. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/integrations/flask/__init__.py +0 -0
  38. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/integrations/flask/extension.py +0 -0
  39. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/__init__.py +0 -0
  40. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/base.py +0 -0
  41. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/engine.py +0 -0
  42. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/exact.py +0 -0
  43. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/fuzzy.py +0 -0
  44. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/glob.py +0 -0
  45. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/range.py +0 -0
  46. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/patterns/regex.py +0 -0
  47. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/storage/__init__.py +0 -0
  48. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/storage/base.py +0 -0
  49. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/storage/cached.py +0 -0
  50. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/storage/database.py +0 -0
  51. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/storage/file.py +0 -0
  52. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/storage/memory.py +0 -0
  53. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/strategies/__init__.py +0 -0
  54. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/strategies/base.py +0 -0
  55. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/strategies/max.py +0 -0
  56. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/strategies/sum.py +0 -0
  57. {pylitmus-1.0.0 → pylitmus-1.1.0}/src/pylitmus/strategies/weighted.py +0 -0
  58. {pylitmus-1.0.0 → pylitmus-1.1.0}/tests/__init__.py +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/
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pylitmus"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "A high-performance rules engine for Python - evaluate data against configurable rules and get clear verdicts"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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
@@ -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."""
@@ -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
 
@@ -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
@@ -5,10 +5,10 @@ Phase 1 Tests: Core Engine - Types, RuleEngine, Exceptions
5
5
  from datetime import datetime, timedelta
6
6
 
7
7
  import pytest
8
-
9
- from cmap_rules_engine import (
8
+ from pylitmus import (
10
9
  AssessmentResult,
11
10
  ConditionError,
11
+ DecisionTier,
12
12
  EvaluationError,
13
13
  Operator,
14
14
  Rule,
@@ -19,8 +19,8 @@ from cmap_rules_engine import (
19
19
  StorageError,
20
20
  UnknownOperatorError,
21
21
  )
22
- from cmap_rules_engine.storage import InMemoryRuleRepository
23
- from cmap_rules_engine.strategies import SumStrategy
22
+ from pylitmus.storage import InMemoryRuleRepository
23
+ from pylitmus.strategies import SumStrategy
24
24
 
25
25
 
26
26
  class TestOperatorEnum:
@@ -207,13 +207,20 @@ class TestAssessmentResult:
207
207
 
208
208
  def test_assessment_result_defaults(self):
209
209
  """Test AssessmentResult default values."""
210
- result = AssessmentResult(total_score=0, decision="APPROVE")
210
+ result = AssessmentResult(total_score=0, decision=None)
211
211
 
212
212
  assert result.triggered_rules == []
213
213
  assert result.all_rules_evaluated == 0
214
214
  assert result.processing_time_ms == 0.0
215
215
  assert result.metadata == {}
216
216
 
217
+ def test_assessment_result_with_none_decision(self):
218
+ """Test AssessmentResult with None decision (no tiers configured)."""
219
+ result = AssessmentResult(total_score=50, decision=None)
220
+
221
+ assert result.total_score == 50
222
+ assert result.decision is None
223
+
217
224
 
218
225
  class TestExceptions:
219
226
  """Tests for custom exceptions."""
@@ -288,10 +295,19 @@ class TestRuleEngine:
288
295
  ]
289
296
 
290
297
  @pytest.fixture
291
- def engine(self, sample_rules):
292
- """Create engine with sample rules."""
298
+ def standard_tiers(self):
299
+ """Create standard decision tiers."""
300
+ return [
301
+ DecisionTier("APPROVE", 0, 30),
302
+ DecisionTier("REVIEW", 30, 70),
303
+ DecisionTier("FLAG", 70, 101),
304
+ ]
305
+
306
+ @pytest.fixture
307
+ def engine(self, sample_rules, standard_tiers):
308
+ """Create engine with sample rules and standard tiers."""
293
309
  repo = InMemoryRuleRepository(sample_rules)
294
- return RuleEngine(repository=repo)
310
+ return RuleEngine(repository=repo, decision_tiers=standard_tiers)
295
311
 
296
312
  def test_engine_creation(self, sample_rules):
297
313
  """Test engine creation."""
@@ -299,16 +315,19 @@ class TestRuleEngine:
299
315
  engine = RuleEngine(repository=repo)
300
316
 
301
317
  assert engine.repository is repo
302
- assert engine._thresholds == {"approve": 30, "review": 70}
318
+ assert engine._decision_tiers is None
303
319
 
304
- def test_engine_with_custom_thresholds(self, sample_rules):
305
- """Test engine with custom thresholds."""
320
+ def test_engine_with_custom_tiers(self, sample_rules):
321
+ """Test engine with custom decision tiers."""
306
322
  repo = InMemoryRuleRepository(sample_rules)
307
- engine = RuleEngine(
308
- repository=repo, decision_thresholds={"approve": 20, "review": 50}
309
- )
323
+ custom_tiers = [
324
+ DecisionTier("LOW", 0, 20),
325
+ DecisionTier("MEDIUM", 20, 50),
326
+ DecisionTier("HIGH", 50, 101),
327
+ ]
328
+ engine = RuleEngine(repository=repo, decision_tiers=custom_tiers)
310
329
 
311
- assert engine._thresholds == {"approve": 20, "review": 50}
330
+ assert engine._decision_tiers == custom_tiers
312
331
 
313
332
  def test_engine_evaluate_no_triggers(self, engine):
314
333
  """Test evaluation with no rules triggered."""
@@ -347,7 +366,7 @@ class TestRuleEngine:
347
366
  result = engine.evaluate({"amount": 6000}) # triggers 60 point rule
348
367
  assert result.decision == "REVIEW"
349
368
 
350
- def test_engine_decision_flag(self, sample_rules):
369
+ def test_engine_decision_flag(self, sample_rules, standard_tiers):
351
370
  """Test FLAG decision (score >= 70)."""
352
371
  # Add a high score rule
353
372
  sample_rules.append(
@@ -364,11 +383,21 @@ class TestRuleEngine:
364
383
  )
365
384
 
366
385
  repo = InMemoryRuleRepository(sample_rules)
367
- engine = RuleEngine(repository=repo)
386
+ engine = RuleEngine(repository=repo, decision_tiers=standard_tiers)
368
387
 
369
388
  result = engine.evaluate({"critical": True, "amount": 1000})
370
389
  assert result.decision == "FLAG"
371
390
 
391
+ def test_engine_no_decision_tiers(self, sample_rules):
392
+ """Test engine without decision tiers returns None decision."""
393
+ repo = InMemoryRuleRepository(sample_rules)
394
+ engine = RuleEngine(repository=repo) # No decision_tiers
395
+
396
+ result = engine.evaluate({"amount": 6000})
397
+
398
+ assert result.total_score == 60
399
+ assert result.decision is None
400
+
372
401
  def test_engine_evaluate_rule_directly(self, engine, sample_rules):
373
402
  """Test evaluating a single rule."""
374
403
  rule = sample_rules[0] # HIGH_AMOUNT
@@ -448,7 +477,7 @@ class TestRuleEngineWithStrategies:
448
477
 
449
478
  def test_sum_strategy(self, rules):
450
479
  """Test SumStrategy adds scores."""
451
- from cmap_rules_engine.strategies import SumStrategy
480
+ from pylitmus.strategies import SumStrategy
452
481
 
453
482
  repo = InMemoryRuleRepository(rules)
454
483
  engine = RuleEngine(repository=repo, scoring_strategy=SumStrategy())
@@ -459,7 +488,7 @@ class TestRuleEngineWithStrategies:
459
488
 
460
489
  def test_sum_strategy_with_cap(self, rules):
461
490
  """Test SumStrategy respects max cap."""
462
- from cmap_rules_engine.strategies import SumStrategy
491
+ from pylitmus.strategies import SumStrategy
463
492
 
464
493
  repo = InMemoryRuleRepository(rules)
465
494
  engine = RuleEngine(repository=repo, scoring_strategy=SumStrategy(max_score=50))
@@ -470,7 +499,7 @@ class TestRuleEngineWithStrategies:
470
499
 
471
500
  def test_max_strategy(self, rules):
472
501
  """Test MaxStrategy takes highest score."""
473
- from cmap_rules_engine.strategies import MaxStrategy
502
+ from pylitmus.strategies import MaxStrategy
474
503
 
475
504
  repo = InMemoryRuleRepository(rules)
476
505
  engine = RuleEngine(repository=repo, scoring_strategy=MaxStrategy())
@@ -481,7 +510,7 @@ class TestRuleEngineWithStrategies:
481
510
 
482
511
  def test_weighted_strategy(self, rules):
483
512
  """Test WeightedStrategy weights by severity."""
484
- from cmap_rules_engine.strategies import WeightedStrategy
513
+ from pylitmus.strategies import WeightedStrategy
485
514
 
486
515
  repo = InMemoryRuleRepository(rules)
487
516
  engine = RuleEngine(repository=repo, scoring_strategy=WeightedStrategy())
@@ -4,13 +4,13 @@ Phase 2 Tests: Conditions System - Simple and Composite Conditions
4
4
 
5
5
  import pytest
6
6
 
7
- from cmap_rules_engine.conditions import (
7
+ from pylitmus.conditions import (
8
8
  CompositeCondition,
9
9
  Condition,
10
10
  ConditionBuilder,
11
11
  SimpleCondition,
12
12
  )
13
- from cmap_rules_engine.exceptions import UnknownOperatorError
13
+ from pylitmus.exceptions import UnknownOperatorError
14
14
 
15
15
 
16
16
  class TestConditionBase:
@@ -523,8 +523,8 @@ class TestConditionIntegration:
523
523
 
524
524
  def test_condition_with_rule_engine(self):
525
525
  """Test conditions work correctly with RuleEngine."""
526
- from cmap_rules_engine import RuleEngine, Rule, Severity
527
- from cmap_rules_engine.storage import InMemoryRuleRepository
526
+ from pylitmus import RuleEngine, Rule, Severity
527
+ from pylitmus.storage import InMemoryRuleRepository
528
528
 
529
529
  rules = [
530
530
  Rule(
@@ -6,8 +6,8 @@ from datetime import datetime, timedelta
6
6
 
7
7
  import pytest
8
8
 
9
- from cmap_rules_engine.evaluators import Evaluator, EvaluatorFactory
10
- from cmap_rules_engine.exceptions import UnknownOperatorError
9
+ from pylitmus.evaluators import Evaluator, EvaluatorFactory
10
+ from pylitmus.exceptions import UnknownOperatorError
11
11
 
12
12
 
13
13
  class TestEvaluatorBase:
@@ -411,7 +411,7 @@ class TestEvaluatorIntegration:
411
411
 
412
412
  def test_all_operators_with_simple_condition(self):
413
413
  """Test all operators work correctly through SimpleCondition."""
414
- from cmap_rules_engine.conditions import SimpleCondition
414
+ from pylitmus.conditions import SimpleCondition
415
415
 
416
416
  # Test a selection of operators
417
417
  test_cases = [
@@ -435,8 +435,8 @@ class TestEvaluatorIntegration:
435
435
 
436
436
  def test_temporal_operators_with_rule_engine(self):
437
437
  """Test temporal operators work with RuleEngine."""
438
- from cmap_rules_engine import Rule, RuleEngine, Severity
439
- from cmap_rules_engine.storage import InMemoryRuleRepository
438
+ from pylitmus import Rule, RuleEngine, Severity
439
+ from pylitmus.storage import InMemoryRuleRepository
440
440
 
441
441
  yesterday = (datetime.utcnow() - timedelta(days=1)).strftime("%Y-%m-%d")
442
442
  old_date = (datetime.utcnow() - timedelta(days=30)).strftime("%Y-%m-%d")
@@ -4,13 +4,13 @@ Phase 4 Tests: Scoring Strategies - Sum, Weighted, Max strategies
4
4
 
5
5
  import pytest
6
6
 
7
- from cmap_rules_engine.strategies import (
7
+ from pylitmus.strategies import (
8
8
  MaxStrategy,
9
9
  ScoringStrategy,
10
10
  SumStrategy,
11
11
  WeightedStrategy,
12
12
  )
13
- from cmap_rules_engine.types import RuleResult, Severity
13
+ from pylitmus.types import RuleResult, Severity
14
14
 
15
15
 
16
16
  def make_result(score: int, severity: Severity = Severity.MEDIUM) -> RuleResult:
@@ -326,8 +326,8 @@ class TestStrategyIntegration:
326
326
 
327
327
  def test_engine_with_sum_strategy(self):
328
328
  """Test RuleEngine uses SumStrategy correctly."""
329
- from cmap_rules_engine import Rule, RuleEngine, Severity
330
- from cmap_rules_engine.storage import InMemoryRuleRepository
329
+ from pylitmus import Rule, RuleEngine, Severity
330
+ from pylitmus.storage import InMemoryRuleRepository
331
331
 
332
332
  rules = [
333
333
  Rule(
@@ -360,8 +360,8 @@ class TestStrategyIntegration:
360
360
 
361
361
  def test_engine_with_weighted_strategy(self):
362
362
  """Test RuleEngine uses WeightedStrategy correctly."""
363
- from cmap_rules_engine import Rule, RuleEngine, Severity
364
- from cmap_rules_engine.storage import InMemoryRuleRepository
363
+ from pylitmus import Rule, RuleEngine, Severity
364
+ from pylitmus.storage import InMemoryRuleRepository
365
365
 
366
366
  rules = [
367
367
  Rule(
@@ -395,8 +395,8 @@ class TestStrategyIntegration:
395
395
 
396
396
  def test_engine_with_max_strategy(self):
397
397
  """Test RuleEngine uses MaxStrategy correctly."""
398
- from cmap_rules_engine import Rule, RuleEngine, Severity
399
- from cmap_rules_engine.storage import InMemoryRuleRepository
398
+ from pylitmus import Rule, RuleEngine, Severity
399
+ from pylitmus.storage import InMemoryRuleRepository
400
400
 
401
401
  rules = [
402
402
  Rule(
@@ -429,8 +429,8 @@ class TestStrategyIntegration:
429
429
 
430
430
  def test_default_strategy_is_sum(self):
431
431
  """Test that default strategy is SumStrategy."""
432
- from cmap_rules_engine import RuleEngine
433
- from cmap_rules_engine.storage import InMemoryRuleRepository
432
+ from pylitmus import RuleEngine
433
+ from pylitmus.storage import InMemoryRuleRepository
434
434
 
435
435
  repo = InMemoryRuleRepository([])
436
436
  engine = RuleEngine(repository=repo)
@@ -8,13 +8,13 @@ import tempfile
8
8
 
9
9
  import pytest
10
10
 
11
- from cmap_rules_engine.storage import (
11
+ from pylitmus.storage import (
12
12
  CachedRuleRepository,
13
13
  FileRuleRepository,
14
14
  InMemoryRuleRepository,
15
15
  RuleRepository,
16
16
  )
17
- from cmap_rules_engine.types import Rule, Severity
17
+ from pylitmus.types import Rule, Severity
18
18
 
19
19
 
20
20
  def make_rule(code: str, score: int = 50, enabled: bool = True) -> Rule:
@@ -516,7 +516,7 @@ class TestStorageIntegration:
516
516
 
517
517
  def test_engine_with_file_repository(self):
518
518
  """Test RuleEngine with FileRuleRepository."""
519
- from cmap_rules_engine import RuleEngine
519
+ from pylitmus import RuleEngine
520
520
 
521
521
  content = {
522
522
  "rules": [
@@ -552,7 +552,7 @@ class TestStorageIntegration:
552
552
 
553
553
  def test_engine_with_cached_repository(self):
554
554
  """Test RuleEngine with CachedRuleRepository."""
555
- from cmap_rules_engine import RuleEngine
555
+ from pylitmus import RuleEngine
556
556
 
557
557
  inner = InMemoryRuleRepository(
558
558
  [
@@ -4,7 +4,7 @@ Phase 6 Tests: Pattern Matching - Regex, Fuzzy, Range matchers
4
4
 
5
5
  import pytest
6
6
 
7
- from cmap_rules_engine.patterns import (
7
+ from pylitmus.patterns import (
8
8
  EnhancedPatternEngine,
9
9
  ExactMatcher,
10
10
  FuzzyMatcher,
@@ -374,8 +374,8 @@ class TestPatternIntegration:
374
374
 
375
375
  def test_register_fuzzy_evaluator(self):
376
376
  """Test registering fuzzy matcher as an evaluator."""
377
- from cmap_rules_engine.evaluators import Evaluator, EvaluatorFactory
378
- from cmap_rules_engine.patterns import FuzzyMatcher
377
+ from pylitmus.evaluators import Evaluator, EvaluatorFactory
378
+ from pylitmus.patterns import FuzzyMatcher
379
379
 
380
380
  class FuzzyMatchEvaluator(Evaluator):
381
381
  def __init__(self):
@@ -400,10 +400,10 @@ class TestPatternIntegration:
400
400
 
401
401
  def test_patterns_with_rule_engine(self):
402
402
  """Test pattern matching integration with RuleEngine."""
403
- from cmap_rules_engine import Rule, RuleEngine, Severity
404
- from cmap_rules_engine.evaluators import Evaluator, EvaluatorFactory
405
- from cmap_rules_engine.patterns import GlobMatcher
406
- from cmap_rules_engine.storage import InMemoryRuleRepository
403
+ from pylitmus import Rule, RuleEngine, Severity
404
+ from pylitmus.evaluators import Evaluator, EvaluatorFactory
405
+ from pylitmus.patterns import GlobMatcher
406
+ from pylitmus.storage import InMemoryRuleRepository
407
407
 
408
408
  # Register glob evaluator
409
409
  class GlobMatchEvaluator(Evaluator):