@reekon-tools/boldr-utils 1.9.0 → 1.9.1

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.
@@ -321,6 +321,10 @@ export const validateCalculatorDefinition = (input) => {
321
321
  push('variable-unit-dimension-mismatch', unitPath, `Unit "${unit}" is not a ${dim} unit (variable ${variable} maps to "${mapped.name}")`);
322
322
  }
323
323
  }
324
+ // The unit the EXPRESSION produces, which is what `resultUnit` claims to
325
+ // be. Hoisted out of the `target` branch because the number-target check
326
+ // further down needs it too.
327
+ const inferred = inferExpressionUnit(def.fields, eq);
324
328
  if (target) {
325
329
  const dim = fieldDimension(target);
326
330
  if (eq.resultUnit != null) {
@@ -331,11 +335,16 @@ export const validateCalculatorDefinition = (input) => {
331
335
  push('result-unit-dimension-mismatch', `${path}.resultUnit`, `Result unit "${eq.resultUnit}" is not a ${dim} unit (target "${target.name}")`);
332
336
  }
333
337
  }
334
- // The unit the EXPRESSION produces, which is what `resultUnit` claims to
335
- // be. Dimension checks against the target field can't see a same-
336
- // dimension scale error: three inch variables multiplied are in³, so
337
- // `cu_ft` passes every check above while reporting 1728× the truth.
338
- const inferred = inferExpressionUnit(def.fields, eq);
338
+ // Dimension checks against the target field can't see a same-dimension
339
+ // scale error: three inch variables multiplied are in³, so `cu_ft`
340
+ // passes every check above while reporting 1728× the truth.
341
+ //
342
+ // Deliberately NOT applied to a dimensionless target. Literals carry no
343
+ // units here, so `area / 33` infers as an area whether the area arrives
344
+ // in µm² or ft² — the inference cannot tell a broken count from a
345
+ // correct one, and every legitimate count written as
346
+ // `area_in_ft² / 33_ft²` would be flagged. The canonical-leak check
347
+ // below is the one that separates them.
339
348
  if (inferred.ok && dim !== 'none') {
340
349
  if (inferred.dimension !== dim) {
341
350
  push('expression-dimension-mismatch', `${path}.expression`, `Expression produces ${describeDimension(inferred.dimension)} but "${target.name}" is ${describeDimension(dim)} — check the variable units and the expression`);
@@ -365,14 +374,30 @@ export const validateCalculatorDefinition = (input) => {
365
374
  }
366
375
  }
367
376
  // The canonical-leak trap: a number target consuming a unit-carrying
368
- // field without a variable unit receives raw µm-scale values.
369
- if (target?.kind === ColumnType.Number) {
377
+ // field without a variable unit receives raw µm-scale values. Reported
378
+ // per variable, because the fix is per variable ("pick a unit for A").
379
+ //
380
+ // Gated on the inference: unit-carrying variables are fine as long as
381
+ // their dimensions CANCEL, which is the common shape for a count —
382
+ // `ceil(area / area)` sheets, `(12 * rise) / run` pitch. Flagging those
383
+ // put a permanent warning on two correct calculators, and a check that
384
+ // cries wolf on correct authoring is one authors learn to scroll past.
385
+ //
386
+ // An ERROR, not a warning. It was a warning so that pre-annotation docs
387
+ // kept validating, but "keeps validating" is how the roofing estimator
388
+ // shipped `Bundles = A / 33` over a canonical µm² area and showed
389
+ // 184,990,000,000 — the right answer times µm²-per-ft². There is no
390
+ // authoring where a µm-scale value in a unitless field is intended, and
391
+ // nothing gates on `ok`: mobile only console.warns in __DEV__, and the
392
+ // web editor shows the banner without blocking save.
393
+ if (target?.kind === ColumnType.Number &&
394
+ (!inferred.ok || inferred.dimension !== 'none')) {
370
395
  for (const [variable, fieldId] of Object.entries(eq.variableToFieldId)) {
371
396
  const mapped = fieldById.get(fieldId);
372
397
  if (!mapped || fieldDimension(mapped) === 'none')
373
398
  continue;
374
399
  if (eq.variableUnits?.[variable] == null) {
375
- push('canonical-value-into-number-target', `${path}.variableToFieldId.${variable}`, `"${target.name}" is a number field but variable ${variable} ("${mapped.name}") has no display unit its canonical (µm-scale) value will be used. Pick a unit for ${variable}.`, 'warning');
400
+ push('canonical-value-into-number-target', `${path}.variableToFieldId.${variable}`, `"${target.name}" is a number field, so it shows the raw result — but variable ${variable} ("${mapped.name}") has no display unit, so it arrives as a canonical µm-scale value and the result will be off by a large factor. Pick a unit for ${variable}.`);
376
401
  }
377
402
  }
378
403
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",