@reekon-tools/boldr-utils 1.3.7 → 1.3.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.
@@ -1,11 +1,32 @@
1
1
  import { Units } from '../types/firestore.js';
2
+ export interface EnhancedMapping {
3
+ id: string;
4
+ type?: 'measurement' | 'formula' | 'column';
5
+ }
6
+ export interface FormulaDefinition {
7
+ id: string;
8
+ name: string;
9
+ expression: string;
10
+ variableToColumnMap: Record<string, EnhancedMapping>;
11
+ }
12
+ export interface FormulaEvaluationOptions {
13
+ expression: string;
14
+ mappings: Record<string, EnhancedMapping | string>;
15
+ valueMap?: Record<string, number>;
16
+ unit: Units;
17
+ formulas?: FormulaDefinition[];
18
+ scope?: Record<string, number>;
19
+ }
20
+ export declare function evaluateFormula({ expression, mappings, valueMap, unit, formulas, scope, }: FormulaEvaluationOptions): number | string | null;
2
21
  export declare function createFormulaScope({ mappings, valueMap, unit, }: {
3
22
  mappings: Record<string, string>;
4
23
  valueMap: Record<string, number>;
5
24
  unit: Units;
6
25
  }): Record<string, number>;
7
- export declare function evaluateFormula({ expression, scope, unit, }: {
8
- expression: string;
9
- scope: Record<string, number>;
26
+ export declare function createEnhancedFormulaScope({ mappings, valueMap, unit, formulas, }: {
27
+ mappings: Record<string, EnhancedMapping | string>;
28
+ valueMap: Record<string, number>;
10
29
  unit: Units;
11
- }): number | null;
30
+ formulas?: FormulaDefinition[];
31
+ }): Record<string, number>;
32
+ export declare function clearFormulaCache(): void;
@@ -1,5 +1,7 @@
1
1
  import { Units } from '../types/firestore.js';
2
2
  import { compile, unit as mathUnit } from 'mathjs';
3
+ // Cache for evaluated formulas to improve performance
4
+ const formulaCache = new Map();
3
5
  function normalizeUnitForMathJS(unit) {
4
6
  switch (unit) {
5
7
  case Units.Centimeters:
@@ -23,6 +25,135 @@ function normalizeUnitForMathJS(unit) {
23
25
  return 'mm'; // fallback
24
26
  }
25
27
  }
28
+ // Normalize mapping to EnhancedMapping format
29
+ function normalizeMapping(mapping) {
30
+ if (typeof mapping === 'string') {
31
+ // Legacy format: just the ID
32
+ console.log('šŸ”§ Converting legacy string mapping:', mapping);
33
+ return { id: mapping, type: 'measurement' };
34
+ }
35
+ // New format: ensure type defaults to 'measurement' and handle 'column' as 'measurement'
36
+ const normalizedType = mapping.type === 'column' ? 'measurement' : mapping.type || 'measurement';
37
+ console.log('šŸ”§ Normalizing mapping:', mapping, '→', {
38
+ id: mapping.id,
39
+ type: normalizedType,
40
+ });
41
+ return { id: mapping.id, type: normalizedType };
42
+ }
43
+ // Unified formula evaluation function
44
+ export function evaluateFormula({ expression, mappings, valueMap, unit, formulas = [], scope, }) {
45
+ // Legacy mode: if scope is provided, use it directly
46
+ if (scope) {
47
+ console.log('šŸ“œ Using legacy scope mode');
48
+ try {
49
+ const normalizedUnit = normalizeUnitForMathJS(unit);
50
+ const compiled = compile(expression);
51
+ const result = compiled.evaluate(scope);
52
+ const asUnit = mathUnit(result, normalizedUnit);
53
+ if (!asUnit.equalBase(mathUnit('1 um'))) {
54
+ throw new Error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
55
+ }
56
+ return Math.round(asUnit.toNumber('um'));
57
+ }
58
+ catch (err) {
59
+ console.error(`Failed to evaluate formula`, err);
60
+ return null;
61
+ }
62
+ }
63
+ // Enhanced mode: use mappings and valueMap
64
+ if (!valueMap) {
65
+ console.error('āŒ valueMap is required when not using scope');
66
+ return 'Error: valueMap is required';
67
+ }
68
+ const evaluationStack = new Set(); // For circular dependency detection
69
+ const cache = new Map(); // Local cache for this evaluation session
70
+ function evaluateNestedFormula(formulaId, currentExpression, currentMappings) {
71
+ console.log(`šŸ” Evaluating nested formula: ${formulaId}`);
72
+ console.log(`šŸ“ Expression: "${currentExpression}"`);
73
+ console.log('šŸ—ŗļø Current mappings:', currentMappings);
74
+ // Check for circular dependency
75
+ if (evaluationStack.has(formulaId)) {
76
+ throw new Error(`Circular dependency detected involving formula: ${formulaId}`);
77
+ }
78
+ // Check cache first
79
+ const cacheKey = `${formulaId}_${unit}`;
80
+ if (cache.has(cacheKey)) {
81
+ console.log(`šŸ’¾ Using cached result for ${formulaId}`);
82
+ return cache.get(cacheKey);
83
+ }
84
+ evaluationStack.add(formulaId);
85
+ try {
86
+ const scope = {};
87
+ const normalizedUnit = normalizeUnitForMathJS(unit);
88
+ console.log(`šŸ”„ Converting to unit: ${unit} → ${normalizedUnit}`);
89
+ // Build scope by resolving each variable
90
+ for (const [variable, rawMapping] of Object.entries(currentMappings)) {
91
+ console.log(`\nšŸ”§ Processing variable "${variable}":`, rawMapping);
92
+ const mapping = normalizeMapping(rawMapping);
93
+ console.log(`āœ… Normalized mapping:`, mapping);
94
+ if (mapping.type === 'measurement') {
95
+ // Handle measurement/column reference
96
+ console.log(`šŸ“Š Looking up measurement ID "${mapping.id}" in valueMap`);
97
+ const micrometers = valueMap[mapping.id] ?? 0;
98
+ console.log(`šŸ“ˆ Found value: ${micrometers} micrometers`);
99
+ if (micrometers === 0) {
100
+ console.warn(`āš ļø Zero or missing value for mapping ID "${mapping.id}"`);
101
+ console.log('šŸ—‚ļø Available valueMap keys:', Object.keys(valueMap));
102
+ }
103
+ const valueInUnit = mathUnit(micrometers, 'um').toNumber(normalizedUnit);
104
+ console.log(`šŸ”¢ Converted to ${normalizedUnit}: ${valueInUnit}`);
105
+ scope[variable] = valueInUnit;
106
+ console.log(`āœ… Set scope["${variable}"] = ${valueInUnit}`);
107
+ }
108
+ else if (mapping.type === 'formula') {
109
+ // Handle formula reference - recursively evaluate
110
+ console.log(`šŸ”„ Recursively evaluating formula "${mapping.id}"`);
111
+ const referencedFormula = formulas.find((f) => f.id === mapping.id);
112
+ if (!referencedFormula) {
113
+ throw new Error(`Referenced formula not found: ${mapping.id}`);
114
+ }
115
+ // Recursively evaluate the referenced formula
116
+ const nestedResult = evaluateNestedFormula(referencedFormula.id, referencedFormula.expression, referencedFormula.variableToColumnMap);
117
+ // Convert result to current unit
118
+ const resultInUnit = mathUnit(nestedResult, 'um').toNumber(normalizedUnit);
119
+ console.log(`šŸ”¢ Nested formula result in ${normalizedUnit}: ${resultInUnit}`);
120
+ scope[variable] = resultInUnit;
121
+ console.log(`āœ… Set scope["${variable}"] = ${resultInUnit} (from formula)`);
122
+ }
123
+ }
124
+ console.log('\nšŸŽÆ Final scope built:', scope);
125
+ // Evaluate the formula expression
126
+ console.log(`šŸ“Š Compiling expression: "${currentExpression}"`);
127
+ const compiled = compile(currentExpression);
128
+ console.log('🧮 Evaluating with scope:', scope);
129
+ const result = compiled.evaluate(scope);
130
+ console.log('šŸ“‹ Raw evaluation result:', result, typeof result);
131
+ const asUnit = mathUnit(result, normalizedUnit);
132
+ console.log('šŸ“ As unit object:', asUnit.toString());
133
+ if (!asUnit.equalBase(mathUnit('1 um'))) {
134
+ throw new Error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
135
+ }
136
+ const finalResult = Math.round(asUnit.toNumber('um'));
137
+ console.log(`šŸŽÆ Final result: ${finalResult} micrometers`);
138
+ // Cache the result
139
+ cache.set(cacheKey, finalResult);
140
+ return finalResult;
141
+ }
142
+ finally {
143
+ evaluationStack.delete(formulaId);
144
+ }
145
+ }
146
+ try {
147
+ const result = evaluateNestedFormula('root', expression, mappings);
148
+ console.log(`šŸŽ‰ Formula evaluation completed successfully: ${result}`);
149
+ return result;
150
+ }
151
+ catch (err) {
152
+ console.error('āŒ Formula evaluation failed:', err);
153
+ return err instanceof Error ? err.message : 'Error';
154
+ }
155
+ }
156
+ // Legacy createFormulaScope function for backward compatibility
26
157
  export function createFormulaScope({ mappings, valueMap, unit, }) {
27
158
  const scope = {};
28
159
  const normalizedUnit = normalizeUnitForMathJS(unit);
@@ -34,19 +165,44 @@ export function createFormulaScope({ mappings, valueMap, unit, }) {
34
165
  }
35
166
  return scope;
36
167
  }
37
- export function evaluateFormula({ expression, scope, unit, }) {
38
- try {
39
- const normalizedUnit = normalizeUnitForMathJS(unit);
40
- const compiled = compile(expression);
41
- const result = compiled.evaluate(scope);
42
- const asUnit = mathUnit(result, normalizedUnit);
43
- if (!asUnit.equalBase(mathUnit('1 um'))) {
44
- throw new Error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
168
+ // Enhanced createFormulaScope for new mapping structure
169
+ export function createEnhancedFormulaScope({ mappings, valueMap, unit, formulas = [], }) {
170
+ const scope = {};
171
+ const normalizedUnit = normalizeUnitForMathJS(unit);
172
+ for (const [variable, rawMapping] of Object.entries(mappings)) {
173
+ const mapping = normalizeMapping(rawMapping);
174
+ if (mapping.type === 'measurement') {
175
+ const micrometers = valueMap[mapping.id] ?? 0;
176
+ const valueInUnit = mathUnit(micrometers, 'um').toNumber(normalizedUnit);
177
+ scope[variable] = valueInUnit;
178
+ }
179
+ else if (mapping.type === 'formula') {
180
+ // Find and evaluate the referenced formula
181
+ const referencedFormula = formulas.find((f) => f.id === mapping.id);
182
+ if (referencedFormula) {
183
+ const result = evaluateFormula({
184
+ expression: referencedFormula.expression,
185
+ mappings: referencedFormula.variableToColumnMap,
186
+ valueMap,
187
+ unit,
188
+ formulas,
189
+ });
190
+ if (typeof result === 'number') {
191
+ const valueInUnit = mathUnit(result, 'um').toNumber(normalizedUnit);
192
+ scope[variable] = valueInUnit;
193
+ }
194
+ else {
195
+ scope[variable] = 0; // Error case
196
+ }
197
+ }
198
+ else {
199
+ scope[variable] = 0; // Formula not found
200
+ }
45
201
  }
46
- return Math.round(asUnit.toNumber('um'));
47
- }
48
- catch (err) {
49
- console.error(`Failed to evaluate formula`, err);
50
- return null;
51
202
  }
203
+ return scope;
204
+ }
205
+ // Utility function to clear formula cache
206
+ export function clearFormulaCache() {
207
+ formulaCache.clear();
52
208
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { evaluateFormula, createFormulaScope, } from './formulas/evaluateFormula.js';
1
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
2
2
  export { convertMicrometers } from './utils/micrometersToUnit.js';
3
3
  export { parseMeasurement } from './utils/parseMeasurement.js';
4
4
  export { useParseMeasurement } from './hooks/useParseMeasurement.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { evaluateFormula, createFormulaScope, } from './formulas/evaluateFormula.js';
1
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
2
2
  export { convertMicrometers } from './utils/micrometersToUnit.js';
3
3
  export { parseMeasurement } from './utils/parseMeasurement.js';
4
4
  export { useParseMeasurement } from './hooks/useParseMeasurement.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",