@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.
- package/dist/core/errors.d.ts +48 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +63 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/memory/working-memory.d.ts +2 -2
- package/dist/core/memory/working-memory.d.ts.map +1 -1
- package/dist/core/memory/working-memory.interface.d.ts +2 -2
- package/dist/core/memory/working-memory.interface.d.ts.map +1 -1
- package/dist/core/memory/working-memory.js +2 -2
- package/dist/core/memory/working-memory.js.map +1 -1
- package/dist/core/types/fact.d.ts +4 -4
- package/dist/core/types/fact.d.ts.map +1 -1
- package/dist/core/types/rule.d.ts +1 -0
- package/dist/core/types/rule.d.ts.map +1 -1
- package/dist/engines/backward/compiler.js +1 -1
- package/dist/engines/backward/compiler.js.map +1 -1
- package/dist/engines/backward/engine.d.ts +2 -2
- package/dist/engines/backward/engine.d.ts.map +1 -1
- package/dist/engines/backward/engine.js.map +1 -1
- package/dist/engines/bayesian/compiler.d.ts.map +1 -1
- package/dist/engines/bayesian/compiler.js +7 -7
- package/dist/engines/bayesian/compiler.js.map +1 -1
- package/dist/engines/bayesian/engine.d.ts +2 -2
- package/dist/engines/bayesian/engine.d.ts.map +1 -1
- package/dist/engines/bayesian/engine.js.map +1 -1
- package/dist/engines/bayesian/strategy.d.ts.map +1 -1
- package/dist/engines/bayesian/strategy.js +5 -5
- package/dist/engines/bayesian/strategy.js.map +1 -1
- package/dist/engines/constraint/compiler.d.ts.map +1 -1
- package/dist/engines/constraint/compiler.js +10 -10
- package/dist/engines/constraint/compiler.js.map +1 -1
- package/dist/engines/constraint/engine.d.ts +2 -2
- package/dist/engines/constraint/engine.d.ts.map +1 -1
- package/dist/engines/constraint/engine.js.map +1 -1
- package/dist/engines/defeasible/compiler.js +1 -1
- package/dist/engines/defeasible/compiler.js.map +1 -1
- package/dist/engines/defeasible/engine.d.ts +2 -2
- package/dist/engines/defeasible/engine.d.ts.map +1 -1
- package/dist/engines/defeasible/engine.js.map +1 -1
- package/dist/engines/expert/compiler.d.ts.map +1 -1
- package/dist/engines/expert/compiler.js +6 -6
- package/dist/engines/expert/compiler.js.map +1 -1
- package/dist/engines/expert/engine.d.ts +2 -2
- package/dist/engines/expert/engine.d.ts.map +1 -1
- package/dist/engines/expert/engine.js.map +1 -1
- package/dist/engines/forward/compiler.d.ts.map +1 -1
- package/dist/engines/forward/compiler.js +4 -4
- package/dist/engines/forward/compiler.js.map +1 -1
- package/dist/engines/forward/engine.d.ts +2 -2
- package/dist/engines/forward/engine.d.ts.map +1 -1
- package/dist/engines/forward/engine.js.map +1 -1
- package/dist/engines/forward/strategy.d.ts.map +1 -1
- package/dist/engines/forward/strategy.js +4 -4
- package/dist/engines/forward/strategy.js.map +1 -1
- package/dist/engines/fuzzy/compiler.js +1 -1
- package/dist/engines/fuzzy/compiler.js.map +1 -1
- package/dist/engines/fuzzy/engine.d.ts +2 -2
- package/dist/engines/fuzzy/engine.d.ts.map +1 -1
- package/dist/engines/fuzzy/engine.js.map +1 -1
- package/dist/engines/fuzzy/strategy.d.ts.map +1 -1
- package/dist/engines/fuzzy/strategy.js +8 -8
- package/dist/engines/fuzzy/strategy.js.map +1 -1
- package/dist/engines/monte-carlo/compiler.d.ts.map +1 -1
- package/dist/engines/monte-carlo/compiler.js +8 -8
- package/dist/engines/monte-carlo/compiler.js.map +1 -1
- package/dist/engines/monte-carlo/engine.d.ts +2 -2
- package/dist/engines/monte-carlo/engine.d.ts.map +1 -1
- package/dist/engines/monte-carlo/engine.js.map +1 -1
- package/dist/engines/monte-carlo/strategy.js +1 -1
- package/dist/engines/monte-carlo/strategy.js.map +1 -1
- package/dist/engines/pricing/compiler.d.ts.map +1 -1
- package/dist/engines/pricing/compiler.js +9 -9
- package/dist/engines/pricing/compiler.js.map +1 -1
- package/dist/engines/pricing/strategy.js +7 -6
- package/dist/engines/pricing/strategy.js.map +1 -1
- package/dist/engines/pricing/types.d.ts +2 -0
- package/dist/engines/pricing/types.d.ts.map +1 -1
- package/dist/engines/scoring/compiler.d.ts.map +1 -1
- package/dist/engines/scoring/compiler.js +8 -6
- package/dist/engines/scoring/compiler.js.map +1 -1
- package/dist/engines/scoring/engine.d.ts +2 -2
- package/dist/engines/scoring/engine.d.ts.map +1 -1
- package/dist/engines/scoring/engine.js.map +1 -1
- 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 +4 -1
- package/dist/engines/scoring/index.js.map +1 -1
- package/dist/engines/scoring/strategy.d.ts.map +1 -1
- package/dist/engines/scoring/strategy.js +28 -2
- package/dist/engines/scoring/strategy.js.map +1 -1
- package/dist/engines/scoring/types.d.ts +43 -4
- package/dist/engines/scoring/types.d.ts.map +1 -1
- package/dist/engines/scoring/types.js +19 -0
- package/dist/engines/scoring/types.js.map +1 -1
- package/dist/engines/sequential/compiler.d.ts.map +1 -1
- package/dist/engines/sequential/compiler.js +4 -4
- package/dist/engines/sequential/compiler.js.map +1 -1
- package/dist/engines/sequential/engine.d.ts +2 -2
- package/dist/engines/sequential/engine.d.ts.map +1 -1
- package/dist/engines/sequential/engine.js.map +1 -1
- package/dist/engines/sequential/strategy.js +1 -1
- package/dist/engines/sequential/strategy.js.map +1 -1
- package/dist/engines/state-machine/compiler.js +4 -4
- package/dist/engines/state-machine/compiler.js.map +1 -1
- package/dist/engines/state-machine/strategy.d.ts.map +1 -1
- package/dist/engines/state-machine/strategy.js +2 -1
- package/dist/engines/state-machine/strategy.js.map +1 -1
- package/dist/engines/utility/compiler.d.ts.map +1 -1
- package/dist/engines/utility/compiler.js +7 -7
- package/dist/engines/utility/compiler.js.map +1 -1
- package/dist/engines/utility/engine.d.ts +2 -2
- package/dist/engines/utility/engine.d.ts.map +1 -1
- package/dist/engines/utility/engine.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/errors.ts +69 -0
- package/src/core/index.ts +4 -0
- package/src/core/memory/working-memory.interface.ts +2 -2
- package/src/core/memory/working-memory.ts +12 -12
- package/src/core/types/fact.ts +4 -4
- package/src/core/types/rule.ts +1 -0
- package/src/engines/backward/compiler.ts +1 -1
- package/src/engines/backward/engine.ts +2 -2
- package/src/engines/bayesian/compiler.ts +9 -8
- package/src/engines/bayesian/engine.ts +2 -2
- package/src/engines/bayesian/strategy.ts +7 -6
- package/src/engines/constraint/compiler.ts +12 -11
- package/src/engines/constraint/engine.ts +2 -2
- package/src/engines/defeasible/compiler.ts +1 -1
- package/src/engines/defeasible/engine.ts +2 -2
- package/src/engines/expert/compiler.ts +8 -7
- package/src/engines/expert/engine.ts +2 -2
- package/src/engines/forward/compiler.ts +6 -5
- package/src/engines/forward/engine.ts +2 -2
- package/src/engines/forward/strategy.ts +6 -5
- package/src/engines/fuzzy/compiler.ts +1 -1
- package/src/engines/fuzzy/engine.ts +2 -2
- package/src/engines/fuzzy/strategy.ts +9 -9
- package/src/engines/monte-carlo/compiler.ts +10 -9
- package/src/engines/monte-carlo/engine.ts +2 -2
- package/src/engines/monte-carlo/strategy.ts +2 -2
- package/src/engines/pricing/compiler.ts +11 -10
- package/src/engines/pricing/strategy.ts +7 -7
- package/src/engines/pricing/types.ts +2 -0
- package/src/engines/scoring/compiler.ts +10 -7
- package/src/engines/scoring/engine.ts +2 -2
- package/src/engines/scoring/index.ts +5 -1
- package/src/engines/scoring/strategy.ts +36 -3
- package/src/engines/scoring/types.ts +49 -4
- package/src/engines/sequential/compiler.ts +6 -5
- package/src/engines/sequential/engine.ts +2 -2
- package/src/engines/sequential/strategy.ts +2 -2
- package/src/engines/state-machine/compiler.ts +5 -5
- package/src/engines/state-machine/strategy.ts +3 -1
- package/src/engines/utility/compiler.ts +9 -8
- package/src/engines/utility/engine.ts +2 -2
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
159
|
+
throw new CompilationError(`Normal variable '${variable.name}' requires mean or value`);
|
|
159
160
|
}
|
|
160
161
|
if (stdDev === undefined || isNaN(stdDev)) {
|
|
161
|
-
throw new
|
|
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
|
|
183
|
+
throw new CompilationError(`Uniform variable '${variable.name}' requires min or low`);
|
|
183
184
|
}
|
|
184
185
|
if (max === undefined || isNaN(max)) {
|
|
185
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
161
|
+
for (const rule of activeRules) {
|
|
160
162
|
if (!rule.category) {
|
|
161
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|