@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.
@@ -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 { compile, unit as mathUnit } from 'mathjs';
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
- const compiled = compile(expression);
51
- const result = compiled.evaluate(scope);
52
- const asUnit = mathUnit(result, normalizedUnit);
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
- throw new Error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
104
+ console.error(`Incompatible unit: ${asUnit.formatUnits()} is not compatible with ${normalizedUnit}`);
105
+ return null;
55
106
  }
56
- return Math.round(asUnit.toNumber('um'));
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 = mathUnit(micrometers, 'um').toNumber(normalizedUnit);
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
- const resultInUnit = mathUnit(nestedResult, 'um').toNumber(normalizedUnit);
119
- console.log(`🔢 Nested formula result in ${normalizedUnit}: ${resultInUnit}`);
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
- 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());
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
- console.log(`🎯 Final result: ${finalResult} micrometers`);
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.error('❌ Formula evaluation failed:', err);
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
- // Convert using math.js directly
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
- const valueInUnit = mathUnit(micrometers, 'um').toNumber(normalizedUnit);
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
- const valueInUnit = mathUnit(result, 'um').toNumber(normalizedUnit);
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
- firstName: string;
9
- lastName: string;
10
- userId: string;
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 Folder extends FirestoreDoc {
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: string;
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
- varaibles: string[];
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",
@@ -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 | null;
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
- const converted = math.unit(micrometers, 'um');
6
- let value = 0;
7
- let displayUnit = '';
8
- switch (unit) {
9
- case Units.Inches:
10
- case Units.FractionalInches: {
11
- const inches = converted.toNumber('in');
12
- displayUnit = 'in';
13
- if (unit === Units.FractionalInches) {
14
- const denominator = parseInt(fractionalTolerance, 10);
15
- const whole = Math.floor(inches);
16
- const fractional = inches - whole;
17
- const numerator = Math.round(fractional * denominator);
18
- if (numerator === denominator) {
19
- value = `${whole + 1}`;
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
- else {
29
- value = inches.toFixed(decimalTolerance.length - 2); // Match decimal places to tolerance
30
- }
31
- break;
32
- }
33
- case Units.Feet:
34
- case Units.FeetInchesFractional:
35
- case Units.FeetInchesDecimal: {
36
- const feet = converted.toNumber('ft');
37
- const wholeFeet = Math.floor(feet);
38
- const fractionalFeet = feet - wholeFeet;
39
- displayUnit = 'ft';
40
- if (unit === Units.FeetInchesFractional) {
41
- const inches = fractionalFeet * 12;
42
- const wholeInches = Math.floor(inches);
43
- const fractional = inches - wholeInches;
44
- const denominator = parseInt(fractionalTolerance, 10);
45
- const numerator = Math.round(fractional * denominator);
46
- if (numerator === denominator) {
47
- value = `${wholeFeet}' ${wholeInches + 1}"`;
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
- else if (unit === Units.FeetInchesDecimal) {
57
- const inches = (fractionalFeet * 12).toFixed(decimalTolerance.length - 2);
58
- value = `${wholeFeet}' ${inches}"`;
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
- else {
61
- value = feet.toFixed(decimalTolerance.length - 2);
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
- default:
73
- throw new Error('Unsupported unit');
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.4.14",
3
+ "version": "1.4.16",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",