@higher.archi/boe 1.0.7 → 1.0.8

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 (51) hide show
  1. package/dist/core/evaluation/decay.d.ts +60 -0
  2. package/dist/core/evaluation/decay.d.ts.map +1 -0
  3. package/dist/core/evaluation/decay.js +111 -0
  4. package/dist/core/evaluation/decay.js.map +1 -0
  5. package/dist/core/evaluation/index.d.ts +2 -0
  6. package/dist/core/evaluation/index.d.ts.map +1 -1
  7. package/dist/core/evaluation/index.js +5 -1
  8. package/dist/core/evaluation/index.js.map +1 -1
  9. package/dist/engines/bayesian/compiler.d.ts.map +1 -1
  10. package/dist/engines/bayesian/compiler.js +14 -2
  11. package/dist/engines/bayesian/compiler.js.map +1 -1
  12. package/dist/engines/bayesian/strategy.d.ts.map +1 -1
  13. package/dist/engines/bayesian/strategy.js +41 -4
  14. package/dist/engines/bayesian/strategy.js.map +1 -1
  15. package/dist/engines/bayesian/types.d.ts +16 -0
  16. package/dist/engines/bayesian/types.d.ts.map +1 -1
  17. package/dist/engines/bayesian/types.js.map +1 -1
  18. package/dist/engines/defeasible/compiler.d.ts.map +1 -1
  19. package/dist/engines/defeasible/compiler.js +16 -2
  20. package/dist/engines/defeasible/compiler.js.map +1 -1
  21. package/dist/engines/defeasible/strategy.d.ts.map +1 -1
  22. package/dist/engines/defeasible/strategy.js +47 -2
  23. package/dist/engines/defeasible/strategy.js.map +1 -1
  24. package/dist/engines/defeasible/types.d.ts +29 -1
  25. package/dist/engines/defeasible/types.d.ts.map +1 -1
  26. package/dist/engines/defeasible/types.js.map +1 -1
  27. package/dist/engines/scoring/compiler.d.ts.map +1 -1
  28. package/dist/engines/scoring/compiler.js +17 -4
  29. package/dist/engines/scoring/compiler.js.map +1 -1
  30. package/dist/engines/scoring/strategy.d.ts.map +1 -1
  31. package/dist/engines/scoring/strategy.js +49 -0
  32. package/dist/engines/scoring/strategy.js.map +1 -1
  33. package/dist/engines/scoring/types.d.ts +16 -0
  34. package/dist/engines/scoring/types.d.ts.map +1 -1
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +5 -1
  38. package/dist/index.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/core/evaluation/decay.ts +165 -0
  41. package/src/core/evaluation/index.ts +13 -0
  42. package/src/engines/bayesian/compiler.ts +15 -2
  43. package/src/engines/bayesian/strategy.ts +54 -4
  44. package/src/engines/bayesian/types.ts +17 -0
  45. package/src/engines/defeasible/compiler.ts +17 -2
  46. package/src/engines/defeasible/strategy.ts +62 -2
  47. package/src/engines/defeasible/types.ts +33 -0
  48. package/src/engines/scoring/compiler.ts +18 -4
  49. package/src/engines/scoring/strategy.ts +63 -0
  50. package/src/engines/scoring/types.ts +17 -0
  51. package/src/index.ts +12 -0
@@ -77,7 +77,18 @@ function compileInputParameter(input: any) {
77
77
  // ========================================
78
78
 
79
79
  function compileScoringAction(action: any): CompiledScoringAction {
80
- const { score, weight, normalize, reason, nullHandling } = action;
80
+ const { score, weight, normalize, reason, nullHandling, decay } = action;
81
+
82
+ // Compile decay timestamp if it's an expression
83
+ let compiledDecay: CompiledScoringAction['decay'] | undefined;
84
+ if (decay) {
85
+ compiledDecay = {
86
+ timestamp: Array.isArray(decay.timestamp)
87
+ ? compileCondition(decay.timestamp as Condition)
88
+ : decay.timestamp,
89
+ config: decay.config
90
+ };
91
+ }
81
92
 
82
93
  // Handle expression scores
83
94
  if (Array.isArray(score)) {
@@ -87,7 +98,8 @@ function compileScoringAction(action: any): CompiledScoringAction {
87
98
  weight,
88
99
  normalize,
89
100
  reason,
90
- nullHandling
101
+ nullHandling,
102
+ decay: compiledDecay
91
103
  };
92
104
  }
93
105
 
@@ -98,7 +110,8 @@ function compileScoringAction(action: any): CompiledScoringAction {
98
110
  weight,
99
111
  normalize,
100
112
  reason,
101
- nullHandling
113
+ nullHandling,
114
+ decay: compiledDecay
102
115
  };
103
116
  }
104
117
 
@@ -200,7 +213,8 @@ export function compileScoringRuleSet(ruleSet: ScoringRuleSet): CompiledScoringR
200
213
  tiers: ruleSet.config?.tiers,
201
214
  categories,
202
215
  overrides,
203
- nullHandling: ruleSet.config?.nullHandling
216
+ nullHandling: ruleSet.config?.nullHandling,
217
+ decay: ruleSet.config?.decay
204
218
  };
205
219
 
206
220
  return {
@@ -10,6 +10,12 @@ import {
10
10
  evaluateCondition
11
11
  } from '../../core';
12
12
  import { IWorkingMemory } from '../../core/memory';
13
+ import {
14
+ calculateDecayMultiplier,
15
+ resolveDecayTimestamp,
16
+ DecayConfig,
17
+ DecayInfo
18
+ } from '../../core/evaluation/decay';
13
19
 
14
20
  import {
15
21
  CompiledScoringRuleSet,
@@ -49,8 +55,12 @@ export class ScoringStrategy {
49
55
  const weights: Record<string, number> = {};
50
56
  const fired: string[] = [];
51
57
  const rawScores: Record<string, number[]> = {}; // Track raw scores for adaptive
58
+ const decayInfoMap: Record<string, DecayInfo> = {};
59
+ const decayMultipliers: Record<string, number> = {};
52
60
 
53
61
  // Phase 1: Calculate base scores
62
+ const now = new Date();
63
+
54
64
  for (const rule of ruleSet.rules) {
55
65
  const activations = match(rule, wm);
56
66
 
@@ -74,6 +84,34 @@ export class ScoringStrategy {
74
84
  throw new Error(`Invalid score type: ${typeof ruleAction.score}`);
75
85
  }
76
86
 
87
+ // Apply temporal decay if configured
88
+ if (!decayMultipliers[rule.id]) {
89
+ const ruleDecay = ruleAction.decay;
90
+ const engineDecay = config.decay;
91
+
92
+ if (ruleDecay || engineDecay) {
93
+ // Resolve timestamp: rule-level overrides engine-level
94
+ const tsRef = ruleDecay?.timestamp ?? engineDecay?.timestamp;
95
+ let dataTimestamp: Date | null = null;
96
+
97
+ if (typeof tsRef === 'string') {
98
+ dataTimestamp = resolveDecayTimestamp(tsRef, activation.facts);
99
+ }
100
+
101
+ if (dataTimestamp) {
102
+ // Merge decay config: rule-level overrides engine-level
103
+ const decayConfig: DecayConfig = {
104
+ ...(engineDecay?.config ?? { curve: 'exponential', timeUnit: 'days' }),
105
+ ...ruleDecay?.config
106
+ } as DecayConfig;
107
+
108
+ const info = calculateDecayMultiplier(dataTimestamp, now, decayConfig);
109
+ decayInfoMap[rule.id] = info;
110
+ decayMultipliers[rule.id] = info.multiplier;
111
+ }
112
+ }
113
+ }
114
+
77
115
  // Store raw scores for adaptive strategy
78
116
  if (!rawScores[rule.id]) {
79
117
  rawScores[rule.id] = [];
@@ -139,6 +177,20 @@ export class ScoringStrategy {
139
177
  contributions[rule.id] = accumulatedScore;
140
178
  }
141
179
 
180
+ // Phase 2.25: Apply decay multipliers to contributions
181
+ const hasDecay = Object.keys(decayInfoMap).length > 0;
182
+ let contributionsBeforeDecay: Record<string, number> | undefined;
183
+
184
+ if (hasDecay) {
185
+ contributionsBeforeDecay = {};
186
+ for (const ruleId in contributions) {
187
+ contributionsBeforeDecay[ruleId] = contributions[ruleId];
188
+ if (decayMultipliers[ruleId] !== undefined) {
189
+ contributions[ruleId] *= decayMultipliers[ruleId];
190
+ }
191
+ }
192
+ }
193
+
142
194
  // Phase 2.5: Null handling — treat unfired 'zero' rules as fired with score 0
143
195
  for (const rule of ruleSet.rules) {
144
196
  if (!fired.includes(rule.id)) {
@@ -501,6 +553,12 @@ export class ScoringStrategy {
501
553
 
502
554
  const executionTimeMs = Math.round((performance.now() - startTime) * 100) / 100;
503
555
 
556
+ // Compute totalScoreBeforeDecay if decay was applied
557
+ let totalScoreBeforeDecay: number | undefined;
558
+ if (hasDecay && contributionsBeforeDecay) {
559
+ totalScoreBeforeDecay = baseScore + Object.values(contributionsBeforeDecay).reduce((sum, s) => sum + s, 0);
560
+ }
561
+
504
562
  return {
505
563
  totalScore,
506
564
  confidence,
@@ -511,6 +569,11 @@ export class ScoringStrategy {
511
569
  tier,
512
570
  categoryBreakdown,
513
571
  override: overrideResult,
572
+ ...(hasDecay ? {
573
+ totalScoreBeforeDecay,
574
+ contributionsBeforeDecay,
575
+ decayInfo: decayInfoMap
576
+ } : {}),
514
577
  executionTimeMs
515
578
  };
516
579
  }
@@ -13,6 +13,8 @@ import {
13
13
  BindingContext
14
14
  } from '../../core';
15
15
 
16
+ import { DecayConfig, DecayInfo } from '../../core/evaluation/decay';
17
+
16
18
  // ========================================
17
19
  // Scoring Method Types
18
20
  // ========================================
@@ -268,6 +270,10 @@ export type ScoringAction = {
268
270
  normalize?: { min: number; max: number }; // Optional normalization bounds
269
271
  reason?: string; // Optional human-readable explanation for the score
270
272
  nullHandling?: NullHandling; // How to handle if this signal is missing (default: 'exclude')
273
+ decay?: {
274
+ timestamp: string | Expression; // e.g. '$inspection.date'
275
+ config?: Partial<DecayConfig>; // overrides engine-level defaults
276
+ };
271
277
  };
272
278
 
273
279
  /**
@@ -300,6 +306,10 @@ export type ScoringConfig = {
300
306
  categories?: ScoringCategory[]; // Optional category grouping for two-level weight hierarchy
301
307
  overrides?: OverrideDefinition[]; // Optional post-scoring overrides evaluated in order (first match wins)
302
308
  nullHandling?: NullHandling; // Default null handling for all rules (default: 'exclude')
309
+ decay?: {
310
+ timestamp?: string | Expression; // default timestamp path for all rules
311
+ config: DecayConfig; // engine-level decay config
312
+ };
303
313
  };
304
314
 
305
315
  // ========================================
@@ -316,6 +326,10 @@ export type CompiledScoringAction = {
316
326
  normalize?: { min: number; max: number };
317
327
  reason?: string; // Preserved from source for reporting
318
328
  nullHandling?: NullHandling; // Preserved from source
329
+ decay?: {
330
+ timestamp: string | CompiledCondition; // string = $path, CompiledCondition = expression
331
+ config?: Partial<DecayConfig>;
332
+ };
319
333
  };
320
334
 
321
335
  /**
@@ -384,5 +398,8 @@ export type ScoringResult = {
384
398
  tier?: ScoringTierMatch; // Matched tier (if tiers configured)
385
399
  categoryBreakdown?: CategoryResult[]; // Per-category breakdown (if categories configured)
386
400
  override?: OverrideResult; // Override that fired (if any)
401
+ totalScoreBeforeDecay?: number; // undecayed total for comparison
402
+ contributionsBeforeDecay?: Record<string, number>; // undecayed per-rule contributions
403
+ decayInfo?: Record<string, DecayInfo>; // per-rule decay audit
387
404
  executionTimeMs: number; // Processing time in milliseconds
388
405
  };
package/src/index.ts CHANGED
@@ -375,6 +375,18 @@ export type {
375
375
 
376
376
  export { soundex, nysiis, caverphone2, cosineSimilarity } from './functions';
377
377
 
378
+ // Temporal Decay (re-exported from core for convenience)
379
+ export {
380
+ calculateDecayMultiplier,
381
+ resolveDecayTimestamp
382
+ } from './core/evaluation/decay';
383
+ export type {
384
+ DecayCurve,
385
+ DecayTimeUnit,
386
+ DecayConfig,
387
+ DecayInfo
388
+ } from './core/evaluation/decay';
389
+
378
390
  // ========================================
379
391
  // QFacts - Probabilistic Fact Hydration
380
392
  // ========================================