@higher.archi/boe 1.0.20 → 1.0.22

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 (44) hide show
  1. package/dist/core/types/rule.d.ts +1 -1
  2. package/dist/core/types/rule.d.ts.map +1 -1
  3. package/dist/engines/ensemble/index.d.ts +2 -0
  4. package/dist/engines/ensemble/index.d.ts.map +1 -1
  5. package/dist/engines/ensemble/index.js +8 -1
  6. package/dist/engines/ensemble/index.js.map +1 -1
  7. package/dist/engines/ensemble/members.d.ts +66 -0
  8. package/dist/engines/ensemble/members.d.ts.map +1 -0
  9. package/dist/engines/ensemble/members.js +86 -0
  10. package/dist/engines/ensemble/members.js.map +1 -0
  11. package/dist/engines/ranking/compiler.d.ts +12 -0
  12. package/dist/engines/ranking/compiler.d.ts.map +1 -0
  13. package/dist/engines/ranking/compiler.js +163 -0
  14. package/dist/engines/ranking/compiler.js.map +1 -0
  15. package/dist/engines/ranking/engine.d.ts +48 -0
  16. package/dist/engines/ranking/engine.d.ts.map +1 -0
  17. package/dist/engines/ranking/engine.js +89 -0
  18. package/dist/engines/ranking/engine.js.map +1 -0
  19. package/dist/engines/ranking/index.d.ts +9 -0
  20. package/dist/engines/ranking/index.d.ts.map +1 -0
  21. package/dist/engines/ranking/index.js +23 -0
  22. package/dist/engines/ranking/index.js.map +1 -0
  23. package/dist/engines/ranking/strategy.d.ts +21 -0
  24. package/dist/engines/ranking/strategy.d.ts.map +1 -0
  25. package/dist/engines/ranking/strategy.js +250 -0
  26. package/dist/engines/ranking/strategy.js.map +1 -0
  27. package/dist/engines/ranking/types.d.ts +142 -0
  28. package/dist/engines/ranking/types.d.ts.map +1 -0
  29. package/dist/engines/ranking/types.js +46 -0
  30. package/dist/engines/ranking/types.js.map +1 -0
  31. package/dist/index.d.ts +4 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +17 -1
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/core/types/rule.ts +1 -1
  37. package/src/engines/ensemble/index.ts +9 -0
  38. package/src/engines/ensemble/members.ts +156 -0
  39. package/src/engines/ranking/compiler.ts +194 -0
  40. package/src/engines/ranking/engine.ts +120 -0
  41. package/src/engines/ranking/index.ts +46 -0
  42. package/src/engines/ranking/strategy.ts +333 -0
  43. package/src/engines/ranking/types.ts +231 -0
  44. package/src/index.ts +41 -2
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Ensemble Member Adapter Factories
3
+ *
4
+ * Factory functions that wrap non-scoring engines (Bayesian, Fuzzy, etc.)
5
+ * into CustomMemberDef or ScoringMemberDef for use in ensemble rulesets.
6
+ */
7
+
8
+ import type { FactInput } from '../../core';
9
+ import type { ScoreExtractor, ConfidenceExtractor, CustomMemberDef, ScoringMemberDef } from './types';
10
+ import type { CompiledBayesianRuleSet, BayesianOptions, BayesianResult } from '../bayesian';
11
+ import type { CompiledFuzzyRuleSet, FuzzyOptions, FuzzyResult } from '../fuzzy';
12
+ import type { CompiledMonteCarloRuleSet, MonteCarloOptions, MonteCarloResult } from '../monte-carlo';
13
+ import type { CompiledExpertRuleSet, ExpertOptions, ExpertResult } from '../expert';
14
+ import type { CompiledScoringRuleSet, ScoringOptions } from '../scoring/types';
15
+ import { BayesianEngine } from '../bayesian';
16
+ import { FuzzyEngine } from '../fuzzy';
17
+ import { MonteCarloEngine } from '../monte-carlo';
18
+ import { ExpertEngine } from '../expert';
19
+
20
+ // ========================================
21
+ // Config Types
22
+ // ========================================
23
+
24
+ export type BayesianMemberConfig = {
25
+ id: string;
26
+ name?: string;
27
+ weight: number;
28
+ ruleset: CompiledBayesianRuleSet;
29
+ extractScore: ScoreExtractor<BayesianResult>;
30
+ extractConfidence?: ConfidenceExtractor<BayesianResult>;
31
+ options?: BayesianOptions;
32
+ };
33
+
34
+ export type FuzzyMemberConfig = {
35
+ id: string;
36
+ name?: string;
37
+ weight: number;
38
+ ruleset: CompiledFuzzyRuleSet;
39
+ extractScore: ScoreExtractor<FuzzyResult>;
40
+ extractConfidence?: ConfidenceExtractor<FuzzyResult>;
41
+ options?: FuzzyOptions;
42
+ };
43
+
44
+ export type MonteCarloMemberConfig = {
45
+ id: string;
46
+ name?: string;
47
+ weight: number;
48
+ ruleset: CompiledMonteCarloRuleSet;
49
+ extractScore: ScoreExtractor<MonteCarloResult>;
50
+ extractConfidence?: ConfidenceExtractor<MonteCarloResult>;
51
+ options?: MonteCarloOptions;
52
+ };
53
+
54
+ export type ExpertMemberConfig = {
55
+ id: string;
56
+ name?: string;
57
+ weight: number;
58
+ ruleset: CompiledExpertRuleSet;
59
+ extractScore: ScoreExtractor<ExpertResult>;
60
+ extractConfidence?: ConfidenceExtractor<ExpertResult>;
61
+ options?: ExpertOptions;
62
+ };
63
+
64
+ export type ScoringMemberConfig = {
65
+ id: string;
66
+ name?: string;
67
+ weight: number;
68
+ ruleset: CompiledScoringRuleSet;
69
+ options?: ScoringOptions;
70
+ };
71
+
72
+ // ========================================
73
+ // Factory Functions
74
+ // ========================================
75
+
76
+ /** Wrap a Bayesian engine as an ensemble member. Default confidence uses result.confidence. */
77
+ export function bayesianMember(config: BayesianMemberConfig): CustomMemberDef {
78
+ const {
79
+ id, name, weight, ruleset, extractScore,
80
+ extractConfidence = (r: BayesianResult) => r.confidence,
81
+ options
82
+ } = config;
83
+ return {
84
+ id, name, weight,
85
+ execute: (facts: FactInput[]) => {
86
+ const engine = new BayesianEngine();
87
+ for (const f of facts) engine.add(f);
88
+ return engine.execute(ruleset, options);
89
+ },
90
+ extractScore,
91
+ extractConfidence
92
+ };
93
+ }
94
+
95
+ /** Wrap a Fuzzy engine as an ensemble member. Default confidence is 1.0 (outputs are crisp). */
96
+ export function fuzzyMember(config: FuzzyMemberConfig): CustomMemberDef {
97
+ const {
98
+ id, name, weight, ruleset, extractScore,
99
+ extractConfidence = () => 1.0,
100
+ options
101
+ } = config;
102
+ return {
103
+ id, name, weight,
104
+ execute: (facts: FactInput[]) => {
105
+ const engine = new FuzzyEngine();
106
+ for (const f of facts) engine.add(f);
107
+ return engine.execute(ruleset, options);
108
+ },
109
+ extractScore,
110
+ extractConfidence
111
+ };
112
+ }
113
+
114
+ /** Wrap a Monte Carlo engine as an ensemble member. Default confidence is 1.0 (domain-specific). */
115
+ export function monteCarloMember(config: MonteCarloMemberConfig): CustomMemberDef {
116
+ const {
117
+ id, name, weight, ruleset, extractScore,
118
+ extractConfidence = () => 1.0,
119
+ options
120
+ } = config;
121
+ return {
122
+ id, name, weight,
123
+ execute: (facts: FactInput[]) => {
124
+ const engine = new MonteCarloEngine();
125
+ for (const f of facts) engine.add(f);
126
+ return engine.execute(ruleset, options);
127
+ },
128
+ extractScore,
129
+ extractConfidence
130
+ };
131
+ }
132
+
133
+ /** Wrap an Expert engine as an ensemble member. Default confidence is 1.0 (per-conclusion only). */
134
+ export function expertMember(config: ExpertMemberConfig): CustomMemberDef {
135
+ const {
136
+ id, name, weight, ruleset, extractScore,
137
+ extractConfidence = () => 1.0,
138
+ options
139
+ } = config;
140
+ return {
141
+ id, name, weight,
142
+ execute: (facts: FactInput[]) => {
143
+ const engine = new ExpertEngine();
144
+ for (const f of facts) engine.add(f);
145
+ return engine.execute(ruleset, options);
146
+ },
147
+ extractScore,
148
+ extractConfidence
149
+ };
150
+ }
151
+
152
+ /** Shorthand for creating a ScoringMemberDef. Consistency helper, no wrapping needed. */
153
+ export function scoringMember(config: ScoringMemberConfig): ScoringMemberDef {
154
+ const { id, name, weight, ruleset, options } = config;
155
+ return { id, name, weight, ruleset, options };
156
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Ranking Engine Compiler
3
+ *
4
+ * Validates ranking 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 { TierDefinition } from '../scoring/types';
11
+ import type {
12
+ RankingRuleSet,
13
+ CompiledRankingRuleSet,
14
+ CompiledScoreRankingRuleSet,
15
+ CompiledEloRankingRuleSet,
16
+ CompiledHeadToHeadRankingRuleSet,
17
+ CompiledRankingCriterion,
18
+ KFactorPreset
19
+ } from './types';
20
+ import { K_FACTOR_VALUES, isKFactorPreset } from './types';
21
+
22
+ /**
23
+ * Compile and validate a ranking ruleset.
24
+ */
25
+ export function compileRankingRuleSet<T extends TierDefinition = TierDefinition>(
26
+ ruleSet: RankingRuleSet<T>
27
+ ): CompiledRankingRuleSet<T> {
28
+ if (!ruleSet.id) {
29
+ throw new CompilationError('Ranking ruleset requires an id');
30
+ }
31
+
32
+ if (ruleSet.mode !== 'ranking') {
33
+ throw new CompilationError(`Expected mode 'ranking', got '${ruleSet.mode}'`);
34
+ }
35
+
36
+ if (!ruleSet.entityType || ruleSet.entityType.trim() === '') {
37
+ throw new CompilationError('entityType is required and must be non-empty');
38
+ }
39
+
40
+ switch (ruleSet.strategy) {
41
+ case 'score':
42
+ return compileScore(ruleSet) as CompiledRankingRuleSet<T>;
43
+ case 'elo':
44
+ return compileElo(ruleSet as any) as CompiledRankingRuleSet<T>;
45
+ case 'head-to-head':
46
+ return compileHeadToHead(ruleSet as any) as CompiledRankingRuleSet<T>;
47
+ default:
48
+ throw new CompilationError(`Unknown ranking strategy: '${(ruleSet as any).strategy}'`);
49
+ }
50
+ }
51
+
52
+ function compileScore<T extends TierDefinition>(
53
+ ruleSet: RankingRuleSet<T> & { strategy: 'score' }
54
+ ): CompiledScoreRankingRuleSet<T> {
55
+ if (!ruleSet.scoringRuleset) {
56
+ throw new CompilationError('Score strategy requires a scoringRuleset');
57
+ }
58
+
59
+ if (ruleSet.scoringRuleset.mode !== 'scoring') {
60
+ throw new CompilationError(`scoringRuleset must have mode 'scoring', got '${ruleSet.scoringRuleset.mode}'`);
61
+ }
62
+
63
+ // Validate tier IDs are unique
64
+ const tiers = ruleSet.config?.tiers;
65
+ if (tiers) {
66
+ const tierIds = new Set<string>();
67
+ for (const tier of tiers) {
68
+ if (tierIds.has(tier.id)) {
69
+ throw new CompilationError(`Duplicate tier id: '${tier.id}'`);
70
+ }
71
+ tierIds.add(tier.id);
72
+ }
73
+ }
74
+
75
+ return {
76
+ id: ruleSet.id,
77
+ name: ruleSet.name,
78
+ mode: 'ranking',
79
+ strategy: 'score',
80
+ scoringRuleset: ruleSet.scoringRuleset,
81
+ scoringOptions: ruleSet.scoringOptions,
82
+ entityType: ruleSet.entityType,
83
+ config: {
84
+ direction: ruleSet.config?.direction ?? 'highest-first',
85
+ tiers: ruleSet.config?.tiers
86
+ }
87
+ };
88
+ }
89
+
90
+ function compileElo(
91
+ ruleSet: RankingRuleSet & { strategy: 'elo' }
92
+ ): CompiledEloRankingRuleSet {
93
+ const config = ruleSet.config ?? {};
94
+
95
+ // Resolve kFactor
96
+ let kFactor: number;
97
+ if (config.kFactor === undefined) {
98
+ kFactor = K_FACTOR_VALUES['standard'];
99
+ } else if (isKFactorPreset(config.kFactor)) {
100
+ kFactor = K_FACTOR_VALUES[config.kFactor as KFactorPreset];
101
+ } else if (typeof config.kFactor === 'number') {
102
+ if (config.kFactor <= 0) {
103
+ throw new CompilationError('kFactor must be a positive number');
104
+ }
105
+ kFactor = config.kFactor;
106
+ } else {
107
+ throw new CompilationError(`Invalid kFactor: '${config.kFactor}'`);
108
+ }
109
+
110
+ const initialRating = config.initialRating ?? 1500;
111
+ if (initialRating <= 0) {
112
+ throw new CompilationError('initialRating must be a positive number');
113
+ }
114
+
115
+ return {
116
+ id: ruleSet.id,
117
+ name: ruleSet.name,
118
+ mode: 'ranking',
119
+ strategy: 'elo',
120
+ entityType: ruleSet.entityType,
121
+ matchType: (ruleSet as any).matchType ?? 'MatchResult',
122
+ config: {
123
+ initialRating,
124
+ kFactor,
125
+ direction: config.direction ?? 'highest-first'
126
+ }
127
+ };
128
+ }
129
+
130
+ function compileHeadToHead(
131
+ ruleSet: RankingRuleSet & { strategy: 'head-to-head' }
132
+ ): CompiledHeadToHeadRankingRuleSet {
133
+ const criteria = (ruleSet as any).criteria;
134
+
135
+ if (!criteria || !Array.isArray(criteria) || criteria.length === 0) {
136
+ throw new CompilationError('Head-to-head strategy requires at least one criterion');
137
+ }
138
+
139
+ // Validate no duplicate criterion IDs
140
+ const criterionIds = new Set<string>();
141
+ for (const c of criteria) {
142
+ if (!c.id) {
143
+ throw new CompilationError('Each criterion requires an id');
144
+ }
145
+ if (criterionIds.has(c.id)) {
146
+ throw new CompilationError(`Duplicate criterion id: '${c.id}'`);
147
+ }
148
+ criterionIds.add(c.id);
149
+ }
150
+
151
+ // Resolve semantic weights to numeric
152
+ const resolvedCriteria: CompiledRankingCriterion[] = criteria.map((c: any) => {
153
+ let weight: number;
154
+ if (isSemanticPriority(c.weight)) {
155
+ weight = SEMANTIC_PRIORITY_VALUES[c.weight as SemanticPriority];
156
+ } else if (typeof c.weight === 'number') {
157
+ weight = c.weight;
158
+ } else {
159
+ throw new CompilationError(`Invalid weight for criterion '${c.id}': '${c.weight}'`);
160
+ }
161
+
162
+ if (!c.direction) {
163
+ throw new CompilationError(`Criterion '${c.id}' requires a direction`);
164
+ }
165
+
166
+ return {
167
+ id: c.id,
168
+ name: c.name,
169
+ weight,
170
+ direction: c.direction
171
+ };
172
+ });
173
+
174
+ // Normalize weights to sum to 1.0
175
+ const totalWeight = resolvedCriteria.reduce((sum, c) => sum + c.weight, 0);
176
+ if (totalWeight === 0) {
177
+ throw new CompilationError('Criteria weights must not all be zero');
178
+ }
179
+ for (const c of resolvedCriteria) {
180
+ c.weight = c.weight / totalWeight;
181
+ }
182
+
183
+ return {
184
+ id: ruleSet.id,
185
+ name: ruleSet.name,
186
+ mode: 'ranking',
187
+ strategy: 'head-to-head',
188
+ entityType: ruleSet.entityType,
189
+ criteria: resolvedCriteria,
190
+ config: {
191
+ direction: (ruleSet as any).config?.direction ?? 'highest-first'
192
+ }
193
+ };
194
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Ranking Engine
3
+ *
4
+ * Comparative scoring engine that ranks N entities relative to each other.
5
+ * Supports score-based ranking, Elo ratings, and head-to-head comparison.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const engine = new RankingEngine();
10
+ * engine.add({ type: 'Vendor', data: { id: 'v1', revenue: 500000 } });
11
+ * engine.add({ type: 'Vendor', data: { id: 'v2', revenue: 120000 } });
12
+ *
13
+ * const result = engine.execute(compiledRanking);
14
+ * console.log(result.rankings[0]); // { rank: 1, entityId: 'v1', percentileLabel: 'top-1%', ... }
15
+ * ```
16
+ */
17
+
18
+ import {
19
+ WorkingMemory,
20
+ Fact,
21
+ FactInput,
22
+ FactChange
23
+ } from '../../core';
24
+
25
+ import type { TierDefinition } from '../scoring/types';
26
+
27
+ import type {
28
+ CompiledRankingRuleSet,
29
+ RankingOptions,
30
+ RankingResult
31
+ } from './types';
32
+
33
+ import { RankingExecutor } from './strategy';
34
+
35
+ export class RankingEngine {
36
+ private wm: WorkingMemory;
37
+ private strategy: RankingExecutor;
38
+
39
+ constructor(workingMemory?: WorkingMemory) {
40
+ this.wm = workingMemory ?? new WorkingMemory();
41
+ this.strategy = new RankingExecutor();
42
+ }
43
+
44
+ // ========================================
45
+ // IWorkingMemory Implementation
46
+ // ========================================
47
+
48
+ add<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
49
+ return this.wm.add(input);
50
+ }
51
+
52
+ remove(factId: string): Fact | undefined {
53
+ return this.wm.remove(factId);
54
+ }
55
+
56
+ update<T = Record<string, any>>(input: FactInput<T>): Fact<T> {
57
+ return this.wm.update(input);
58
+ }
59
+
60
+ get(factId: string): Fact | undefined {
61
+ return this.wm.get(factId);
62
+ }
63
+
64
+ getByType(type: string): Fact[] {
65
+ return this.wm.getByType(type);
66
+ }
67
+
68
+ getAll(): Fact[] {
69
+ return this.wm.getAll();
70
+ }
71
+
72
+ has(factId: string): boolean {
73
+ return this.wm.has(factId);
74
+ }
75
+
76
+ size(): number {
77
+ return this.wm.size();
78
+ }
79
+
80
+ clear(): void {
81
+ this.wm.clear();
82
+ }
83
+
84
+ getChanges(): FactChange[] {
85
+ return this.wm.getChanges();
86
+ }
87
+
88
+ clearChanges(): void {
89
+ this.wm.clearChanges();
90
+ }
91
+
92
+ // ========================================
93
+ // Engine Execution
94
+ // ========================================
95
+
96
+ /**
97
+ * Execute a ranking ruleset.
98
+ *
99
+ * Scores all entities of the configured type and produces
100
+ * a ranked list with percentiles and optional tier/movement tracking.
101
+ *
102
+ * @param ruleSet - Compiled ranking ruleset
103
+ * @param options - Runtime options (previousRankings for movement, onRank callback)
104
+ * @returns Ranking result with sorted entities
105
+ */
106
+ execute<T extends TierDefinition = TierDefinition>(
107
+ ruleSet: CompiledRankingRuleSet<T>,
108
+ options: RankingOptions = {}
109
+ ): RankingResult<T> {
110
+ return this.strategy.run(ruleSet, this.wm, options);
111
+ }
112
+
113
+ // ========================================
114
+ // Utility Methods
115
+ // ========================================
116
+
117
+ getWorkingMemory(): WorkingMemory {
118
+ return this.wm;
119
+ }
120
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Ranking Engine — Comparative Entity Ranking
3
+ */
4
+
5
+ // Types
6
+ export type {
7
+ RankingStrategy,
8
+ RankingDirection,
9
+ PercentileLabel,
10
+ Movement,
11
+ KFactorPreset,
12
+ RankingCriterion,
13
+ CompiledRankingCriterion,
14
+ ScoreRankingConfig,
15
+ EloConfig,
16
+ HeadToHeadConfig,
17
+ ScoreRankingRuleSet,
18
+ EloRankingRuleSet,
19
+ HeadToHeadRankingRuleSet,
20
+ RankingRuleSet,
21
+ CompiledScoreRankingRuleSet,
22
+ CompiledEloRankingRuleSet,
23
+ CompiledHeadToHeadRankingRuleSet,
24
+ CompiledRankingRuleSet,
25
+ PreviousRanking,
26
+ RankingOptions,
27
+ RankedEntity,
28
+ RankingResult
29
+ } from './types';
30
+
31
+ // Constants & utilities
32
+ export {
33
+ K_FACTOR_VALUES,
34
+ isKFactorPreset,
35
+ resolvePercentileLabel,
36
+ resolveMovement
37
+ } from './types';
38
+
39
+ // Compiler
40
+ export { compileRankingRuleSet } from './compiler';
41
+
42
+ // Strategy
43
+ export { RankingExecutor, rankingStrategy } from './strategy';
44
+
45
+ // Engine
46
+ export { RankingEngine } from './engine';