@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.
Files changed (241) hide show
  1. package/dist/core/evaluation/decay.d.ts +13 -2
  2. package/dist/core/evaluation/decay.d.ts.map +1 -1
  3. package/dist/core/evaluation/decay.js +24 -0
  4. package/dist/core/evaluation/decay.js.map +1 -1
  5. package/dist/core/types/rule.d.ts +25 -1
  6. package/dist/core/types/rule.d.ts.map +1 -1
  7. package/dist/core/types/rule.js +28 -0
  8. package/dist/core/types/rule.js.map +1 -1
  9. package/dist/engines/bayesian/index.d.ts +1 -1
  10. package/dist/engines/bayesian/index.d.ts.map +1 -1
  11. package/dist/engines/bayesian/index.js +2 -1
  12. package/dist/engines/bayesian/index.js.map +1 -1
  13. package/dist/engines/bayesian/types.d.ts +10 -1
  14. package/dist/engines/bayesian/types.d.ts.map +1 -1
  15. package/dist/engines/bayesian/types.js +16 -1
  16. package/dist/engines/bayesian/types.js.map +1 -1
  17. package/dist/engines/constraint/types.d.ts +36 -5
  18. package/dist/engines/constraint/types.d.ts.map +1 -1
  19. package/dist/engines/constraint/types.js +44 -1
  20. package/dist/engines/constraint/types.js.map +1 -1
  21. package/dist/engines/decay/index.d.ts +1 -1
  22. package/dist/engines/decay/index.d.ts.map +1 -1
  23. package/dist/engines/decay/index.js +5 -1
  24. package/dist/engines/decay/index.js.map +1 -1
  25. package/dist/engines/decay/types.d.ts +26 -4
  26. package/dist/engines/decay/types.d.ts.map +1 -1
  27. package/dist/engines/decay/types.js +30 -1
  28. package/dist/engines/decay/types.js.map +1 -1
  29. package/dist/engines/defeasible/index.d.ts +1 -1
  30. package/dist/engines/defeasible/index.d.ts.map +1 -1
  31. package/dist/engines/defeasible/index.js +8 -1
  32. package/dist/engines/defeasible/index.js.map +1 -1
  33. package/dist/engines/defeasible/types.d.ts +40 -5
  34. package/dist/engines/defeasible/types.d.ts.map +1 -1
  35. package/dist/engines/defeasible/types.js +56 -1
  36. package/dist/engines/defeasible/types.js.map +1 -1
  37. package/dist/engines/ensemble/index.d.ts +1 -0
  38. package/dist/engines/ensemble/index.d.ts.map +1 -1
  39. package/dist/engines/ensemble/index.js +5 -1
  40. package/dist/engines/ensemble/index.js.map +1 -1
  41. package/dist/engines/ensemble/types.d.ts +17 -2
  42. package/dist/engines/ensemble/types.d.ts.map +1 -1
  43. package/dist/engines/ensemble/types.js +23 -0
  44. package/dist/engines/ensemble/types.js.map +1 -1
  45. package/dist/engines/expert/index.d.ts +1 -1
  46. package/dist/engines/expert/index.d.ts.map +1 -1
  47. package/dist/engines/expert/index.js +3 -1
  48. package/dist/engines/expert/index.js.map +1 -1
  49. package/dist/engines/expert/types.d.ts +11 -1
  50. package/dist/engines/expert/types.d.ts.map +1 -1
  51. package/dist/engines/expert/types.js +18 -1
  52. package/dist/engines/expert/types.js.map +1 -1
  53. package/dist/engines/fuzzy/fuzzy.types.d.ts +65 -8
  54. package/dist/engines/fuzzy/fuzzy.types.d.ts.map +1 -1
  55. package/dist/engines/fuzzy/fuzzy.types.js +89 -1
  56. package/dist/engines/fuzzy/fuzzy.types.js.map +1 -1
  57. package/dist/engines/loyalty/compiler.d.ts +11 -0
  58. package/dist/engines/loyalty/compiler.d.ts.map +1 -0
  59. package/dist/engines/loyalty/compiler.js +144 -0
  60. package/dist/engines/loyalty/compiler.js.map +1 -0
  61. package/dist/engines/loyalty/engine.d.ts +76 -0
  62. package/dist/engines/loyalty/engine.d.ts.map +1 -0
  63. package/dist/engines/loyalty/engine.js +132 -0
  64. package/dist/engines/loyalty/engine.js.map +1 -0
  65. package/dist/engines/loyalty/index.d.ts +9 -0
  66. package/dist/engines/loyalty/index.d.ts.map +1 -0
  67. package/dist/engines/loyalty/index.js +24 -0
  68. package/dist/engines/loyalty/index.js.map +1 -0
  69. package/dist/engines/loyalty/strategy.d.ts +35 -0
  70. package/dist/engines/loyalty/strategy.d.ts.map +1 -0
  71. package/dist/engines/loyalty/strategy.js +405 -0
  72. package/dist/engines/loyalty/strategy.js.map +1 -0
  73. package/dist/engines/loyalty/types.d.ts +221 -0
  74. package/dist/engines/loyalty/types.d.ts.map +1 -0
  75. package/dist/engines/loyalty/types.js +51 -0
  76. package/dist/engines/loyalty/types.js.map +1 -0
  77. package/dist/engines/menu-engineering/compiler.d.ts +11 -0
  78. package/dist/engines/menu-engineering/compiler.d.ts.map +1 -0
  79. package/dist/engines/menu-engineering/compiler.js +119 -0
  80. package/dist/engines/menu-engineering/compiler.js.map +1 -0
  81. package/dist/engines/menu-engineering/engine.d.ts +32 -0
  82. package/dist/engines/menu-engineering/engine.d.ts.map +1 -0
  83. package/dist/engines/menu-engineering/engine.js +40 -0
  84. package/dist/engines/menu-engineering/engine.js.map +1 -0
  85. package/dist/engines/menu-engineering/index.d.ts +9 -0
  86. package/dist/engines/menu-engineering/index.d.ts.map +1 -0
  87. package/dist/engines/menu-engineering/index.js +21 -0
  88. package/dist/engines/menu-engineering/index.js.map +1 -0
  89. package/dist/engines/menu-engineering/strategy.d.ts +18 -0
  90. package/dist/engines/menu-engineering/strategy.d.ts.map +1 -0
  91. package/dist/engines/menu-engineering/strategy.js +318 -0
  92. package/dist/engines/menu-engineering/strategy.js.map +1 -0
  93. package/dist/engines/menu-engineering/types.d.ts +187 -0
  94. package/dist/engines/menu-engineering/types.d.ts.map +1 -0
  95. package/dist/engines/menu-engineering/types.js +27 -0
  96. package/dist/engines/menu-engineering/types.js.map +1 -0
  97. package/dist/engines/monte-carlo/index.d.ts +1 -1
  98. package/dist/engines/monte-carlo/index.d.ts.map +1 -1
  99. package/dist/engines/monte-carlo/index.js +5 -1
  100. package/dist/engines/monte-carlo/index.js.map +1 -1
  101. package/dist/engines/monte-carlo/types.d.ts +16 -1
  102. package/dist/engines/monte-carlo/types.d.ts.map +1 -1
  103. package/dist/engines/monte-carlo/types.js +23 -1
  104. package/dist/engines/monte-carlo/types.js.map +1 -1
  105. package/dist/engines/negotiation/index.d.ts +1 -0
  106. package/dist/engines/negotiation/index.d.ts.map +1 -1
  107. package/dist/engines/negotiation/index.js +7 -1
  108. package/dist/engines/negotiation/index.js.map +1 -1
  109. package/dist/engines/negotiation/types.d.ts +23 -4
  110. package/dist/engines/negotiation/types.d.ts.map +1 -1
  111. package/dist/engines/negotiation/types.js +27 -0
  112. package/dist/engines/negotiation/types.js.map +1 -1
  113. package/dist/engines/prediction/index.d.ts +1 -1
  114. package/dist/engines/prediction/index.d.ts.map +1 -1
  115. package/dist/engines/prediction/index.js +6 -1
  116. package/dist/engines/prediction/index.js.map +1 -1
  117. package/dist/engines/prediction/types.d.ts +35 -5
  118. package/dist/engines/prediction/types.d.ts.map +1 -1
  119. package/dist/engines/prediction/types.js +39 -1
  120. package/dist/engines/prediction/types.js.map +1 -1
  121. package/dist/engines/pricing/index.d.ts +2 -2
  122. package/dist/engines/pricing/index.d.ts.map +1 -1
  123. package/dist/engines/pricing/index.js +3 -1
  124. package/dist/engines/pricing/index.js.map +1 -1
  125. package/dist/engines/pricing/types.d.ts +15 -1
  126. package/dist/engines/pricing/types.d.ts.map +1 -1
  127. package/dist/engines/pricing/types.js +16 -1
  128. package/dist/engines/pricing/types.js.map +1 -1
  129. package/dist/engines/ranking/index.d.ts +1 -1
  130. package/dist/engines/ranking/index.d.ts.map +1 -1
  131. package/dist/engines/ranking/index.js +6 -1
  132. package/dist/engines/ranking/index.js.map +1 -1
  133. package/dist/engines/ranking/types.d.ts +32 -5
  134. package/dist/engines/ranking/types.d.ts.map +1 -1
  135. package/dist/engines/ranking/types.js +36 -1
  136. package/dist/engines/ranking/types.js.map +1 -1
  137. package/dist/engines/recipe-costing/compiler.d.ts +11 -0
  138. package/dist/engines/recipe-costing/compiler.d.ts.map +1 -0
  139. package/dist/engines/recipe-costing/compiler.js +177 -0
  140. package/dist/engines/recipe-costing/compiler.js.map +1 -0
  141. package/dist/engines/recipe-costing/engine.d.ts +32 -0
  142. package/dist/engines/recipe-costing/engine.d.ts.map +1 -0
  143. package/dist/engines/recipe-costing/engine.js +40 -0
  144. package/dist/engines/recipe-costing/engine.js.map +1 -0
  145. package/dist/engines/recipe-costing/index.d.ts +9 -0
  146. package/dist/engines/recipe-costing/index.d.ts.map +1 -0
  147. package/dist/engines/recipe-costing/index.js +21 -0
  148. package/dist/engines/recipe-costing/index.js.map +1 -0
  149. package/dist/engines/recipe-costing/strategy.d.ts +20 -0
  150. package/dist/engines/recipe-costing/strategy.d.ts.map +1 -0
  151. package/dist/engines/recipe-costing/strategy.js +265 -0
  152. package/dist/engines/recipe-costing/strategy.js.map +1 -0
  153. package/dist/engines/recipe-costing/types.d.ts +213 -0
  154. package/dist/engines/recipe-costing/types.d.ts.map +1 -0
  155. package/dist/engines/recipe-costing/types.js +36 -0
  156. package/dist/engines/recipe-costing/types.js.map +1 -0
  157. package/dist/engines/scoring/index.d.ts +1 -1
  158. package/dist/engines/scoring/index.d.ts.map +1 -1
  159. package/dist/engines/scoring/index.js +3 -1
  160. package/dist/engines/scoring/index.js.map +1 -1
  161. package/dist/engines/scoring/types.d.ts +8 -1
  162. package/dist/engines/scoring/types.d.ts.map +1 -1
  163. package/dist/engines/scoring/types.js +18 -1
  164. package/dist/engines/scoring/types.js.map +1 -1
  165. package/dist/engines/sentiment/index.d.ts +1 -1
  166. package/dist/engines/sentiment/index.d.ts.map +1 -1
  167. package/dist/engines/sentiment/index.js +3 -1
  168. package/dist/engines/sentiment/index.js.map +1 -1
  169. package/dist/engines/sentiment/types.d.ts +13 -2
  170. package/dist/engines/sentiment/types.d.ts.map +1 -1
  171. package/dist/engines/sentiment/types.js +17 -1
  172. package/dist/engines/sentiment/types.js.map +1 -1
  173. package/dist/engines/state-machine/index.d.ts +1 -1
  174. package/dist/engines/state-machine/index.d.ts.map +1 -1
  175. package/dist/engines/state-machine/index.js +5 -1
  176. package/dist/engines/state-machine/index.js.map +1 -1
  177. package/dist/engines/state-machine/types.d.ts +7 -0
  178. package/dist/engines/state-machine/types.d.ts.map +1 -1
  179. package/dist/engines/state-machine/types.js +14 -0
  180. package/dist/engines/state-machine/types.js.map +1 -1
  181. package/dist/engines/utility/index.d.ts +2 -2
  182. package/dist/engines/utility/index.d.ts.map +1 -1
  183. package/dist/engines/utility/index.js +4 -1
  184. package/dist/engines/utility/index.js.map +1 -1
  185. package/dist/engines/utility/types.d.ts +21 -3
  186. package/dist/engines/utility/types.d.ts.map +1 -1
  187. package/dist/engines/utility/types.js +37 -1
  188. package/dist/engines/utility/types.js.map +1 -1
  189. package/dist/index.d.ts +30 -21
  190. package/dist/index.d.ts.map +1 -1
  191. package/dist/index.js +80 -3
  192. package/dist/index.js.map +1 -1
  193. package/package.json +1 -1
  194. package/src/core/evaluation/decay.ts +13 -2
  195. package/src/core/types/rule.ts +25 -1
  196. package/src/engines/bayesian/index.ts +1 -0
  197. package/src/engines/bayesian/types.ts +10 -8
  198. package/src/engines/constraint/types.ts +40 -11
  199. package/src/engines/decay/index.ts +4 -0
  200. package/src/engines/decay/types.ts +26 -4
  201. package/src/engines/defeasible/index.ts +7 -0
  202. package/src/engines/defeasible/types.ts +42 -18
  203. package/src/engines/ensemble/index.ts +6 -0
  204. package/src/engines/ensemble/types.ts +17 -13
  205. package/src/engines/expert/index.ts +1 -0
  206. package/src/engines/expert/types.ts +11 -9
  207. package/src/engines/fuzzy/fuzzy.types.ts +65 -31
  208. package/src/engines/loyalty/compiler.ts +174 -0
  209. package/src/engines/loyalty/engine.ts +174 -0
  210. package/src/engines/loyalty/index.ts +52 -0
  211. package/src/engines/loyalty/strategy.ts +532 -0
  212. package/src/engines/loyalty/types.ts +283 -0
  213. package/src/engines/menu-engineering/compiler.ts +145 -0
  214. package/src/engines/menu-engineering/engine.ts +48 -0
  215. package/src/engines/menu-engineering/index.ts +47 -0
  216. package/src/engines/menu-engineering/strategy.ts +414 -0
  217. package/src/engines/menu-engineering/types.ts +242 -0
  218. package/src/engines/monte-carlo/index.ts +1 -0
  219. package/src/engines/monte-carlo/types.ts +16 -21
  220. package/src/engines/negotiation/index.ts +8 -0
  221. package/src/engines/negotiation/types.ts +23 -4
  222. package/src/engines/prediction/index.ts +5 -0
  223. package/src/engines/prediction/types.ts +35 -5
  224. package/src/engines/pricing/index.ts +3 -1
  225. package/src/engines/pricing/types.ts +17 -1
  226. package/src/engines/ranking/index.ts +5 -0
  227. package/src/engines/ranking/types.ts +32 -11
  228. package/src/engines/recipe-costing/compiler.ts +219 -0
  229. package/src/engines/recipe-costing/engine.ts +48 -0
  230. package/src/engines/recipe-costing/index.ts +48 -0
  231. package/src/engines/recipe-costing/strategy.ts +357 -0
  232. package/src/engines/recipe-costing/types.ts +269 -0
  233. package/src/engines/scoring/index.ts +2 -0
  234. package/src/engines/scoring/types.ts +8 -6
  235. package/src/engines/sentiment/index.ts +2 -0
  236. package/src/engines/sentiment/types.ts +13 -2
  237. package/src/engines/state-machine/index.ts +3 -0
  238. package/src/engines/state-machine/types.ts +8 -0
  239. package/src/engines/utility/index.ts +5 -0
  240. package/src/engines/utility/types.ts +23 -3
  241. 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 type RuleStrength = 'strict' | 'defeasible' | 'defeater';
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 type ConclusionStatus =
45
- | 'definitely' // Proven by strict rule (cannot be defeated)
46
- | 'defeasibly' // Proven by defeasible rule (not currently defeated)
47
- | 'defeated' // Was concluded but defeated by another rule
48
- | 'blocked' // Blocked by a defeater
49
- | 'undecided'; // No applicable rules
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 type SemanticDefeatStrength =
59
- | 'completely' // 1.0 - fully defeats
60
- | 'strongly' // 0.85
61
- | 'moderately' // 0.6
62
- | 'weakly'; // 0.3
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 type AggregationStrategy =
196
- | 'average' // Mean of supporters vs mean of attackers
197
- | 'max-min' // Strongest support vs strongest attack
198
- | 'weighted-sum' // Sum with normalization to 0-1
199
- | 'probabilistic'; // Independent probability combination
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?: 'specificity' | 'priority' | 'explicit';
249
+ conflictResolution?: ConflictResolution;
226
250
 
227
251
  /**
228
252
  * Maximum iterations for computing defeat cycles (default: 100)
@@ -22,6 +22,12 @@ export type {
22
22
  EnsembleSummaryOptions
23
23
  } from './types';
24
24
 
25
+ // Constants
26
+ export {
27
+ FusionStrategies,
28
+ AgreementLevels
29
+ } from './types';
30
+
25
31
  // Compiler
26
32
  export { compileEnsembleRuleSet } from './compiler';
27
33
 
@@ -18,13 +18,15 @@ import type { FactInput } from '../../core';
18
18
  // Fusion Strategy
19
19
  // ========================================
20
20
 
21
- export type FusionStrategy =
22
- | 'weighted-average' // weighted mean of member scores
23
- | 'median' // middle value, robust to outliers
24
- | 'min' // most conservative member score
25
- | 'max' // most optimistic member score
26
- | 'voting' // highest confidence*weight member wins
27
- | 'stacking'; // second-layer scoring ruleset over member scores
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 type AgreementLevel =
127
- | 'unanimous' // agreement = 1.0 (all identical)
128
- | 'strong' // agreement >= 0.90
129
- | 'moderate' // agreement >= 0.70
130
- | 'weak' // agreement >= 0.50
131
- | 'divided'; // agreement < 0.50
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,6 +32,7 @@
32
32
  // Types
33
33
  export {
34
34
  // Semantic confidence
35
+ SemanticConfidences,
35
36
  SemanticConfidence,
36
37
  SEMANTIC_CONFIDENCES,
37
38
  Confidence,
@@ -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 type SemanticConfidence =
36
- | 'certain' // 1.0 - Absolute certainty (e.g., mathematical facts)
37
- | 'very-high' // 0.95 - Near certain (e.g., well-established patterns)
38
- | 'high' // 0.85 - Strong confidence (e.g., clear evidence)
39
- | 'likely' // 0.75 - Probable (e.g., good indicators)
40
- | 'moderate' // 0.65 - Moderate confidence (e.g., partial evidence)
41
- | 'possible' // 0.50 - Even odds (e.g., uncertain)
42
- | 'unlikely' // 0.35 - Improbable (e.g., weak evidence)
43
- | 'low'; // 0.20 - Low confidence (e.g., minimal support)
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 type MembershipShape = 'triangle' | 'trapezoid' | 'gaussian' | 'shoulder' | 'block';
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 type PositionSemantic = 'very-low' | 'low' | 'medium' | 'high' | 'very-high';
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 type WidthSemantic = 'tight' | 'narrow' | 'medium' | 'wide' | 'very-wide';
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 type EdgeSemantic = 'open-left' | 'open-right' | 'closed';
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 type AggregationMethod =
131
- | 'max' // Maximum membership (standard fuzzy OR)
132
- | 'sum' // Sum memberships (can exceed 1)
133
- | 'probor' // Probabilistic OR: a + b - ab
134
- | 'bound' // Bounded sum: min(1, a + b)
135
- | 'min'; // Intersection (rare)
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 type DefuzzifyMethod =
141
- | 'centroid' // Center of gravity (most common)
142
- | 'bisector' // Line that divides area in half
143
- | 'mom' // Mean of maximum
144
- | 'som' // Smallest of maximum
145
- | 'lom' // Largest of maximum
146
- | 'wtaver'; // Weighted average of term centers
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 type TransformMethod =
152
- | 'clamp' // Enforce output bounds
153
- | 'round' // Round to integer
154
- | 'deadband' // Zero out small values
155
- | 'scale' // Scale to different range
156
- | 'quantize'; // Snap to discrete steps
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 type DefuzzifyPreset =
167
- | 'balanced' // ['max', 'centroid', 'clamp'] - general purpose
168
- | 'smooth' // ['probor', 'centroid', 'clamp'] - gradual transitions
169
- | 'decisive' // ['max', 'mom'] - snappy response
170
- | 'cautious' // ['max', 'som', 'clamp'] - conservative
171
- | 'aggressive' // ['max', 'lom', 'clamp'] - responsive
172
- | 'discrete' // ['max', 'centroid', 'clamp', 'round'] - integer outputs
173
- | 'stable'; // ['max', 'centroid', 'deadband', 'clamp'] - reduce chatter
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
+ }