pylitmus 1.1.0__tar.gz → 1.2.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 (64) hide show
  1. {pylitmus-1.1.0 → pylitmus-1.2.0}/PKG-INFO +1 -1
  2. {pylitmus-1.1.0 → pylitmus-1.2.0}/docs/flask-integration.md +11 -11
  3. {pylitmus-1.1.0 → pylitmus-1.2.0}/docs/quickstart.md +8 -8
  4. {pylitmus-1.1.0 → pylitmus-1.2.0}/pyproject.toml +1 -1
  5. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/__init__.py +1 -1
  6. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/engine.py +176 -4
  7. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/factory.py +13 -0
  8. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/integrations/flask/extension.py +51 -9
  9. pylitmus-1.2.0/src/pylitmus/rete/__init__.py +26 -0
  10. pylitmus-1.2.0/src/pylitmus/rete/compiler.py +356 -0
  11. pylitmus-1.2.0/src/pylitmus/rete/network.py +269 -0
  12. pylitmus-1.2.0/src/pylitmus/rete/nodes.py +318 -0
  13. pylitmus-1.2.0/tests/test_phase10_backward_compat.py +1421 -0
  14. pylitmus-1.2.0/tests/test_phase11_rete_spec.py +1154 -0
  15. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase7_flask.py +17 -6
  16. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase8_factory.py +2 -1
  17. {pylitmus-1.1.0 → pylitmus-1.2.0}/CHANGELOG.md +0 -0
  18. {pylitmus-1.1.0 → pylitmus-1.2.0}/LICENSE +0 -0
  19. {pylitmus-1.1.0 → pylitmus-1.2.0}/README.md +0 -0
  20. {pylitmus-1.1.0 → pylitmus-1.2.0}/docs/api-reference.md +0 -0
  21. {pylitmus-1.1.0 → pylitmus-1.2.0}/docs/rules-format.md +0 -0
  22. {pylitmus-1.1.0 → pylitmus-1.2.0}/examples/basic_usage.py +0 -0
  23. {pylitmus-1.1.0 → pylitmus-1.2.0}/examples/flask_app/app.py +0 -0
  24. {pylitmus-1.1.0 → pylitmus-1.2.0}/examples/flask_app/requirements.txt +0 -0
  25. {pylitmus-1.1.0 → pylitmus-1.2.0}/examples/flask_app/rules.yaml +0 -0
  26. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/conditions/__init__.py +0 -0
  27. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/conditions/base.py +0 -0
  28. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/conditions/builder.py +0 -0
  29. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/conditions/composite.py +0 -0
  30. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/conditions/simple.py +0 -0
  31. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/evaluators/__init__.py +0 -0
  32. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/evaluators/base.py +0 -0
  33. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/evaluators/factory.py +0 -0
  34. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/exceptions.py +0 -0
  35. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/integrations/__init__.py +0 -0
  36. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/integrations/flask/__init__.py +0 -0
  37. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/__init__.py +0 -0
  38. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/base.py +0 -0
  39. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/engine.py +0 -0
  40. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/exact.py +0 -0
  41. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/fuzzy.py +0 -0
  42. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/glob.py +0 -0
  43. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/range.py +0 -0
  44. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/patterns/regex.py +0 -0
  45. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/storage/__init__.py +0 -0
  46. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/storage/base.py +0 -0
  47. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/storage/cached.py +0 -0
  48. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/storage/database.py +0 -0
  49. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/storage/file.py +0 -0
  50. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/storage/memory.py +0 -0
  51. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/strategies/__init__.py +0 -0
  52. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/strategies/base.py +0 -0
  53. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/strategies/max.py +0 -0
  54. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/strategies/sum.py +0 -0
  55. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/strategies/weighted.py +0 -0
  56. {pylitmus-1.1.0 → pylitmus-1.2.0}/src/pylitmus/types.py +0 -0
  57. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/__init__.py +0 -0
  58. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase1_core.py +0 -0
  59. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase2_conditions.py +0 -0
  60. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase3_evaluators.py +0 -0
  61. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase4_strategies.py +0 -0
  62. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase5_storage.py +0 -0
  63. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase6_patterns.py +0 -0
  64. {pylitmus-1.1.0 → pylitmus-1.2.0}/tests/test_phase9_decision_tiers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylitmus
3
- Version: 1.1.0
3
+ Version: 1.2.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,11 +1,11 @@
1
1
  # Flask Integration Guide
2
2
 
3
- This guide covers integrating cmap-rules-engine with Flask applications.
3
+ This guide covers integrating pylitmus with Flask applications.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pip install cmap-rules-engine[flask]
8
+ pip install pylitmus[flask]
9
9
  ```
10
10
 
11
11
  ## Basic Setup
@@ -14,7 +14,7 @@ pip install cmap-rules-engine[flask]
14
14
 
15
15
  ```python
16
16
  from flask import Flask
17
- from cmap_rules_engine.integrations.flask import CmapRulesEngine
17
+ from pylitmus.integrations.flask import CmapRulesEngine
18
18
 
19
19
  rules_engine = CmapRulesEngine()
20
20
 
@@ -35,7 +35,7 @@ def create_app():
35
35
 
36
36
  ```python
37
37
  from flask import Flask
38
- from cmap_rules_engine.integrations.flask import CmapRulesEngine
38
+ from pylitmus.integrations.flask import CmapRulesEngine
39
39
 
40
40
  app = Flask(__name__)
41
41
  app.config['CMAP_RULES_FILE'] = 'rules.yaml'
@@ -62,7 +62,7 @@ rules_engine = CmapRulesEngine(app)
62
62
 
63
63
  ```python
64
64
  from flask import Flask, request, jsonify
65
- from cmap_rules_engine.integrations.flask import CmapRulesEngine, get_engine
65
+ from pylitmus.integrations.flask import CmapRulesEngine, get_engine
66
66
 
67
67
  app = Flask(__name__)
68
68
  app.config['CMAP_RULES_FILE'] = 'rules.yaml'
@@ -98,7 +98,7 @@ def evaluate():
98
98
 
99
99
  ```python
100
100
  from flask import Blueprint, request, jsonify
101
- from cmap_rules_engine.integrations.flask import get_engine
101
+ from pylitmus.integrations.flask import get_engine
102
102
 
103
103
  api = Blueprint('api', __name__)
104
104
 
@@ -201,7 +201,7 @@ rules:
201
201
 
202
202
  ```python
203
203
  from flask import Flask, request, jsonify
204
- from cmap_rules_engine.integrations.flask import CmapRulesEngine, get_engine
204
+ from pylitmus.integrations.flask import CmapRulesEngine, get_engine
205
205
 
206
206
  def create_app(config_class='config.Config'):
207
207
  app = Flask(__name__)
@@ -326,7 +326,7 @@ curl -X POST http://localhost:5000/evaluate \
326
326
  ```python
327
327
  from flask import Flask
328
328
  from flask_sqlalchemy import SQLAlchemy
329
- from cmap_rules_engine.integrations.flask import CmapRulesEngine
329
+ from pylitmus.integrations.flask import CmapRulesEngine
330
330
 
331
331
  app = Flask(__name__)
332
332
  app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://localhost/mydb'
@@ -341,7 +341,7 @@ rules_engine = CmapRulesEngine(app)
341
341
 
342
342
  ```python
343
343
  from flask import Flask
344
- from cmap_rules_engine.integrations.flask import CmapRulesEngine
344
+ from pylitmus.integrations.flask import CmapRulesEngine
345
345
 
346
346
  app = Flask(__name__)
347
347
 
@@ -357,8 +357,8 @@ rules_engine = CmapRulesEngine(app)
357
357
 
358
358
  ```python
359
359
  from flask import Flask, jsonify
360
- from cmap_rules_engine.integrations.flask import CmapRulesEngine, get_engine
361
- from cmap_rules_engine import RuleEngineError, EvaluationError
360
+ from pylitmus.integrations.flask import CmapRulesEngine, get_engine
361
+ from pylitmus import RuleEngineError, EvaluationError
362
362
 
363
363
  app = Flask(__name__)
364
364
  rules_engine = CmapRulesEngine(app)
@@ -1,27 +1,27 @@
1
1
  # Quick Start Guide
2
2
 
3
- This guide will help you get started with cmap-rules-engine in minutes.
3
+ This guide will help you get started with pylitmus in minutes.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pip install cmap-rules-engine
8
+ pip install pylitmus
9
9
  ```
10
10
 
11
11
  For additional features:
12
12
 
13
13
  ```bash
14
14
  # Database support (PostgreSQL, MySQL, SQLite)
15
- pip install cmap-rules-engine[database]
15
+ pip install pylitmus[database]
16
16
 
17
17
  # Redis caching
18
- pip install cmap-rules-engine[redis]
18
+ pip install pylitmus[redis]
19
19
 
20
20
  # Flask integration
21
- pip install cmap-rules-engine[flask]
21
+ pip install pylitmus[flask]
22
22
 
23
23
  # All extras
24
- pip install cmap-rules-engine[all]
24
+ pip install pylitmus[all]
25
25
  ```
26
26
 
27
27
  ## Basic Usage
@@ -31,7 +31,7 @@ pip install cmap-rules-engine[all]
31
31
  The simplest way to create a rules engine:
32
32
 
33
33
  ```python
34
- from cmap_rules_engine import create_engine
34
+ from pylitmus import create_engine
35
35
 
36
36
  # Simple in-memory engine
37
37
  engine = create_engine()
@@ -40,7 +40,7 @@ engine = create_engine()
40
40
  ### Defining Rules in Python
41
41
 
42
42
  ```python
43
- from cmap_rules_engine import create_engine, Rule, Severity
43
+ from pylitmus import create_engine, Rule, Severity
44
44
 
45
45
  rules = [
46
46
  Rule(
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pylitmus"
7
- version = "1.1.0"
7
+ version = "1.2.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"}
@@ -28,7 +28,7 @@ from .storage import (
28
28
  from .strategies import MaxStrategy, ScoringStrategy, SumStrategy, WeightedStrategy
29
29
  from .types import AssessmentResult, DecisionTier, Operator, Rule, RuleResult, Severity
30
30
 
31
- __version__ = "1.1.0"
31
+ __version__ = "1.2.0"
32
32
 
33
33
  __all__ = [
34
34
  # Main
@@ -10,6 +10,7 @@ from .exceptions import EvaluationError
10
10
  from .types import AssessmentResult, DecisionTier, Rule, RuleResult
11
11
 
12
12
  if TYPE_CHECKING:
13
+ from .rete import RETENetwork, RuleCompiler
13
14
  from .storage.base import RuleRepository
14
15
  from .strategies.base import ScoringStrategy
15
16
 
@@ -41,6 +42,13 @@ class RuleEngine:
41
42
  ]
42
43
  )
43
44
 
45
+ # With RETE algorithm for optimized evaluation (recommended for 50+ rules)
46
+ engine = RuleEngine(
47
+ repository=repo,
48
+ scoring_strategy=WeightedStrategy(),
49
+ use_rete=True # Enable RETE optimization
50
+ )
51
+
44
52
  result = engine.evaluate(claim, context)
45
53
  """
46
54
 
@@ -50,6 +58,7 @@ class RuleEngine:
50
58
  scoring_strategy: Optional["ScoringStrategy"] = None,
51
59
  decision_tiers: Optional[List[DecisionTier]] = None,
52
60
  condition_builder: Optional[Any] = None,
61
+ use_rete: bool = False,
53
62
  ):
54
63
  """
55
64
  Initialize the rule engine.
@@ -60,6 +69,9 @@ class RuleEngine:
60
69
  decision_tiers: User-defined decision tiers with score ranges.
61
70
  If not provided, decision will be None.
62
71
  condition_builder: Builder for creating conditions from dicts
72
+ use_rete: Enable RETE algorithm for optimized rule evaluation.
73
+ Recommended for rule sets with 50+ rules or significant
74
+ condition sharing. Default is False (use standard evaluation).
63
75
  """
64
76
  self.repository = repository
65
77
  self._scoring_strategy = scoring_strategy
@@ -67,7 +79,26 @@ class RuleEngine:
67
79
  self._condition_builder = condition_builder
68
80
  self._rules_cache: Optional[List[Rule]] = None
69
81
 
70
- logger.info("RuleEngine initialized")
82
+ # RETE configuration
83
+ self._use_rete = use_rete
84
+ self._rete_network: Optional["RETENetwork"] = None
85
+ self._rete_compiled = False
86
+
87
+ logger.info(f"RuleEngine initialized (use_rete={use_rete})")
88
+
89
+ @property
90
+ def use_rete(self) -> bool:
91
+ """Check if RETE algorithm is enabled."""
92
+ return self._use_rete
93
+
94
+ @use_rete.setter
95
+ def use_rete(self, value: bool) -> None:
96
+ """Enable or disable RETE algorithm."""
97
+ if value != self._use_rete:
98
+ self._use_rete = value
99
+ self._rete_network = None
100
+ self._rete_compiled = False
101
+ logger.info(f"RETE algorithm {'enabled' if value else 'disabled'}")
71
102
 
72
103
  @property
73
104
  def scoring_strategy(self) -> "ScoringStrategy":
@@ -87,6 +118,30 @@ class RuleEngine:
87
118
  self._condition_builder = ConditionBuilder()
88
119
  return self._condition_builder
89
120
 
121
+ def _ensure_rete_compiled(self, filters: Optional[Dict[str, Any]] = None) -> None:
122
+ """
123
+ Ensure the RETE network is compiled with current rules.
124
+
125
+ Args:
126
+ filters: Filters to apply when fetching rules
127
+ """
128
+ if self._rete_compiled and self._rete_network is not None:
129
+ return
130
+
131
+ from .rete import RuleCompiler
132
+
133
+ rules = self.repository.get_enabled(filters)
134
+ compiler = RuleCompiler()
135
+ self._rete_network = compiler.compile(rules)
136
+ self._rete_compiled = True
137
+
138
+ stats = self._rete_network.get_stats()
139
+ logger.info(
140
+ f"RETE network compiled: {stats['alpha_nodes']} alpha nodes, "
141
+ f"{stats['shared_conditions']} shared conditions, "
142
+ f"{stats['terminal_nodes']} rules"
143
+ )
144
+
90
145
  def evaluate(
91
146
  self,
92
147
  data: Dict[str, Any],
@@ -96,6 +151,104 @@ class RuleEngine:
96
151
  """
97
152
  Evaluate data against all applicable rules.
98
153
 
154
+ Args:
155
+ data: Data to evaluate
156
+ context: Additional context for evaluation
157
+ filters: Filters to apply when fetching rules
158
+
159
+ Returns:
160
+ AssessmentResult with score, decision, and triggered rules
161
+ """
162
+ if self._use_rete:
163
+ return self._evaluate_with_rete(data, context, filters)
164
+ else:
165
+ return self._evaluate_standard(data, context, filters)
166
+
167
+ def _evaluate_with_rete(
168
+ self,
169
+ data: Dict[str, Any],
170
+ context: Optional[Dict[str, Any]] = None,
171
+ filters: Optional[Dict[str, Any]] = None,
172
+ ) -> AssessmentResult:
173
+ """
174
+ Evaluate data using the RETE algorithm.
175
+
176
+ Args:
177
+ data: Data to evaluate
178
+ context: Additional context for evaluation
179
+ filters: Filters to apply when fetching rules
180
+
181
+ Returns:
182
+ AssessmentResult with score, decision, and triggered rules
183
+ """
184
+ start_time = time.time()
185
+ context = context or {}
186
+
187
+ logger.debug(f"Starting RETE evaluation with {len(data)} data fields")
188
+
189
+ # Ensure RETE network is compiled
190
+ self._ensure_rete_compiled(filters)
191
+
192
+ try:
193
+ # Evaluate through RETE network
194
+ triggered_results = self._rete_network.evaluate(data)
195
+
196
+ # Convert severity strings back to Severity enum for consistency
197
+ from .types import Severity
198
+
199
+ for result in triggered_results:
200
+ if isinstance(result.severity, str):
201
+ try:
202
+ result.severity = Severity(result.severity)
203
+ except ValueError:
204
+ pass # Keep as string if not a valid Severity
205
+
206
+ except Exception as e:
207
+ logger.error(f"Error in RETE evaluation: {e}")
208
+ raise EvaluationError(f"RETE evaluation failed: {e}") from e
209
+
210
+ # Calculate total score using scoring strategy
211
+ total_score = self.scoring_strategy.calculate(triggered_results)
212
+
213
+ # Determine decision
214
+ decision = self._make_decision(total_score)
215
+
216
+ processing_time = (time.time() - start_time) * 1000
217
+
218
+ # Get total rules count from network stats
219
+ stats = self._rete_network.get_stats()
220
+ total_rules = stats["terminal_nodes"]
221
+
222
+ result = AssessmentResult(
223
+ total_score=total_score,
224
+ decision=decision,
225
+ triggered_rules=triggered_results,
226
+ all_rules_evaluated=total_rules,
227
+ processing_time_ms=processing_time,
228
+ metadata={
229
+ "context": context,
230
+ "filters": filters,
231
+ "evaluation_method": "rete",
232
+ "rete_stats": stats,
233
+ },
234
+ )
235
+
236
+ logger.info(
237
+ f"RETE evaluation complete: score={total_score}, decision={decision}, "
238
+ f"triggered={len(triggered_results)}/{total_rules}, time={processing_time:.2f}ms"
239
+ )
240
+
241
+ return result
242
+
243
+ def _evaluate_standard(
244
+ self,
245
+ data: Dict[str, Any],
246
+ context: Optional[Dict[str, Any]] = None,
247
+ filters: Optional[Dict[str, Any]] = None,
248
+ ) -> AssessmentResult:
249
+ """
250
+ Evaluate data using standard (non-RETE) algorithm.
251
+
99
252
  Args:
100
253
  data: Data to evaluate
101
254
  context: Additional context for evaluation
@@ -107,7 +260,7 @@ class RuleEngine:
107
260
  start_time = time.time()
108
261
  context = context or {}
109
262
 
110
- logger.debug(f"Starting evaluation with {len(data)} data fields")
263
+ logger.debug(f"Starting standard evaluation with {len(data)} data fields")
111
264
 
112
265
  # Get applicable rules
113
266
  rules = self.repository.get_enabled(filters)
@@ -146,11 +299,15 @@ class RuleEngine:
146
299
  triggered_rules=triggered_results,
147
300
  all_rules_evaluated=len(rules),
148
301
  processing_time_ms=processing_time,
149
- metadata={"context": context, "filters": filters},
302
+ metadata={
303
+ "context": context,
304
+ "filters": filters,
305
+ "evaluation_method": "standard",
306
+ },
150
307
  )
151
308
 
152
309
  logger.info(
153
- f"Evaluation complete: score={total_score}, decision={decision}, "
310
+ f"Standard evaluation complete: score={total_score}, decision={decision}, "
154
311
  f"triggered={len(triggered_results)}/{len(rules)}, time={processing_time:.2f}ms"
155
312
  )
156
313
 
@@ -224,6 +381,10 @@ class RuleEngine:
224
381
  """Force reload rules from repository."""
225
382
  self._rules_cache = None
226
383
 
384
+ # Invalidate RETE network to force recompilation
385
+ self._rete_network = None
386
+ self._rete_compiled = False
387
+
227
388
  # If repository has cache, invalidate it
228
389
  if hasattr(self.repository, "invalidate"):
229
390
  self.repository.invalidate()
@@ -253,3 +414,14 @@ class RuleEngine:
253
414
  Rule if found, None otherwise
254
415
  """
255
416
  return self.repository.get_by_code(code)
417
+
418
+ def get_rete_stats(self) -> Optional[Dict[str, int]]:
419
+ """
420
+ Get RETE network statistics.
421
+
422
+ Returns:
423
+ Dictionary with stats if RETE is enabled and compiled, None otherwise
424
+ """
425
+ if self._rete_network is not None:
426
+ return self._rete_network.get_stats()
427
+ return None
@@ -36,6 +36,8 @@ def create_engine(
36
36
  scoring_strategy: Union[str, ScoringStrategy] = "sum",
37
37
  # Decision
38
38
  decision_tiers: Optional[List[DecisionTier]] = None,
39
+ # RETE optimization
40
+ use_rete: bool = False,
39
41
  ) -> RuleEngine:
40
42
  """
41
43
  Create a configured RuleEngine instance.
@@ -55,6 +57,9 @@ def create_engine(
55
57
  scoring_strategy: 'sum', 'weighted', 'max', or ScoringStrategy instance
56
58
  decision_tiers: User-defined decision tiers with score ranges.
57
59
  If not provided, decision will be None in results.
60
+ use_rete: Enable RETE algorithm for optimized rule evaluation.
61
+ Recommended for rule sets with 50+ rules or significant
62
+ condition sharing across rules. Default is False.
58
63
 
59
64
  Returns:
60
65
  Configured RuleEngine instance
@@ -92,6 +97,13 @@ def create_engine(
92
97
  DecisionTier("AUTO_REJECT", 95, 101, "Very high risk"),
93
98
  ]
94
99
  )
100
+
101
+ # With RETE algorithm optimization (for large rule sets)
102
+ engine = create_engine(
103
+ storage_backend='database',
104
+ database_url='postgresql://localhost/mydb',
105
+ use_rete=True # Enable RETE for performance
106
+ )
95
107
  """
96
108
 
97
109
  # Build or use provided repository
@@ -122,6 +134,7 @@ def create_engine(
122
134
  repository=repo,
123
135
  scoring_strategy=strategy,
124
136
  decision_tiers=decision_tiers,
137
+ use_rete=use_rete,
125
138
  )
126
139
 
127
140
 
@@ -2,11 +2,12 @@
2
2
  Flask extension for cmap-rules-engine.
3
3
  """
4
4
 
5
- from typing import Any, Dict, Optional
5
+ from typing import Any, Dict, List, Optional
6
6
 
7
7
  from ...engine import RuleEngine
8
8
  from ...storage import CachedRuleRepository, InMemoryRuleRepository
9
9
  from ...strategies import MaxStrategy, SumStrategy, WeightedStrategy
10
+ from ...types import DecisionTier
10
11
 
11
12
  try:
12
13
  from flask import Flask, current_app
@@ -83,6 +84,47 @@ def create_strategy(config: Dict[str, Any]) -> Any:
83
84
  return strategies[strategy_name]()
84
85
 
85
86
 
87
+ def create_decision_tiers(config: Dict[str, Any]) -> Optional[List[DecisionTier]]:
88
+ """
89
+ Create decision tiers from Flask config.
90
+
91
+ Supports two formats:
92
+ 1. Legacy dict format: {"approve": 30, "review": 70}
93
+ - Creates APPROVE (0-30), REVIEW (30-70), FLAG (70-101) tiers
94
+ 2. List format: [DecisionTier(...), ...]
95
+
96
+ Args:
97
+ config: Flask app configuration
98
+
99
+ Returns:
100
+ List of DecisionTier objects or None
101
+ """
102
+ thresholds = config.get("CMAP_RULES_THRESHOLDS")
103
+ decision_tiers = config.get("CMAP_RULES_DECISION_TIERS")
104
+
105
+ # If explicit decision tiers are provided, use them
106
+ if decision_tiers is not None:
107
+ return decision_tiers
108
+
109
+ # Convert legacy thresholds format to decision tiers
110
+ if thresholds is not None:
111
+ approve_threshold = thresholds.get("approve", 30)
112
+ review_threshold = thresholds.get("review", 70)
113
+
114
+ return [
115
+ DecisionTier("APPROVE", 0, approve_threshold),
116
+ DecisionTier("REVIEW", approve_threshold, review_threshold),
117
+ DecisionTier("FLAG", review_threshold, 101),
118
+ ]
119
+
120
+ # Default decision tiers
121
+ return [
122
+ DecisionTier("APPROVE", 0, 30),
123
+ DecisionTier("REVIEW", 30, 70),
124
+ DecisionTier("FLAG", 70, 101),
125
+ ]
126
+
127
+
86
128
  class CmapRulesEngine:
87
129
  """
88
130
  Flask extension for cmap-rules-engine.
@@ -119,7 +161,7 @@ class CmapRulesEngine:
119
161
  if not HAS_FLASK:
120
162
  raise ImportError(
121
163
  "Flask is required for the Flask integration. "
122
- "Install it with: pip install cmap-rules-engine[flask]"
164
+ "Install it with: pip install pylitmus[flask]"
123
165
  )
124
166
 
125
167
  self._engine: Optional[RuleEngine] = None
@@ -137,7 +179,7 @@ class CmapRulesEngine:
137
179
  # Store reference in extensions
138
180
  if not hasattr(app, "extensions"):
139
181
  app.extensions = {}
140
- app.extensions["cmap_rules_engine"] = self
182
+ app.extensions["pylitmus"] = self
141
183
 
142
184
  # Set default configuration
143
185
  app.config.setdefault("CMAP_RULES_STORAGE", "memory")
@@ -181,13 +223,13 @@ class CmapRulesEngine:
181
223
  # Create strategy
182
224
  strategy = create_strategy(config)
183
225
 
184
- # Get thresholds
185
- thresholds = config.get("CMAP_RULES_THRESHOLDS")
226
+ # Create decision tiers from config
227
+ decision_tiers = create_decision_tiers(config)
186
228
 
187
229
  return RuleEngine(
188
230
  repository=repository,
189
231
  scoring_strategy=strategy,
190
- decision_thresholds=thresholds,
232
+ decision_tiers=decision_tiers,
191
233
  )
192
234
 
193
235
  @property
@@ -209,7 +251,7 @@ def get_engine() -> RuleEngine:
209
251
  Get rule engine from current Flask app.
210
252
 
211
253
  Usage:
212
- from cmap_rules_engine.integrations.flask import get_engine
254
+ from pylitmus.integrations.flask import get_engine
213
255
 
214
256
  @app.route('/assess')
215
257
  def assess():
@@ -226,9 +268,9 @@ def get_engine() -> RuleEngine:
226
268
  if not HAS_FLASK:
227
269
  raise ImportError("Flask is required for the Flask integration")
228
270
 
229
- if "cmap_rules_engine" not in current_app.extensions:
271
+ if "pylitmus" not in current_app.extensions:
230
272
  raise RuntimeError(
231
273
  "CmapRulesEngine not initialized for this app. Did you call init_app()?"
232
274
  )
233
275
 
234
- return current_app.extensions["cmap_rules_engine"].engine
276
+ return current_app.extensions["pylitmus"].engine
@@ -0,0 +1,26 @@
1
+ """
2
+ RETE Algorithm Implementation for PyLitmus.
3
+
4
+ This module provides a lightweight RETE network implementation optimized
5
+ for the PyLitmus rule engine. It maintains the same user-facing API while
6
+ providing performance benefits for large rule sets through:
7
+
8
+ 1. Condition sharing - identical conditions across rules are evaluated once
9
+ 2. Alpha network - single-condition tests with memory
10
+ 3. Beta network - join nodes for combining conditions (AND/OR logic)
11
+
12
+ Usage:
13
+ from pylitmus.rete import RETENetwork, RuleCompiler
14
+
15
+ # Compile rules into RETE network
16
+ compiler = RuleCompiler()
17
+ network = compiler.compile(rules)
18
+
19
+ # Evaluate data
20
+ triggered_rules = network.evaluate(data)
21
+ """
22
+
23
+ from .compiler import RuleCompiler
24
+ from .network import RETENetwork
25
+
26
+ __all__ = ["RETENetwork", "RuleCompiler"]