@reekon-tools/boldr-utils 1.4.14 → 1.4.16
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.
- package/dist/formulas/calculateFormula.d.ts +2 -0
- package/dist/formulas/calculateFormula.js +131 -0
- package/dist/formulas/evaluateFormula.js +129 -51
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/firestore.d.ts +62 -9
- package/dist/types/firestore.js +33 -0
- package/dist/utils/micrometersToUnit.d.ts +2 -2
- package/dist/utils/micrometersToUnit.js +69 -60
- package/package.json +1 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ColumnConfig, DecimalTolerance, FractionalTolerance, Formula, Measurement, Units } from '../types/firestore.js';
|
|
2
|
+
export declare const calculateFormula: (formula: Formula, formulas: Formula[], columns: Record<string, string>, tableConfig: ColumnConfig[], measurements: Measurement[], unit: Units, fractionalTolerance: FractionalTolerance, decimalTolerance: DecimalTolerance) => string | number | null;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ColumnType, } from '../types/firestore.js';
|
|
2
|
+
import { evaluateFormula } from './evaluateFormula.js';
|
|
3
|
+
import { convertMicrometers } from '../utils/micrometersToUnit.js';
|
|
4
|
+
function parseMixedNumber(str) {
|
|
5
|
+
const parts = str.trim().split(' ');
|
|
6
|
+
let whole = 0;
|
|
7
|
+
let fraction = 0;
|
|
8
|
+
if (parts.length === 2) {
|
|
9
|
+
whole = parseFloat(parts[0]);
|
|
10
|
+
const [num, den] = parts[1].split('/').map(Number);
|
|
11
|
+
fraction = num / den;
|
|
12
|
+
}
|
|
13
|
+
else if (parts[0].includes('/')) {
|
|
14
|
+
const [num, den] = parts[0].split('/').map(Number);
|
|
15
|
+
fraction = num / den;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
whole = parseFloat(parts[0]);
|
|
19
|
+
}
|
|
20
|
+
return whole + fraction;
|
|
21
|
+
}
|
|
22
|
+
export const calculateFormula = (formula, formulas, columns, tableConfig, measurements, unit, fractionalTolerance, decimalTolerance) => {
|
|
23
|
+
// Convert Firestore Map to plain object if necessary
|
|
24
|
+
let variableToColumnMap = {};
|
|
25
|
+
if (formula.variableToColumnMap) {
|
|
26
|
+
if (formula.variableToColumnMap instanceof Map) {
|
|
27
|
+
// Convert Firestore Map to plain object
|
|
28
|
+
variableToColumnMap = Object.fromEntries(formula.variableToColumnMap);
|
|
29
|
+
}
|
|
30
|
+
else if (typeof formula.variableToColumnMap === 'object') {
|
|
31
|
+
// Already a plain object
|
|
32
|
+
variableToColumnMap = formula.variableToColumnMap;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (Object.keys(variableToColumnMap).length === 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Build valueMap from group.columns and measurementMap
|
|
39
|
+
const valueMap = {};
|
|
40
|
+
for (const [columnId, measurementId] of Object.entries(columns)) {
|
|
41
|
+
const column = tableConfig.find((c) => c.id === columnId);
|
|
42
|
+
if (!column)
|
|
43
|
+
continue;
|
|
44
|
+
if (column.type === ColumnType.Measurement) {
|
|
45
|
+
const measurement = measurements.find((m) => m.id === measurementId);
|
|
46
|
+
if (!measurement)
|
|
47
|
+
continue;
|
|
48
|
+
const { value, unit: displayUnit } = convertMicrometers(measurement.value, unit, fractionalTolerance, decimalTolerance);
|
|
49
|
+
valueMap[columnId] = parseMixedNumber(value);
|
|
50
|
+
}
|
|
51
|
+
else if (column.type === ColumnType.Angle) {
|
|
52
|
+
const measurement = measurements.find((m) => m.id === measurementId);
|
|
53
|
+
if (!measurement)
|
|
54
|
+
continue;
|
|
55
|
+
valueMap[columnId] = measurement.value;
|
|
56
|
+
}
|
|
57
|
+
else if (column.type === ColumnType.ConversionTable) {
|
|
58
|
+
const value = measurementId.split(':')[1];
|
|
59
|
+
valueMap[columnId] = parseFloat(value);
|
|
60
|
+
}
|
|
61
|
+
else if (column.type === ColumnType.Number) {
|
|
62
|
+
valueMap[columnId] = parseFloat(measurementId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Convert formulas to the expected FormulaDefinition format
|
|
66
|
+
const formulaDefinitions = formulas
|
|
67
|
+
.filter((f) => f.variableToColumnMap) // Only include formulas with new schema
|
|
68
|
+
.map((f) => {
|
|
69
|
+
const mappings = {};
|
|
70
|
+
// Convert Firestore Map to plain object if necessary
|
|
71
|
+
let fVariableToColumnMap = {};
|
|
72
|
+
if (f.variableToColumnMap instanceof Map) {
|
|
73
|
+
fVariableToColumnMap = Object.fromEntries(f.variableToColumnMap);
|
|
74
|
+
}
|
|
75
|
+
else if (typeof f.variableToColumnMap === 'object') {
|
|
76
|
+
fVariableToColumnMap = f.variableToColumnMap;
|
|
77
|
+
}
|
|
78
|
+
for (const [variable, mapping] of Object.entries(fVariableToColumnMap)) {
|
|
79
|
+
if (typeof mapping === 'string') {
|
|
80
|
+
// Handle transition period - old format in new structure
|
|
81
|
+
mappings[variable] = { id: mapping, type: 'column' };
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// New format: { id: string; type: 'column' | 'formula' }
|
|
85
|
+
mappings[variable] = mapping;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
id: f.id,
|
|
90
|
+
name: f.name,
|
|
91
|
+
expression: f.expression,
|
|
92
|
+
variableToColumnMap: mappings,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
// Convert the current formula's mappings
|
|
96
|
+
const currentMappings = {};
|
|
97
|
+
for (const [variable, mapping] of Object.entries(variableToColumnMap)) {
|
|
98
|
+
if (typeof mapping === 'string') {
|
|
99
|
+
currentMappings[variable] = { id: mapping, type: 'column' };
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
currentMappings[variable] = mapping;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Validate that all required inputs are filled out
|
|
106
|
+
const missingInputs = [];
|
|
107
|
+
for (const [variable, mapping] of Object.entries(currentMappings)) {
|
|
108
|
+
const mappingObj = typeof mapping === 'string'
|
|
109
|
+
? { id: mapping, type: 'column' }
|
|
110
|
+
: mapping;
|
|
111
|
+
// Only check column references (formula references are handled separately)
|
|
112
|
+
if (mappingObj.type === 'column' || !mappingObj.type) {
|
|
113
|
+
const columnId = mappingObj.id;
|
|
114
|
+
if (!(columnId in valueMap)) {
|
|
115
|
+
missingInputs.push(variable);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (missingInputs.length > 0) {
|
|
120
|
+
console.warn(`Not all inputs are filled out. Missing values for: ${missingInputs.join(', ')}`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const result = evaluateFormula({
|
|
124
|
+
expression: formula.expression,
|
|
125
|
+
mappings: currentMappings,
|
|
126
|
+
valueMap: valueMap,
|
|
127
|
+
unit: unit,
|
|
128
|
+
formulas: formulaDefinitions,
|
|
129
|
+
});
|
|
130
|
+
return result;
|
|
131
|
+
};
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { Units } from '../types/firestore.js';
|
|
2
|
-
import {
|
|
2
|
+
import { create, all } from 'mathjs';
|
|
3
|
+
// Create a custom mathjs instance with degree-based trigonometric functions
|
|
4
|
+
const math = create(all);
|
|
5
|
+
// Helper to convert degrees to radians
|
|
6
|
+
const degToRad = (degrees) => (degrees * Math.PI) / 180;
|
|
7
|
+
// Import degree-based trigonometric functions
|
|
8
|
+
math.import({
|
|
9
|
+
sin: (x) => Math.sin(degToRad(x)),
|
|
10
|
+
cos: (x) => Math.cos(degToRad(x)),
|
|
11
|
+
tan: (x) => Math.tan(degToRad(x)),
|
|
12
|
+
asin: (x) => (Math.asin(x) * 180) / Math.PI,
|
|
13
|
+
acos: (x) => (Math.acos(x) * 180) / Math.PI,
|
|
14
|
+
atan: (x) => (Math.atan(x) * 180) / Math.PI,
|
|
15
|
+
atan2: (y, x) => (Math.atan2(y, x) * 180) / Math.PI,
|
|
16
|
+
}, { override: true });
|
|
17
|
+
const compile = math.compile.bind(math);
|
|
18
|
+
const mathUnit = math.unit.bind(math);
|
|
3
19
|
// Cache for evaluated formulas to improve performance
|
|
4
20
|
const formulaCache = new Map();
|
|
5
21
|
function normalizeUnitForMathJS(unit) {
|
|
@@ -29,31 +45,72 @@ function normalizeUnitForMathJS(unit) {
|
|
|
29
45
|
function normalizeMapping(mapping) {
|
|
30
46
|
if (typeof mapping === 'string') {
|
|
31
47
|
// Legacy format: just the ID
|
|
32
|
-
console.log('🔧 Converting legacy string mapping:', mapping);
|
|
33
48
|
return { id: mapping, type: 'measurement' };
|
|
34
49
|
}
|
|
35
50
|
// New format: ensure type defaults to 'measurement' and handle 'column' as 'measurement'
|
|
36
51
|
const normalizedType = mapping.type === 'column' ? 'measurement' : mapping.type || 'measurement';
|
|
37
|
-
console.log('🔧 Normalizing mapping:', mapping, '→', {
|
|
38
|
-
id: mapping.id,
|
|
39
|
-
type: normalizedType,
|
|
40
|
-
});
|
|
41
52
|
return { id: mapping.id, type: normalizedType };
|
|
42
53
|
}
|
|
43
54
|
// Unified formula evaluation function
|
|
44
55
|
export function evaluateFormula({ expression, mappings, valueMap, unit, formulas = [], scope, }) {
|
|
45
56
|
// Legacy mode: if scope is provided, use it directly
|
|
46
57
|
if (scope) {
|
|
47
|
-
console.log('📜 Using legacy scope mode');
|
|
48
58
|
try {
|
|
49
59
|
const normalizedUnit = normalizeUnitForMathJS(unit);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
let compiled;
|
|
61
|
+
try {
|
|
62
|
+
compiled = compile(expression);
|
|
63
|
+
}
|
|
64
|
+
catch (compileErr) {
|
|
65
|
+
console.error(`Failed to compile formula expression`, compileErr);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = compiled.evaluate(scope);
|
|
71
|
+
}
|
|
72
|
+
catch (evalErr) {
|
|
73
|
+
// Handle divide by zero and other math errors
|
|
74
|
+
const errorMessage = evalErr instanceof Error ? evalErr.message : String(evalErr);
|
|
75
|
+
if (errorMessage.includes('divide') ||
|
|
76
|
+
errorMessage.includes('division') ||
|
|
77
|
+
errorMessage.includes('/') ||
|
|
78
|
+
errorMessage.includes('Division by zero') ||
|
|
79
|
+
errorMessage.includes('Cannot divide by zero')) {
|
|
80
|
+
console.error(`Division by zero in formula`, evalErr);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
console.error(`Failed to evaluate formula`, evalErr);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
// Check for invalid results (Infinity, NaN)
|
|
87
|
+
if (Number.isNaN(result)) {
|
|
88
|
+
console.error(`Formula result is NaN`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (!Number.isFinite(result)) {
|
|
92
|
+
console.error(`Formula result is Infinity`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
let asUnit;
|
|
96
|
+
try {
|
|
97
|
+
asUnit = mathUnit(result, normalizedUnit);
|
|
98
|
+
}
|
|
99
|
+
catch (unitErr) {
|
|
100
|
+
console.error(`Failed to convert result to unit`, unitErr);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
53
103
|
if (!asUnit.equalBase(mathUnit('1 um'))) {
|
|
54
|
-
|
|
104
|
+
console.error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
|
|
105
|
+
return null;
|
|
55
106
|
}
|
|
56
|
-
|
|
107
|
+
const finalResult = Math.round(asUnit.toNumber('um'));
|
|
108
|
+
// Check if final result is valid
|
|
109
|
+
if (!Number.isFinite(finalResult)) {
|
|
110
|
+
console.error(`Formula result is not a finite number`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return finalResult;
|
|
57
114
|
}
|
|
58
115
|
catch (err) {
|
|
59
116
|
console.error(`Failed to evaluate formula`, err);
|
|
@@ -68,9 +125,6 @@ export function evaluateFormula({ expression, mappings, valueMap, unit, formulas
|
|
|
68
125
|
const evaluationStack = new Set(); // For circular dependency detection
|
|
69
126
|
const cache = new Map(); // Local cache for this evaluation session
|
|
70
127
|
function evaluateNestedFormula(formulaId, currentExpression, currentMappings) {
|
|
71
|
-
console.log(`🔍 Evaluating nested formula: ${formulaId}`);
|
|
72
|
-
console.log(`📝 Expression: "${currentExpression}"`);
|
|
73
|
-
console.log('🗺️ Current mappings:', currentMappings);
|
|
74
128
|
// Check for circular dependency
|
|
75
129
|
if (evaluationStack.has(formulaId)) {
|
|
76
130
|
throw new Error(`Circular dependency detected involving formula: ${formulaId}`);
|
|
@@ -78,103 +132,128 @@ export function evaluateFormula({ expression, mappings, valueMap, unit, formulas
|
|
|
78
132
|
// Check cache first
|
|
79
133
|
const cacheKey = `${formulaId}_${unit}`;
|
|
80
134
|
if (cache.has(cacheKey)) {
|
|
81
|
-
console.log(`💾 Using cached result for ${formulaId}`);
|
|
82
135
|
return cache.get(cacheKey);
|
|
83
136
|
}
|
|
84
137
|
evaluationStack.add(formulaId);
|
|
85
138
|
try {
|
|
86
139
|
const scope = {};
|
|
87
140
|
const normalizedUnit = normalizeUnitForMathJS(unit);
|
|
88
|
-
console.log(`🔄 Converting to unit: ${unit} → ${normalizedUnit}`);
|
|
89
141
|
// Build scope by resolving each variable
|
|
90
142
|
for (const [variable, rawMapping] of Object.entries(currentMappings)) {
|
|
91
|
-
console.log(`\n🔧 Processing variable "${variable}":`, rawMapping);
|
|
92
143
|
const mapping = normalizeMapping(rawMapping);
|
|
93
|
-
console.log(`✅ Normalized mapping:`, mapping);
|
|
94
144
|
if (mapping.type === 'measurement') {
|
|
95
145
|
// Handle measurement/column reference
|
|
96
|
-
console.log(`📊 Looking up measurement ID "${mapping.id}" in valueMap`);
|
|
97
146
|
const micrometers = valueMap[mapping.id] ?? 0;
|
|
98
|
-
console.log(`📈 Found value: ${micrometers} micrometers`);
|
|
99
147
|
if (micrometers === 0) {
|
|
100
148
|
console.warn(`⚠️ Zero or missing value for mapping ID "${mapping.id}"`);
|
|
101
|
-
console.log('🗂️ Available valueMap keys:', Object.keys(valueMap));
|
|
102
149
|
}
|
|
103
|
-
const valueInUnit =
|
|
104
|
-
console.log(`🔢 Converted to ${normalizedUnit}: ${valueInUnit}`);
|
|
150
|
+
const valueInUnit = micrometers;
|
|
105
151
|
scope[variable] = valueInUnit;
|
|
106
|
-
console.log(`✅ Set scope["${variable}"] = ${valueInUnit}`);
|
|
107
152
|
}
|
|
108
153
|
else if (mapping.type === 'formula') {
|
|
109
154
|
// Handle formula reference - recursively evaluate
|
|
110
|
-
console.log(`🔄 Recursively evaluating formula "${mapping.id}"`);
|
|
111
155
|
const referencedFormula = formulas.find((f) => f.id === mapping.id);
|
|
112
156
|
if (!referencedFormula) {
|
|
113
157
|
throw new Error(`Referenced formula not found: ${mapping.id}`);
|
|
114
158
|
}
|
|
115
159
|
// Recursively evaluate the referenced formula
|
|
116
160
|
const nestedResult = evaluateNestedFormula(referencedFormula.id, referencedFormula.expression, referencedFormula.variableToColumnMap);
|
|
117
|
-
// Convert result to current unit
|
|
118
|
-
|
|
119
|
-
|
|
161
|
+
// Convert result to current unit with error handling
|
|
162
|
+
let resultInUnit;
|
|
163
|
+
try {
|
|
164
|
+
resultInUnit = mathUnit(nestedResult, 'um').toNumber(normalizedUnit);
|
|
165
|
+
}
|
|
166
|
+
catch (unitErr) {
|
|
167
|
+
throw new Error(`Failed to convert nested formula result to unit: ${unitErr instanceof Error ? unitErr.message : 'Unknown error'}`);
|
|
168
|
+
}
|
|
120
169
|
scope[variable] = resultInUnit;
|
|
121
|
-
console.log(`✅ Set scope["${variable}"] = ${resultInUnit} (from formula)`);
|
|
122
170
|
}
|
|
123
171
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
172
|
+
// Evaluate the formula expression with error handling for math errors
|
|
173
|
+
let compiled;
|
|
174
|
+
try {
|
|
175
|
+
compiled = compile(currentExpression);
|
|
176
|
+
}
|
|
177
|
+
catch (compileErr) {
|
|
178
|
+
throw new Error(`Failed to compile formula expression: ${compileErr instanceof Error ? compileErr.message : 'Unknown error'}`);
|
|
179
|
+
}
|
|
180
|
+
let result;
|
|
181
|
+
try {
|
|
182
|
+
result = compiled.evaluate(scope);
|
|
183
|
+
}
|
|
184
|
+
catch (evalErr) {
|
|
185
|
+
// Handle divide by zero and other math errors
|
|
186
|
+
const errorMessage = evalErr instanceof Error ? evalErr.message : String(evalErr);
|
|
187
|
+
if (errorMessage.includes('divide') ||
|
|
188
|
+
errorMessage.includes('division') ||
|
|
189
|
+
errorMessage.includes('/') ||
|
|
190
|
+
errorMessage.includes('Division by zero') ||
|
|
191
|
+
errorMessage.includes('Cannot divide by zero')) {
|
|
192
|
+
throw new Error(`Division by zero in formula: ${formulaId}`);
|
|
193
|
+
}
|
|
194
|
+
// Re-throw other evaluation errors
|
|
195
|
+
throw new Error(`Failed to evaluate formula: ${errorMessage}`);
|
|
196
|
+
}
|
|
197
|
+
// Check for invalid results (Infinity, NaN)
|
|
198
|
+
if (Number.isNaN(result)) {
|
|
199
|
+
throw new Error(`Formula result is NaN: ${formulaId}`);
|
|
200
|
+
}
|
|
201
|
+
if (!Number.isFinite(result)) {
|
|
202
|
+
throw new Error(`Formula result is Infinity: ${formulaId}`);
|
|
203
|
+
}
|
|
204
|
+
let asUnit;
|
|
205
|
+
try {
|
|
206
|
+
asUnit = mathUnit(result, normalizedUnit);
|
|
207
|
+
}
|
|
208
|
+
catch (unitErr) {
|
|
209
|
+
throw new Error(`Failed to convert result to unit: ${unitErr instanceof Error ? unitErr.message : 'Unknown error'}`);
|
|
210
|
+
}
|
|
133
211
|
if (!asUnit.equalBase(mathUnit('1 um'))) {
|
|
134
212
|
throw new Error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
|
|
135
213
|
}
|
|
136
214
|
const finalResult = Math.round(asUnit.toNumber('um'));
|
|
137
|
-
|
|
215
|
+
// Check if final result is valid
|
|
216
|
+
if (!Number.isFinite(finalResult)) {
|
|
217
|
+
throw new Error(`Formula result is not a finite number: ${formulaId}`);
|
|
218
|
+
}
|
|
138
219
|
// Cache the result
|
|
139
220
|
cache.set(cacheKey, finalResult);
|
|
140
221
|
return finalResult;
|
|
141
222
|
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
// Re-throw the error so it can be caught by the outer try-catch
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
142
227
|
finally {
|
|
143
228
|
evaluationStack.delete(formulaId);
|
|
144
229
|
}
|
|
145
230
|
}
|
|
146
231
|
try {
|
|
147
232
|
const result = evaluateNestedFormula('root', expression, mappings);
|
|
148
|
-
console.log(`🎉 Formula evaluation completed successfully: ${result}`);
|
|
149
233
|
return result;
|
|
150
234
|
}
|
|
151
235
|
catch (err) {
|
|
152
|
-
console.
|
|
236
|
+
console.warn('❌ Formula evaluation failed:', err);
|
|
153
237
|
return err instanceof Error ? err.message : 'Error';
|
|
154
238
|
}
|
|
155
239
|
}
|
|
156
240
|
// Legacy createFormulaScope function for backward compatibility
|
|
157
241
|
export function createFormulaScope({ mappings, valueMap, unit, }) {
|
|
158
242
|
const scope = {};
|
|
159
|
-
const normalizedUnit = normalizeUnitForMathJS(unit);
|
|
160
243
|
for (const [variable, columnId] of Object.entries(mappings)) {
|
|
161
244
|
const micrometers = valueMap[columnId] ?? 0;
|
|
162
|
-
|
|
163
|
-
const valueInUnit = mathUnit(micrometers, 'um').toNumber(normalizedUnit);
|
|
164
|
-
scope[variable] = valueInUnit;
|
|
245
|
+
scope[variable] = micrometers;
|
|
165
246
|
}
|
|
166
247
|
return scope;
|
|
167
248
|
}
|
|
168
249
|
// Enhanced createFormulaScope for new mapping structure
|
|
169
250
|
export function createEnhancedFormulaScope({ mappings, valueMap, unit, formulas = [], }) {
|
|
170
251
|
const scope = {};
|
|
171
|
-
const normalizedUnit = normalizeUnitForMathJS(unit);
|
|
172
252
|
for (const [variable, rawMapping] of Object.entries(mappings)) {
|
|
173
253
|
const mapping = normalizeMapping(rawMapping);
|
|
174
254
|
if (mapping.type === 'measurement') {
|
|
175
255
|
const micrometers = valueMap[mapping.id] ?? 0;
|
|
176
|
-
|
|
177
|
-
scope[variable] = valueInUnit;
|
|
256
|
+
scope[variable] = micrometers;
|
|
178
257
|
}
|
|
179
258
|
else if (mapping.type === 'formula') {
|
|
180
259
|
// Find and evaluate the referenced formula
|
|
@@ -188,8 +267,7 @@ export function createEnhancedFormulaScope({ mappings, valueMap, unit, formulas
|
|
|
188
267
|
formulas,
|
|
189
268
|
});
|
|
190
269
|
if (typeof result === 'number') {
|
|
191
|
-
|
|
192
|
-
scope[variable] = valueInUnit;
|
|
270
|
+
scope[variable] = result;
|
|
193
271
|
}
|
|
194
272
|
else {
|
|
195
273
|
scope[variable] = 0; // Error case
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
|
|
2
|
+
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
2
3
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
3
4
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
4
5
|
export { useParseMeasurement } from './hooks/useParseMeasurement.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
|
|
2
|
+
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
2
3
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
3
4
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
4
5
|
export { useParseMeasurement } from './hooks/useParseMeasurement.js';
|
|
@@ -5,9 +5,11 @@ interface Timestamps {
|
|
|
5
5
|
createdAt: Date;
|
|
6
6
|
}
|
|
7
7
|
interface CreatedBy {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
createdBy: {
|
|
9
|
+
firstName: string;
|
|
10
|
+
lastName: string;
|
|
11
|
+
userId: string;
|
|
12
|
+
};
|
|
11
13
|
}
|
|
12
14
|
export interface Organization extends FirestoreDoc, Timestamps {
|
|
13
15
|
name: string;
|
|
@@ -62,13 +64,20 @@ export interface Project extends FirestoreDoc, Timestamps {
|
|
|
62
64
|
city: string;
|
|
63
65
|
zipCode: string;
|
|
64
66
|
state: string;
|
|
67
|
+
collectionId: string | null;
|
|
68
|
+
index: number | null;
|
|
69
|
+
}
|
|
70
|
+
export declare enum TemplateType {
|
|
71
|
+
Layout = "layout",
|
|
72
|
+
Annotation = "annotation"
|
|
65
73
|
}
|
|
66
|
-
export interface Template {
|
|
74
|
+
export interface Template extends Timestamps {
|
|
67
75
|
id: string;
|
|
68
76
|
name: string;
|
|
69
77
|
description: string;
|
|
70
78
|
diagramFileId: string | null;
|
|
71
79
|
diagramUrl: string;
|
|
80
|
+
type?: TemplateType;
|
|
72
81
|
tableConfig: ColumnConfig[];
|
|
73
82
|
tolerances?: {
|
|
74
83
|
[key: string]: {
|
|
@@ -78,17 +87,49 @@ export interface Template {
|
|
|
78
87
|
deviation: number;
|
|
79
88
|
};
|
|
80
89
|
};
|
|
90
|
+
isPublic: boolean;
|
|
91
|
+
orgId: string;
|
|
92
|
+
orgName: string;
|
|
93
|
+
parentFolderId?: string | null;
|
|
94
|
+
}
|
|
95
|
+
export interface SelectedTemplate extends FirestoreDoc {
|
|
96
|
+
count: number;
|
|
97
|
+
isPublic: boolean;
|
|
98
|
+
name: string;
|
|
99
|
+
groupType?: string;
|
|
100
|
+
}
|
|
101
|
+
export declare enum FileUploadType {
|
|
102
|
+
Annotation = "annotation",
|
|
103
|
+
Template = "template"
|
|
104
|
+
}
|
|
105
|
+
export interface FileUpload {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
projectId: string;
|
|
109
|
+
jobId: string;
|
|
110
|
+
groupId: string;
|
|
111
|
+
type: FileUploadType;
|
|
112
|
+
folderPath: string;
|
|
113
|
+
thumbnailUrl: string | null;
|
|
114
|
+
fileType: string | null;
|
|
81
115
|
}
|
|
82
116
|
export declare enum FolderType {
|
|
83
117
|
Project = "project",
|
|
84
|
-
Job = "job"
|
|
118
|
+
Job = "job",
|
|
119
|
+
Template = "template"
|
|
85
120
|
}
|
|
86
|
-
export interface
|
|
121
|
+
export interface ProjectFolder extends FirestoreDoc, Timestamps {
|
|
122
|
+
name: string;
|
|
123
|
+
parentFolderId: string | null;
|
|
124
|
+
projectId: string;
|
|
125
|
+
}
|
|
126
|
+
export interface Folder extends FirestoreDoc, Timestamps {
|
|
87
127
|
name: string;
|
|
88
128
|
index: number;
|
|
89
129
|
parentFolderId?: string | null;
|
|
90
130
|
projectId: string | null;
|
|
91
131
|
type: FolderType;
|
|
132
|
+
isPublic?: boolean;
|
|
92
133
|
}
|
|
93
134
|
export declare enum JobType {
|
|
94
135
|
DEFAULT = "default",
|
|
@@ -99,7 +140,8 @@ export interface Job extends FirestoreDoc, Timestamps {
|
|
|
99
140
|
folderId: string;
|
|
100
141
|
progress: number;
|
|
101
142
|
projectId: string;
|
|
102
|
-
totalTasks:
|
|
143
|
+
totalTasks: number;
|
|
144
|
+
lastAccessed: Date;
|
|
103
145
|
toleranceConfig?: {
|
|
104
146
|
thresholds: {
|
|
105
147
|
id: string;
|
|
@@ -110,8 +152,10 @@ export interface Job extends FirestoreDoc, Timestamps {
|
|
|
110
152
|
};
|
|
111
153
|
areaMapFileId?: string;
|
|
112
154
|
type?: JobType;
|
|
155
|
+
address?: string;
|
|
156
|
+
description?: string;
|
|
113
157
|
}
|
|
114
|
-
export interface Section {
|
|
158
|
+
export interface Section extends Timestamps {
|
|
115
159
|
id: string;
|
|
116
160
|
name: string;
|
|
117
161
|
tableConfig: ColumnConfig[];
|
|
@@ -140,6 +184,12 @@ export interface Group extends FirestoreDoc, Timestamps {
|
|
|
140
184
|
formula?: any;
|
|
141
185
|
isCompleted?: boolean;
|
|
142
186
|
type?: string;
|
|
187
|
+
details?: {
|
|
188
|
+
material: string;
|
|
189
|
+
finish: string;
|
|
190
|
+
finishDate: string;
|
|
191
|
+
};
|
|
192
|
+
path?: string;
|
|
143
193
|
}
|
|
144
194
|
export declare enum ColumnType {
|
|
145
195
|
Text = "text",
|
|
@@ -155,7 +205,8 @@ export declare enum ColumnType {
|
|
|
155
205
|
export interface Formula extends FirestoreDoc, Timestamps {
|
|
156
206
|
expression: string;
|
|
157
207
|
name: string;
|
|
158
|
-
|
|
208
|
+
variables: string[];
|
|
209
|
+
variableToColumnMap: Record<string, any>;
|
|
159
210
|
}
|
|
160
211
|
export interface ColumnConfig {
|
|
161
212
|
id: string;
|
|
@@ -166,6 +217,7 @@ export interface ColumnConfig {
|
|
|
166
217
|
withTolerance?: boolean;
|
|
167
218
|
options?: string[];
|
|
168
219
|
conversions?: Conversion[];
|
|
220
|
+
size?: number;
|
|
169
221
|
}
|
|
170
222
|
export interface Conversion {
|
|
171
223
|
label: string;
|
|
@@ -207,6 +259,7 @@ export declare enum Units {
|
|
|
207
259
|
FeetInchesFractional = "ft_in_frac"
|
|
208
260
|
}
|
|
209
261
|
export declare const convertUnitsToReadable: (targetUnit: Units) => "cm" | "mm" | "m" | "in" | "ft" | "in (fractional)" | "ft-in (decimal)" | "ft-in (fractional)" | null;
|
|
262
|
+
export declare const convertUnitsToReadableShort: (targetUnit: Units) => "cm" | "mm" | "m" | "in" | "ft" | "ft-in";
|
|
210
263
|
export declare enum FractionalTolerance {
|
|
211
264
|
Fourth = "4",
|
|
212
265
|
Eighth = "8",
|
package/dist/types/firestore.js
CHANGED
|
@@ -10,10 +10,21 @@ export var InvitationStatus;
|
|
|
10
10
|
InvitationStatus["Pending"] = "pending";
|
|
11
11
|
InvitationStatus["Accepted"] = "accepted";
|
|
12
12
|
})(InvitationStatus || (InvitationStatus = {}));
|
|
13
|
+
export var TemplateType;
|
|
14
|
+
(function (TemplateType) {
|
|
15
|
+
TemplateType["Layout"] = "layout";
|
|
16
|
+
TemplateType["Annotation"] = "annotation";
|
|
17
|
+
})(TemplateType || (TemplateType = {}));
|
|
18
|
+
export var FileUploadType;
|
|
19
|
+
(function (FileUploadType) {
|
|
20
|
+
FileUploadType["Annotation"] = "annotation";
|
|
21
|
+
FileUploadType["Template"] = "template";
|
|
22
|
+
})(FileUploadType || (FileUploadType = {}));
|
|
13
23
|
export var FolderType;
|
|
14
24
|
(function (FolderType) {
|
|
15
25
|
FolderType["Project"] = "project";
|
|
16
26
|
FolderType["Job"] = "job";
|
|
27
|
+
FolderType["Template"] = "template";
|
|
17
28
|
})(FolderType || (FolderType = {}));
|
|
18
29
|
export var JobType;
|
|
19
30
|
(function (JobType) {
|
|
@@ -70,6 +81,28 @@ export const convertUnitsToReadable = (targetUnit) => {
|
|
|
70
81
|
return null;
|
|
71
82
|
}
|
|
72
83
|
};
|
|
84
|
+
export const convertUnitsToReadableShort = (targetUnit) => {
|
|
85
|
+
switch (targetUnit) {
|
|
86
|
+
case Units.Meters:
|
|
87
|
+
return 'm';
|
|
88
|
+
case Units.Millimeters:
|
|
89
|
+
return 'mm';
|
|
90
|
+
case Units.Centimeters:
|
|
91
|
+
return 'cm';
|
|
92
|
+
case Units.Feet:
|
|
93
|
+
return 'ft';
|
|
94
|
+
case Units.FractionalInches:
|
|
95
|
+
return 'in';
|
|
96
|
+
case Units.Inches:
|
|
97
|
+
return 'in';
|
|
98
|
+
case Units.FeetInchesDecimal:
|
|
99
|
+
return 'ft-in';
|
|
100
|
+
case Units.FeetInchesFractional:
|
|
101
|
+
return 'ft-in';
|
|
102
|
+
default:
|
|
103
|
+
return targetUnit;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
73
106
|
export var FractionalTolerance;
|
|
74
107
|
(function (FractionalTolerance) {
|
|
75
108
|
FractionalTolerance["Fourth"] = "4";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DecimalTolerance, FractionalTolerance, Units } from '../types/firestore.js';
|
|
2
|
-
export declare const convertMicrometers: (micrometers: number, unit: Units, fractionalTolerance: FractionalTolerance, decimalTolerance: DecimalTolerance) => {
|
|
2
|
+
export declare const convertMicrometers: (micrometers: number | null | undefined, unit: Units, fractionalTolerance: FractionalTolerance, decimalTolerance: DecimalTolerance) => {
|
|
3
3
|
value: string;
|
|
4
|
-
unit: string
|
|
4
|
+
unit: string;
|
|
5
5
|
};
|
|
@@ -2,75 +2,84 @@ import { Units, convertUnitsToReadable, } from '../types/firestore.js';
|
|
|
2
2
|
import { create, all } from 'mathjs';
|
|
3
3
|
const math = create(all);
|
|
4
4
|
export const convertMicrometers = (micrometers, unit, fractionalTolerance, decimalTolerance) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
if (micrometers == null || isNaN(micrometers)) {
|
|
6
|
+
return { value: 'NaN', unit: '' };
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const converted = math.unit(micrometers, 'um');
|
|
10
|
+
let value = 0;
|
|
11
|
+
let displayUnit = '';
|
|
12
|
+
switch (unit) {
|
|
13
|
+
case Units.Inches:
|
|
14
|
+
case Units.FractionalInches: {
|
|
15
|
+
const inches = converted.toNumber('in');
|
|
16
|
+
displayUnit = 'in';
|
|
17
|
+
if (unit === Units.FractionalInches) {
|
|
18
|
+
const denominator = parseInt(fractionalTolerance, 10);
|
|
19
|
+
const whole = Math.floor(inches);
|
|
20
|
+
const fractional = inches - whole;
|
|
21
|
+
const numerator = Math.round(fractional * denominator);
|
|
22
|
+
if (numerator === denominator) {
|
|
23
|
+
value = `${whole + 1}`;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
value =
|
|
27
|
+
numerator === 0
|
|
28
|
+
? `${whole}`
|
|
29
|
+
: `${whole} ${numerator}/${denominator}`;
|
|
30
|
+
}
|
|
20
31
|
}
|
|
21
32
|
else {
|
|
22
|
-
value =
|
|
23
|
-
numerator === 0
|
|
24
|
-
? `${whole}`
|
|
25
|
-
: `${whole} ${numerator}/${denominator}`;
|
|
33
|
+
value = inches.toFixed(decimalTolerance.length - 2);
|
|
26
34
|
}
|
|
35
|
+
break;
|
|
27
36
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
case Units.Feet:
|
|
38
|
+
case Units.FeetInchesFractional:
|
|
39
|
+
case Units.FeetInchesDecimal: {
|
|
40
|
+
const feet = converted.toNumber('ft');
|
|
41
|
+
const wholeFeet = Math.floor(feet);
|
|
42
|
+
const fractionalFeet = feet - wholeFeet;
|
|
43
|
+
displayUnit = 'ft';
|
|
44
|
+
if (unit === Units.FeetInchesFractional) {
|
|
45
|
+
const inches = fractionalFeet * 12;
|
|
46
|
+
const wholeInches = Math.floor(inches);
|
|
47
|
+
const fractional = inches - wholeInches;
|
|
48
|
+
const denominator = parseInt(fractionalTolerance, 10);
|
|
49
|
+
const numerator = Math.round(fractional * denominator);
|
|
50
|
+
if (numerator === denominator) {
|
|
51
|
+
value = `${wholeFeet}' ${wholeInches + 1}"`;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
value =
|
|
55
|
+
numerator === 0
|
|
56
|
+
? `${wholeFeet}' ${wholeInches}"`
|
|
57
|
+
: `${wholeFeet}' ${wholeInches} ${numerator}/${denominator}"`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (unit === Units.FeetInchesDecimal) {
|
|
61
|
+
const inches = (fractionalFeet * 12).toFixed(decimalTolerance.length - 2);
|
|
62
|
+
value = `${wholeFeet}' ${inches}"`;
|
|
48
63
|
}
|
|
49
64
|
else {
|
|
50
|
-
value =
|
|
51
|
-
numerator === 0
|
|
52
|
-
? `${wholeFeet}' ${wholeInches}"`
|
|
53
|
-
: `${wholeFeet}' ${wholeInches} ${numerator}/${denominator}"`;
|
|
65
|
+
value = feet.toFixed(decimalTolerance.length - 2);
|
|
54
66
|
}
|
|
67
|
+
break;
|
|
55
68
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
case Units.Meters:
|
|
70
|
+
case Units.Centimeters:
|
|
71
|
+
case Units.Millimeters: {
|
|
72
|
+
displayUnit = convertUnitsToReadable(unit) ?? '';
|
|
73
|
+
value = converted.toNumber(unit).toFixed(decimalTolerance.length - 2);
|
|
74
|
+
break;
|
|
59
75
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
case Units.Meters:
|
|
66
|
-
case Units.Centimeters:
|
|
67
|
-
case Units.Millimeters: {
|
|
68
|
-
displayUnit = convertUnitsToReadable(unit);
|
|
69
|
-
value = converted.toNumber(unit).toFixed(decimalTolerance.length - 2);
|
|
70
|
-
break;
|
|
76
|
+
default:
|
|
77
|
+
throw new Error('Unsupported unit');
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
return { value, unit: displayUnit };
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.warn('Error converting micrometers:', err);
|
|
83
|
+
return { value: 'NaN', unit: '' };
|
|
74
84
|
}
|
|
75
|
-
return { value, unit: displayUnit };
|
|
76
85
|
};
|