@higher.archi/boe 1.0.26 → 1.0.28

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 (74) hide show
  1. package/dist/engines/decay/compiler.d.ts +11 -0
  2. package/dist/engines/decay/compiler.d.ts.map +1 -0
  3. package/dist/engines/decay/compiler.js +216 -0
  4. package/dist/engines/decay/compiler.js.map +1 -0
  5. package/dist/engines/decay/engine.d.ts +48 -0
  6. package/dist/engines/decay/engine.d.ts.map +1 -0
  7. package/dist/engines/decay/engine.js +90 -0
  8. package/dist/engines/decay/engine.js.map +1 -0
  9. package/dist/engines/decay/index.d.ts +9 -0
  10. package/dist/engines/decay/index.d.ts.map +1 -0
  11. package/dist/engines/decay/index.js +21 -0
  12. package/dist/engines/decay/index.js.map +1 -0
  13. package/dist/engines/decay/strategy.d.ts +19 -0
  14. package/dist/engines/decay/strategy.d.ts.map +1 -0
  15. package/dist/engines/decay/strategy.js +284 -0
  16. package/dist/engines/decay/strategy.js.map +1 -0
  17. package/dist/engines/decay/types.d.ts +148 -0
  18. package/dist/engines/decay/types.d.ts.map +1 -0
  19. package/dist/engines/decay/types.js +39 -0
  20. package/dist/engines/decay/types.js.map +1 -0
  21. package/dist/engines/negotiation/compiler.d.ts +11 -0
  22. package/dist/engines/negotiation/compiler.d.ts.map +1 -0
  23. package/dist/engines/negotiation/compiler.js +177 -0
  24. package/dist/engines/negotiation/compiler.js.map +1 -0
  25. package/dist/engines/negotiation/engine.d.ts +46 -0
  26. package/dist/engines/negotiation/engine.d.ts.map +1 -0
  27. package/dist/engines/negotiation/engine.js +88 -0
  28. package/dist/engines/negotiation/engine.js.map +1 -0
  29. package/dist/engines/negotiation/index.d.ts +8 -0
  30. package/dist/engines/negotiation/index.d.ts.map +1 -0
  31. package/dist/engines/negotiation/index.js +17 -0
  32. package/dist/engines/negotiation/index.js.map +1 -0
  33. package/dist/engines/negotiation/strategy.d.ts +18 -0
  34. package/dist/engines/negotiation/strategy.d.ts.map +1 -0
  35. package/dist/engines/negotiation/strategy.js +439 -0
  36. package/dist/engines/negotiation/strategy.js.map +1 -0
  37. package/dist/engines/negotiation/types.d.ts +179 -0
  38. package/dist/engines/negotiation/types.d.ts.map +1 -0
  39. package/dist/engines/negotiation/types.js +10 -0
  40. package/dist/engines/negotiation/types.js.map +1 -0
  41. package/dist/engines/sentiment/engine.d.ts +25 -1
  42. package/dist/engines/sentiment/engine.d.ts.map +1 -1
  43. package/dist/engines/sentiment/engine.js +119 -0
  44. package/dist/engines/sentiment/engine.js.map +1 -1
  45. package/dist/engines/sentiment/index.d.ts +1 -1
  46. package/dist/engines/sentiment/index.d.ts.map +1 -1
  47. package/dist/engines/sentiment/index.js.map +1 -1
  48. package/dist/engines/sentiment/strategy.d.ts +2 -0
  49. package/dist/engines/sentiment/strategy.d.ts.map +1 -1
  50. package/dist/engines/sentiment/strategy.js +7 -8
  51. package/dist/engines/sentiment/strategy.js.map +1 -1
  52. package/dist/engines/sentiment/types.d.ts +9 -0
  53. package/dist/engines/sentiment/types.d.ts.map +1 -1
  54. package/dist/engines/sentiment/types.js.map +1 -1
  55. package/dist/index.d.ts +7 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +22 -6
  58. package/dist/index.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/engines/decay/compiler.ts +276 -0
  61. package/src/engines/decay/engine.ts +119 -0
  62. package/src/engines/decay/index.ts +42 -0
  63. package/src/engines/decay/strategy.ts +400 -0
  64. package/src/engines/decay/types.ts +221 -0
  65. package/src/engines/negotiation/compiler.ts +229 -0
  66. package/src/engines/negotiation/engine.ts +117 -0
  67. package/src/engines/negotiation/index.ts +42 -0
  68. package/src/engines/negotiation/strategy.ts +587 -0
  69. package/src/engines/negotiation/types.ts +244 -0
  70. package/src/engines/sentiment/engine.ts +157 -1
  71. package/src/engines/sentiment/index.ts +2 -1
  72. package/src/engines/sentiment/strategy.ts +12 -9
  73. package/src/engines/sentiment/types.ts +10 -0
  74. package/src/index.ts +70 -1
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Decay Engine Compiler
3
+ *
4
+ * Validates decay rulesets and resolves defaults.
5
+ */
6
+
7
+ import { CompilationError } from '../../core/errors';
8
+ import { SEMANTIC_PRIORITY_VALUES, isSemanticPriority, type SemanticPriority } from '../utility/types';
9
+
10
+ import type {
11
+ DecayRuleSet,
12
+ CompiledDecayRuleSet,
13
+ CompiledSingleDimensionDecayRuleSet,
14
+ CompiledMultiDimensionDecayRuleSet,
15
+ CompiledEventDrivenDecayRuleSet,
16
+ CompiledDecayDimension,
17
+ DecayDimension,
18
+ ReEngagementRule,
19
+ UrgencyTier
20
+ } from './types';
21
+ import { URGENCY_THRESHOLDS } from './types';
22
+
23
+ /**
24
+ * Compile and validate a decay ruleset.
25
+ */
26
+ export function compileDecayRuleSet(
27
+ ruleSet: DecayRuleSet
28
+ ): CompiledDecayRuleSet {
29
+ if (!ruleSet.id) {
30
+ throw new CompilationError('Decay ruleset requires an id');
31
+ }
32
+
33
+ if (ruleSet.mode !== 'decay') {
34
+ throw new CompilationError(`Expected mode 'decay', got '${ruleSet.mode}'`);
35
+ }
36
+
37
+ if (!ruleSet.entityType || ruleSet.entityType.trim() === '') {
38
+ throw new CompilationError('entityType is required and must be non-empty');
39
+ }
40
+
41
+ const entityIdField = ruleSet.entityIdField ?? 'id';
42
+ const urgencyThresholds = resolveUrgencyThresholds(ruleSet.urgencyThresholds);
43
+
44
+ switch (ruleSet.strategy) {
45
+ case 'single-dimension':
46
+ return compileSingleDimension(ruleSet, entityIdField, urgencyThresholds);
47
+ case 'multi-dimension':
48
+ return compileMultiDimension(ruleSet, entityIdField, urgencyThresholds);
49
+ case 'event-driven':
50
+ return compileEventDriven(ruleSet, entityIdField, urgencyThresholds);
51
+ default:
52
+ throw new CompilationError(`Unknown decay strategy: '${(ruleSet as any).strategy}'`);
53
+ }
54
+ }
55
+
56
+ // ========================================
57
+ // Strategy-Specific Compilers
58
+ // ========================================
59
+
60
+ function compileSingleDimension(
61
+ ruleSet: DecayRuleSet & { strategy: 'single-dimension' },
62
+ entityIdField: string,
63
+ urgencyThresholds: Record<UrgencyTier, number>
64
+ ): CompiledSingleDimensionDecayRuleSet {
65
+ const dimension = ruleSet.dimension;
66
+ if (!dimension) {
67
+ throw new CompilationError('Single-dimension strategy requires a dimension');
68
+ }
69
+
70
+ validateDimension(dimension);
71
+
72
+ return {
73
+ id: ruleSet.id,
74
+ name: ruleSet.name,
75
+ mode: 'decay',
76
+ strategy: 'single-dimension',
77
+ entityType: ruleSet.entityType,
78
+ entityIdField,
79
+ urgencyThresholds,
80
+ dimension: compileDimension(dimension, 1.0)
81
+ };
82
+ }
83
+
84
+ function compileMultiDimension(
85
+ ruleSet: DecayRuleSet & { strategy: 'multi-dimension' },
86
+ entityIdField: string,
87
+ urgencyThresholds: Record<UrgencyTier, number>
88
+ ): CompiledMultiDimensionDecayRuleSet {
89
+ const { dimensions } = ruleSet;
90
+
91
+ if (!dimensions || !Array.isArray(dimensions) || dimensions.length === 0) {
92
+ throw new CompilationError('Multi-dimension strategy requires at least one dimension');
93
+ }
94
+
95
+ validateDimensionIds(dimensions);
96
+ for (const dim of dimensions) {
97
+ validateDimension(dim);
98
+ }
99
+
100
+ const compiledDimensions = compileDimensions(dimensions);
101
+
102
+ return {
103
+ id: ruleSet.id,
104
+ name: ruleSet.name,
105
+ mode: 'decay',
106
+ strategy: 'multi-dimension',
107
+ entityType: ruleSet.entityType,
108
+ entityIdField,
109
+ urgencyThresholds,
110
+ dimensions: compiledDimensions,
111
+ aggregation: ruleSet.aggregation ?? 'weighted-average'
112
+ };
113
+ }
114
+
115
+ function compileEventDriven(
116
+ ruleSet: DecayRuleSet & { strategy: 'event-driven' },
117
+ entityIdField: string,
118
+ urgencyThresholds: Record<UrgencyTier, number>
119
+ ): CompiledEventDrivenDecayRuleSet {
120
+ const { dimensions, reEngagementRules } = ruleSet;
121
+
122
+ if (!dimensions || !Array.isArray(dimensions) || dimensions.length === 0) {
123
+ throw new CompilationError('Event-driven strategy requires at least one dimension');
124
+ }
125
+
126
+ validateDimensionIds(dimensions);
127
+ for (const dim of dimensions) {
128
+ validateDimension(dim);
129
+ }
130
+
131
+ if (!reEngagementRules || !Array.isArray(reEngagementRules) || reEngagementRules.length === 0) {
132
+ throw new CompilationError('Event-driven strategy requires at least one re-engagement rule');
133
+ }
134
+
135
+ validateReEngagementRules(reEngagementRules);
136
+
137
+ const compiledDimensions = compileDimensions(dimensions);
138
+
139
+ return {
140
+ id: ruleSet.id,
141
+ name: ruleSet.name,
142
+ mode: 'decay',
143
+ strategy: 'event-driven',
144
+ entityType: ruleSet.entityType,
145
+ entityIdField,
146
+ urgencyThresholds,
147
+ dimensions: compiledDimensions,
148
+ aggregation: ruleSet.aggregation ?? 'weighted-average',
149
+ reEngagementRules
150
+ };
151
+ }
152
+
153
+ // ========================================
154
+ // Validation Helpers
155
+ // ========================================
156
+
157
+ function validateDimension(dim: DecayDimension): void {
158
+ if (!dim.id) {
159
+ throw new CompilationError('Each dimension requires an id');
160
+ }
161
+
162
+ if (!dim.timestampField) {
163
+ throw new CompilationError(`Dimension '${dim.id}' requires a timestampField`);
164
+ }
165
+
166
+ switch (dim.curve) {
167
+ case 'exponential':
168
+ if (dim.halfLife === undefined || dim.halfLife <= 0) {
169
+ throw new CompilationError(`Dimension '${dim.id}' with exponential curve requires a positive halfLife`);
170
+ }
171
+ break;
172
+ case 'linear':
173
+ if (dim.maxAge === undefined || dim.maxAge <= 0) {
174
+ throw new CompilationError(`Dimension '${dim.id}' with linear curve requires a positive maxAge`);
175
+ }
176
+ break;
177
+ case 'step':
178
+ if (dim.threshold === undefined || dim.threshold <= 0) {
179
+ throw new CompilationError(`Dimension '${dim.id}' with step curve requires a positive threshold`);
180
+ }
181
+ break;
182
+ default:
183
+ throw new CompilationError(`Dimension '${dim.id}' has unknown curve: '${dim.curve}'`);
184
+ }
185
+ }
186
+
187
+ function validateDimensionIds(dimensions: DecayDimension[]): void {
188
+ const ids = new Set<string>();
189
+ for (const dim of dimensions) {
190
+ if (!dim.id) {
191
+ throw new CompilationError('Each dimension requires an id');
192
+ }
193
+ if (ids.has(dim.id)) {
194
+ throw new CompilationError(`Duplicate dimension id: '${dim.id}'`);
195
+ }
196
+ ids.add(dim.id);
197
+ }
198
+ }
199
+
200
+ function validateReEngagementRules(rules: ReEngagementRule[]): void {
201
+ const ids = new Set<string>();
202
+ for (const rule of rules) {
203
+ if (!rule.id) {
204
+ throw new CompilationError('Each re-engagement rule requires an id');
205
+ }
206
+ if (ids.has(rule.id)) {
207
+ throw new CompilationError(`Duplicate re-engagement rule id: '${rule.id}'`);
208
+ }
209
+ ids.add(rule.id);
210
+
211
+ if (!rule.eventType || rule.eventType.trim() === '') {
212
+ throw new CompilationError(`Re-engagement rule '${rule.id}' requires a non-empty eventType`);
213
+ }
214
+
215
+ const validEffects = ['reset', 'boost', 'extend'];
216
+ if (!validEffects.includes(rule.effect)) {
217
+ throw new CompilationError(`Re-engagement rule '${rule.id}' has invalid effect: '${rule.effect}'`);
218
+ }
219
+
220
+ if (rule.effect === 'boost' && (rule.boostAmount === undefined || rule.boostAmount <= 0)) {
221
+ throw new CompilationError(`Re-engagement rule '${rule.id}' with boost effect requires a positive boostAmount`);
222
+ }
223
+
224
+ if (rule.effect === 'extend' && (rule.extensionDays === undefined || rule.extensionDays <= 0)) {
225
+ throw new CompilationError(`Re-engagement rule '${rule.id}' with extend effect requires a positive extensionDays`);
226
+ }
227
+ }
228
+ }
229
+
230
+ // ========================================
231
+ // Compilation Helpers
232
+ // ========================================
233
+
234
+ function compileDimension(dim: DecayDimension, weight: number): CompiledDecayDimension {
235
+ return {
236
+ id: dim.id,
237
+ name: dim.name,
238
+ timestampField: dim.timestampField,
239
+ curve: dim.curve,
240
+ timeUnit: dim.timeUnit,
241
+ halfLife: dim.halfLife,
242
+ maxAge: dim.maxAge,
243
+ threshold: dim.threshold,
244
+ floor: dim.floor,
245
+ weight
246
+ };
247
+ }
248
+
249
+ function compileDimensions(dimensions: DecayDimension[]): CompiledDecayDimension[] {
250
+ // Resolve semantic weights to numeric
251
+ const resolved: { dim: DecayDimension; weight: number }[] = dimensions.map(dim => {
252
+ let weight: number;
253
+ if (isSemanticPriority(dim.weight)) {
254
+ weight = SEMANTIC_PRIORITY_VALUES[dim.weight as SemanticPriority];
255
+ } else if (typeof dim.weight === 'number') {
256
+ weight = dim.weight;
257
+ } else {
258
+ throw new CompilationError(`Invalid weight for dimension '${dim.id}': '${dim.weight}'`);
259
+ }
260
+ return { dim, weight };
261
+ });
262
+
263
+ // Normalize weights to sum to 1.0
264
+ const totalWeight = resolved.reduce((sum, r) => sum + r.weight, 0);
265
+ if (totalWeight === 0) {
266
+ throw new CompilationError('Dimension weights must not all be zero');
267
+ }
268
+
269
+ return resolved.map(r => compileDimension(r.dim, r.weight / totalWeight));
270
+ }
271
+
272
+ function resolveUrgencyThresholds(
273
+ overrides?: Partial<Record<UrgencyTier, number>>
274
+ ): Record<UrgencyTier, number> {
275
+ return { ...URGENCY_THRESHOLDS, ...overrides };
276
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Decay Engine
3
+ *
4
+ * Temporal decay engine that computes freshness scores for entities based
5
+ * on how recently their data was updated. Supports single-dimension,
6
+ * multi-dimension, and event-driven decay strategies.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const engine = new DecayEngine();
11
+ * engine.add({ type: 'Lead', data: { id: 'l1', lastContact: '2024-01-15' } });
12
+ * engine.add({ type: 'Lead', data: { id: 'l2', lastContact: '2024-03-01' } });
13
+ *
14
+ * const result = engine.execute(compiledDecay);
15
+ * console.log(result.entities[0]); // { entityId: 'l1', compositeFreshness: 0.42, urgencyTier: 'medium', ... }
16
+ * ```
17
+ */
18
+
19
+ import {
20
+ WorkingMemory,
21
+ Fact,
22
+ FactInput,
23
+ FactChange
24
+ } from '../../core';
25
+
26
+ import type {
27
+ CompiledDecayRuleSet,
28
+ DecayOptions,
29
+ DecayResult
30
+ } from './types';
31
+
32
+ import { DecayExecutor } from './strategy';
33
+
34
+ export class DecayEngine {
35
+ private wm: WorkingMemory;
36
+ private strategy: DecayExecutor;
37
+
38
+ constructor(workingMemory?: WorkingMemory) {
39
+ this.wm = workingMemory ?? new WorkingMemory();
40
+ this.strategy = new DecayExecutor();
41
+ }
42
+
43
+ // ========================================
44
+ // IWorkingMemory Implementation
45
+ // ========================================
46
+
47
+ add<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
48
+ return this.wm.add(input);
49
+ }
50
+
51
+ remove(factId: string): Fact | undefined {
52
+ return this.wm.remove(factId);
53
+ }
54
+
55
+ update<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
56
+ return this.wm.update(input);
57
+ }
58
+
59
+ get(factId: string): Fact | undefined {
60
+ return this.wm.get(factId);
61
+ }
62
+
63
+ getByType(type: string): Fact[] {
64
+ return this.wm.getByType(type);
65
+ }
66
+
67
+ getAll(): Fact[] {
68
+ return this.wm.getAll();
69
+ }
70
+
71
+ has(factId: string): boolean {
72
+ return this.wm.has(factId);
73
+ }
74
+
75
+ size(): number {
76
+ return this.wm.size();
77
+ }
78
+
79
+ clear(): void {
80
+ this.wm.clear();
81
+ }
82
+
83
+ getChanges(): FactChange[] {
84
+ return this.wm.getChanges();
85
+ }
86
+
87
+ clearChanges(): void {
88
+ this.wm.clearChanges();
89
+ }
90
+
91
+ // ========================================
92
+ // Engine Execution
93
+ // ========================================
94
+
95
+ /**
96
+ * Execute a decay ruleset.
97
+ *
98
+ * Computes freshness scores for all entities of the configured type
99
+ * and maps each to an urgency tier.
100
+ *
101
+ * @param ruleSet - Compiled decay ruleset
102
+ * @param options - Runtime options (asOf date, onEntity callback)
103
+ * @returns Decay result with per-entity freshness and tier distribution
104
+ */
105
+ execute(
106
+ ruleSet: CompiledDecayRuleSet,
107
+ options: DecayOptions = {}
108
+ ): DecayResult {
109
+ return this.strategy.run(ruleSet, this.wm, options);
110
+ }
111
+
112
+ // ========================================
113
+ // Utility Methods
114
+ // ========================================
115
+
116
+ getWorkingMemory(): WorkingMemory {
117
+ return this.wm;
118
+ }
119
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Decay Engine -- Temporal Freshness Scoring
3
+ */
4
+
5
+ // Types
6
+ export type {
7
+ DecayStrategy,
8
+ UrgencyTier,
9
+ ReEngagementEffect,
10
+ DecayAggregation,
11
+ DecayDimension,
12
+ CompiledDecayDimension,
13
+ ReEngagementRule,
14
+ SingleDimensionDecayRuleSet,
15
+ MultiDimensionDecayRuleSet,
16
+ EventDrivenDecayRuleSet,
17
+ DecayRuleSet,
18
+ CompiledSingleDimensionDecayRuleSet,
19
+ CompiledMultiDimensionDecayRuleSet,
20
+ CompiledEventDrivenDecayRuleSet,
21
+ CompiledDecayRuleSet,
22
+ DimensionDecayResult,
23
+ ReEngagementEvent,
24
+ EntityDecayResult,
25
+ DecayResult,
26
+ DecayOptions
27
+ } from './types';
28
+
29
+ // Constants & utilities
30
+ export {
31
+ URGENCY_THRESHOLDS,
32
+ resolveUrgencyTier
33
+ } from './types';
34
+
35
+ // Compiler
36
+ export { compileDecayRuleSet } from './compiler';
37
+
38
+ // Strategy
39
+ export { DecayExecutor, decayStrategy } from './strategy';
40
+
41
+ // Engine
42
+ export { DecayEngine } from './engine';