@higher.archi/boe 1.0.7 → 1.0.9
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/evaluation/decay.d.ts +60 -0
- package/dist/core/evaluation/decay.d.ts.map +1 -0
- package/dist/core/evaluation/decay.js +111 -0
- package/dist/core/evaluation/decay.js.map +1 -0
- package/dist/core/evaluation/index.d.ts +2 -0
- package/dist/core/evaluation/index.d.ts.map +1 -1
- package/dist/core/evaluation/index.js +5 -1
- package/dist/core/evaluation/index.js.map +1 -1
- 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/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/bayesian/compiler.d.ts.map +1 -1
- package/dist/engines/bayesian/compiler.js +21 -9
- package/dist/engines/bayesian/compiler.js.map +1 -1
- package/dist/engines/bayesian/strategy.d.ts.map +1 -1
- package/dist/engines/bayesian/strategy.js +46 -9
- package/dist/engines/bayesian/strategy.js.map +1 -1
- package/dist/engines/bayesian/types.d.ts +16 -0
- package/dist/engines/bayesian/types.d.ts.map +1 -1
- package/dist/engines/bayesian/types.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/defeasible/compiler.d.ts.map +1 -1
- package/dist/engines/defeasible/compiler.js +17 -3
- package/dist/engines/defeasible/compiler.js.map +1 -1
- package/dist/engines/defeasible/strategy.d.ts.map +1 -1
- package/dist/engines/defeasible/strategy.js +47 -2
- package/dist/engines/defeasible/strategy.js.map +1 -1
- package/dist/engines/defeasible/types.d.ts +29 -1
- package/dist/engines/defeasible/types.d.ts.map +1 -1
- package/dist/engines/defeasible/types.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/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/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/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/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 +24 -10
- package/dist/engines/scoring/compiler.js.map +1 -1
- package/dist/engines/scoring/strategy.d.ts.map +1 -1
- package/dist/engines/scoring/strategy.js +51 -2
- package/dist/engines/scoring/strategy.js.map +1 -1
- package/dist/engines/scoring/types.d.ts +16 -0
- package/dist/engines/scoring/types.d.ts.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/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/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/errors.ts +69 -0
- package/src/core/evaluation/decay.ts +165 -0
- package/src/core/evaluation/index.ts +13 -0
- package/src/core/index.ts +4 -0
- package/src/core/types/rule.ts +1 -0
- package/src/engines/backward/compiler.ts +1 -1
- package/src/engines/bayesian/compiler.ts +24 -10
- package/src/engines/bayesian/strategy.ts +61 -10
- package/src/engines/bayesian/types.ts +17 -0
- package/src/engines/constraint/compiler.ts +12 -11
- package/src/engines/defeasible/compiler.ts +18 -3
- package/src/engines/defeasible/strategy.ts +62 -2
- package/src/engines/defeasible/types.ts +33 -0
- package/src/engines/expert/compiler.ts +8 -7
- package/src/engines/forward/compiler.ts +6 -5
- package/src/engines/forward/strategy.ts +6 -5
- package/src/engines/fuzzy/compiler.ts +1 -1
- package/src/engines/fuzzy/strategy.ts +9 -9
- package/src/engines/monte-carlo/compiler.ts +10 -9
- 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 +27 -11
- package/src/engines/scoring/strategy.ts +67 -3
- package/src/engines/scoring/types.ts +17 -0
- package/src/engines/sequential/compiler.ts +6 -5
- 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/index.ts +16 -0
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
BindingContext
|
|
18
18
|
} from '../../core';
|
|
19
19
|
|
|
20
|
+
import { DecayConfig, DecayInfo } from '../../core/evaluation/decay';
|
|
21
|
+
|
|
20
22
|
// ========================================
|
|
21
23
|
// Likelihood Value Types
|
|
22
24
|
// ========================================
|
|
@@ -100,6 +102,10 @@ export type PriorDefault =
|
|
|
100
102
|
*/
|
|
101
103
|
export type BayesianRule = BaseRule & {
|
|
102
104
|
likelihood: LikelihoodMap; // How this evidence affects each hypothesis
|
|
105
|
+
decay?: {
|
|
106
|
+
timestamp: string | Expression; // e.g. '$sensor.readingDate'
|
|
107
|
+
config?: Partial<DecayConfig>; // overrides engine-level defaults
|
|
108
|
+
};
|
|
103
109
|
};
|
|
104
110
|
|
|
105
111
|
/**
|
|
@@ -109,6 +115,10 @@ export type BayesianConfig = {
|
|
|
109
115
|
mode: 'bayesian';
|
|
110
116
|
threshold?: number; // Flag if any posterior exceeds this (e.g., 0.5)
|
|
111
117
|
priorDefault?: PriorDefault; // How to fill missing priors (default: 'uniform')
|
|
118
|
+
decay?: {
|
|
119
|
+
timestamp?: string | Expression; // default timestamp path for all rules
|
|
120
|
+
config: DecayConfig; // engine-level decay config
|
|
121
|
+
};
|
|
112
122
|
};
|
|
113
123
|
|
|
114
124
|
/**
|
|
@@ -152,6 +162,10 @@ export type CompiledLikelihoodMap = Record<string, number>;
|
|
|
152
162
|
*/
|
|
153
163
|
export type CompiledBayesianRule = CompiledBaseRule & {
|
|
154
164
|
likelihood: CompiledLikelihoodMap;
|
|
165
|
+
decay?: {
|
|
166
|
+
timestamp: string; // $-prefixed path
|
|
167
|
+
config?: Partial<DecayConfig>;
|
|
168
|
+
};
|
|
155
169
|
};
|
|
156
170
|
|
|
157
171
|
/**
|
|
@@ -176,6 +190,8 @@ export type EvidenceEntry = {
|
|
|
176
190
|
ruleId: string;
|
|
177
191
|
ruleName?: string;
|
|
178
192
|
likelihoods: CompiledLikelihoodMap;
|
|
193
|
+
likelihoodsBeforeDecay?: CompiledLikelihoodMap; // original likelihoods before decay
|
|
194
|
+
decayInfo?: DecayInfo;
|
|
179
195
|
};
|
|
180
196
|
|
|
181
197
|
/**
|
|
@@ -197,6 +213,7 @@ export type BayesianResult = {
|
|
|
197
213
|
fired: string[]; // Rules that matched and provided evidence
|
|
198
214
|
evidence: EvidenceEntry[]; // Audit trail
|
|
199
215
|
thresholdExceeded?: string; // First hypothesis to exceed threshold (if any)
|
|
216
|
+
decayInfo?: Record<string, DecayInfo>; // per-rule decay audit
|
|
200
217
|
iterations: 1; // Bayesian is always one-pass
|
|
201
218
|
executionTimeMs: number; // Processing time in milliseconds
|
|
202
219
|
};
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import {
|
|
9
9
|
compileCondition,
|
|
10
10
|
normalizeShape,
|
|
11
|
-
CompiledInputParameter
|
|
11
|
+
CompiledInputParameter,
|
|
12
|
+
CompilationError
|
|
12
13
|
} from '../../core';
|
|
13
14
|
|
|
14
15
|
import {
|
|
@@ -49,11 +50,11 @@ function compileVariable(
|
|
|
49
50
|
index: number
|
|
50
51
|
): CompiledConstraintVariable {
|
|
51
52
|
if (!variable.name) {
|
|
52
|
-
throw new
|
|
53
|
+
throw new CompilationError(`Variable at index ${index} must have a name`);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
if (!Array.isArray(variable.domain) || variable.domain.length === 0) {
|
|
56
|
-
throw new
|
|
57
|
+
throw new CompilationError(`Variable '${variable.name}' must have a non-empty domain array`);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
return {
|
|
@@ -73,11 +74,11 @@ function compileConstraint(
|
|
|
73
74
|
constraintIndex: number
|
|
74
75
|
): CompiledConstraint {
|
|
75
76
|
if (!constraint.id) {
|
|
76
|
-
throw new
|
|
77
|
+
throw new CompilationError(`Constraint at index ${constraintIndex} must have an id`);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
if (!Array.isArray(constraint.variables) || constraint.variables.length === 0) {
|
|
80
|
-
throw new
|
|
81
|
+
throw new CompilationError(`Constraint '${constraint.id}' must reference at least one variable`);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// Map variable names to indices
|
|
@@ -85,7 +86,7 @@ function compileConstraint(
|
|
|
85
86
|
for (const varName of constraint.variables) {
|
|
86
87
|
const index = variableMap.get(varName);
|
|
87
88
|
if (index === undefined) {
|
|
88
|
-
throw new
|
|
89
|
+
throw new CompilationError(
|
|
89
90
|
`Constraint '${constraint.id}' references unknown variable '${varName}'`
|
|
90
91
|
);
|
|
91
92
|
}
|
|
@@ -125,11 +126,11 @@ function compilePreference(
|
|
|
125
126
|
preferenceIndex: number
|
|
126
127
|
): CompiledPreference {
|
|
127
128
|
if (!preference.id) {
|
|
128
|
-
throw new
|
|
129
|
+
throw new CompilationError(`Preference at index ${preferenceIndex} must have an id`);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
if (!Array.isArray(preference.variables) || preference.variables.length === 0) {
|
|
132
|
-
throw new
|
|
133
|
+
throw new CompilationError(`Preference '${preference.id}' must reference at least one variable`);
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
// Map variable names to indices
|
|
@@ -137,7 +138,7 @@ function compilePreference(
|
|
|
137
138
|
for (const varName of preference.variables) {
|
|
138
139
|
const index = variableMap.get(varName);
|
|
139
140
|
if (index === undefined) {
|
|
140
|
-
throw new
|
|
141
|
+
throw new CompilationError(
|
|
141
142
|
`Preference '${preference.id}' references unknown variable '${varName}'`
|
|
142
143
|
);
|
|
143
144
|
}
|
|
@@ -211,7 +212,7 @@ export function compileConstraintRuleSet(
|
|
|
211
212
|
): CompiledConstraintRuleSet {
|
|
212
213
|
// Validate mode
|
|
213
214
|
if (ruleSet.mode !== 'constraint') {
|
|
214
|
-
throw new
|
|
215
|
+
throw new CompilationError(`Expected mode 'constraint', got '${ruleSet.mode}'`);
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
// Compile variables and build name->index map
|
|
@@ -220,7 +221,7 @@ export function compileConstraintRuleSet(
|
|
|
220
221
|
const compiled = compileVariable(variable, index);
|
|
221
222
|
|
|
222
223
|
if (variableMap.has(variable.name)) {
|
|
223
|
-
throw new
|
|
224
|
+
throw new CompilationError(`Duplicate variable name: '${variable.name}'`);
|
|
224
225
|
}
|
|
225
226
|
variableMap.set(variable.name, index);
|
|
226
227
|
|
|
@@ -34,7 +34,8 @@ const DEFAULT_CONFIG: Required<DefeasibleConfig> = {
|
|
|
34
34
|
aggregation: 'average',
|
|
35
35
|
threshold: 0.5,
|
|
36
36
|
conflictResolution: 'specificity',
|
|
37
|
-
maxIterations: 100
|
|
37
|
+
maxIterations: 100,
|
|
38
|
+
decay: undefined as any // Optional at runtime; Required<> demands a key
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
// ========================================
|
|
@@ -198,6 +199,19 @@ function compileRule(rule: DefeasibleRule, index: number): CompiledDefeasibleRul
|
|
|
198
199
|
const inputs = (rule.inputs || []).map(compileInputParameter);
|
|
199
200
|
const when = rule.when ? compileCondition(rule.when) : undefined;
|
|
200
201
|
|
|
202
|
+
// Compile decay — only pass through $-prefixed string timestamps
|
|
203
|
+
let compiledDecay: CompiledDefeasibleRule['decay'] | undefined;
|
|
204
|
+
if (rule.decay) {
|
|
205
|
+
const ts = rule.decay.timestamp;
|
|
206
|
+
if (typeof ts === 'string') {
|
|
207
|
+
compiledDecay = {
|
|
208
|
+
timestamp: ts,
|
|
209
|
+
config: rule.decay.config,
|
|
210
|
+
target: rule.decay.target
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
201
215
|
return {
|
|
202
216
|
id: rule.id,
|
|
203
217
|
name: rule.name,
|
|
@@ -210,7 +224,8 @@ function compileRule(rule: DefeasibleRule, index: number): CompiledDefeasibleRul
|
|
|
210
224
|
credibility: normalizeCredibility(rule.credibility),
|
|
211
225
|
conclusion: compileConclusion(rule.conclude),
|
|
212
226
|
defeats: (rule.defeats || []).map(normalizeDefeat),
|
|
213
|
-
specificity: countSpecificity(rule)
|
|
227
|
+
specificity: countSpecificity(rule),
|
|
228
|
+
decay: compiledDecay
|
|
214
229
|
};
|
|
215
230
|
}
|
|
216
231
|
|
|
@@ -288,7 +303,7 @@ export function compileDefeasibleRuleSet(ruleSet: DefeasibleRuleSet): CompiledDe
|
|
|
288
303
|
const inputs = (ruleSet.inputs || []).map(compileInputParameter);
|
|
289
304
|
|
|
290
305
|
// Compile rules
|
|
291
|
-
const rules = ruleSet.rules.map((rule, index) => compileRule(rule, index));
|
|
306
|
+
const rules = ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileRule(rule, index));
|
|
292
307
|
|
|
293
308
|
// Build superiority map
|
|
294
309
|
const superiority = buildSuperiorityMap(rules, ruleSet.superiority);
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
import { match } from '../../core/matching';
|
|
9
9
|
import { IWorkingMemory } from '../../core/memory';
|
|
10
|
+
import {
|
|
11
|
+
calculateDecayMultiplier,
|
|
12
|
+
resolveDecayTimestamp,
|
|
13
|
+
DecayConfig,
|
|
14
|
+
DecayInfo
|
|
15
|
+
} from '../../core/evaluation/decay';
|
|
10
16
|
|
|
11
17
|
import {
|
|
12
18
|
CompiledDefeasibleRuleSet,
|
|
@@ -101,17 +107,57 @@ export class DefeasibleStrategy {
|
|
|
101
107
|
// Phase 1: Match all rules and collect fired rules
|
|
102
108
|
const firedRules: FiredRule[] = [];
|
|
103
109
|
const firedRuleIds: string[] = [];
|
|
110
|
+
const decayInfoMap: Record<string, DecayInfo> = {};
|
|
111
|
+
const decayMultipliersByRule: Record<string, number> = {};
|
|
112
|
+
const now = new Date();
|
|
104
113
|
|
|
105
114
|
for (const rule of ruleSet.rules) {
|
|
106
115
|
// Match inputs against working memory (includes when clause evaluation)
|
|
107
116
|
const activations = match(rule, wm);
|
|
108
117
|
|
|
109
118
|
for (const activation of activations) {
|
|
119
|
+
// Compute decay if configured
|
|
120
|
+
let effectiveCredibility = rule.credibility;
|
|
121
|
+
let credibilityBeforeDecay: number | undefined;
|
|
122
|
+
let ruleDecayInfo: DecayInfo | undefined;
|
|
123
|
+
|
|
124
|
+
const ruleDecay = rule.decay;
|
|
125
|
+
const engineDecay = config.decay;
|
|
126
|
+
|
|
127
|
+
if (ruleDecay || engineDecay) {
|
|
128
|
+
const tsRef = ruleDecay?.timestamp ?? (engineDecay?.timestamp as string | undefined);
|
|
129
|
+
const decayTarget = ruleDecay?.target ?? engineDecay?.target ?? 'credibility';
|
|
130
|
+
|
|
131
|
+
if (typeof tsRef === 'string') {
|
|
132
|
+
const dataTimestamp = resolveDecayTimestamp(tsRef, activation.facts);
|
|
133
|
+
|
|
134
|
+
if (dataTimestamp) {
|
|
135
|
+
const decayConfig: DecayConfig = {
|
|
136
|
+
...(engineDecay?.config ?? { curve: 'exponential', timeUnit: 'days' }),
|
|
137
|
+
...ruleDecay?.config
|
|
138
|
+
} as DecayConfig;
|
|
139
|
+
|
|
140
|
+
const info = calculateDecayMultiplier(dataTimestamp, now, decayConfig);
|
|
141
|
+
ruleDecayInfo = info;
|
|
142
|
+
decayInfoMap[rule.id] = info;
|
|
143
|
+
decayMultipliersByRule[rule.id] = info.multiplier;
|
|
144
|
+
|
|
145
|
+
// Apply decay to credibility if target includes it
|
|
146
|
+
if (decayTarget === 'credibility' || decayTarget === 'both') {
|
|
147
|
+
credibilityBeforeDecay = rule.credibility;
|
|
148
|
+
effectiveCredibility = rule.credibility * info.multiplier;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
110
154
|
// Rule fires
|
|
111
155
|
firedRules.push({
|
|
112
156
|
ruleId: rule.id,
|
|
113
157
|
conclusion: rule.conclusion,
|
|
114
|
-
credibility:
|
|
158
|
+
credibility: effectiveCredibility,
|
|
159
|
+
credibilityBeforeDecay,
|
|
160
|
+
decayInfo: ruleDecayInfo,
|
|
115
161
|
bindings: activation.facts
|
|
116
162
|
});
|
|
117
163
|
|
|
@@ -166,9 +212,21 @@ export class DefeasibleStrategy {
|
|
|
166
212
|
if (!defeatsMap.has(defeat.rule)) {
|
|
167
213
|
defeatsMap.set(defeat.rule, []);
|
|
168
214
|
}
|
|
215
|
+
|
|
216
|
+
// Apply decay to defeat strength if target includes it
|
|
217
|
+
let effectiveStrength = defeat.strength;
|
|
218
|
+
const ruleDecay = rule.decay;
|
|
219
|
+
const engineDecay = config.decay;
|
|
220
|
+
const decayTarget = ruleDecay?.target ?? engineDecay?.target ?? 'credibility';
|
|
221
|
+
|
|
222
|
+
if ((decayTarget === 'defeat-strength' || decayTarget === 'both') &&
|
|
223
|
+
decayMultipliersByRule[fired.ruleId] !== undefined) {
|
|
224
|
+
effectiveStrength = defeat.strength * decayMultipliersByRule[fired.ruleId];
|
|
225
|
+
}
|
|
226
|
+
|
|
169
227
|
defeatsMap.get(defeat.rule)!.push({
|
|
170
228
|
by: fired.ruleId,
|
|
171
|
-
strength:
|
|
229
|
+
strength: effectiveStrength
|
|
172
230
|
});
|
|
173
231
|
}
|
|
174
232
|
}
|
|
@@ -291,6 +349,7 @@ export class DefeasibleStrategy {
|
|
|
291
349
|
};
|
|
292
350
|
|
|
293
351
|
const executionTimeMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
352
|
+
const hasDecay = Object.keys(decayInfoMap).length > 0;
|
|
294
353
|
|
|
295
354
|
return {
|
|
296
355
|
conclusions,
|
|
@@ -300,6 +359,7 @@ export class DefeasibleStrategy {
|
|
|
300
359
|
conflicts,
|
|
301
360
|
fired: firedRuleIds,
|
|
302
361
|
iterations: 1, // Current implementation is single-pass
|
|
362
|
+
...(hasDecay ? { decayInfo: decayInfoMap } : {}),
|
|
303
363
|
executionTimeMs,
|
|
304
364
|
query
|
|
305
365
|
};
|
|
@@ -20,9 +20,12 @@ import {
|
|
|
20
20
|
CompiledBaseRule,
|
|
21
21
|
CompiledBaseRuleSet,
|
|
22
22
|
CompiledInputParameter,
|
|
23
|
+
Expression,
|
|
23
24
|
BindingContext
|
|
24
25
|
} from '../../core';
|
|
25
26
|
|
|
27
|
+
import { DecayConfig, DecayInfo } from '../../core/evaluation/decay';
|
|
28
|
+
|
|
26
29
|
// ========================================
|
|
27
30
|
// Rule Strength Types
|
|
28
31
|
// ========================================
|
|
@@ -167,6 +170,15 @@ export type DefeasibleRule = BaseRule & {
|
|
|
167
170
|
* Useful for audit trails and explainability.
|
|
168
171
|
*/
|
|
169
172
|
explanation?: string;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Temporal decay configuration for this rule
|
|
176
|
+
*/
|
|
177
|
+
decay?: {
|
|
178
|
+
timestamp: string | Expression;
|
|
179
|
+
config?: Partial<DecayConfig>;
|
|
180
|
+
target?: 'credibility' | 'defeat-strength' | 'both'; // default: 'credibility'
|
|
181
|
+
};
|
|
170
182
|
};
|
|
171
183
|
|
|
172
184
|
/**
|
|
@@ -216,6 +228,15 @@ export type DefeasibleConfig = {
|
|
|
216
228
|
* Maximum iterations for computing defeat cycles (default: 100)
|
|
217
229
|
*/
|
|
218
230
|
maxIterations?: number;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Engine-level temporal decay configuration
|
|
234
|
+
*/
|
|
235
|
+
decay?: {
|
|
236
|
+
timestamp?: string | Expression;
|
|
237
|
+
config: DecayConfig;
|
|
238
|
+
target?: 'credibility' | 'defeat-strength' | 'both';
|
|
239
|
+
};
|
|
219
240
|
};
|
|
220
241
|
|
|
221
242
|
/**
|
|
@@ -263,6 +284,11 @@ export type CompiledDefeasibleRule = CompiledBaseRule & {
|
|
|
263
284
|
conclusion: CompiledConclusion | null; // null for pure defeaters
|
|
264
285
|
defeats: CompiledDefeat[]; // Normalized defeats
|
|
265
286
|
specificity: number; // Number of conditions (for tie-breaking)
|
|
287
|
+
decay?: {
|
|
288
|
+
timestamp: string; // $-prefixed path
|
|
289
|
+
config?: Partial<DecayConfig>;
|
|
290
|
+
target?: 'credibility' | 'defeat-strength' | 'both';
|
|
291
|
+
};
|
|
266
292
|
};
|
|
267
293
|
|
|
268
294
|
/**
|
|
@@ -293,6 +319,8 @@ export type FiredRule = {
|
|
|
293
319
|
ruleId: string;
|
|
294
320
|
conclusion: CompiledConclusion | null;
|
|
295
321
|
credibility: number;
|
|
322
|
+
credibilityBeforeDecay?: number; // original credibility before decay
|
|
323
|
+
decayInfo?: DecayInfo;
|
|
296
324
|
bindings: BindingContext;
|
|
297
325
|
};
|
|
298
326
|
|
|
@@ -385,6 +413,11 @@ export type DefeasibleResult = {
|
|
|
385
413
|
*/
|
|
386
414
|
iterations: number;
|
|
387
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Per-rule decay audit trail
|
|
418
|
+
*/
|
|
419
|
+
decayInfo?: Record<string, DecayInfo>;
|
|
420
|
+
|
|
388
421
|
/**
|
|
389
422
|
* Processing time in milliseconds
|
|
390
423
|
*/
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import {
|
|
9
9
|
compileCondition,
|
|
10
10
|
compileOperand,
|
|
11
|
-
normalizeShape
|
|
11
|
+
normalizeShape,
|
|
12
|
+
CompilationError
|
|
12
13
|
} from '../../core';
|
|
13
14
|
|
|
14
15
|
import {
|
|
@@ -42,7 +43,7 @@ function normalizeConfidence(confidence: Confidence): number {
|
|
|
42
43
|
}
|
|
43
44
|
const value = SEMANTIC_CONFIDENCES[confidence as SemanticConfidence];
|
|
44
45
|
if (value === undefined) {
|
|
45
|
-
throw new
|
|
46
|
+
throw new CompilationError(`Invalid semantic confidence: '${confidence}'. Valid values: ${Object.keys(SEMANTIC_CONFIDENCES).join(', ')}`);
|
|
46
47
|
}
|
|
47
48
|
return value;
|
|
48
49
|
}
|
|
@@ -179,13 +180,13 @@ function compileExpertRule(
|
|
|
179
180
|
): CompiledExpertRule {
|
|
180
181
|
// Validate required fields
|
|
181
182
|
if (!rule.description) {
|
|
182
|
-
throw new
|
|
183
|
+
throw new CompilationError(`Rule '${rule.id}' is missing required 'description' field`);
|
|
183
184
|
}
|
|
184
185
|
if (!rule.explain) {
|
|
185
|
-
throw new
|
|
186
|
+
throw new CompilationError(`Rule '${rule.id}' is missing required 'explain' block`);
|
|
186
187
|
}
|
|
187
188
|
if (!rule.explain.onFire) {
|
|
188
|
-
throw new
|
|
189
|
+
throw new CompilationError(`Rule '${rule.id}' explain block is missing required 'onFire' template`);
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
// Normalize confidence (handles both numeric and semantic values)
|
|
@@ -244,14 +245,14 @@ function compileExpertRule(
|
|
|
244
245
|
export function compileExpertRuleSet(ruleSet: ExpertRuleSet): CompiledExpertRuleSet {
|
|
245
246
|
// Validate mode
|
|
246
247
|
if (ruleSet.mode !== 'expert') {
|
|
247
|
-
throw new
|
|
248
|
+
throw new CompilationError(`Invalid mode '${ruleSet.mode}' for expert ruleset`);
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
// Collect all derived facts for reference validation
|
|
251
252
|
const derivedFacts = collectDerivedFacts(ruleSet.rules);
|
|
252
253
|
|
|
253
254
|
// Compile all rules
|
|
254
|
-
const compiledRules = ruleSet.rules.map((rule, index) =>
|
|
255
|
+
const compiledRules = ruleSet.rules.filter(r => !r.disabled).map((rule, index) =>
|
|
255
256
|
compileExpertRule(rule, index, derivedFacts)
|
|
256
257
|
);
|
|
257
258
|
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
normalizeShape,
|
|
10
10
|
DEFAULT_STRATEGIES,
|
|
11
11
|
Condition,
|
|
12
|
-
PropertyRef
|
|
12
|
+
PropertyRef,
|
|
13
|
+
CompilationError
|
|
13
14
|
} from '../../core';
|
|
14
15
|
|
|
15
16
|
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 (e.g., '$person.status'), got: ${targetPath}`);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// Check if value is an expression that needs to be compiled as a condition
|
|
@@ -102,7 +103,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
|
|
|
102
103
|
if ('retract' in item) {
|
|
103
104
|
const ref = item.retract;
|
|
104
105
|
if (!isPropertyRef(ref)) {
|
|
105
|
-
throw new
|
|
106
|
+
throw new CompilationError(`Retract must be a property reference (e.g., '$duplicate'), got: ${ref}`);
|
|
106
107
|
}
|
|
107
108
|
// Extract just the parameter name (first part after $)
|
|
108
109
|
const paramName = ref.slice(1).split('.')[0];
|
|
@@ -119,7 +120,7 @@ function compileActionItem(item: ActionItem): CompiledActionItem {
|
|
|
119
120
|
return { halt: true };
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
throw new
|
|
123
|
+
throw new CompilationError(`Unknown action item type: ${JSON.stringify(item)}`);
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
function normalizeThenValue(value: ThenValue): ActionItem[] {
|
|
@@ -199,7 +200,7 @@ export function compileForwardRuleSet(ruleSet: ForwardRuleSet): CompiledForwardR
|
|
|
199
200
|
name: ruleSet.name,
|
|
200
201
|
mode: 'forward',
|
|
201
202
|
schema: ruleSet.shape ? normalizeShape(ruleSet.shape) : { type: 'object' },
|
|
202
|
-
rules: ruleSet.rules.map((rule, index) => compileForwardRule(rule, index)),
|
|
203
|
+
rules: ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileForwardRule(rule, index)),
|
|
203
204
|
config
|
|
204
205
|
};
|
|
205
206
|
}
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
resolveOperand,
|
|
10
10
|
setValueAtPath,
|
|
11
11
|
evaluateExpression,
|
|
12
|
-
BindingContext
|
|
12
|
+
BindingContext,
|
|
13
|
+
RuntimeError
|
|
13
14
|
} from '../../core';
|
|
14
15
|
import { IWorkingMemory } from '../../core/memory';
|
|
15
16
|
|
|
@@ -105,7 +106,7 @@ export class ForwardChainingStrategy {
|
|
|
105
106
|
for (const { target, value, expr } of item.set) {
|
|
106
107
|
const fact = context[target.param];
|
|
107
108
|
if (!fact) {
|
|
108
|
-
throw new
|
|
109
|
+
throw new RuntimeError(`Cannot set property on unknown fact: ${target.param}`);
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
// Resolve the value (could be constant, property ref, or expression)
|
|
@@ -119,7 +120,7 @@ export class ForwardChainingStrategy {
|
|
|
119
120
|
resolvedValue = resolveOperand(value, context);
|
|
120
121
|
} else {
|
|
121
122
|
// Function operand - not yet supported
|
|
122
|
-
throw new
|
|
123
|
+
throw new RuntimeError('Function operands in set actions are not yet supported');
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
// Set the value at the target path (auto-creates intermediate objects)
|
|
@@ -138,7 +139,7 @@ export class ForwardChainingStrategy {
|
|
|
138
139
|
if ('retract' in item) {
|
|
139
140
|
const fact = context[item.retract];
|
|
140
141
|
if (!fact) {
|
|
141
|
-
throw new
|
|
142
|
+
throw new RuntimeError(`Cannot retract unknown fact: ${item.retract}`);
|
|
142
143
|
}
|
|
143
144
|
refraction.retractByFactId(fact.id);
|
|
144
145
|
wm.remove(fact.id);
|
|
@@ -149,7 +150,7 @@ export class ForwardChainingStrategy {
|
|
|
149
150
|
if ('call' in item) {
|
|
150
151
|
const handler = actions[item.call];
|
|
151
152
|
if (!handler) {
|
|
152
|
-
throw new
|
|
153
|
+
throw new RuntimeError(`Action handler not found: ${item.call}`);
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
const result = handler(context, wm);
|
|
@@ -174,7 +174,7 @@ export function compileFuzzyRuleSet(ruleSet: FuzzyRuleSet): CompiledFuzzyRuleSet
|
|
|
174
174
|
name: ruleSet.name,
|
|
175
175
|
mode: 'fuzzy',
|
|
176
176
|
schema: ruleSet.shape ? normalizeShape(ruleSet.shape) : { type: 'object' },
|
|
177
|
-
rules: ruleSet.rules.map((rule, index) => compileFuzzyRule(rule, index)),
|
|
177
|
+
rules: ruleSet.rules.filter(r => !r.disabled).map((rule, index) => compileFuzzyRule(rule, index)),
|
|
178
178
|
config,
|
|
179
179
|
variables,
|
|
180
180
|
variableIndex
|
|
@@ -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
|
|