@measurequick/measurequick-report-generator 1.5.222 → 1.5.224

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@measurequick/measurequick-report-generator",
3
- "version": "1.5.222",
3
+ "version": "1.5.224",
4
4
  "description": "Generates PDF documents for various measureQuick applications.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -254,18 +254,69 @@ export async function getReport(payload, _test) {
254
254
  return isFinite(n) ? n : NaN;
255
255
  }
256
256
  var coAmbRaw = toNum(data && data.co_ambient);
257
- if (isFinite(coAmbRaw) && coAmbRaw > 9) {
258
- safeSetText(
259
- form,
260
- "COWarning",
261
- "WARNING: Elevated CO Above 9 ppm Detected!\nVentilate the area and investigate the source."
262
- );
263
- safeSetText(
264
- form,
265
- "COWarning2",
266
- "WARNING: Elevated CO Above 9 ppm Detected! Ventilate the area and investigate the source."
267
- );
257
+ var coRetRaw = toNum(data && data.co_return);
258
+ var coSupRaw = toNum(data && data.co_supply);
259
+
260
+ // CO level helper function
261
+ // Normal: 0-8 ppm, Elevated: 9-35 ppm, Dangerous: 36-69 ppm, Emergency: 70+ ppm
262
+ function getCOLevel(ppm) {
263
+ if (!isFinite(ppm) || ppm <= 8) return "Normal";
264
+ if (ppm >= 9 && ppm <= 35) return "Elevated";
265
+ if (ppm >= 36 && ppm <= 69) return "Dangerous";
266
+ return "Emergency";
267
+ }
268
+
269
+ var reasons = [];
270
+ if (isFinite(coAmbRaw) && coAmbRaw > 8) reasons.push({ type: "Ambient", value: coAmbRaw, level: getCOLevel(coAmbRaw) });
271
+ if (isFinite(coRetRaw) && coRetRaw > 8) reasons.push({ type: "Return", value: coRetRaw, level: getCOLevel(coRetRaw) });
272
+ if (isFinite(coSupRaw) && coSupRaw > 8) reasons.push({ type: "Supply", value: coSupRaw, level: getCOLevel(coSupRaw) });
273
+ var coHigh = reasons.length > 0;
274
+
275
+ if (coHigh) {
276
+ // Find the highest severity level and the ambient CO reading
277
+ var highestLevel = "Elevated";
278
+ var ambientReading = null;
279
+ for (var r = 0; r < reasons.length; r++) {
280
+ if (reasons[r].type === "Ambient") ambientReading = reasons[r];
281
+ if (reasons[r].level === "Emergency") highestLevel = "Emergency";
282
+ else if (reasons[r].level === "Dangerous" && highestLevel !== "Emergency") highestLevel = "Dangerous";
283
+ }
284
+
285
+ // Build warning message focused on ambient CO
286
+ var warningMsg, warningMsg2;
287
+ if (ambientReading) {
288
+ warningMsg = "WARNING: " + ambientReading.level + " Ambient CO " + Math.round(ambientReading.value) + " ppm\nVentilate the area and investigate source.";
289
+ warningMsg2 = "WARNING: " + ambientReading.level + " Ambient CO " + Math.round(ambientReading.value) + " ppm. Ventilate the area and investigate source.";
290
+ } else {
291
+ // Fallback if no ambient reading (use first reading)
292
+ var firstReading = reasons[0];
293
+ warningMsg = "WARNING: " + firstReading.level + " " + firstReading.type + " CO " + Math.round(firstReading.value) + " ppm\nVentilate the area and investigate source.";
294
+ warningMsg2 = "WARNING: " + firstReading.level + " " + firstReading.type + " CO " + Math.round(firstReading.value) + " ppm. Ventilate the area and investigate source.";
295
+ }
296
+
297
+ // Set text with color based on severity level
298
+ // Orange for Elevated, Red for Dangerous, Dark Red for Emergency
299
+ var warningColor;
300
+ if (highestLevel === "Emergency") {
301
+ warningColor = rgb(0.72, 0.11, 0.11); // Dark red (#b71c1c)
302
+ } else if (highestLevel === "Dangerous") {
303
+ warningColor = rgb(0.96, 0.26, 0.21); // Red (#f44336)
304
+ } else {
305
+ warningColor = rgb(1, 0.6, 0); // Orange (#ff9800)
306
+ }
307
+
308
+ try {
309
+ var tf1 = form.getTextField("COWarning");
310
+ tf1.setText(warningMsg);
311
+ tf1.setTextColor(warningColor);
312
+ } catch (e) {}
313
+ try {
314
+ var tf2 = form.getTextField("COWarning2");
315
+ tf2.setText(warningMsg2);
316
+ tf2.setTextColor(warningColor);
317
+ } catch (e) {}
268
318
  } else {
319
+ // clear warnings if not applicable
269
320
  safeSetText(form, "COWarning", "");
270
321
  safeSetText(form, "COWarning2", "");
271
322
  }
@@ -511,16 +562,57 @@ export async function getReport(payload, _test) {
511
562
  passFails.splice(3, 1);
512
563
  pfLabels.splice(3, 1);
513
564
  }
565
+
566
+ // CO threshold color helpers
567
+ function getCOIcon(coValue) {
568
+ if (coValue === null || coValue === undefined || isNaN(coValue)) {
569
+ return iconRangeGreen;
570
+ }
571
+ if (coValue <= 9) {
572
+ return iconRangeGreen;
573
+ } else if (coValue > 9 && coValue <= 35) {
574
+ return iconRangeYellow;
575
+ } else {
576
+ return iconRangeRed;
577
+ }
578
+ }
579
+
580
+ function getCOTextColor(coValue) {
581
+ if (coValue === null || coValue === undefined || isNaN(coValue)) {
582
+ return rgb(0, 0, 0);
583
+ }
584
+ if (coValue <= 9) {
585
+ return rgb(0, 0, 0); // black - OK
586
+ } else if (coValue > 9 && coValue <= 35) {
587
+ return rgb(1, 0.6, 0); // orange - Elevated
588
+ } else {
589
+ return rgb(0.96, 0.26, 0.21); // red - Dangerous
590
+ }
591
+ }
592
+
593
+ function getCOStatus(coValue) {
594
+ if (coValue === null || coValue === undefined || isNaN(coValue)) {
595
+ return "";
596
+ }
597
+ if (coValue <= 9) {
598
+ return "OK";
599
+ } else {
600
+ return "Elevated";
601
+ }
602
+ }
603
+
604
+ // Collect valid items first (no gaps)
605
+ let validItems = [];
606
+
514
607
  for (let i = 0; i < passFails.length; i++) {
515
608
  const meas = test.testInfo[passFails[i]];
516
609
 
517
- // If no value provided, hide the label (set empty) and skip icon
610
+ // Skip items with no value
518
611
  if (meas === undefined || meas === null || meas === "") {
519
- safeSetText(form, `SSR${i + 1}`, "");
520
612
  continue;
521
613
  }
522
614
 
523
- // Choose icon based on value
615
+ // Determine icon based on value
524
616
  let icon;
525
617
  if (meas === "Caution") {
526
618
  icon = iconRangeYellow;
@@ -529,53 +621,85 @@ export async function getReport(payload, _test) {
529
621
  } else {
530
622
  icon = iconRangeRed;
531
623
  }
532
- if (passFails[i] === "heating_efficiency_pass_fail")
624
+ if (passFails[i] === "heating_efficiency_pass_fail") {
533
625
  icon = iconRangeGreen;
534
-
535
- // Set the icon as before
536
- safeSetImage(form, `ImageSubsystem${i + 1}_af_image`, icon);
626
+ }
537
627
 
538
628
  const suffix = test.testInfo[passFails[i] + "_override"] ? " *" : "";
539
- const fieldName = `SSR${i + 1}`;
540
-
541
- // Default label for non-special cases
542
629
  let label = pfLabels[i] + suffix;
630
+ let textColor = rgb(0, 0, 0); // default black
543
631
 
544
- // Special handling: Ambient CO failure should be red and show a stronger warning
632
+ // Special handling for Ambient CO - show actual values
545
633
  if (passFails[i] === "ambient_pass_fail") {
546
- const isFail = !(
547
- meas === "Pass" ||
548
- meas === "High" ||
549
- meas === "Mid" ||
550
- meas === "Caution"
551
- );
552
-
553
- if (isFail) {
554
- label = "CO Ambient High, Investigate Source!";
555
- try {
556
- const tf = form.getTextField(fieldName);
557
- tf.setText(label);
558
- tf.setTextColor(rgb(1, 0, 0)); // red
559
- } catch (e) {}
560
- continue; // move to next item; we've already set text & color
561
- } else {
562
- // Non-fail states: ensure text is black
563
- try {
564
- const tf = form.getTextField(fieldName);
565
- tf.setText(label);
566
- tf.setTextColor(rgb(0, 0, 0)); // black
567
- } catch (e) {}
568
- continue;
634
+ // Calculate CO delta
635
+ const coDelta = (isFinite(coSupRaw) && isFinite(coRetRaw)) ? (coSupRaw - coRetRaw) : null;
636
+ const hasCODeltaWarning = test.testInfo.ambient_pass_fail_co_delta_warning || (coDelta !== null && coDelta >= 1);
637
+
638
+ // Add CO Ambient with value
639
+ if (isFinite(coAmbRaw)) {
640
+ const coAmbIcon = getCOIcon(coAmbRaw);
641
+ const coAmbColor = getCOTextColor(coAmbRaw);
642
+ const coAmbStatus = getCOStatus(coAmbRaw);
643
+ const coAmbLabel = "CO Ambient: " + Math.round(coAmbRaw) + " ppm (" + coAmbStatus + ")" + suffix;
644
+ validItems.push({ label: coAmbLabel, icon: coAmbIcon, textColor: coAmbColor });
645
+ } else if (meas) {
646
+ // No CO value but has pass/fail - show generic
647
+ const isFail = !(meas === "Pass" || meas === "High" || meas === "Mid" || meas === "Caution");
648
+ if (isFail) {
649
+ validItems.push({ label: "CO Ambient: Elevated" + suffix, icon: iconRangeYellow, textColor: rgb(1, 0.6, 0) });
650
+ } else {
651
+ validItems.push({ label: "CO Ambient: OK" + suffix, icon: iconRangeGreen, textColor: rgb(0, 0, 0) });
652
+ }
569
653
  }
654
+
655
+ // Add CO Return with value (if available and not boiler)
656
+ if (isFinite(coRetRaw) && !payload.meta.boiler_type) {
657
+ const coRetIcon = getCOIcon(coRetRaw);
658
+ const coRetColor = getCOTextColor(coRetRaw);
659
+ const coRetStatus = getCOStatus(coRetRaw);
660
+ const coRetLabel = "CO Return: " + Math.round(coRetRaw) + " ppm (" + coRetStatus + ")";
661
+ validItems.push({ label: coRetLabel, icon: coRetIcon, textColor: coRetColor });
662
+ }
663
+
664
+ // Add CO Supply with value (if available and not boiler)
665
+ if (isFinite(coSupRaw) && !payload.meta.boiler_type) {
666
+ const coSupIcon = getCOIcon(coSupRaw);
667
+ const coSupColor = getCOTextColor(coSupRaw);
668
+ const coSupStatus = getCOStatus(coSupRaw);
669
+ const coSupLabel = "CO Supply: " + Math.round(coSupRaw) + " ppm (" + coSupStatus + ")";
670
+ validItems.push({ label: coSupLabel, icon: coSupIcon, textColor: coSupColor });
671
+ }
672
+
673
+ // Add CO Delta (if both return and supply available and not boiler)
674
+ if (coDelta !== null && !payload.meta.boiler_type) {
675
+ const deltaIcon = hasCODeltaWarning ? iconRangeRed : iconRangeGreen;
676
+ const deltaColor = hasCODeltaWarning ? rgb(0.96, 0.26, 0.21) : rgb(0, 0, 0);
677
+ const deltaStatus = hasCODeltaWarning ? "Investigate Heat Exchanger" : "OK";
678
+ const deltaLabel = "CO Delta: " + Math.round(coDelta) + " ppm (" + deltaStatus + ")";
679
+ validItems.push({ label: deltaLabel, icon: deltaIcon, textColor: deltaColor });
680
+ }
681
+
682
+ continue; // Skip adding the default ambient_pass_fail item
570
683
  }
571
684
 
572
- // All other pass/fail items: keep existing label, ensure black text
685
+ validItems.push({ label, icon, textColor });
686
+ }
687
+
688
+ // Fill fields sequentially with valid items (no gaps)
689
+ for (let i = 0; i < validItems.length; i++) {
690
+ const item = validItems[i];
691
+ safeSetImage(form, `ImageSubsystem${i + 1}_af_image`, item.icon);
573
692
  try {
574
- const tf = form.getTextField(fieldName);
575
- tf.setText(label);
576
- tf.setTextColor(rgb(0, 0, 0));
693
+ const tf = form.getTextField(`SSR${i + 1}`);
694
+ tf.setText(item.label);
695
+ tf.setTextColor(item.textColor);
577
696
  } catch (e) {}
578
697
  }
698
+
699
+ // Clear any remaining unused fields
700
+ for (let i = validItems.length; i < passFails.length + 3; i++) {
701
+ safeSetText(form, `SSR${i + 1}`, "");
702
+ }
579
703
  } else safeSetText(form, `SSR1`, "Not yet reviewed");
580
704
 
581
705
  if (payload.meta.report_type != "FullReport") {
@@ -672,13 +672,23 @@ export async function getReport(payload, _test) {
672
672
  // Use coAmbRaw which is already defined and validated earlier in the function
673
673
  const coValue = isFinite(coAmbRaw) ? coAmbRaw : null;
674
674
 
675
+ // Check for CO delta warning (Supply - Return >= 1 ppm)
676
+ const coDelta = (isFinite(coSupRaw) && isFinite(coRetRaw)) ? (coSupRaw - coRetRaw) : null;
677
+ const hasCODeltaWarning = test.testInfo.ambient_pass_fail_co_delta_warning || (coDelta !== null && coDelta >= 1);
678
+
675
679
  // Use CO-specific icon based on actual value
676
680
  if (coValue !== null) {
677
681
  icon = getCOIcon(coValue);
678
682
  const stateText = getCOStateText(coValue);
679
683
 
680
- // Format label based on CO level
681
- if (coValue <= 8) {
684
+ // Format label based on CO level and delta warning
685
+ if (hasCODeltaWarning) {
686
+ // CO delta detected - recommend heat exchanger investigation
687
+ icon = iconRangeRed;
688
+ textColor = rgb(0.96, 0.26, 0.21); // Red
689
+ const deltaValue = coDelta !== null ? Math.round(coDelta) : "";
690
+ label = "CO Delta " + deltaValue + " ppm: Investigate heat exchanger" + suffix;
691
+ } else if (coValue <= 8) {
682
692
  // Normal - just show value
683
693
  label = "Ambient CO: " + Math.round(coValue) + " ppm" + suffix;
684
694
  } else {
@@ -686,15 +696,23 @@ export async function getReport(payload, _test) {
686
696
  label = "CO Ambient " + stateText + " " + Math.round(coValue) + " ppm, Investigate!" + suffix;
687
697
  }
688
698
 
689
- // Set text color based on CO level
690
- // Orange for Elevated, Red for Dangerous, Dark Red for Emergency
691
- if (coValue >= 70) {
692
- textColor = rgb(0.72, 0.11, 0.11); // Dark red for Emergency
693
- } else if (coValue >= 36) {
694
- textColor = rgb(0.96, 0.26, 0.21); // Red for Dangerous
695
- } else if (coValue >= 9) {
696
- textColor = rgb(1, 0.6, 0); // Orange for Elevated
699
+ // Set text color based on CO level (if not already set by delta warning)
700
+ if (!hasCODeltaWarning) {
701
+ // Orange for Elevated, Red for Dangerous, Dark Red for Emergency
702
+ if (coValue >= 70) {
703
+ textColor = rgb(0.72, 0.11, 0.11); // Dark red for Emergency
704
+ } else if (coValue >= 36) {
705
+ textColor = rgb(0.96, 0.26, 0.21); // Red for Dangerous
706
+ } else if (coValue >= 9) {
707
+ textColor = rgb(1, 0.6, 0); // Orange for Elevated
708
+ }
697
709
  }
710
+ } else if (hasCODeltaWarning) {
711
+ // No ambient CO value but delta warning detected
712
+ icon = iconRangeRed;
713
+ textColor = rgb(0.96, 0.26, 0.21); // Red
714
+ const deltaValue = coDelta !== null ? Math.round(coDelta) : "";
715
+ label = "CO Delta " + deltaValue + " ppm: Investigate heat exchanger" + suffix;
698
716
  } else {
699
717
  // No CO value, use pass/fail status
700
718
  const isFail = !(meas === "Pass" || meas === "High" || meas === "Mid" || meas === "Caution");