@higher.archi/boe 1.0.8 → 1.0.10

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 (164) hide show
  1. package/dist/core/errors.d.ts +48 -0
  2. package/dist/core/errors.d.ts.map +1 -0
  3. package/dist/core/errors.js +63 -0
  4. package/dist/core/errors.js.map +1 -0
  5. package/dist/core/index.d.ts +2 -0
  6. package/dist/core/index.d.ts.map +1 -1
  7. package/dist/core/index.js +6 -1
  8. package/dist/core/index.js.map +1 -1
  9. package/dist/core/memory/working-memory.d.ts +2 -2
  10. package/dist/core/memory/working-memory.d.ts.map +1 -1
  11. package/dist/core/memory/working-memory.interface.d.ts +2 -2
  12. package/dist/core/memory/working-memory.interface.d.ts.map +1 -1
  13. package/dist/core/memory/working-memory.js +2 -2
  14. package/dist/core/memory/working-memory.js.map +1 -1
  15. package/dist/core/types/fact.d.ts +4 -4
  16. package/dist/core/types/fact.d.ts.map +1 -1
  17. package/dist/core/types/rule.d.ts +1 -0
  18. package/dist/core/types/rule.d.ts.map +1 -1
  19. package/dist/engines/backward/compiler.js +1 -1
  20. package/dist/engines/backward/compiler.js.map +1 -1
  21. package/dist/engines/backward/engine.d.ts +2 -2
  22. package/dist/engines/backward/engine.d.ts.map +1 -1
  23. package/dist/engines/backward/engine.js.map +1 -1
  24. package/dist/engines/bayesian/compiler.d.ts.map +1 -1
  25. package/dist/engines/bayesian/compiler.js +7 -7
  26. package/dist/engines/bayesian/compiler.js.map +1 -1
  27. package/dist/engines/bayesian/engine.d.ts +2 -2
  28. package/dist/engines/bayesian/engine.d.ts.map +1 -1
  29. package/dist/engines/bayesian/engine.js.map +1 -1
  30. package/dist/engines/bayesian/strategy.d.ts.map +1 -1
  31. package/dist/engines/bayesian/strategy.js +5 -5
  32. package/dist/engines/bayesian/strategy.js.map +1 -1
  33. package/dist/engines/constraint/compiler.d.ts.map +1 -1
  34. package/dist/engines/constraint/compiler.js +10 -10
  35. package/dist/engines/constraint/compiler.js.map +1 -1
  36. package/dist/engines/constraint/engine.d.ts +2 -2
  37. package/dist/engines/constraint/engine.d.ts.map +1 -1
  38. package/dist/engines/constraint/engine.js.map +1 -1
  39. package/dist/engines/defeasible/compiler.js +1 -1
  40. package/dist/engines/defeasible/compiler.js.map +1 -1
  41. package/dist/engines/defeasible/engine.d.ts +2 -2
  42. package/dist/engines/defeasible/engine.d.ts.map +1 -1
  43. package/dist/engines/defeasible/engine.js.map +1 -1
  44. package/dist/engines/expert/compiler.d.ts.map +1 -1
  45. package/dist/engines/expert/compiler.js +6 -6
  46. package/dist/engines/expert/compiler.js.map +1 -1
  47. package/dist/engines/expert/engine.d.ts +2 -2
  48. package/dist/engines/expert/engine.d.ts.map +1 -1
  49. package/dist/engines/expert/engine.js.map +1 -1
  50. package/dist/engines/forward/compiler.d.ts.map +1 -1
  51. package/dist/engines/forward/compiler.js +4 -4
  52. package/dist/engines/forward/compiler.js.map +1 -1
  53. package/dist/engines/forward/engine.d.ts +2 -2
  54. package/dist/engines/forward/engine.d.ts.map +1 -1
  55. package/dist/engines/forward/engine.js.map +1 -1
  56. package/dist/engines/forward/strategy.d.ts.map +1 -1
  57. package/dist/engines/forward/strategy.js +4 -4
  58. package/dist/engines/forward/strategy.js.map +1 -1
  59. package/dist/engines/fuzzy/compiler.js +1 -1
  60. package/dist/engines/fuzzy/compiler.js.map +1 -1
  61. package/dist/engines/fuzzy/engine.d.ts +2 -2
  62. package/dist/engines/fuzzy/engine.d.ts.map +1 -1
  63. package/dist/engines/fuzzy/engine.js.map +1 -1
  64. package/dist/engines/fuzzy/strategy.d.ts.map +1 -1
  65. package/dist/engines/fuzzy/strategy.js +8 -8
  66. package/dist/engines/fuzzy/strategy.js.map +1 -1
  67. package/dist/engines/monte-carlo/compiler.d.ts.map +1 -1
  68. package/dist/engines/monte-carlo/compiler.js +8 -8
  69. package/dist/engines/monte-carlo/compiler.js.map +1 -1
  70. package/dist/engines/monte-carlo/engine.d.ts +2 -2
  71. package/dist/engines/monte-carlo/engine.d.ts.map +1 -1
  72. package/dist/engines/monte-carlo/engine.js.map +1 -1
  73. package/dist/engines/monte-carlo/strategy.js +1 -1
  74. package/dist/engines/monte-carlo/strategy.js.map +1 -1
  75. package/dist/engines/pricing/compiler.d.ts.map +1 -1
  76. package/dist/engines/pricing/compiler.js +9 -9
  77. package/dist/engines/pricing/compiler.js.map +1 -1
  78. package/dist/engines/pricing/strategy.js +7 -6
  79. package/dist/engines/pricing/strategy.js.map +1 -1
  80. package/dist/engines/pricing/types.d.ts +2 -0
  81. package/dist/engines/pricing/types.d.ts.map +1 -1
  82. package/dist/engines/scoring/compiler.d.ts.map +1 -1
  83. package/dist/engines/scoring/compiler.js +8 -6
  84. package/dist/engines/scoring/compiler.js.map +1 -1
  85. package/dist/engines/scoring/engine.d.ts +2 -2
  86. package/dist/engines/scoring/engine.d.ts.map +1 -1
  87. package/dist/engines/scoring/engine.js.map +1 -1
  88. package/dist/engines/scoring/index.d.ts +1 -1
  89. package/dist/engines/scoring/index.d.ts.map +1 -1
  90. package/dist/engines/scoring/index.js +4 -1
  91. package/dist/engines/scoring/index.js.map +1 -1
  92. package/dist/engines/scoring/strategy.d.ts.map +1 -1
  93. package/dist/engines/scoring/strategy.js +28 -2
  94. package/dist/engines/scoring/strategy.js.map +1 -1
  95. package/dist/engines/scoring/types.d.ts +43 -4
  96. package/dist/engines/scoring/types.d.ts.map +1 -1
  97. package/dist/engines/scoring/types.js +19 -0
  98. package/dist/engines/scoring/types.js.map +1 -1
  99. package/dist/engines/sequential/compiler.d.ts.map +1 -1
  100. package/dist/engines/sequential/compiler.js +4 -4
  101. package/dist/engines/sequential/compiler.js.map +1 -1
  102. package/dist/engines/sequential/engine.d.ts +2 -2
  103. package/dist/engines/sequential/engine.d.ts.map +1 -1
  104. package/dist/engines/sequential/engine.js.map +1 -1
  105. package/dist/engines/sequential/strategy.js +1 -1
  106. package/dist/engines/sequential/strategy.js.map +1 -1
  107. package/dist/engines/state-machine/compiler.js +4 -4
  108. package/dist/engines/state-machine/compiler.js.map +1 -1
  109. package/dist/engines/state-machine/strategy.d.ts.map +1 -1
  110. package/dist/engines/state-machine/strategy.js +2 -1
  111. package/dist/engines/state-machine/strategy.js.map +1 -1
  112. package/dist/engines/utility/compiler.d.ts.map +1 -1
  113. package/dist/engines/utility/compiler.js +7 -7
  114. package/dist/engines/utility/compiler.js.map +1 -1
  115. package/dist/engines/utility/engine.d.ts +2 -2
  116. package/dist/engines/utility/engine.d.ts.map +1 -1
  117. package/dist/engines/utility/engine.js.map +1 -1
  118. package/dist/index.d.ts +4 -2
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +8 -2
  121. package/dist/index.js.map +1 -1
  122. package/package.json +1 -1
  123. package/src/core/errors.ts +69 -0
  124. package/src/core/index.ts +4 -0
  125. package/src/core/memory/working-memory.interface.ts +2 -2
  126. package/src/core/memory/working-memory.ts +12 -12
  127. package/src/core/types/fact.ts +4 -4
  128. package/src/core/types/rule.ts +1 -0
  129. package/src/engines/backward/compiler.ts +1 -1
  130. package/src/engines/backward/engine.ts +2 -2
  131. package/src/engines/bayesian/compiler.ts +9 -8
  132. package/src/engines/bayesian/engine.ts +2 -2
  133. package/src/engines/bayesian/strategy.ts +7 -6
  134. package/src/engines/constraint/compiler.ts +12 -11
  135. package/src/engines/constraint/engine.ts +2 -2
  136. package/src/engines/defeasible/compiler.ts +1 -1
  137. package/src/engines/defeasible/engine.ts +2 -2
  138. package/src/engines/expert/compiler.ts +8 -7
  139. package/src/engines/expert/engine.ts +2 -2
  140. package/src/engines/forward/compiler.ts +6 -5
  141. package/src/engines/forward/engine.ts +2 -2
  142. package/src/engines/forward/strategy.ts +6 -5
  143. package/src/engines/fuzzy/compiler.ts +1 -1
  144. package/src/engines/fuzzy/engine.ts +2 -2
  145. package/src/engines/fuzzy/strategy.ts +9 -9
  146. package/src/engines/monte-carlo/compiler.ts +10 -9
  147. package/src/engines/monte-carlo/engine.ts +2 -2
  148. package/src/engines/monte-carlo/strategy.ts +2 -2
  149. package/src/engines/pricing/compiler.ts +11 -10
  150. package/src/engines/pricing/strategy.ts +7 -7
  151. package/src/engines/pricing/types.ts +2 -0
  152. package/src/engines/scoring/compiler.ts +10 -7
  153. package/src/engines/scoring/engine.ts +2 -2
  154. package/src/engines/scoring/index.ts +5 -1
  155. package/src/engines/scoring/strategy.ts +36 -3
  156. package/src/engines/scoring/types.ts +49 -4
  157. package/src/engines/sequential/compiler.ts +6 -5
  158. package/src/engines/sequential/engine.ts +2 -2
  159. package/src/engines/sequential/strategy.ts +2 -2
  160. package/src/engines/state-machine/compiler.ts +5 -5
  161. package/src/engines/state-machine/strategy.ts +3 -1
  162. package/src/engines/utility/compiler.ts +9 -8
  163. package/src/engines/utility/engine.ts +2 -2
  164. package/src/index.ts +9 -1
@@ -5,7 +5,7 @@
5
5
  * Self-contained within the fuzzy engine.
6
6
  */
7
7
 
8
- import { IWorkingMemory, BindingContext, evaluateCondition, matchInputsOnly } from '../../core';
8
+ import { IWorkingMemory, BindingContext, evaluateCondition, matchInputsOnly, RuntimeError } from '../../core';
9
9
  import { CompiledFuzzyRuleSet, CompiledFuzzyRule } from './compiler';
10
10
  import {
11
11
  FuzzyOptions,
@@ -54,7 +54,7 @@ export function evaluateFuzzyIs(
54
54
  const variable = ctx.variables.get(varName);
55
55
 
56
56
  if (!variable) {
57
- throw new Error(`Fuzzy variable '${varName}' not found`);
57
+ throw new RuntimeError(`Fuzzy variable '${varName}' not found`);
58
58
  }
59
59
 
60
60
  return getMembershipDegree(value, variable, termName);
@@ -86,7 +86,7 @@ export class FuzzyStrategy {
86
86
 
87
87
  // Ensure ruleset is fuzzy mode
88
88
  if (ruleSet.mode !== 'fuzzy') {
89
- throw new Error(`FuzzyStrategy requires mode='fuzzy', got '${ruleSet.mode}'`);
89
+ throw new RuntimeError(`FuzzyStrategy requires mode='fuzzy', got '${ruleSet.mode}'`);
90
90
  }
91
91
 
92
92
  const { onFire } = options;
@@ -171,7 +171,7 @@ export class FuzzyStrategy {
171
171
  for (const [varName, termMemberships] of Object.entries(aggregated)) {
172
172
  const variable = ruleSet.variableIndex.get(varName);
173
173
  if (!variable) {
174
- throw new Error(`Output variable '${varName}' not found`);
174
+ throw new RuntimeError(`Output variable '${varName}' not found`);
175
175
  }
176
176
 
177
177
  const crispValue = executePipeline(defuzzifyConfig, {
@@ -262,7 +262,7 @@ export class FuzzyStrategy {
262
262
  const variable = variables.get(varName);
263
263
 
264
264
  if (!variable) {
265
- throw new Error(`Fuzzy variable '${varName}' not found`);
265
+ throw new RuntimeError(`Fuzzy variable '${varName}' not found`);
266
266
  }
267
267
 
268
268
  const degree = getMembershipDegree(value, variable, termName);
@@ -284,7 +284,7 @@ export class FuzzyStrategy {
284
284
  const variable = variables.get(varName);
285
285
 
286
286
  if (!variable) {
287
- throw new Error(`Fuzzy variable '${varName}' not found`);
287
+ throw new RuntimeError(`Fuzzy variable '${varName}' not found`);
288
288
  }
289
289
 
290
290
  const degree = 1 - getMembershipDegree(value, variable, termName);
@@ -350,7 +350,7 @@ export class FuzzyStrategy {
350
350
  if (operand.type === 'property') {
351
351
  const fact = bindings[operand.ref.param];
352
352
  if (!fact) {
353
- throw new Error(`Fact '${operand.ref.param}' not found in bindings`);
353
+ throw new RuntimeError(`Fact '${operand.ref.param}' not found in bindings`);
354
354
  }
355
355
 
356
356
  let value = fact.data;
@@ -359,13 +359,13 @@ export class FuzzyStrategy {
359
359
  }
360
360
 
361
361
  if (typeof value !== 'number') {
362
- throw new Error(`Fuzzy membership requires numeric value, got ${typeof value}`);
362
+ throw new RuntimeError(`Fuzzy membership requires numeric value, got ${typeof value}`);
363
363
  }
364
364
 
365
365
  return value;
366
366
  }
367
367
 
368
- throw new Error(`Unknown operand type: ${operand.type}`);
368
+ throw new RuntimeError(`Unknown operand type: ${operand.type}`);
369
369
  }
370
370
  }
371
371
 
@@ -8,7 +8,8 @@
8
8
  import {
9
9
  compileCondition,
10
10
  normalizeShape,
11
- Expression
11
+ Expression,
12
+ CompilationError
12
13
  } from '../../core';
13
14
 
14
15
  import {
@@ -104,7 +105,7 @@ function getDistributionType(estimate: string): 'triangular' | 'normal' | 'unifo
104
105
  return 'discrete';
105
106
 
106
107
  default:
107
- throw new Error(`Unknown estimate type: ${estimate}`);
108
+ throw new CompilationError(`Unknown estimate type: ${estimate}`);
108
109
  }
109
110
  }
110
111
 
@@ -134,7 +135,7 @@ function compileTriangularVariable(variable: MonteCarloVariable): CompiledVariab
134
135
  mode = parsePercentOrNumber(v.realistic);
135
136
  max = parsePercentOrNumber(v.pessimistic);
136
137
  } else {
137
- throw new Error(`Invalid triangular variable: ${variable.name}`);
138
+ throw new CompilationError(`Invalid triangular variable: ${variable.name}`);
138
139
  }
139
140
 
140
141
  return {
@@ -155,10 +156,10 @@ function compileNormalVariable(variable: MonteCarloVariable): CompiledVariable {
155
156
  const stdDev = parsePercentOrNumber(v.stdDev ?? v.spread ?? v.varies);
156
157
 
157
158
  if (mean === undefined || isNaN(mean)) {
158
- throw new Error(`Normal variable '${variable.name}' requires mean or value`);
159
+ throw new CompilationError(`Normal variable '${variable.name}' requires mean or value`);
159
160
  }
160
161
  if (stdDev === undefined || isNaN(stdDev)) {
161
- throw new Error(`Normal variable '${variable.name}' requires stdDev, spread, or varies`);
162
+ throw new CompilationError(`Normal variable '${variable.name}' requires stdDev, spread, or varies`);
162
163
  }
163
164
 
164
165
  return {
@@ -179,10 +180,10 @@ function compileUniformVariable(variable: MonteCarloVariable): CompiledVariable
179
180
  const max = parsePercentOrNumber(v.max ?? v.high);
180
181
 
181
182
  if (min === undefined || isNaN(min)) {
182
- throw new Error(`Uniform variable '${variable.name}' requires min or low`);
183
+ throw new CompilationError(`Uniform variable '${variable.name}' requires min or low`);
183
184
  }
184
185
  if (max === undefined || isNaN(max)) {
185
- throw new Error(`Uniform variable '${variable.name}' requires max or high`);
186
+ throw new CompilationError(`Uniform variable '${variable.name}' requires max or high`);
186
187
  }
187
188
 
188
189
  return {
@@ -199,7 +200,7 @@ function compileDiscreteVariable(variable: MonteCarloVariable): CompiledVariable
199
200
  const v = variable as any;
200
201
 
201
202
  if (!v.options || !Array.isArray(v.options)) {
202
- throw new Error(`Discrete variable '${variable.name}' requires options array`);
203
+ throw new CompilationError(`Discrete variable '${variable.name}' requires options array`);
203
204
  }
204
205
 
205
206
  // Default to equal weights if not specified
@@ -235,7 +236,7 @@ function compileVariable(variable: MonteCarloVariable): CompiledVariable {
235
236
  case 'discrete':
236
237
  return compileDiscreteVariable(variable);
237
238
  default:
238
- throw new Error(`Unknown distribution: ${distribution}`);
239
+ throw new CompilationError(`Unknown distribution: ${distribution}`);
239
240
  }
240
241
  }
241
242
 
@@ -55,7 +55,7 @@ export class MonteCarloEngine implements IRuleEngine<CompiledMonteCarloRuleSet,
55
55
  // IWorkingMemory Implementation
56
56
  // ========================================
57
57
 
58
- add(input: FactInput): Fact {
58
+ add<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
59
59
  return this.wm.add(input);
60
60
  }
61
61
 
@@ -63,7 +63,7 @@ export class MonteCarloEngine implements IRuleEngine<CompiledMonteCarloRuleSet,
63
63
  return this.wm.remove(factId);
64
64
  }
65
65
 
66
- update(input: FactInput): Fact {
66
+ update<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
67
67
  return this.wm.update(input);
68
68
  }
69
69
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { IWorkingMemory } from '../../core/memory';
16
16
  import { Fact } from '../../core/types/fact';
17
- import { compileCondition, evaluateCondition, Condition } from '../../core';
17
+ import { compileCondition, evaluateCondition, Condition, RuntimeError } from '../../core';
18
18
 
19
19
  import {
20
20
  CompiledMonteCarloRuleSet,
@@ -137,7 +137,7 @@ function sampleVariable(
137
137
  case 'discrete':
138
138
  return sampleDiscrete(params.options!, params.weights!, random);
139
139
  default:
140
- throw new Error(`Unknown distribution: ${distribution}`);
140
+ throw new RuntimeError(`Unknown distribution: ${distribution}`);
141
141
  }
142
142
  }
143
143
 
@@ -9,7 +9,8 @@
9
9
 
10
10
  import {
11
11
  compileCondition,
12
- compileOperand
12
+ compileOperand,
13
+ CompilationError
13
14
  } from '../../core';
14
15
 
15
16
  import {
@@ -61,7 +62,7 @@ function validateScenarioValues(
61
62
  for (const [field, schema] of Object.entries(scenarioSchema)) {
62
63
  if (schema.required && values[field] === undefined && schema.default === undefined) {
63
64
  if (strict) {
64
- throw new Error(
65
+ throw new CompilationError(
65
66
  `Component '${component.sku}' missing required scenario value '${field}' for scenario '${scenarioId}'`
66
67
  );
67
68
  }
@@ -72,7 +73,7 @@ function validateScenarioValues(
72
73
  for (const [field, value] of Object.entries(values)) {
73
74
  const schema = scenarioSchema[field];
74
75
  if (!schema) {
75
- throw new Error(
76
+ throw new CompilationError(
76
77
  `Component '${component.sku}' has unknown scenario field '${field}' for scenario '${scenarioId}'`
77
78
  );
78
79
  }
@@ -94,19 +95,19 @@ function validateValue(
94
95
 
95
96
  const actualType = Array.isArray(value) ? 'array' : typeof value;
96
97
  if (actualType !== schema.type) {
97
- throw new Error(
98
+ throw new CompilationError(
98
99
  `Invalid type for '${path}': expected ${schema.type}, got ${actualType}`
99
100
  );
100
101
  }
101
102
 
102
103
  if (schema.type === 'number' && typeof value === 'number') {
103
104
  if (schema.min !== undefined && value < schema.min) {
104
- throw new Error(
105
+ throw new CompilationError(
105
106
  `Value for '${path}' is below minimum: ${value} < ${schema.min}`
106
107
  );
107
108
  }
108
109
  if (schema.max !== undefined && value > schema.max) {
109
- throw new Error(
110
+ throw new CompilationError(
110
111
  `Value for '${path}' exceeds maximum: ${value} > ${schema.max}`
111
112
  );
112
113
  }
@@ -114,7 +115,7 @@ function validateValue(
114
115
 
115
116
  if (schema.type === 'string' && schema.enum && typeof value === 'string') {
116
117
  if (!schema.enum.includes(value)) {
117
- throw new Error(
118
+ throw new CompilationError(
118
119
  `Invalid value for '${path}': '${value}' not in [${schema.enum.join(', ')}]`
119
120
  );
120
121
  }
@@ -161,7 +162,7 @@ function compilePricingMode(pricing: PricingMode): CompiledPricingMode {
161
162
  // Validate tiers are in order
162
163
  for (let i = 1; i < pricing.tiers.length; i++) {
163
164
  if (pricing.tiers[i].upTo <= pricing.tiers[i - 1].upTo) {
164
- throw new Error(
165
+ throw new CompilationError(
165
166
  `Graduated tiers must be in ascending order by 'upTo' value`
166
167
  );
167
168
  }
@@ -175,7 +176,7 @@ function compilePricingMode(pricing: PricingMode): CompiledPricingMode {
175
176
  // Validate brackets are in order
176
177
  for (let i = 1; i < pricing.brackets.length; i++) {
177
178
  if (pricing.brackets[i].minVolume <= pricing.brackets[i - 1].minVolume) {
178
- throw new Error(
179
+ throw new CompilationError(
179
180
  `Volume brackets must be in ascending order by 'minVolume' value`
180
181
  );
181
182
  }
@@ -362,7 +363,7 @@ export function compilePricingRuleSet(ruleSet: PricingRuleSet): CompiledPricingR
362
363
  currencyInfo,
363
364
  precision
364
365
  },
365
- rules: ruleSet.rules.map(rule =>
366
+ rules: ruleSet.rules.filter(r => !r.disabled).map(rule =>
366
367
  compileRule(rule, productTypes, scenarioIds, strict)
367
368
  )
368
369
  };
@@ -8,7 +8,7 @@
8
8
  * - Computes metrics for comparison
9
9
  */
10
10
 
11
- import { CompiledOperand, CompiledCondition } from '../../core';
11
+ import { CompiledOperand, CompiledCondition, RuntimeError } from '../../core';
12
12
 
13
13
  import {
14
14
  CompiledPricingRuleSet,
@@ -218,7 +218,7 @@ function executePricingMode(
218
218
  }
219
219
 
220
220
  case 'multi-meter':
221
- throw new Error('Multi-meter should be handled by executeMeter');
221
+ throw new RuntimeError('Multi-meter should be handled by executeMeter');
222
222
  }
223
223
  }
224
224
 
@@ -564,7 +564,7 @@ export class PricingStrategy {
564
564
  // Find the rule
565
565
  const rule = ruleSet.rules.find(r => r.id === ruleId);
566
566
  if (!rule) {
567
- throw new Error(`Rule not found: ${ruleId}`);
567
+ throw new RuntimeError(`Rule not found: ${ruleId}`);
568
568
  }
569
569
 
570
570
  // Determine scenario
@@ -573,12 +573,12 @@ export class PricingStrategy {
573
573
  || Object.keys(ruleSet.scenarios.definitions)[0];
574
574
 
575
575
  if (!scenarioId) {
576
- throw new Error('No scenario specified and no default scenario defined');
576
+ throw new RuntimeError('No scenario specified and no default scenario defined');
577
577
  }
578
578
 
579
579
  const scenarioDefinition = ruleSet.scenarios.definitions[scenarioId];
580
580
  if (!scenarioDefinition && Object.keys(ruleSet.scenarios.definitions).length > 0) {
581
- throw new Error(`Unknown scenario: ${scenarioId}`);
581
+ throw new RuntimeError(`Unknown scenario: ${scenarioId}`);
582
582
  }
583
583
 
584
584
  // Execute components
@@ -678,7 +678,7 @@ export class PricingStrategy {
678
678
  || Object.keys(ruleSet.scenarios.definitions);
679
679
 
680
680
  if (scenarios.length === 0) {
681
- throw new Error('No scenarios to calculate');
681
+ throw new RuntimeError('No scenarios to calculate');
682
682
  }
683
683
 
684
684
  // Calculate each scenario
@@ -687,7 +687,7 @@ export class PricingStrategy {
687
687
  for (const scenarioId of scenarios) {
688
688
  const scenarioDefinition = ruleSet.scenarios.definitions[scenarioId];
689
689
  if (!scenarioDefinition) {
690
- throw new Error(`Unknown scenario: ${scenarioId}`);
690
+ throw new RuntimeError(`Unknown scenario: ${scenarioId}`);
691
691
  }
692
692
 
693
693
  const pricingResult = this.calculate(ruleSet, ruleId, { scenario: scenarioId });
@@ -430,6 +430,8 @@ export type PricingRule = {
430
430
  name?: string;
431
431
  /** Description */
432
432
  description?: string;
433
+ /** Skip this rule during compilation (default: false) */
434
+ disabled?: boolean;
433
435
  /** Input bindings (for potential future filtering) */
434
436
  inputs?: PricingInput[];
435
437
  /** The pricing package */
@@ -5,7 +5,8 @@
5
5
  import {
6
6
  compileCondition,
7
7
  normalizeShape,
8
- Condition
8
+ Condition,
9
+ CompilationError
9
10
  } from '../../core';
10
11
 
11
12
  import {
@@ -149,6 +150,7 @@ function compileOverride(override: OverrideDefinition): CompiledOverrideDefiniti
149
150
  // ========================================
150
151
 
151
152
  export function compileScoringRuleSet(ruleSet: ScoringRuleSet): CompiledScoringRuleSet {
153
+ const activeRules = ruleSet.rules.filter(r => !r.disabled);
152
154
  const categories = ruleSet.config?.categories;
153
155
 
154
156
  // Validate categories if defined
@@ -156,14 +158,14 @@ export function compileScoringRuleSet(ruleSet: ScoringRuleSet): CompiledScoringR
156
158
  const categoryIds = new Set(categories.map(c => c.id));
157
159
 
158
160
  // Every rule must reference a valid category
159
- for (const rule of ruleSet.rules) {
161
+ for (const rule of activeRules) {
160
162
  if (!rule.category) {
161
- throw new Error(
163
+ throw new CompilationError(
162
164
  `Rule "${rule.id}" is missing a category. When categories are defined, every rule must have a category.`
163
165
  );
164
166
  }
165
167
  if (!categoryIds.has(rule.category)) {
166
- throw new Error(
168
+ throw new CompilationError(
167
169
  `Rule "${rule.id}" references unknown category "${rule.category}". Valid categories: ${[...categoryIds].join(', ')}`
168
170
  );
169
171
  }
@@ -189,12 +191,12 @@ export function compileScoringRuleSet(ruleSet: ScoringRuleSet): CompiledScoringR
189
191
  // Validate tier effects reference valid tiers
190
192
  if (override.effect === 'force_tier' || override.effect === 'floor_tier' || override.effect === 'cap_tier') {
191
193
  if (!tierIds.size) {
192
- throw new Error(
194
+ throw new CompilationError(
193
195
  `Override "${override.id}" uses effect "${override.effect}" but no tiers are defined.`
194
196
  );
195
197
  }
196
198
  if (!tierIds.has(override.targetTier)) {
197
- throw new Error(
199
+ throw new CompilationError(
198
200
  `Override "${override.id}" references unknown tier "${override.targetTier}". Valid tiers: ${[...tierIds].join(', ')}`
199
201
  );
200
202
  }
@@ -214,6 +216,7 @@ export function compileScoringRuleSet(ruleSet: ScoringRuleSet): CompiledScoringR
214
216
  categories,
215
217
  overrides,
216
218
  nullHandling: ruleSet.config?.nullHandling,
219
+ confidenceConfig: ruleSet.config?.confidenceConfig,
217
220
  decay: ruleSet.config?.decay
218
221
  };
219
222
 
@@ -222,7 +225,7 @@ export function compileScoringRuleSet(ruleSet: ScoringRuleSet): CompiledScoringR
222
225
  name: ruleSet.name,
223
226
  mode: 'scoring',
224
227
  schema: ruleSet.shape ? normalizeShape(ruleSet.shape) : { type: 'object' },
225
- rules: ruleSet.rules.map((rule, index) => compileScoringRule(rule, index)),
228
+ rules: activeRules.map((rule, index) => compileScoringRule(rule, index)),
226
229
  config,
227
230
  overrides: compiledOverrides
228
231
  };
@@ -49,7 +49,7 @@ export class ScoringEngine implements IRuleEngine<CompiledScoringRuleSet, Scorin
49
49
  // IWorkingMemory Implementation
50
50
  // ========================================
51
51
 
52
- add(input: FactInput): Fact {
52
+ add<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
53
53
  return this.wm.add(input);
54
54
  }
55
55
 
@@ -57,7 +57,7 @@ export class ScoringEngine implements IRuleEngine<CompiledScoringRuleSet, Scorin
57
57
  return this.wm.remove(factId);
58
58
  }
59
59
 
60
- update(input: FactInput): Fact {
60
+ update<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
61
61
  return this.wm.update(input);
62
62
  }
63
63
 
@@ -40,11 +40,15 @@ export {
40
40
  CompiledScoringAction,
41
41
  CompiledScoringRule,
42
42
  CompiledScoringRuleSet,
43
+ // Confidence types
44
+ ConfidenceConfig,
45
+ ConfidenceBreakdown,
43
46
  // Runtime types
44
47
  ScoreFunction,
45
48
  ScoreFunctionRegistry,
46
49
  ScoringOptions,
47
- ScoringResult
50
+ ScoringResult,
51
+ createScoreFunction
48
52
  } from './types';
49
53
 
50
54
  // Compiler
@@ -7,7 +7,8 @@
7
7
 
8
8
  import {
9
9
  match,
10
- evaluateCondition
10
+ evaluateCondition,
11
+ RuntimeError
11
12
  } from '../../core';
12
13
  import { IWorkingMemory } from '../../core/memory';
13
14
  import {
@@ -25,6 +26,7 @@ import {
25
26
  ScoringTierMatch,
26
27
  OverrideResult,
27
28
  CategoryResult,
29
+ ConfidenceBreakdown,
28
30
  TierDefinition,
29
31
  OutputBounds
30
32
  } from './types';
@@ -74,14 +76,14 @@ export class ScoringStrategy {
74
76
  } else if (typeof ruleAction.score === 'string') {
75
77
  const scoreFunc = scoreFunctions[ruleAction.score];
76
78
  if (!scoreFunc) {
77
- throw new Error(`Score function not found: ${ruleAction.score}`);
79
+ throw new RuntimeError(`Score function not found: ${ruleAction.score}`);
78
80
  }
79
81
  baseScore = scoreFunc(activation.facts);
80
82
  } else if (typeof ruleAction.score === 'object' && 'op' in ruleAction.score) {
81
83
  const result = evaluateCondition(ruleAction.score as any, activation.facts);
82
84
  baseScore = typeof result === 'number' ? result : 0;
83
85
  } else {
84
- throw new Error(`Invalid score type: ${typeof ruleAction.score}`);
86
+ throw new RuntimeError(`Invalid score type: ${typeof ruleAction.score}`);
85
87
  }
86
88
 
87
89
  // Apply temporal decay if configured
@@ -256,6 +258,35 @@ export class ScoringStrategy {
256
258
  confidence = allWeight > 0 ? firedWeight / allWeight : 0;
257
259
  }
258
260
 
261
+ // Phase 2.8: Composite confidence (when confidenceConfig provided)
262
+ let confidenceBreakdown: ConfidenceBreakdown | undefined;
263
+
264
+ if (config.confidenceConfig) {
265
+ const scW = config.confidenceConfig.signalCoverageWeight ?? 1.0;
266
+ const dfW = config.confidenceConfig.dataFreshnessWeight ?? 0;
267
+ const totalW = scW + dfW;
268
+ const signalCoverage = confidence; // from Phase 2.75
269
+
270
+ // dataFreshness = average decay multiplier across fired rules (1.0 if no decay)
271
+ let dataFreshness = 1.0;
272
+ if (fired.length > 0 && hasDecay) {
273
+ let sum = 0;
274
+ for (const ruleId of fired) {
275
+ sum += (decayMultipliers[ruleId] ?? 1.0);
276
+ }
277
+ dataFreshness = sum / fired.length;
278
+ }
279
+
280
+ if (totalW > 0) {
281
+ confidence = (signalCoverage * scW + dataFreshness * dfW) / totalW;
282
+ }
283
+
284
+ confidenceBreakdown = {
285
+ signalCoverage, dataFreshness,
286
+ weights: { signalCoverage: scW, dataFreshness: dfW }
287
+ };
288
+ }
289
+
259
290
  // Phase 3: Apply global strategies
260
291
  let categoryBreakdown: CategoryResult[] | undefined;
261
292
 
@@ -501,6 +532,7 @@ export class ScoringStrategy {
501
532
  applied: true
502
533
  },
503
534
  categoryBreakdown,
535
+ confidenceBreakdown,
504
536
  executionTimeMs
505
537
  };
506
538
  }
@@ -569,6 +601,7 @@ export class ScoringStrategy {
569
601
  tier,
570
602
  categoryBreakdown,
571
603
  override: overrideResult,
604
+ confidenceBreakdown,
572
605
  ...(hasDecay ? {
573
606
  totalScoreBeforeDecay,
574
607
  contributionsBeforeDecay,
@@ -293,6 +293,26 @@ export type ScoringRuleSet = BaseRuleSet & {
293
293
  config?: Partial<ScoringConfig>;
294
294
  };
295
295
 
296
+ /**
297
+ * Composite confidence configuration
298
+ *
299
+ * Controls how confidence is computed from multiple components.
300
+ * Without this config, confidence = signal coverage (existing behavior).
301
+ */
302
+ export type ConfidenceConfig = {
303
+ signalCoverageWeight?: number; // default: 1.0
304
+ dataFreshnessWeight?: number; // default: 0 (existing behavior)
305
+ };
306
+
307
+ /**
308
+ * Breakdown of composite confidence components
309
+ */
310
+ export type ConfidenceBreakdown = {
311
+ signalCoverage: number;
312
+ dataFreshness: number;
313
+ weights: { signalCoverage: number; dataFreshness: number };
314
+ };
315
+
296
316
  /**
297
317
  * Scoring configuration
298
318
  */
@@ -306,6 +326,7 @@ export type ScoringConfig = {
306
326
  categories?: ScoringCategory[]; // Optional category grouping for two-level weight hierarchy
307
327
  overrides?: OverrideDefinition[]; // Optional post-scoring overrides evaluated in order (first match wins)
308
328
  nullHandling?: NullHandling; // Default null handling for all rules (default: 'exclude')
329
+ confidenceConfig?: ConfidenceConfig; // Optional composite confidence configuration
309
330
  decay?: {
310
331
  timestamp?: string | Expression; // default timestamp path for all rules
311
332
  config: DecayConfig; // engine-level decay config
@@ -370,18 +391,41 @@ export type CompiledScoringRuleSet = CompiledBaseRuleSet & {
370
391
  /**
371
392
  * Score function for dynamic scoring
372
393
  */
373
- export type ScoreFunction = (context: BindingContext) => number;
394
+ export type ScoreFunction<TContext extends BindingContext = BindingContext> =
395
+ (context: TContext) => number;
374
396
 
375
397
  /**
376
398
  * Registry of score functions
377
399
  */
378
- export type ScoreFunctionRegistry = Record<string, ScoreFunction>;
400
+ export type ScoreFunctionRegistry<TContext extends BindingContext = BindingContext> =
401
+ Record<string, ScoreFunction<TContext>>;
402
+
403
+ /**
404
+ * Create a typed score function with autocomplete on context keys.
405
+ *
406
+ * Due to function contravariance, a narrowly-typed `(NarrowCtx) => number` isn't
407
+ * directly assignable to `(BindingContext) => number`. This helper handles the cast
408
+ * safely — rule inputs guarantee the correct facts at runtime.
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * type MyContext = { applicant: Fact<{ income: number; creditScore: number }> };
413
+ * const myFn = createScoreFunction<MyContext>((ctx) => {
414
+ * return ctx.applicant.data.creditScore / 850 * 100; // fully typed!
415
+ * });
416
+ * ```
417
+ */
418
+ export function createScoreFunction<TContext extends BindingContext = BindingContext>(
419
+ fn: (context: TContext) => number
420
+ ): ScoreFunction {
421
+ return fn as ScoreFunction;
422
+ }
379
423
 
380
424
  /**
381
425
  * Runtime options for scoring execution
382
426
  */
383
- export type ScoringOptions = {
384
- scoreFunctions?: ScoreFunctionRegistry; // Optional dynamic score functions
427
+ export type ScoringOptions<TContext extends BindingContext = BindingContext> = {
428
+ scoreFunctions?: ScoreFunctionRegistry<TContext>; // Optional dynamic score functions
385
429
  onFire?: (ruleId: string, context: BindingContext, score: number) => void;
386
430
  };
387
431
 
@@ -398,6 +442,7 @@ export type ScoringResult = {
398
442
  tier?: ScoringTierMatch; // Matched tier (if tiers configured)
399
443
  categoryBreakdown?: CategoryResult[]; // Per-category breakdown (if categories configured)
400
444
  override?: OverrideResult; // Override that fired (if any)
445
+ confidenceBreakdown?: ConfidenceBreakdown; // composite confidence components (if confidenceConfig set)
401
446
  totalScoreBeforeDecay?: number; // undecayed total for comparison
402
447
  contributionsBeforeDecay?: Record<string, number>; // undecayed per-rule contributions
403
448
  decayInfo?: Record<string, DecayInfo>; // per-rule decay audit
@@ -8,7 +8,8 @@ import {
8
8
  parsePath,
9
9
  normalizeShape,
10
10
  Condition,
11
- PropertyRef
11
+ PropertyRef,
12
+ CompilationError
12
13
  } from '../../core';
13
14
 
14
15
  import {
@@ -77,7 +78,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
77
78
 
78
79
  for (const [targetPath, value] of Object.entries(item.set)) {
79
80
  if (!isPropertyRef(targetPath)) {
80
- throw new Error(`Set target must be a property reference, got: ${targetPath}`);
81
+ throw new CompilationError(`Set target must be a property reference, got: ${targetPath}`);
81
82
  }
82
83
 
83
84
  if (isExpression(value)) {
@@ -101,7 +102,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
101
102
  if ('retract' in item) {
102
103
  const ref = item.retract;
103
104
  if (!isPropertyRef(ref)) {
104
- throw new Error(`Retract must be a property reference, got: ${ref}`);
105
+ throw new CompilationError(`Retract must be a property reference, got: ${ref}`);
105
106
  }
106
107
  const paramName = ref.slice(1).split('.')[0];
107
108
  return { retract: paramName };
@@ -117,7 +118,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
117
118
  return { halt: true };
118
119
  }
119
120
 
120
- throw new Error(`Unknown action item type: ${JSON.stringify(item)}`);
121
+ throw new CompilationError(`Unknown action item type: ${JSON.stringify(item)}`);
121
122
  }
122
123
 
123
124
  function normalizeThenValue(value: ThenValue): ActionItem[] {
@@ -195,7 +196,7 @@ export function compileSequentialRuleSet(ruleSet: SequentialRuleSet): CompiledSe
195
196
  name: ruleSet.name,
196
197
  mode: 'sequential',
197
198
  schema: ruleSet.shape ? normalizeShape(ruleSet.shape) : { type: 'object' },
198
- rules: ruleSet.rules.map((rule, index) => compileSequentialRule(rule, index)),
199
+ rules: ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileSequentialRule(rule, index)),
199
200
  config
200
201
  };
201
202
  }