@higher.archi/boe 1.0.30 → 1.0.31

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 (226) hide show
  1. package/dist/core/evaluation/decay.d.ts +13 -2
  2. package/dist/core/evaluation/decay.d.ts.map +1 -1
  3. package/dist/core/evaluation/decay.js +24 -0
  4. package/dist/core/evaluation/decay.js.map +1 -1
  5. package/dist/core/types/rule.d.ts +25 -1
  6. package/dist/core/types/rule.d.ts.map +1 -1
  7. package/dist/core/types/rule.js +28 -0
  8. package/dist/core/types/rule.js.map +1 -1
  9. package/dist/engines/bayesian/index.d.ts +1 -1
  10. package/dist/engines/bayesian/index.d.ts.map +1 -1
  11. package/dist/engines/bayesian/index.js +2 -1
  12. package/dist/engines/bayesian/index.js.map +1 -1
  13. package/dist/engines/bayesian/types.d.ts +10 -1
  14. package/dist/engines/bayesian/types.d.ts.map +1 -1
  15. package/dist/engines/bayesian/types.js +16 -1
  16. package/dist/engines/bayesian/types.js.map +1 -1
  17. package/dist/engines/constraint/types.d.ts +36 -5
  18. package/dist/engines/constraint/types.d.ts.map +1 -1
  19. package/dist/engines/constraint/types.js +44 -1
  20. package/dist/engines/constraint/types.js.map +1 -1
  21. package/dist/engines/decay/index.d.ts +1 -1
  22. package/dist/engines/decay/index.d.ts.map +1 -1
  23. package/dist/engines/decay/index.js +5 -1
  24. package/dist/engines/decay/index.js.map +1 -1
  25. package/dist/engines/decay/types.d.ts +26 -4
  26. package/dist/engines/decay/types.d.ts.map +1 -1
  27. package/dist/engines/decay/types.js +30 -1
  28. package/dist/engines/decay/types.js.map +1 -1
  29. package/dist/engines/defeasible/index.d.ts +1 -1
  30. package/dist/engines/defeasible/index.d.ts.map +1 -1
  31. package/dist/engines/defeasible/index.js +8 -1
  32. package/dist/engines/defeasible/index.js.map +1 -1
  33. package/dist/engines/defeasible/types.d.ts +40 -5
  34. package/dist/engines/defeasible/types.d.ts.map +1 -1
  35. package/dist/engines/defeasible/types.js +56 -1
  36. package/dist/engines/defeasible/types.js.map +1 -1
  37. package/dist/engines/ensemble/index.d.ts +1 -0
  38. package/dist/engines/ensemble/index.d.ts.map +1 -1
  39. package/dist/engines/ensemble/index.js +5 -1
  40. package/dist/engines/ensemble/index.js.map +1 -1
  41. package/dist/engines/ensemble/types.d.ts +17 -2
  42. package/dist/engines/ensemble/types.d.ts.map +1 -1
  43. package/dist/engines/ensemble/types.js +23 -0
  44. package/dist/engines/ensemble/types.js.map +1 -1
  45. package/dist/engines/expert/index.d.ts +1 -1
  46. package/dist/engines/expert/index.d.ts.map +1 -1
  47. package/dist/engines/expert/index.js +3 -1
  48. package/dist/engines/expert/index.js.map +1 -1
  49. package/dist/engines/expert/types.d.ts +11 -1
  50. package/dist/engines/expert/types.d.ts.map +1 -1
  51. package/dist/engines/expert/types.js +18 -1
  52. package/dist/engines/expert/types.js.map +1 -1
  53. package/dist/engines/fuzzy/fuzzy.types.d.ts +65 -8
  54. package/dist/engines/fuzzy/fuzzy.types.d.ts.map +1 -1
  55. package/dist/engines/fuzzy/fuzzy.types.js +89 -1
  56. package/dist/engines/fuzzy/fuzzy.types.js.map +1 -1
  57. package/dist/engines/loyalty/index.d.ts +1 -0
  58. package/dist/engines/loyalty/index.d.ts.map +1 -1
  59. package/dist/engines/loyalty/index.js +8 -1
  60. package/dist/engines/loyalty/index.js.map +1 -1
  61. package/dist/engines/loyalty/types.d.ts +36 -5
  62. package/dist/engines/loyalty/types.d.ts.map +1 -1
  63. package/dist/engines/loyalty/types.js +40 -0
  64. package/dist/engines/loyalty/types.js.map +1 -1
  65. package/dist/engines/menu-engineering/compiler.d.ts +11 -0
  66. package/dist/engines/menu-engineering/compiler.d.ts.map +1 -0
  67. package/dist/engines/menu-engineering/compiler.js +119 -0
  68. package/dist/engines/menu-engineering/compiler.js.map +1 -0
  69. package/dist/engines/menu-engineering/engine.d.ts +32 -0
  70. package/dist/engines/menu-engineering/engine.d.ts.map +1 -0
  71. package/dist/engines/menu-engineering/engine.js +40 -0
  72. package/dist/engines/menu-engineering/engine.js.map +1 -0
  73. package/dist/engines/menu-engineering/index.d.ts +9 -0
  74. package/dist/engines/menu-engineering/index.d.ts.map +1 -0
  75. package/dist/engines/menu-engineering/index.js +21 -0
  76. package/dist/engines/menu-engineering/index.js.map +1 -0
  77. package/dist/engines/menu-engineering/strategy.d.ts +18 -0
  78. package/dist/engines/menu-engineering/strategy.d.ts.map +1 -0
  79. package/dist/engines/menu-engineering/strategy.js +318 -0
  80. package/dist/engines/menu-engineering/strategy.js.map +1 -0
  81. package/dist/engines/menu-engineering/types.d.ts +187 -0
  82. package/dist/engines/menu-engineering/types.d.ts.map +1 -0
  83. package/dist/engines/menu-engineering/types.js +27 -0
  84. package/dist/engines/menu-engineering/types.js.map +1 -0
  85. package/dist/engines/monte-carlo/index.d.ts +1 -1
  86. package/dist/engines/monte-carlo/index.d.ts.map +1 -1
  87. package/dist/engines/monte-carlo/index.js +5 -1
  88. package/dist/engines/monte-carlo/index.js.map +1 -1
  89. package/dist/engines/monte-carlo/types.d.ts +16 -1
  90. package/dist/engines/monte-carlo/types.d.ts.map +1 -1
  91. package/dist/engines/monte-carlo/types.js +23 -1
  92. package/dist/engines/monte-carlo/types.js.map +1 -1
  93. package/dist/engines/negotiation/index.d.ts +1 -0
  94. package/dist/engines/negotiation/index.d.ts.map +1 -1
  95. package/dist/engines/negotiation/index.js +7 -1
  96. package/dist/engines/negotiation/index.js.map +1 -1
  97. package/dist/engines/negotiation/types.d.ts +23 -4
  98. package/dist/engines/negotiation/types.d.ts.map +1 -1
  99. package/dist/engines/negotiation/types.js +27 -0
  100. package/dist/engines/negotiation/types.js.map +1 -1
  101. package/dist/engines/prediction/index.d.ts +1 -1
  102. package/dist/engines/prediction/index.d.ts.map +1 -1
  103. package/dist/engines/prediction/index.js +6 -1
  104. package/dist/engines/prediction/index.js.map +1 -1
  105. package/dist/engines/prediction/types.d.ts +35 -5
  106. package/dist/engines/prediction/types.d.ts.map +1 -1
  107. package/dist/engines/prediction/types.js +39 -1
  108. package/dist/engines/prediction/types.js.map +1 -1
  109. package/dist/engines/pricing/index.d.ts +2 -2
  110. package/dist/engines/pricing/index.d.ts.map +1 -1
  111. package/dist/engines/pricing/index.js +3 -1
  112. package/dist/engines/pricing/index.js.map +1 -1
  113. package/dist/engines/pricing/types.d.ts +15 -1
  114. package/dist/engines/pricing/types.d.ts.map +1 -1
  115. package/dist/engines/pricing/types.js +16 -1
  116. package/dist/engines/pricing/types.js.map +1 -1
  117. package/dist/engines/ranking/index.d.ts +1 -1
  118. package/dist/engines/ranking/index.d.ts.map +1 -1
  119. package/dist/engines/ranking/index.js +6 -1
  120. package/dist/engines/ranking/index.js.map +1 -1
  121. package/dist/engines/ranking/types.d.ts +32 -5
  122. package/dist/engines/ranking/types.d.ts.map +1 -1
  123. package/dist/engines/ranking/types.js +36 -1
  124. package/dist/engines/ranking/types.js.map +1 -1
  125. package/dist/engines/recipe-costing/compiler.d.ts +11 -0
  126. package/dist/engines/recipe-costing/compiler.d.ts.map +1 -0
  127. package/dist/engines/recipe-costing/compiler.js +177 -0
  128. package/dist/engines/recipe-costing/compiler.js.map +1 -0
  129. package/dist/engines/recipe-costing/engine.d.ts +32 -0
  130. package/dist/engines/recipe-costing/engine.d.ts.map +1 -0
  131. package/dist/engines/recipe-costing/engine.js +40 -0
  132. package/dist/engines/recipe-costing/engine.js.map +1 -0
  133. package/dist/engines/recipe-costing/index.d.ts +9 -0
  134. package/dist/engines/recipe-costing/index.d.ts.map +1 -0
  135. package/dist/engines/recipe-costing/index.js +21 -0
  136. package/dist/engines/recipe-costing/index.js.map +1 -0
  137. package/dist/engines/recipe-costing/strategy.d.ts +20 -0
  138. package/dist/engines/recipe-costing/strategy.d.ts.map +1 -0
  139. package/dist/engines/recipe-costing/strategy.js +265 -0
  140. package/dist/engines/recipe-costing/strategy.js.map +1 -0
  141. package/dist/engines/recipe-costing/types.d.ts +213 -0
  142. package/dist/engines/recipe-costing/types.d.ts.map +1 -0
  143. package/dist/engines/recipe-costing/types.js +36 -0
  144. package/dist/engines/recipe-costing/types.js.map +1 -0
  145. package/dist/engines/scoring/index.d.ts +1 -1
  146. package/dist/engines/scoring/index.d.ts.map +1 -1
  147. package/dist/engines/scoring/index.js +3 -1
  148. package/dist/engines/scoring/index.js.map +1 -1
  149. package/dist/engines/scoring/types.d.ts +8 -1
  150. package/dist/engines/scoring/types.d.ts.map +1 -1
  151. package/dist/engines/scoring/types.js +18 -1
  152. package/dist/engines/scoring/types.js.map +1 -1
  153. package/dist/engines/sentiment/index.d.ts +1 -1
  154. package/dist/engines/sentiment/index.d.ts.map +1 -1
  155. package/dist/engines/sentiment/index.js +3 -1
  156. package/dist/engines/sentiment/index.js.map +1 -1
  157. package/dist/engines/sentiment/types.d.ts +13 -2
  158. package/dist/engines/sentiment/types.d.ts.map +1 -1
  159. package/dist/engines/sentiment/types.js +17 -1
  160. package/dist/engines/sentiment/types.js.map +1 -1
  161. package/dist/engines/state-machine/index.d.ts +1 -1
  162. package/dist/engines/state-machine/index.d.ts.map +1 -1
  163. package/dist/engines/state-machine/index.js +5 -1
  164. package/dist/engines/state-machine/index.js.map +1 -1
  165. package/dist/engines/state-machine/types.d.ts +7 -0
  166. package/dist/engines/state-machine/types.d.ts.map +1 -1
  167. package/dist/engines/state-machine/types.js +14 -0
  168. package/dist/engines/state-machine/types.js.map +1 -1
  169. package/dist/engines/utility/index.d.ts +2 -2
  170. package/dist/engines/utility/index.d.ts.map +1 -1
  171. package/dist/engines/utility/index.js +4 -1
  172. package/dist/engines/utility/index.js.map +1 -1
  173. package/dist/engines/utility/types.d.ts +21 -3
  174. package/dist/engines/utility/types.d.ts.map +1 -1
  175. package/dist/engines/utility/types.js +37 -1
  176. package/dist/engines/utility/types.js.map +1 -1
  177. package/dist/index.d.ts +28 -22
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +73 -3
  180. package/dist/index.js.map +1 -1
  181. package/package.json +1 -1
  182. package/src/core/evaluation/decay.ts +13 -2
  183. package/src/core/types/rule.ts +25 -1
  184. package/src/engines/bayesian/index.ts +1 -0
  185. package/src/engines/bayesian/types.ts +10 -8
  186. package/src/engines/constraint/types.ts +40 -11
  187. package/src/engines/decay/index.ts +4 -0
  188. package/src/engines/decay/types.ts +26 -4
  189. package/src/engines/defeasible/index.ts +7 -0
  190. package/src/engines/defeasible/types.ts +42 -18
  191. package/src/engines/ensemble/index.ts +6 -0
  192. package/src/engines/ensemble/types.ts +17 -13
  193. package/src/engines/expert/index.ts +1 -0
  194. package/src/engines/expert/types.ts +11 -9
  195. package/src/engines/fuzzy/fuzzy.types.ts +65 -31
  196. package/src/engines/loyalty/index.ts +9 -0
  197. package/src/engines/loyalty/types.ts +36 -5
  198. package/src/engines/menu-engineering/compiler.ts +145 -0
  199. package/src/engines/menu-engineering/engine.ts +48 -0
  200. package/src/engines/menu-engineering/index.ts +47 -0
  201. package/src/engines/menu-engineering/strategy.ts +414 -0
  202. package/src/engines/menu-engineering/types.ts +242 -0
  203. package/src/engines/monte-carlo/index.ts +1 -0
  204. package/src/engines/monte-carlo/types.ts +16 -21
  205. package/src/engines/negotiation/index.ts +8 -0
  206. package/src/engines/negotiation/types.ts +23 -4
  207. package/src/engines/prediction/index.ts +5 -0
  208. package/src/engines/prediction/types.ts +35 -5
  209. package/src/engines/pricing/index.ts +3 -1
  210. package/src/engines/pricing/types.ts +17 -1
  211. package/src/engines/ranking/index.ts +5 -0
  212. package/src/engines/ranking/types.ts +32 -11
  213. package/src/engines/recipe-costing/compiler.ts +219 -0
  214. package/src/engines/recipe-costing/engine.ts +48 -0
  215. package/src/engines/recipe-costing/index.ts +48 -0
  216. package/src/engines/recipe-costing/strategy.ts +357 -0
  217. package/src/engines/recipe-costing/types.ts +269 -0
  218. package/src/engines/scoring/index.ts +2 -0
  219. package/src/engines/scoring/types.ts +8 -6
  220. package/src/engines/sentiment/index.ts +2 -0
  221. package/src/engines/sentiment/types.ts +13 -2
  222. package/src/engines/state-machine/index.ts +3 -0
  223. package/src/engines/state-machine/types.ts +8 -0
  224. package/src/engines/utility/index.ts +5 -0
  225. package/src/engines/utility/types.ts +23 -3
  226. package/src/index.ts +146 -8
@@ -59,27 +59,22 @@ export const ITERATION_PRESETS: Record<IterationPreset, number> = {
59
59
  * Estimate type determines the probability distribution shape.
60
60
  * Multiple aliases supported for different audiences.
61
61
  */
62
- export type EstimateType =
63
- // Triangular distribution (three-point estimate)
64
- | 'best-likely-worst' // Project management terminology
65
- | 'low-mid-high' // Simple/casual
66
- | 'min-expected-max' // Statistical terminology
67
- | 'optimistic-realistic-pessimistic' // Risk analysis terminology
68
-
69
- // Normal distribution (bell curve)
70
- | 'typically-around' // Layperson-friendly
71
- | 'bell-curve' // Technical
72
- | 'normal' // Statistical
73
-
74
- // Uniform distribution (equal probability)
75
- | 'anywhere-from' // Layperson-friendly
76
- | 'uniform' // Technical
77
- | 'any-between' // Alternative
78
-
79
- // Discrete distribution (weighted choices)
80
- | 'one-of' // Layperson-friendly
81
- | 'weighted' // Technical
82
- | 'choice'; // Alternative
62
+ export const EstimateTypes = {
63
+ bestLikelyWorst: 'best-likely-worst',
64
+ lowMidHigh: 'low-mid-high',
65
+ minExpectedMax: 'min-expected-max',
66
+ optimisticRealisticPessimistic: 'optimistic-realistic-pessimistic',
67
+ typicallyAround: 'typically-around',
68
+ bellCurve: 'bell-curve',
69
+ normal: 'normal',
70
+ anywhereFrom: 'anywhere-from',
71
+ uniform: 'uniform',
72
+ anyBetween: 'any-between',
73
+ oneOf: 'one-of',
74
+ weighted: 'weighted',
75
+ choice: 'choice',
76
+ } as const;
77
+ export type EstimateType = typeof EstimateTypes[keyof typeof EstimateTypes];
83
78
 
84
79
  // ========================================
85
80
  // Variable Definitions
@@ -32,6 +32,14 @@ export type {
32
32
  NegotiationOptions
33
33
  } from './types';
34
34
 
35
+ // Constants
36
+ export {
37
+ NegotiationStrategies,
38
+ ConcessionStyles,
39
+ DealOutcomes,
40
+ DimensionDirections
41
+ } from './types';
42
+
35
43
  // Compiler
36
44
  export { compileNegotiationRuleSet } from './compiler';
37
45
 
@@ -13,16 +13,35 @@ import type { SemanticPriority } from '../utility/types';
13
13
  // ========================================
14
14
 
15
15
  /** Negotiation algorithm to use */
16
- export type NegotiationStrategy = 'zopa-analysis' | 'concession-planning' | 'package-optimization';
16
+ export const NegotiationStrategies = {
17
+ zopaAnalysis: 'zopa-analysis',
18
+ concessionPlanning: 'concession-planning',
19
+ packageOptimization: 'package-optimization',
20
+ } as const;
21
+ export type NegotiationStrategy = typeof NegotiationStrategies[keyof typeof NegotiationStrategies];
17
22
 
18
23
  /** Concession aggressiveness style */
19
- export type ConcessionStyle = 'aggressive' | 'moderate' | 'collaborative';
24
+ export const ConcessionStyles = {
25
+ aggressive: 'aggressive',
26
+ moderate: 'moderate',
27
+ collaborative: 'collaborative',
28
+ } as const;
29
+ export type ConcessionStyle = typeof ConcessionStyles[keyof typeof ConcessionStyles];
20
30
 
21
31
  /** Outcome of a negotiation analysis */
22
- export type DealOutcome = 'agreed' | 'no-zopa' | 'batna-preferred';
32
+ export const DealOutcomes = {
33
+ agreed: 'agreed',
34
+ noZopa: 'no-zopa',
35
+ batnaPreferred: 'batna-preferred',
36
+ } as const;
37
+ export type DealOutcome = typeof DealOutcomes[keyof typeof DealOutcomes];
23
38
 
24
39
  /** Whether higher or lower values are preferred for a dimension */
25
- export type DimensionDirection = 'higher-is-better' | 'lower-is-better';
40
+ export const DimensionDirections = {
41
+ higherIsBetter: 'higher-is-better',
42
+ lowerIsBetter: 'lower-is-better',
43
+ } as const;
44
+ export type DimensionDirection = typeof DimensionDirections[keyof typeof DimensionDirections];
26
45
 
27
46
  // ========================================
28
47
  // Dimension & Party Types
@@ -31,6 +31,11 @@ export type {
31
31
 
32
32
  // Constants & utilities
33
33
  export {
34
+ PredictionStrategies,
35
+ TrendDirections,
36
+ SmoothingPresets,
37
+ HorizonPresets,
38
+ ConfidenceBrackets,
34
39
  SMOOTHING_PRESET_VALUES,
35
40
  HORIZON_PRESET_VALUES,
36
41
  isSmoothingPreset,
@@ -11,19 +11,49 @@
11
11
  // ========================================
12
12
 
13
13
  /** Prediction algorithm to use */
14
- export type PredictionStrategy = 'linear-regression' | 'exponential-smoothing' | 'weighted-moving-average';
14
+ export const PredictionStrategies = {
15
+ linearRegression: 'linear-regression',
16
+ exponentialSmoothing: 'exponential-smoothing',
17
+ weightedMovingAverage: 'weighted-moving-average',
18
+ } as const;
19
+ export type PredictionStrategy = typeof PredictionStrategies[keyof typeof PredictionStrategies];
15
20
 
16
21
  /** Trend direction derived from slope */
17
- export type TrendDirection = 'rising' | 'stable' | 'declining' | 'insufficient-data';
22
+ export const TrendDirections = {
23
+ rising: 'rising',
24
+ stable: 'stable',
25
+ declining: 'declining',
26
+ insufficientData: 'insufficient-data',
27
+ } as const;
28
+ export type TrendDirection = typeof TrendDirections[keyof typeof TrendDirections];
18
29
 
19
30
  /** Exponential smoothing sensitivity presets */
20
- export type SmoothingPreset = 'responsive' | 'balanced' | 'smooth';
31
+ export const SmoothingPresets = {
32
+ responsive: 'responsive',
33
+ balanced: 'balanced',
34
+ smooth: 'smooth',
35
+ } as const;
36
+ export type SmoothingPreset = typeof SmoothingPresets[keyof typeof SmoothingPresets];
21
37
 
22
38
  /** Forecast horizon presets */
23
- export type HorizonPreset = '1-day' | '1-week' | '1-month' | '3-months' | '6-months' | '1-year';
39
+ export const HorizonPresets = {
40
+ oneDay: '1-day',
41
+ oneWeek: '1-week',
42
+ oneMonth: '1-month',
43
+ threeMonths: '3-months',
44
+ sixMonths: '6-months',
45
+ oneYear: '1-year',
46
+ } as const;
47
+ export type HorizonPreset = typeof HorizonPresets[keyof typeof HorizonPresets];
24
48
 
25
49
  /** Human-readable confidence bracket */
26
- export type ConfidenceBracket = 'high' | 'moderate' | 'low' | 'insufficient-data';
50
+ export const ConfidenceBrackets = {
51
+ high: 'high',
52
+ moderate: 'moderate',
53
+ low: 'low',
54
+ insufficientData: 'insufficient-data',
55
+ } as const;
56
+ export type ConfidenceBracket = typeof ConfidenceBrackets[keyof typeof ConfidenceBrackets];
27
57
 
28
58
  // ========================================
29
59
  // Preset Value Maps
@@ -72,6 +72,8 @@ export type {
72
72
  ProductType,
73
73
 
74
74
  // Pricing mode types
75
+ PricingModeName,
76
+ MetricFormat,
75
77
  FlatPricing,
76
78
  GraduatedTier,
77
79
  GraduatedPricing,
@@ -129,7 +131,7 @@ export type {
129
131
  } from './types';
130
132
 
131
133
  // Currency utilities
132
- export { CURRENCIES, getCurrencyInfo } from './types';
134
+ export { PricingModeNames, MetricFormats, CURRENCIES, getCurrencyInfo } from './types';
133
135
 
134
136
  // Compiler
135
137
  export { compilePricingRuleSet, compilePricingRule } from './compiler';
@@ -288,6 +288,15 @@ export type FormulaPricing = {
288
288
  calculate: Operand;
289
289
  };
290
290
 
291
+ export const PricingModeNames = {
292
+ flat: 'flat',
293
+ graduated: 'graduated',
294
+ volume: 'volume',
295
+ multiMeter: 'multi-meter',
296
+ formula: 'formula',
297
+ } as const;
298
+ export type PricingModeName = typeof PricingModeNames[keyof typeof PricingModeNames];
299
+
291
300
  /**
292
301
  * All pricing modes.
293
302
  */
@@ -377,6 +386,13 @@ export type Adjustment = DiscountAdjustment | MinimumAdjustment | MaximumAdjustm
377
386
  // Metrics
378
387
  // ========================================
379
388
 
389
+ export const MetricFormats = {
390
+ currency: 'currency',
391
+ percent: 'percent',
392
+ number: 'number',
393
+ } as const;
394
+ export type MetricFormat = typeof MetricFormats[keyof typeof MetricFormats];
395
+
380
396
  /**
381
397
  * Computed metric for comparison.
382
398
  */
@@ -388,7 +404,7 @@ export type Metric = {
388
404
  /** Expression to compute the metric value */
389
405
  calculate: Operand;
390
406
  /** Display format */
391
- format?: 'currency' | 'percent' | 'number';
407
+ format?: MetricFormat;
392
408
  };
393
409
 
394
410
  // ========================================
@@ -30,6 +30,11 @@ export type {
30
30
 
31
31
  // Constants & utilities
32
32
  export {
33
+ RankingStrategies,
34
+ RankingDirections,
35
+ PercentileLabels,
36
+ Movements,
37
+ KFactorPresets,
33
38
  K_FACTOR_VALUES,
34
39
  isKFactorPreset,
35
40
  resolvePercentileLabel,
@@ -18,25 +18,46 @@ import type { SemanticPriority } from '../utility/types';
18
18
  // ========================================
19
19
 
20
20
  /** Ranking algorithm to use */
21
- export type RankingStrategy = 'score' | 'elo' | 'head-to-head';
21
+ export const RankingStrategies = {
22
+ score: 'score',
23
+ elo: 'elo',
24
+ headToHead: 'head-to-head',
25
+ } as const;
26
+ export type RankingStrategy = typeof RankingStrategies[keyof typeof RankingStrategies];
22
27
 
23
28
  /** Sort direction for rankings */
24
- export type RankingDirection = 'highest-first' | 'lowest-first';
29
+ export const RankingDirections = {
30
+ highestFirst: 'highest-first',
31
+ lowestFirst: 'lowest-first',
32
+ } as const;
33
+ export type RankingDirection = typeof RankingDirections[keyof typeof RankingDirections];
25
34
 
26
35
  /** Human-readable percentile bracket */
27
- export type PercentileLabel =
28
- | 'top-1%'
29
- | 'top-5%'
30
- | 'top-10%'
31
- | 'top-25%'
32
- | 'top-50%'
33
- | 'bottom-half';
36
+ export const PercentileLabels = {
37
+ top1: 'top-1%',
38
+ top5: 'top-5%',
39
+ top10: 'top-10%',
40
+ top25: 'top-25%',
41
+ top50: 'top-50%',
42
+ bottomHalf: 'bottom-half',
43
+ } as const;
44
+ export type PercentileLabel = typeof PercentileLabels[keyof typeof PercentileLabels];
34
45
 
35
46
  /** Rank movement since previous ranking */
36
- export type Movement = 'climbed' | 'steady' | 'dropped';
47
+ export const Movements = {
48
+ climbed: 'climbed',
49
+ steady: 'steady',
50
+ dropped: 'dropped',
51
+ } as const;
52
+ export type Movement = typeof Movements[keyof typeof Movements];
37
53
 
38
54
  /** Elo K-factor presets controlling rating volatility */
39
- export type KFactorPreset = 'volatile' | 'standard' | 'stable';
55
+ export const KFactorPresets = {
56
+ volatile: 'volatile',
57
+ standard: 'standard',
58
+ stable: 'stable',
59
+ } as const;
60
+ export type KFactorPreset = typeof KFactorPresets[keyof typeof KFactorPresets];
40
61
 
41
62
  export const K_FACTOR_VALUES: Record<KFactorPreset, number> = {
42
63
  'volatile': 32, // New entities, fast adjustment
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Recipe Costing Engine Compiler
3
+ *
4
+ * Validates recipe costing rulesets and resolves defaults.
5
+ */
6
+
7
+ import { CompilationError } from '../../core/errors';
8
+
9
+ import type {
10
+ RecipeCostingRuleSet,
11
+ CompiledRecipeCostingRuleSet,
12
+ CompiledPlateCostRuleSet,
13
+ CompiledMarginAnalysisRuleSet,
14
+ CompiledCostImpactRuleSet
15
+ } from './types';
16
+
17
+ /**
18
+ * Compile and validate a recipe costing ruleset.
19
+ */
20
+ export function compileRecipeCostingRuleSet(
21
+ ruleSet: RecipeCostingRuleSet
22
+ ): CompiledRecipeCostingRuleSet {
23
+ if (!ruleSet.id) {
24
+ throw new CompilationError('Recipe costing ruleset requires an id');
25
+ }
26
+
27
+ if (ruleSet.mode !== 'recipe-costing') {
28
+ throw new CompilationError(`Expected mode 'recipe-costing', got '${ruleSet.mode}'`);
29
+ }
30
+
31
+ if (!ruleSet.ingredients || ruleSet.ingredients.length === 0) {
32
+ throw new CompilationError('Recipe costing ruleset requires at least one ingredient');
33
+ }
34
+
35
+ if (!ruleSet.recipes || ruleSet.recipes.length === 0) {
36
+ throw new CompilationError('Recipe costing ruleset requires at least one recipe');
37
+ }
38
+
39
+ // Validate unique ingredient IDs
40
+ const ingredientIds = new Set<string>();
41
+ for (const ing of ruleSet.ingredients) {
42
+ if (!ing.id) {
43
+ throw new CompilationError('Each ingredient requires an id');
44
+ }
45
+ if (ingredientIds.has(ing.id)) {
46
+ throw new CompilationError(`Duplicate ingredient id: '${ing.id}'`);
47
+ }
48
+ if (ing.purchaseQuantity <= 0) {
49
+ throw new CompilationError(`Ingredient '${ing.id}' purchaseQuantity must be positive`);
50
+ }
51
+ if (ing.purchaseCost <= 0) {
52
+ throw new CompilationError(`Ingredient '${ing.id}' purchaseCost must be positive`);
53
+ }
54
+ if (ing.yieldFactor !== undefined && (ing.yieldFactor <= 0 || ing.yieldFactor > 1)) {
55
+ throw new CompilationError(`Ingredient '${ing.id}' yieldFactor must be in (0, 1]`);
56
+ }
57
+ if (ing.wastePct !== undefined && (ing.wastePct < 0 || ing.wastePct >= 100)) {
58
+ throw new CompilationError(`Ingredient '${ing.id}' wastePct must be in [0, 100)`);
59
+ }
60
+ ingredientIds.add(ing.id);
61
+ }
62
+
63
+ // Validate unique recipe IDs
64
+ const recipeIds = new Set<string>();
65
+ for (const recipe of ruleSet.recipes) {
66
+ if (!recipe.id) {
67
+ throw new CompilationError('Each recipe requires an id');
68
+ }
69
+ if (recipeIds.has(recipe.id)) {
70
+ throw new CompilationError(`Duplicate recipe id: '${recipe.id}'`);
71
+ }
72
+ recipeIds.add(recipe.id);
73
+ }
74
+
75
+ // Validate recipe components reference valid ingredients or recipes
76
+ for (const recipe of ruleSet.recipes) {
77
+ for (const comp of recipe.components) {
78
+ if (comp.ingredientId && comp.subRecipeId) {
79
+ throw new CompilationError(
80
+ `Recipe '${recipe.id}' component references both ingredientId and subRecipeId. Must be one or the other.`
81
+ );
82
+ }
83
+ if (!comp.ingredientId && !comp.subRecipeId) {
84
+ throw new CompilationError(
85
+ `Recipe '${recipe.id}' component must reference an ingredientId or subRecipeId`
86
+ );
87
+ }
88
+ if (comp.ingredientId && !ingredientIds.has(comp.ingredientId)) {
89
+ throw new CompilationError(
90
+ `Recipe '${recipe.id}' references unknown ingredient: '${comp.ingredientId}'`
91
+ );
92
+ }
93
+ if (comp.subRecipeId && !recipeIds.has(comp.subRecipeId)) {
94
+ throw new CompilationError(
95
+ `Recipe '${recipe.id}' references unknown sub-recipe: '${comp.subRecipeId}'`
96
+ );
97
+ }
98
+ }
99
+ }
100
+
101
+ // Detect circular sub-recipe references
102
+ detectCircularRecipes(ruleSet.recipes);
103
+
104
+ switch (ruleSet.strategy) {
105
+ case 'plate-cost':
106
+ return compilePlateCost(ruleSet);
107
+ case 'margin-analysis':
108
+ return compileMarginAnalysis(ruleSet);
109
+ case 'cost-impact':
110
+ return compileCostImpact(ruleSet);
111
+ default:
112
+ throw new CompilationError(`Unknown recipe costing strategy: '${(ruleSet as any).strategy}'`);
113
+ }
114
+ }
115
+
116
+ function compilePlateCost(
117
+ ruleSet: RecipeCostingRuleSet & { strategy: 'plate-cost' }
118
+ ): CompiledPlateCostRuleSet {
119
+ return {
120
+ id: ruleSet.id,
121
+ name: ruleSet.name,
122
+ mode: 'recipe-costing',
123
+ strategy: 'plate-cost',
124
+ ingredients: ruleSet.ingredients,
125
+ recipes: ruleSet.recipes,
126
+ config: {
127
+ targetFoodCostPct: ruleSet.config?.targetFoodCostPct ?? 30
128
+ }
129
+ };
130
+ }
131
+
132
+ function compileMarginAnalysis(
133
+ ruleSet: RecipeCostingRuleSet & { strategy: 'margin-analysis' }
134
+ ): CompiledMarginAnalysisRuleSet {
135
+ return {
136
+ id: ruleSet.id,
137
+ name: ruleSet.name,
138
+ mode: 'recipe-costing',
139
+ strategy: 'margin-analysis',
140
+ ingredients: ruleSet.ingredients,
141
+ recipes: ruleSet.recipes,
142
+ config: {
143
+ targetFoodCostPct: ruleSet.config?.targetFoodCostPct ?? 30,
144
+ flagThreshold: ruleSet.config?.flagThreshold ?? 35
145
+ }
146
+ };
147
+ }
148
+
149
+ function compileCostImpact(
150
+ ruleSet: RecipeCostingRuleSet & { strategy: 'cost-impact' }
151
+ ): CompiledCostImpactRuleSet {
152
+ if (!ruleSet.config || !ruleSet.config.changes || ruleSet.config.changes.length === 0) {
153
+ throw new CompilationError('Cost-impact strategy requires at least one cost change');
154
+ }
155
+
156
+ const ingredientIds = new Set(ruleSet.ingredients.map(i => i.id));
157
+ for (const change of ruleSet.config.changes) {
158
+ if (!ingredientIds.has(change.ingredientId)) {
159
+ throw new CompilationError(`Cost change references unknown ingredient: '${change.ingredientId}'`);
160
+ }
161
+ if (change.newCost !== undefined && change.percentChange !== undefined) {
162
+ throw new CompilationError(
163
+ `Cost change for '${change.ingredientId}' must have newCost or percentChange, not both`
164
+ );
165
+ }
166
+ if (change.newCost === undefined && change.percentChange === undefined) {
167
+ throw new CompilationError(
168
+ `Cost change for '${change.ingredientId}' must have newCost or percentChange`
169
+ );
170
+ }
171
+ }
172
+
173
+ return {
174
+ id: ruleSet.id,
175
+ name: ruleSet.name,
176
+ mode: 'recipe-costing',
177
+ strategy: 'cost-impact',
178
+ ingredients: ruleSet.ingredients,
179
+ recipes: ruleSet.recipes,
180
+ config: {
181
+ changes: ruleSet.config.changes,
182
+ targetFoodCostPct: ruleSet.config.targetFoodCostPct ?? 30
183
+ }
184
+ };
185
+ }
186
+
187
+ function detectCircularRecipes(recipes: { id: string; components: { subRecipeId?: string }[] }[]): void {
188
+ const adjacency = new Map<string, string[]>();
189
+ for (const recipe of recipes) {
190
+ const deps: string[] = [];
191
+ for (const comp of recipe.components) {
192
+ if (comp.subRecipeId) {
193
+ deps.push(comp.subRecipeId);
194
+ }
195
+ }
196
+ adjacency.set(recipe.id, deps);
197
+ }
198
+
199
+ const visited = new Set<string>();
200
+ const inStack = new Set<string>();
201
+
202
+ function dfs(id: string): void {
203
+ if (inStack.has(id)) {
204
+ throw new CompilationError(`Circular sub-recipe dependency detected involving '${id}'`);
205
+ }
206
+ if (visited.has(id)) return;
207
+ visited.add(id);
208
+ inStack.add(id);
209
+ const deps = adjacency.get(id) ?? [];
210
+ for (const dep of deps) {
211
+ dfs(dep);
212
+ }
213
+ inStack.delete(id);
214
+ }
215
+
216
+ for (const recipe of recipes) {
217
+ dfs(recipe.id);
218
+ }
219
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Recipe Costing Engine
3
+ *
4
+ * Computes plate cost by rolling up ingredient costs through a bill of
5
+ * materials with yield factors and waste percentages. Supports margin
6
+ * analysis and cost-impact modeling when ingredient prices change.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const engine = new RecipeCostingEngine();
11
+ *
12
+ * const result = engine.execute(compiledRecipeCosting);
13
+ * // result.recipes -> per-recipe cost breakdowns
14
+ * ```
15
+ */
16
+
17
+ import type {
18
+ CompiledRecipeCostingRuleSet,
19
+ RecipeCostingOptions,
20
+ RecipeCostingResult
21
+ } from './types';
22
+
23
+ import { RecipeCostingExecutor } from './strategy';
24
+
25
+ export class RecipeCostingEngine {
26
+ private strategy: RecipeCostingExecutor;
27
+
28
+ constructor() {
29
+ this.strategy = new RecipeCostingExecutor();
30
+ }
31
+
32
+ /**
33
+ * Execute a recipe costing ruleset.
34
+ *
35
+ * Computes plate costs, analyzes margins, or models cost impacts
36
+ * depending on the strategy.
37
+ *
38
+ * @param ruleSet - Compiled recipe costing ruleset
39
+ * @param options - Runtime options
40
+ * @returns Recipe costing result
41
+ */
42
+ execute(
43
+ ruleSet: CompiledRecipeCostingRuleSet,
44
+ options: RecipeCostingOptions = {}
45
+ ): RecipeCostingResult {
46
+ return this.strategy.run(ruleSet, options);
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Recipe Costing Engine -- Plate Cost & Margin Analysis
3
+ */
4
+
5
+ // Types
6
+ export type {
7
+ RecipeCostingStrategy,
8
+ MeasurementUnit,
9
+ Ingredient,
10
+ RecipeComponent,
11
+ Recipe,
12
+ CostChange,
13
+ PlateCostConfig,
14
+ MarginAnalysisConfig,
15
+ CostImpactConfig,
16
+ PlateCostRuleSet,
17
+ MarginAnalysisRuleSet,
18
+ CostImpactRuleSet,
19
+ RecipeCostingRuleSet,
20
+ CompiledPlateCostRuleSet,
21
+ CompiledMarginAnalysisRuleSet,
22
+ CompiledCostImpactRuleSet,
23
+ CompiledRecipeCostingRuleSet,
24
+ IngredientCostBreakdown,
25
+ SubRecipeCostBreakdown,
26
+ RecipeCostResult,
27
+ CostImpactItem,
28
+ PlateCostResult,
29
+ MarginAnalysisResult,
30
+ CostImpactResult,
31
+ RecipeCostingResult,
32
+ RecipeCostingOptions
33
+ } from './types';
34
+
35
+ // Constants
36
+ export {
37
+ RecipeCostingStrategies,
38
+ MeasurementUnits
39
+ } from './types';
40
+
41
+ // Compiler
42
+ export { compileRecipeCostingRuleSet } from './compiler';
43
+
44
+ // Strategy
45
+ export { RecipeCostingExecutor, recipeCostingStrategy } from './strategy';
46
+
47
+ // Engine
48
+ export { RecipeCostingEngine } from './engine';