@higher.archi/boe 1.0.29 → 1.0.31
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.
- package/dist/core/evaluation/decay.d.ts +13 -2
- package/dist/core/evaluation/decay.d.ts.map +1 -1
- package/dist/core/evaluation/decay.js +24 -0
- package/dist/core/evaluation/decay.js.map +1 -1
- package/dist/core/types/rule.d.ts +25 -1
- package/dist/core/types/rule.d.ts.map +1 -1
- package/dist/core/types/rule.js +28 -0
- package/dist/core/types/rule.js.map +1 -1
- package/dist/engines/bayesian/index.d.ts +1 -1
- package/dist/engines/bayesian/index.d.ts.map +1 -1
- package/dist/engines/bayesian/index.js +2 -1
- package/dist/engines/bayesian/index.js.map +1 -1
- package/dist/engines/bayesian/types.d.ts +10 -1
- package/dist/engines/bayesian/types.d.ts.map +1 -1
- package/dist/engines/bayesian/types.js +16 -1
- package/dist/engines/bayesian/types.js.map +1 -1
- package/dist/engines/constraint/types.d.ts +36 -5
- package/dist/engines/constraint/types.d.ts.map +1 -1
- package/dist/engines/constraint/types.js +44 -1
- package/dist/engines/constraint/types.js.map +1 -1
- package/dist/engines/decay/index.d.ts +1 -1
- package/dist/engines/decay/index.d.ts.map +1 -1
- package/dist/engines/decay/index.js +5 -1
- package/dist/engines/decay/index.js.map +1 -1
- package/dist/engines/decay/types.d.ts +26 -4
- package/dist/engines/decay/types.d.ts.map +1 -1
- package/dist/engines/decay/types.js +30 -1
- package/dist/engines/decay/types.js.map +1 -1
- package/dist/engines/defeasible/index.d.ts +1 -1
- package/dist/engines/defeasible/index.d.ts.map +1 -1
- package/dist/engines/defeasible/index.js +8 -1
- package/dist/engines/defeasible/index.js.map +1 -1
- package/dist/engines/defeasible/types.d.ts +40 -5
- package/dist/engines/defeasible/types.d.ts.map +1 -1
- package/dist/engines/defeasible/types.js +56 -1
- package/dist/engines/defeasible/types.js.map +1 -1
- package/dist/engines/ensemble/index.d.ts +1 -0
- package/dist/engines/ensemble/index.d.ts.map +1 -1
- package/dist/engines/ensemble/index.js +5 -1
- package/dist/engines/ensemble/index.js.map +1 -1
- package/dist/engines/ensemble/types.d.ts +17 -2
- package/dist/engines/ensemble/types.d.ts.map +1 -1
- package/dist/engines/ensemble/types.js +23 -0
- package/dist/engines/ensemble/types.js.map +1 -1
- package/dist/engines/expert/index.d.ts +1 -1
- package/dist/engines/expert/index.d.ts.map +1 -1
- package/dist/engines/expert/index.js +3 -1
- package/dist/engines/expert/index.js.map +1 -1
- package/dist/engines/expert/types.d.ts +11 -1
- package/dist/engines/expert/types.d.ts.map +1 -1
- package/dist/engines/expert/types.js +18 -1
- package/dist/engines/expert/types.js.map +1 -1
- package/dist/engines/fuzzy/fuzzy.types.d.ts +65 -8
- package/dist/engines/fuzzy/fuzzy.types.d.ts.map +1 -1
- package/dist/engines/fuzzy/fuzzy.types.js +89 -1
- package/dist/engines/fuzzy/fuzzy.types.js.map +1 -1
- package/dist/engines/loyalty/compiler.d.ts +11 -0
- package/dist/engines/loyalty/compiler.d.ts.map +1 -0
- package/dist/engines/loyalty/compiler.js +144 -0
- package/dist/engines/loyalty/compiler.js.map +1 -0
- package/dist/engines/loyalty/engine.d.ts +76 -0
- package/dist/engines/loyalty/engine.d.ts.map +1 -0
- package/dist/engines/loyalty/engine.js +132 -0
- package/dist/engines/loyalty/engine.js.map +1 -0
- package/dist/engines/loyalty/index.d.ts +9 -0
- package/dist/engines/loyalty/index.d.ts.map +1 -0
- package/dist/engines/loyalty/index.js +24 -0
- package/dist/engines/loyalty/index.js.map +1 -0
- package/dist/engines/loyalty/strategy.d.ts +35 -0
- package/dist/engines/loyalty/strategy.d.ts.map +1 -0
- package/dist/engines/loyalty/strategy.js +405 -0
- package/dist/engines/loyalty/strategy.js.map +1 -0
- package/dist/engines/loyalty/types.d.ts +221 -0
- package/dist/engines/loyalty/types.d.ts.map +1 -0
- package/dist/engines/loyalty/types.js +51 -0
- package/dist/engines/loyalty/types.js.map +1 -0
- package/dist/engines/menu-engineering/compiler.d.ts +11 -0
- package/dist/engines/menu-engineering/compiler.d.ts.map +1 -0
- package/dist/engines/menu-engineering/compiler.js +119 -0
- package/dist/engines/menu-engineering/compiler.js.map +1 -0
- package/dist/engines/menu-engineering/engine.d.ts +32 -0
- package/dist/engines/menu-engineering/engine.d.ts.map +1 -0
- package/dist/engines/menu-engineering/engine.js +40 -0
- package/dist/engines/menu-engineering/engine.js.map +1 -0
- package/dist/engines/menu-engineering/index.d.ts +9 -0
- package/dist/engines/menu-engineering/index.d.ts.map +1 -0
- package/dist/engines/menu-engineering/index.js +21 -0
- package/dist/engines/menu-engineering/index.js.map +1 -0
- package/dist/engines/menu-engineering/strategy.d.ts +18 -0
- package/dist/engines/menu-engineering/strategy.d.ts.map +1 -0
- package/dist/engines/menu-engineering/strategy.js +318 -0
- package/dist/engines/menu-engineering/strategy.js.map +1 -0
- package/dist/engines/menu-engineering/types.d.ts +187 -0
- package/dist/engines/menu-engineering/types.d.ts.map +1 -0
- package/dist/engines/menu-engineering/types.js +27 -0
- package/dist/engines/menu-engineering/types.js.map +1 -0
- package/dist/engines/monte-carlo/index.d.ts +1 -1
- package/dist/engines/monte-carlo/index.d.ts.map +1 -1
- package/dist/engines/monte-carlo/index.js +5 -1
- package/dist/engines/monte-carlo/index.js.map +1 -1
- package/dist/engines/monte-carlo/types.d.ts +16 -1
- package/dist/engines/monte-carlo/types.d.ts.map +1 -1
- package/dist/engines/monte-carlo/types.js +23 -1
- package/dist/engines/monte-carlo/types.js.map +1 -1
- package/dist/engines/negotiation/index.d.ts +1 -0
- package/dist/engines/negotiation/index.d.ts.map +1 -1
- package/dist/engines/negotiation/index.js +7 -1
- package/dist/engines/negotiation/index.js.map +1 -1
- package/dist/engines/negotiation/types.d.ts +23 -4
- package/dist/engines/negotiation/types.d.ts.map +1 -1
- package/dist/engines/negotiation/types.js +27 -0
- package/dist/engines/negotiation/types.js.map +1 -1
- package/dist/engines/prediction/index.d.ts +1 -1
- package/dist/engines/prediction/index.d.ts.map +1 -1
- package/dist/engines/prediction/index.js +6 -1
- package/dist/engines/prediction/index.js.map +1 -1
- package/dist/engines/prediction/types.d.ts +35 -5
- package/dist/engines/prediction/types.d.ts.map +1 -1
- package/dist/engines/prediction/types.js +39 -1
- package/dist/engines/prediction/types.js.map +1 -1
- package/dist/engines/pricing/index.d.ts +2 -2
- package/dist/engines/pricing/index.d.ts.map +1 -1
- package/dist/engines/pricing/index.js +3 -1
- package/dist/engines/pricing/index.js.map +1 -1
- package/dist/engines/pricing/types.d.ts +15 -1
- package/dist/engines/pricing/types.d.ts.map +1 -1
- package/dist/engines/pricing/types.js +16 -1
- package/dist/engines/pricing/types.js.map +1 -1
- package/dist/engines/ranking/index.d.ts +1 -1
- package/dist/engines/ranking/index.d.ts.map +1 -1
- package/dist/engines/ranking/index.js +6 -1
- package/dist/engines/ranking/index.js.map +1 -1
- package/dist/engines/ranking/types.d.ts +32 -5
- package/dist/engines/ranking/types.d.ts.map +1 -1
- package/dist/engines/ranking/types.js +36 -1
- package/dist/engines/ranking/types.js.map +1 -1
- package/dist/engines/recipe-costing/compiler.d.ts +11 -0
- package/dist/engines/recipe-costing/compiler.d.ts.map +1 -0
- package/dist/engines/recipe-costing/compiler.js +177 -0
- package/dist/engines/recipe-costing/compiler.js.map +1 -0
- package/dist/engines/recipe-costing/engine.d.ts +32 -0
- package/dist/engines/recipe-costing/engine.d.ts.map +1 -0
- package/dist/engines/recipe-costing/engine.js +40 -0
- package/dist/engines/recipe-costing/engine.js.map +1 -0
- package/dist/engines/recipe-costing/index.d.ts +9 -0
- package/dist/engines/recipe-costing/index.d.ts.map +1 -0
- package/dist/engines/recipe-costing/index.js +21 -0
- package/dist/engines/recipe-costing/index.js.map +1 -0
- package/dist/engines/recipe-costing/strategy.d.ts +20 -0
- package/dist/engines/recipe-costing/strategy.d.ts.map +1 -0
- package/dist/engines/recipe-costing/strategy.js +265 -0
- package/dist/engines/recipe-costing/strategy.js.map +1 -0
- package/dist/engines/recipe-costing/types.d.ts +213 -0
- package/dist/engines/recipe-costing/types.d.ts.map +1 -0
- package/dist/engines/recipe-costing/types.js +36 -0
- package/dist/engines/recipe-costing/types.js.map +1 -0
- package/dist/engines/scoring/index.d.ts +1 -1
- package/dist/engines/scoring/index.d.ts.map +1 -1
- package/dist/engines/scoring/index.js +3 -1
- package/dist/engines/scoring/index.js.map +1 -1
- package/dist/engines/scoring/types.d.ts +8 -1
- package/dist/engines/scoring/types.d.ts.map +1 -1
- package/dist/engines/scoring/types.js +18 -1
- package/dist/engines/scoring/types.js.map +1 -1
- package/dist/engines/sentiment/index.d.ts +1 -1
- package/dist/engines/sentiment/index.d.ts.map +1 -1
- package/dist/engines/sentiment/index.js +3 -1
- package/dist/engines/sentiment/index.js.map +1 -1
- package/dist/engines/sentiment/types.d.ts +13 -2
- package/dist/engines/sentiment/types.d.ts.map +1 -1
- package/dist/engines/sentiment/types.js +17 -1
- package/dist/engines/sentiment/types.js.map +1 -1
- package/dist/engines/state-machine/index.d.ts +1 -1
- package/dist/engines/state-machine/index.d.ts.map +1 -1
- package/dist/engines/state-machine/index.js +5 -1
- package/dist/engines/state-machine/index.js.map +1 -1
- package/dist/engines/state-machine/types.d.ts +7 -0
- package/dist/engines/state-machine/types.d.ts.map +1 -1
- package/dist/engines/state-machine/types.js +14 -0
- package/dist/engines/state-machine/types.js.map +1 -1
- package/dist/engines/utility/index.d.ts +2 -2
- package/dist/engines/utility/index.d.ts.map +1 -1
- package/dist/engines/utility/index.js +4 -1
- package/dist/engines/utility/index.js.map +1 -1
- package/dist/engines/utility/types.d.ts +21 -3
- package/dist/engines/utility/types.d.ts.map +1 -1
- package/dist/engines/utility/types.js +37 -1
- package/dist/engines/utility/types.js.map +1 -1
- package/dist/index.d.ts +30 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/evaluation/decay.ts +13 -2
- package/src/core/types/rule.ts +25 -1
- package/src/engines/bayesian/index.ts +1 -0
- package/src/engines/bayesian/types.ts +10 -8
- package/src/engines/constraint/types.ts +40 -11
- package/src/engines/decay/index.ts +4 -0
- package/src/engines/decay/types.ts +26 -4
- package/src/engines/defeasible/index.ts +7 -0
- package/src/engines/defeasible/types.ts +42 -18
- package/src/engines/ensemble/index.ts +6 -0
- package/src/engines/ensemble/types.ts +17 -13
- package/src/engines/expert/index.ts +1 -0
- package/src/engines/expert/types.ts +11 -9
- package/src/engines/fuzzy/fuzzy.types.ts +65 -31
- package/src/engines/loyalty/compiler.ts +174 -0
- package/src/engines/loyalty/engine.ts +174 -0
- package/src/engines/loyalty/index.ts +52 -0
- package/src/engines/loyalty/strategy.ts +532 -0
- package/src/engines/loyalty/types.ts +283 -0
- package/src/engines/menu-engineering/compiler.ts +145 -0
- package/src/engines/menu-engineering/engine.ts +48 -0
- package/src/engines/menu-engineering/index.ts +47 -0
- package/src/engines/menu-engineering/strategy.ts +414 -0
- package/src/engines/menu-engineering/types.ts +242 -0
- package/src/engines/monte-carlo/index.ts +1 -0
- package/src/engines/monte-carlo/types.ts +16 -21
- package/src/engines/negotiation/index.ts +8 -0
- package/src/engines/negotiation/types.ts +23 -4
- package/src/engines/prediction/index.ts +5 -0
- package/src/engines/prediction/types.ts +35 -5
- package/src/engines/pricing/index.ts +3 -1
- package/src/engines/pricing/types.ts +17 -1
- package/src/engines/ranking/index.ts +5 -0
- package/src/engines/ranking/types.ts +32 -11
- package/src/engines/recipe-costing/compiler.ts +219 -0
- package/src/engines/recipe-costing/engine.ts +48 -0
- package/src/engines/recipe-costing/index.ts +48 -0
- package/src/engines/recipe-costing/strategy.ts +357 -0
- package/src/engines/recipe-costing/types.ts +269 -0
- package/src/engines/scoring/index.ts +2 -0
- package/src/engines/scoring/types.ts +8 -6
- package/src/engines/sentiment/index.ts +2 -0
- package/src/engines/sentiment/types.ts +13 -2
- package/src/engines/state-machine/index.ts +3 -0
- package/src/engines/state-machine/types.ts +8 -0
- package/src/engines/utility/index.ts +5 -0
- package/src/engines/utility/types.ts +23 -3
- package/src/index.ts +182 -7
|
@@ -36,17 +36,24 @@ import { DecayConfig, DecayInfo } from '../../core/evaluation/decay';
|
|
|
36
36
|
* - defeasible: Can be defeated by other rules
|
|
37
37
|
* - defeater: Only blocks conclusions, doesn't assert anything
|
|
38
38
|
*/
|
|
39
|
-
export
|
|
39
|
+
export const RuleStrengths = {
|
|
40
|
+
strict: 'strict',
|
|
41
|
+
defeasible: 'defeasible',
|
|
42
|
+
defeater: 'defeater',
|
|
43
|
+
} as const;
|
|
44
|
+
export type RuleStrength = typeof RuleStrengths[keyof typeof RuleStrengths];
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* Conclusion status after evaluation
|
|
43
48
|
*/
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
export const ConclusionStatuses = {
|
|
50
|
+
definitely: 'definitely',
|
|
51
|
+
defeasibly: 'defeasibly',
|
|
52
|
+
defeated: 'defeated',
|
|
53
|
+
blocked: 'blocked',
|
|
54
|
+
undecided: 'undecided',
|
|
55
|
+
} as const;
|
|
56
|
+
export type ConclusionStatus = typeof ConclusionStatuses[keyof typeof ConclusionStatuses];
|
|
50
57
|
|
|
51
58
|
// ========================================
|
|
52
59
|
// Defeat Strength Types
|
|
@@ -55,11 +62,13 @@ export type ConclusionStatus =
|
|
|
55
62
|
/**
|
|
56
63
|
* Semantic defeat strength labels - human-readable
|
|
57
64
|
*/
|
|
58
|
-
export
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
export const SemanticDefeatStrengths = {
|
|
66
|
+
completely: 'completely',
|
|
67
|
+
strongly: 'strongly',
|
|
68
|
+
moderately: 'moderately',
|
|
69
|
+
weakly: 'weakly',
|
|
70
|
+
} as const;
|
|
71
|
+
export type SemanticDefeatStrength = typeof SemanticDefeatStrengths[keyof typeof SemanticDefeatStrengths];
|
|
63
72
|
|
|
64
73
|
/**
|
|
65
74
|
* Semantic strength to numeric mapping
|
|
@@ -91,6 +100,12 @@ export type Credibility =
|
|
|
91
100
|
| 'medium' // 0.7
|
|
92
101
|
| 'low'; // 0.4
|
|
93
102
|
|
|
103
|
+
export const SemanticCredibilityLevels = {
|
|
104
|
+
high: 'high',
|
|
105
|
+
medium: 'medium',
|
|
106
|
+
low: 'low',
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
94
109
|
/**
|
|
95
110
|
* Credibility to numeric mapping
|
|
96
111
|
*/
|
|
@@ -192,11 +207,20 @@ export type SuperiorityRelation = {
|
|
|
192
207
|
/**
|
|
193
208
|
* Aggregation strategy for confidence calculation
|
|
194
209
|
*/
|
|
195
|
-
export
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
export const DefeasibleAggregations = {
|
|
211
|
+
average: 'average',
|
|
212
|
+
maxMin: 'max-min',
|
|
213
|
+
weightedSum: 'weighted-sum',
|
|
214
|
+
probabilistic: 'probabilistic',
|
|
215
|
+
} as const;
|
|
216
|
+
export type AggregationStrategy = typeof DefeasibleAggregations[keyof typeof DefeasibleAggregations];
|
|
217
|
+
|
|
218
|
+
export const ConflictResolutions = {
|
|
219
|
+
specificity: 'specificity',
|
|
220
|
+
priority: 'priority',
|
|
221
|
+
explicit: 'explicit',
|
|
222
|
+
} as const;
|
|
223
|
+
export type ConflictResolution = typeof ConflictResolutions[keyof typeof ConflictResolutions];
|
|
200
224
|
|
|
201
225
|
/**
|
|
202
226
|
* Defeasible engine configuration
|
|
@@ -222,7 +246,7 @@ export type DefeasibleConfig = {
|
|
|
222
246
|
* - 'explicit': Only explicit defeats/superiority apply
|
|
223
247
|
* Default: 'specificity'
|
|
224
248
|
*/
|
|
225
|
-
conflictResolution?:
|
|
249
|
+
conflictResolution?: ConflictResolution;
|
|
226
250
|
|
|
227
251
|
/**
|
|
228
252
|
* Maximum iterations for computing defeat cycles (default: 100)
|
|
@@ -18,13 +18,15 @@ import type { FactInput } from '../../core';
|
|
|
18
18
|
// Fusion Strategy
|
|
19
19
|
// ========================================
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
export const FusionStrategies = {
|
|
22
|
+
weightedAverage: 'weighted-average',
|
|
23
|
+
median: 'median',
|
|
24
|
+
min: 'min',
|
|
25
|
+
max: 'max',
|
|
26
|
+
voting: 'voting',
|
|
27
|
+
stacking: 'stacking',
|
|
28
|
+
} as const;
|
|
29
|
+
export type FusionStrategy = typeof FusionStrategies[keyof typeof FusionStrategies];
|
|
28
30
|
|
|
29
31
|
// ========================================
|
|
30
32
|
// Member Definitions (Source)
|
|
@@ -123,12 +125,14 @@ export type MemberResult = {
|
|
|
123
125
|
// ========================================
|
|
124
126
|
|
|
125
127
|
/** Semantic agreement levels based on coefficient of variation */
|
|
126
|
-
export
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
export const AgreementLevels = {
|
|
129
|
+
unanimous: 'unanimous',
|
|
130
|
+
strong: 'strong',
|
|
131
|
+
moderate: 'moderate',
|
|
132
|
+
weak: 'weak',
|
|
133
|
+
divided: 'divided',
|
|
134
|
+
} as const;
|
|
135
|
+
export type AgreementLevel = typeof AgreementLevels[keyof typeof AgreementLevels];
|
|
132
136
|
|
|
133
137
|
/** Ensemble result — mirrors ScoringResult shape for downstream compatibility */
|
|
134
138
|
export type EnsembleResult<T extends TierDefinition = TierDefinition> = {
|
|
@@ -32,15 +32,17 @@ import {
|
|
|
32
32
|
* Semantic confidence labels for human-readable rule authoring.
|
|
33
33
|
* Maps to numeric values between 0.0 and 1.0.
|
|
34
34
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
export const SemanticConfidences = {
|
|
36
|
+
certain: 'certain',
|
|
37
|
+
veryHigh: 'very-high',
|
|
38
|
+
high: 'high',
|
|
39
|
+
likely: 'likely',
|
|
40
|
+
moderate: 'moderate',
|
|
41
|
+
possible: 'possible',
|
|
42
|
+
unlikely: 'unlikely',
|
|
43
|
+
low: 'low',
|
|
44
|
+
} as const;
|
|
45
|
+
export type SemanticConfidence = typeof SemanticConfidences[keyof typeof SemanticConfidences];
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* Mapping from semantic labels to numeric confidence values.
|
|
@@ -15,22 +15,48 @@ import { BindingContext } from '../../core';
|
|
|
15
15
|
* Membership function shapes
|
|
16
16
|
* These define how membership degree (0-1) is calculated from a crisp value
|
|
17
17
|
*/
|
|
18
|
-
export
|
|
18
|
+
export const MembershipShapes = {
|
|
19
|
+
triangle: 'triangle',
|
|
20
|
+
trapezoid: 'trapezoid',
|
|
21
|
+
gaussian: 'gaussian',
|
|
22
|
+
shoulder: 'shoulder',
|
|
23
|
+
block: 'block',
|
|
24
|
+
} as const;
|
|
25
|
+
export type MembershipShape = typeof MembershipShapes[keyof typeof MembershipShapes];
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Semantic position within domain (compiles to numeric center)
|
|
22
29
|
*/
|
|
23
|
-
export
|
|
30
|
+
export const PositionSemantics = {
|
|
31
|
+
veryLow: 'very-low',
|
|
32
|
+
low: 'low',
|
|
33
|
+
medium: 'medium',
|
|
34
|
+
high: 'high',
|
|
35
|
+
veryHigh: 'very-high',
|
|
36
|
+
} as const;
|
|
37
|
+
export type PositionSemantic = typeof PositionSemantics[keyof typeof PositionSemantics];
|
|
24
38
|
|
|
25
39
|
/**
|
|
26
40
|
* Semantic width/spread (compiles to numeric spread as % of domain)
|
|
27
41
|
*/
|
|
28
|
-
export
|
|
42
|
+
export const WidthSemantics = {
|
|
43
|
+
tight: 'tight',
|
|
44
|
+
narrow: 'narrow',
|
|
45
|
+
medium: 'medium',
|
|
46
|
+
wide: 'wide',
|
|
47
|
+
veryWide: 'very-wide',
|
|
48
|
+
} as const;
|
|
49
|
+
export type WidthSemantic = typeof WidthSemantics[keyof typeof WidthSemantics];
|
|
29
50
|
|
|
30
51
|
/**
|
|
31
52
|
* Edge behavior for shoulder/block shapes
|
|
32
53
|
*/
|
|
33
|
-
export
|
|
54
|
+
export const EdgeSemantics = {
|
|
55
|
+
openLeft: 'open-left',
|
|
56
|
+
openRight: 'open-right',
|
|
57
|
+
closed: 'closed',
|
|
58
|
+
} as const;
|
|
59
|
+
export type EdgeSemantic = typeof EdgeSemantics[keyof typeof EdgeSemantics];
|
|
34
60
|
|
|
35
61
|
// ========================================
|
|
36
62
|
// Fuzzy Term Definitions
|
|
@@ -127,33 +153,39 @@ export type CompiledFuzzyVariable = {
|
|
|
127
153
|
/**
|
|
128
154
|
* Aggregation methods - combine multiple rule activations for same output
|
|
129
155
|
*/
|
|
130
|
-
export
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
156
|
+
export const AggregationMethods = {
|
|
157
|
+
max: 'max',
|
|
158
|
+
sum: 'sum',
|
|
159
|
+
probor: 'probor',
|
|
160
|
+
bound: 'bound',
|
|
161
|
+
min: 'min',
|
|
162
|
+
} as const;
|
|
163
|
+
export type AggregationMethod = typeof AggregationMethods[keyof typeof AggregationMethods];
|
|
136
164
|
|
|
137
165
|
/**
|
|
138
166
|
* Defuzzification methods - convert fuzzy output to crisp value
|
|
139
167
|
*/
|
|
140
|
-
export
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
export const DefuzzifyMethods = {
|
|
169
|
+
centroid: 'centroid',
|
|
170
|
+
bisector: 'bisector',
|
|
171
|
+
mom: 'mom',
|
|
172
|
+
som: 'som',
|
|
173
|
+
lom: 'lom',
|
|
174
|
+
wtaver: 'wtaver',
|
|
175
|
+
} as const;
|
|
176
|
+
export type DefuzzifyMethod = typeof DefuzzifyMethods[keyof typeof DefuzzifyMethods];
|
|
147
177
|
|
|
148
178
|
/**
|
|
149
179
|
* Transform methods - post-process the crisp output
|
|
150
180
|
*/
|
|
151
|
-
export
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
181
|
+
export const TransformMethods = {
|
|
182
|
+
clamp: 'clamp',
|
|
183
|
+
round: 'round',
|
|
184
|
+
deadband: 'deadband',
|
|
185
|
+
scale: 'scale',
|
|
186
|
+
quantize: 'quantize',
|
|
187
|
+
} as const;
|
|
188
|
+
export type TransformMethod = typeof TransformMethods[keyof typeof TransformMethods];
|
|
157
189
|
|
|
158
190
|
/**
|
|
159
191
|
* Any defuzzification pipeline stage
|
|
@@ -163,14 +195,16 @@ export type DefuzzifyStage = AggregationMethod | DefuzzifyMethod | TransformMeth
|
|
|
163
195
|
/**
|
|
164
196
|
* Named presets that expand to full pipelines
|
|
165
197
|
*/
|
|
166
|
-
export
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
198
|
+
export const DefuzzifyPresets = {
|
|
199
|
+
balanced: 'balanced',
|
|
200
|
+
smooth: 'smooth',
|
|
201
|
+
decisive: 'decisive',
|
|
202
|
+
cautious: 'cautious',
|
|
203
|
+
aggressive: 'aggressive',
|
|
204
|
+
discrete: 'discrete',
|
|
205
|
+
stable: 'stable',
|
|
206
|
+
} as const;
|
|
207
|
+
export type DefuzzifyPreset = typeof DefuzzifyPresets[keyof typeof DefuzzifyPresets];
|
|
174
208
|
|
|
175
209
|
/**
|
|
176
210
|
* Defuzzification pipeline configuration
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loyalty Engine Compiler
|
|
3
|
+
*
|
|
4
|
+
* Validates loyalty rulesets and resolves defaults.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CompilationError } from '../../core/errors';
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
LoyaltyRuleSet,
|
|
11
|
+
CompiledLoyaltyRuleSet,
|
|
12
|
+
CompiledEarningRuleSet,
|
|
13
|
+
CompiledRedemptionRuleSet,
|
|
14
|
+
CompiledTierEvaluationRuleSet
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compile and validate a loyalty ruleset.
|
|
19
|
+
*/
|
|
20
|
+
export function compileLoyaltyRuleSet(
|
|
21
|
+
ruleSet: LoyaltyRuleSet
|
|
22
|
+
): CompiledLoyaltyRuleSet {
|
|
23
|
+
if (!ruleSet.id) {
|
|
24
|
+
throw new CompilationError('Loyalty ruleset requires an id');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (ruleSet.mode !== 'loyalty') {
|
|
28
|
+
throw new CompilationError(`Expected mode 'loyalty', got '${ruleSet.mode}'`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (ruleSet.strategy) {
|
|
32
|
+
case 'earning':
|
|
33
|
+
return compileEarning(ruleSet);
|
|
34
|
+
case 'redemption':
|
|
35
|
+
return compileRedemption(ruleSet);
|
|
36
|
+
case 'tier-evaluation':
|
|
37
|
+
return compileTierEvaluation(ruleSet);
|
|
38
|
+
default:
|
|
39
|
+
throw new CompilationError(`Unknown loyalty strategy: '${(ruleSet as any).strategy}'`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function compileEarning(
|
|
44
|
+
ruleSet: LoyaltyRuleSet & { strategy: 'earning' }
|
|
45
|
+
): CompiledEarningRuleSet {
|
|
46
|
+
if (!ruleSet.earningRules || ruleSet.earningRules.length === 0) {
|
|
47
|
+
throw new CompilationError('Earning strategy requires at least one earning rule');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate unique rule IDs
|
|
51
|
+
const ruleIds = new Set<string>();
|
|
52
|
+
for (const rule of ruleSet.earningRules) {
|
|
53
|
+
if (!rule.id) {
|
|
54
|
+
throw new CompilationError('Each earning rule requires an id');
|
|
55
|
+
}
|
|
56
|
+
if (ruleIds.has(rule.id)) {
|
|
57
|
+
throw new CompilationError(`Duplicate earning rule id: '${rule.id}'`);
|
|
58
|
+
}
|
|
59
|
+
if (rule.baseRate < 0) {
|
|
60
|
+
throw new CompilationError(`Earning rule '${rule.id}' baseRate must be non-negative`);
|
|
61
|
+
}
|
|
62
|
+
ruleIds.add(rule.id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate tier IDs are unique
|
|
66
|
+
validateTierIds(ruleSet.tiers);
|
|
67
|
+
|
|
68
|
+
// Validate promotions
|
|
69
|
+
validatePromotions(ruleSet.promotions);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
id: ruleSet.id,
|
|
73
|
+
name: ruleSet.name,
|
|
74
|
+
mode: 'loyalty',
|
|
75
|
+
strategy: 'earning',
|
|
76
|
+
earningRules: ruleSet.earningRules,
|
|
77
|
+
defaultRate: ruleSet.defaultRate ?? 1,
|
|
78
|
+
tiers: ruleSet.tiers ?? [],
|
|
79
|
+
expirationPolicy: ruleSet.expirationPolicy ?? {},
|
|
80
|
+
promotions: ruleSet.promotions ?? []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function compileRedemption(
|
|
85
|
+
ruleSet: LoyaltyRuleSet & { strategy: 'redemption' }
|
|
86
|
+
): CompiledRedemptionRuleSet {
|
|
87
|
+
if (!ruleSet.redemptionOptions || ruleSet.redemptionOptions.length === 0) {
|
|
88
|
+
throw new CompilationError('Redemption strategy requires at least one redemption option');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validate unique option IDs
|
|
92
|
+
const optionIds = new Set<string>();
|
|
93
|
+
for (const option of ruleSet.redemptionOptions) {
|
|
94
|
+
if (!option.id) {
|
|
95
|
+
throw new CompilationError('Each redemption option requires an id');
|
|
96
|
+
}
|
|
97
|
+
if (optionIds.has(option.id)) {
|
|
98
|
+
throw new CompilationError(`Duplicate redemption option id: '${option.id}'`);
|
|
99
|
+
}
|
|
100
|
+
if (option.pointsRequired <= 0) {
|
|
101
|
+
throw new CompilationError(`Redemption option '${option.id}' pointsRequired must be positive`);
|
|
102
|
+
}
|
|
103
|
+
optionIds.add(option.id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
id: ruleSet.id,
|
|
108
|
+
name: ruleSet.name,
|
|
109
|
+
mode: 'loyalty',
|
|
110
|
+
strategy: 'redemption',
|
|
111
|
+
redemptionOptions: ruleSet.redemptionOptions,
|
|
112
|
+
tiers: ruleSet.tiers ?? [],
|
|
113
|
+
expirationPolicy: ruleSet.expirationPolicy ?? {},
|
|
114
|
+
promotions: ruleSet.promotions ?? []
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function compileTierEvaluation(
|
|
119
|
+
ruleSet: LoyaltyRuleSet & { strategy: 'tier-evaluation' }
|
|
120
|
+
): CompiledTierEvaluationRuleSet {
|
|
121
|
+
if (!ruleSet.tiers || ruleSet.tiers.length === 0) {
|
|
122
|
+
throw new CompilationError('Tier-evaluation strategy requires at least one tier definition');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validate tier IDs are unique
|
|
126
|
+
validateTierIds(ruleSet.tiers);
|
|
127
|
+
|
|
128
|
+
// Validate tiers are sorted by threshold ascending
|
|
129
|
+
for (let i = 1; i < ruleSet.tiers.length; i++) {
|
|
130
|
+
if (ruleSet.tiers[i].qualifyingThreshold < ruleSet.tiers[i - 1].qualifyingThreshold) {
|
|
131
|
+
throw new CompilationError(
|
|
132
|
+
`Tier '${ruleSet.tiers[i].id}' threshold (${ruleSet.tiers[i].qualifyingThreshold}) ` +
|
|
133
|
+
`is less than previous tier '${ruleSet.tiers[i - 1].id}' (${ruleSet.tiers[i - 1].qualifyingThreshold}). ` +
|
|
134
|
+
`Tiers must be ordered by ascending threshold.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
id: ruleSet.id,
|
|
141
|
+
name: ruleSet.name,
|
|
142
|
+
mode: 'loyalty',
|
|
143
|
+
strategy: 'tier-evaluation',
|
|
144
|
+
evaluationPeriod: ruleSet.evaluationPeriod ?? 'rolling-12-months',
|
|
145
|
+
tiers: ruleSet.tiers,
|
|
146
|
+
expirationPolicy: ruleSet.expirationPolicy ?? {},
|
|
147
|
+
promotions: ruleSet.promotions ?? []
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function validateTierIds(tiers?: { id: string }[]): void {
|
|
152
|
+
if (!tiers) return;
|
|
153
|
+
const tierIds = new Set<string>();
|
|
154
|
+
for (const tier of tiers) {
|
|
155
|
+
if (tierIds.has(tier.id)) {
|
|
156
|
+
throw new CompilationError(`Duplicate tier id: '${tier.id}'`);
|
|
157
|
+
}
|
|
158
|
+
tierIds.add(tier.id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function validatePromotions(promotions?: { id: string }[]): void {
|
|
163
|
+
if (!promotions) return;
|
|
164
|
+
const promoIds = new Set<string>();
|
|
165
|
+
for (const promo of promotions) {
|
|
166
|
+
if (!promo.id) {
|
|
167
|
+
throw new CompilationError('Each promotion requires an id');
|
|
168
|
+
}
|
|
169
|
+
if (promoIds.has(promo.id)) {
|
|
170
|
+
throw new CompilationError(`Duplicate promotion id: '${promo.id}'`);
|
|
171
|
+
}
|
|
172
|
+
promoIds.add(promo.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loyalty Engine
|
|
3
|
+
*
|
|
4
|
+
* Point ledger engine that manages earning rules with category multipliers,
|
|
5
|
+
* point transactions, tier-qualified balances, and promotion stacking.
|
|
6
|
+
* Unlike other BOE engines, the Loyalty engine maintains a running balance
|
|
7
|
+
* ledger across operations.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const engine = new LoyaltyEngine();
|
|
12
|
+
* const compiled = compileLoyaltyRuleSet({ ... });
|
|
13
|
+
*
|
|
14
|
+
* // Stream purchases as they arrive
|
|
15
|
+
* const r1 = engine.ingest({ memberId: 'M001', amount: 250, category: 'dining' }, compiled);
|
|
16
|
+
* // r1.pointsEarned: 500, r1.newBalance: 500
|
|
17
|
+
*
|
|
18
|
+
* engine.getBalance('M001'); // { currentBalance: 500, ... }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
WorkingMemory,
|
|
24
|
+
Fact,
|
|
25
|
+
FactInput,
|
|
26
|
+
FactChange
|
|
27
|
+
} from '../../core';
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
CompiledLoyaltyRuleSet,
|
|
31
|
+
CompiledEarningRuleSet,
|
|
32
|
+
LoyaltyOptions,
|
|
33
|
+
LoyaltyResult,
|
|
34
|
+
LoyaltyIngestResult,
|
|
35
|
+
MemberBalance
|
|
36
|
+
} from './types';
|
|
37
|
+
|
|
38
|
+
import { LoyaltyExecutor } from './strategy';
|
|
39
|
+
|
|
40
|
+
export class LoyaltyEngine {
|
|
41
|
+
private wm: WorkingMemory;
|
|
42
|
+
private strategy: LoyaltyExecutor;
|
|
43
|
+
private _ledger: Map<string, MemberBalance> = new Map();
|
|
44
|
+
|
|
45
|
+
constructor(workingMemory?: WorkingMemory) {
|
|
46
|
+
this.wm = workingMemory ?? new WorkingMemory();
|
|
47
|
+
this.strategy = new LoyaltyExecutor();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ========================================
|
|
51
|
+
// IWorkingMemory Implementation
|
|
52
|
+
// ========================================
|
|
53
|
+
|
|
54
|
+
add<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
|
|
55
|
+
return this.wm.add(input);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
remove(factId: string): Fact | undefined {
|
|
59
|
+
return this.wm.remove(factId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
update<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
|
|
63
|
+
return this.wm.update(input);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get(factId: string): Fact | undefined {
|
|
67
|
+
return this.wm.get(factId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getByType(type: string): Fact[] {
|
|
71
|
+
return this.wm.getByType(type);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getAll(): Fact[] {
|
|
75
|
+
return this.wm.getAll();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
has(factId: string): boolean {
|
|
79
|
+
return this.wm.has(factId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
size(): number {
|
|
83
|
+
return this.wm.size();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
clear(): void {
|
|
87
|
+
this.wm.clear();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getChanges(): FactChange[] {
|
|
91
|
+
return this.wm.getChanges();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
clearChanges(): void {
|
|
95
|
+
this.wm.clearChanges();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ========================================
|
|
99
|
+
// Engine Execution
|
|
100
|
+
// ========================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Execute a loyalty ruleset against all facts in working memory.
|
|
104
|
+
*
|
|
105
|
+
* @param ruleSet - Compiled loyalty ruleset
|
|
106
|
+
* @param options - Runtime options (asOf date)
|
|
107
|
+
* @returns Loyalty result (earning, redemption, or tier-evaluation)
|
|
108
|
+
*/
|
|
109
|
+
execute(
|
|
110
|
+
ruleSet: CompiledLoyaltyRuleSet,
|
|
111
|
+
options: LoyaltyOptions = {}
|
|
112
|
+
): LoyaltyResult {
|
|
113
|
+
return this.strategy.run(ruleSet, this.wm, this._ledger, options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ========================================
|
|
117
|
+
// Streaming Ingest
|
|
118
|
+
// ========================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Process a single purchase/activity event incrementally.
|
|
122
|
+
*
|
|
123
|
+
* Each call computes points earned, updates the internal ledger,
|
|
124
|
+
* evaluates tier status, and returns the result with the new balance.
|
|
125
|
+
* Only works with earning strategy rulesets.
|
|
126
|
+
*
|
|
127
|
+
* @param eventData - Event data with memberId, amount, and optional category
|
|
128
|
+
* @param ruleSet - Compiled earning ruleset
|
|
129
|
+
* @param asOf - Reference time (default: now)
|
|
130
|
+
* @returns Earning result with updated balance
|
|
131
|
+
*/
|
|
132
|
+
ingest(
|
|
133
|
+
eventData: Record<string, any>,
|
|
134
|
+
ruleSet: CompiledEarningRuleSet,
|
|
135
|
+
asOf: Date = new Date()
|
|
136
|
+
): LoyaltyIngestResult {
|
|
137
|
+
return this.strategy.earnSingle(eventData, ruleSet, this._ledger, asOf);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ========================================
|
|
141
|
+
// Ledger Operations
|
|
142
|
+
// ========================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the current balance for a member.
|
|
146
|
+
* Returns undefined if the member has no transactions.
|
|
147
|
+
*/
|
|
148
|
+
getBalance(memberId: string): MemberBalance | undefined {
|
|
149
|
+
return this._ledger.get(memberId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get the full ledger state (all member balances).
|
|
154
|
+
*/
|
|
155
|
+
getLedger(): Map<string, MemberBalance> {
|
|
156
|
+
return new Map(this._ledger);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Reset the ledger, clearing all member balances.
|
|
161
|
+
* Does not affect working memory.
|
|
162
|
+
*/
|
|
163
|
+
resetLedger(): void {
|
|
164
|
+
this._ledger.clear();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ========================================
|
|
168
|
+
// Utility Methods
|
|
169
|
+
// ========================================
|
|
170
|
+
|
|
171
|
+
getWorkingMemory(): WorkingMemory {
|
|
172
|
+
return this.wm;
|
|
173
|
+
}
|
|
174
|
+
}
|