@higher.archi/boe 1.0.0 → 1.0.2

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.
@@ -8,6 +8,7 @@ import {
8
8
  CompiledBaseRule,
9
9
  CompiledBaseRuleSet,
10
10
  CompiledCondition,
11
+ Condition,
11
12
  Expression,
12
13
  BindingContext
13
14
  } from '../../core';
@@ -93,6 +94,155 @@ export type ScoringTierMatch = {
93
94
  threshold: number;
94
95
  };
95
96
 
97
+ // ========================================
98
+ // Category Types
99
+ // ========================================
100
+
101
+ /**
102
+ * Category definition for grouping related scoring signals
103
+ *
104
+ * Categories provide a two-level weight hierarchy: category weights
105
+ * control how much each group contributes to the total, and signal
106
+ * weights within each category control relative importance of rules.
107
+ */
108
+ export type ScoringCategory = {
109
+ id: string;
110
+ name?: string;
111
+ weight: number; // 0-1, normalized internally
112
+ minSignalRatio?: number; // default 0.5 — below this, category is excluded
113
+ };
114
+
115
+ /**
116
+ * Per-category result breakdown showing how each category contributed
117
+ */
118
+ export type CategoryResult = {
119
+ id: string;
120
+ name?: string;
121
+ weight: number; // original configured weight
122
+ effectiveWeight: number; // after redistribution from excluded categories
123
+ rawScore: number; // weighted avg of signal scores within category
124
+ weightedContribution: number; // effectiveWeight × rawScore
125
+ signalsWithData: string[]; // rule IDs that fired
126
+ signalsMissing: string[]; // rule IDs that didn't fire
127
+ excluded: boolean;
128
+ };
129
+
130
+ // ========================================
131
+ // Override Types
132
+ // ========================================
133
+
134
+ /**
135
+ * Override effect types
136
+ */
137
+ export type OverrideEffect =
138
+ | 'force_tier'
139
+ | 'floor_tier'
140
+ | 'cap_tier'
141
+ | 'exclude'
142
+ | 'adjust_score'
143
+ | 'clamp_score'
144
+ | 'force_score';
145
+
146
+ /**
147
+ * Base override fields shared by all effects
148
+ */
149
+ type BaseOverride = {
150
+ id: string;
151
+ condition: Condition;
152
+ };
153
+
154
+ /**
155
+ * Force tier — override to exact tier, optionally replace score
156
+ */
157
+ export type ForceTierOverride = BaseOverride & {
158
+ effect: 'force_tier';
159
+ targetTier: string;
160
+ score?: number;
161
+ };
162
+
163
+ /**
164
+ * Floor tier — set minimum tier (bump up if below)
165
+ */
166
+ export type FloorTierOverride = BaseOverride & {
167
+ effect: 'floor_tier';
168
+ targetTier: string;
169
+ };
170
+
171
+ /**
172
+ * Cap tier — set maximum tier (cap down if above)
173
+ */
174
+ export type CapTierOverride = BaseOverride & {
175
+ effect: 'cap_tier';
176
+ targetTier: string;
177
+ };
178
+
179
+ /**
180
+ * Exclude — nullify the entire scoring result
181
+ */
182
+ export type ExcludeOverride = BaseOverride & {
183
+ effect: 'exclude';
184
+ };
185
+
186
+ /**
187
+ * Adjust score — add/subtract from total score
188
+ */
189
+ export type AdjustScoreOverride = BaseOverride & {
190
+ effect: 'adjust_score';
191
+ value: number;
192
+ };
193
+
194
+ /**
195
+ * Clamp score — enforce floor/ceiling on the score
196
+ */
197
+ export type ClampScoreOverride = BaseOverride & {
198
+ effect: 'clamp_score';
199
+ min?: number;
200
+ max?: number;
201
+ };
202
+
203
+ /**
204
+ * Force score — replace the total score entirely
205
+ */
206
+ export type ForceScoreOverride = BaseOverride & {
207
+ effect: 'force_score';
208
+ score: number | Expression;
209
+ };
210
+
211
+ /**
212
+ * Discriminated union of all override definitions
213
+ *
214
+ * Overrides are evaluated post-scoring in order — first match wins.
215
+ * Each effect type has its own required fields enforced by TypeScript.
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const overrides: OverrideDefinition[] = [
220
+ * { id: 'closed-won', condition: ['equals', '$deal.stage', 'Closed Won'], effect: 'force_tier', targetTier: 'strong', score: 1000 },
221
+ * { id: 'fraud-flag', condition: ['equals', '$app.fraud', true], effect: 'exclude' },
222
+ * { id: 'vip-bonus', condition: ['equals', '$customer.vip', true], effect: 'adjust_score', value: 50 }
223
+ * ];
224
+ * ```
225
+ */
226
+ export type OverrideDefinition =
227
+ | ForceTierOverride
228
+ | FloorTierOverride
229
+ | CapTierOverride
230
+ | ExcludeOverride
231
+ | AdjustScoreOverride
232
+ | ClampScoreOverride
233
+ | ForceScoreOverride;
234
+
235
+ /**
236
+ * Override result — attached to ScoringResult when an override fires
237
+ */
238
+ export type OverrideResult = {
239
+ id: string;
240
+ effect: OverrideEffect;
241
+ originalScore: number;
242
+ originalTier?: ScoringTierMatch;
243
+ applied: true;
244
+ };
245
+
96
246
  // ========================================
97
247
  // Source Types (User-Defined)
98
248
  // ========================================
@@ -111,6 +261,7 @@ export type ScoringAction = {
111
261
  * Scoring rule - uses 'then' for scoring properties
112
262
  */
113
263
  export type ScoringRule = BaseRule & {
264
+ category?: string;
114
265
  then: ScoringAction;
115
266
  };
116
267
 
@@ -133,6 +284,8 @@ export type ScoringConfig = {
133
284
  outputBounds?: OutputBounds; // Optional output scaling
134
285
  baseScore?: number; // Starting score before rule contributions (default: 0)
135
286
  tiers?: TierDefinition[]; // Optional tier definitions for score classification
287
+ categories?: ScoringCategory[]; // Optional category grouping for two-level weight hierarchy
288
+ overrides?: OverrideDefinition[]; // Optional post-scoring overrides evaluated in order (first match wins)
136
289
  };
137
290
 
138
291
  // ========================================
@@ -154,9 +307,23 @@ export type CompiledScoringAction = {
154
307
  * Compiled scoring rule
155
308
  */
156
309
  export type CompiledScoringRule = CompiledBaseRule & {
310
+ category?: string;
157
311
  action: CompiledScoringAction;
158
312
  };
159
313
 
314
+ /**
315
+ * Compiled override — replaces source Condition with CompiledCondition
316
+ * Each variant preserves its effect-specific fields
317
+ */
318
+ export type CompiledOverrideDefinition =
319
+ | (Omit<ForceTierOverride, 'condition'> & { condition: CompiledCondition })
320
+ | (Omit<FloorTierOverride, 'condition'> & { condition: CompiledCondition })
321
+ | (Omit<CapTierOverride, 'condition'> & { condition: CompiledCondition })
322
+ | (Omit<ExcludeOverride, 'condition'> & { condition: CompiledCondition })
323
+ | (Omit<AdjustScoreOverride, 'condition'> & { condition: CompiledCondition })
324
+ | (Omit<ClampScoreOverride, 'condition'> & { condition: CompiledCondition })
325
+ | (Omit<ForceScoreOverride, 'condition'> & { condition: CompiledCondition });
326
+
160
327
  /**
161
328
  * Compiled scoring ruleset
162
329
  */
@@ -164,6 +331,7 @@ export type CompiledScoringRuleSet = CompiledBaseRuleSet & {
164
331
  mode: 'scoring';
165
332
  rules: CompiledScoringRule[];
166
333
  config: ScoringConfig;
334
+ overrides?: CompiledOverrideDefinition[];
167
335
  };
168
336
 
169
337
  // ========================================
@@ -197,5 +365,7 @@ export type ScoringResult = {
197
365
  fired: string[]; // Rules that matched and contributed
198
366
  iterations: 1; // Scoring is always one-pass
199
367
  tier?: ScoringTierMatch; // Matched tier (if tiers configured)
368
+ categoryBreakdown?: CategoryResult[]; // Per-category breakdown (if categories configured)
369
+ override?: OverrideResult; // Override that fired (if any)
200
370
  executionTimeMs: number; // Processing time in milliseconds
201
371
  };
package/src/index.ts CHANGED
@@ -92,7 +92,20 @@ export type {
92
92
  ScoringResult,
93
93
  ScoreFunctionRegistry,
94
94
  ScoringMethod,
95
- OutputBounds
95
+ OutputBounds,
96
+ ScoringCategory,
97
+ CategoryResult,
98
+ OverrideEffect,
99
+ OverrideDefinition,
100
+ ForceTierOverride,
101
+ FloorTierOverride,
102
+ CapTierOverride,
103
+ ExcludeOverride,
104
+ AdjustScoreOverride,
105
+ ClampScoreOverride,
106
+ ForceScoreOverride,
107
+ OverrideResult,
108
+ CompiledOverrideDefinition
96
109
  } from './engines/scoring';
97
110
 
98
111
  // Sequential