From 6958d90a21e7f22945625307d7cd86124fc09437 Mon Sep 17 00:00:00 2001
From: Benedikt Seeger <benedikt.seeger@ptb.de>
Date: Thu, 27 Feb 2025 14:20:30 +0100
Subject: [PATCH] added conformety parsing

---
 src/dccQuantity.js                   | 110 ++++++++++++++++++++++++--
 src/renderers/MeasurementRenderer.js | 112 ++++++++++++++-------------
 2 files changed, 160 insertions(+), 62 deletions(-)

diff --git a/src/dccQuantity.js b/src/dccQuantity.js
index 8387c51..ec6afdf 100644
--- a/src/dccQuantity.js
+++ b/src/dccQuantity.js
@@ -20,6 +20,18 @@ export class DCCQuantity {
         }
         return content._ || content;
     }
+
+    getConformity(language) {
+        if (this.jsonData['dcc:measurementMetaData'] && this.jsonData['dcc:measurementMetaData']['dcc:metaData']) {
+            let metaData = this.jsonData['dcc:measurementMetaData']['dcc:metaData'];
+            if (!Array.isArray(metaData)) metaData = [metaData];
+            const confMeta = metaData.find(m => m.$ && m.$.refType && m.$.refType.includes('basic_conformity'));
+            if (confMeta) {
+                return new DCCConformity(confMeta, language);
+            }
+        }
+        return null;
+    }
 }
 
 export class DCCRealListQuantity extends DCCQuantity {
@@ -71,9 +83,9 @@ export class DCCRealQuantity extends DCCQuantity {
     getValues() {
         const realData = this.jsonData['si:real'];
         if (realData && realData['si:value']) {
-            return parseFloat(realData['si:value']);
+            return [parseFloat(realData['si:value'])];
         }
-        return null;
+        return [];
     }
 
     getUnit() {
@@ -85,17 +97,99 @@ export class DCCRealQuantity extends DCCQuantity {
         }
         return '';
     }
+
     getUncertainty() {
-        const realList = this.jsonData['si:real'];
+        const realData = this.jsonData['si:real'];
         if (
-            realList &&
-            realList['si:measurementUncertaintyUnivariate'] &&
-            realList['si:measurementUncertaintyUnivariate']['si:expandedMU'] &&
-            realList['si:measurementUncertaintyUnivariate']['si:expandedMU']['si:valueExpandedMU']
+            realData &&
+            realData['si:measurementUncertaintyUnivariate'] &&
+            realData['si:measurementUncertaintyUnivariate']['si:expandedMU'] &&
+            realData['si:measurementUncertaintyUnivariate']['si:expandedMU']['si:valueExpandedMU']
         ) {
-            const errStr = realList['si:measurementUncertaintyUnivariate']['si:expandedMU']['si:valueExpandedMU'];
+            const errStr = realData['si:measurementUncertaintyUnivariate']['si:expandedMU']['si:valueExpandedMU'];
             return errStr.trim().split(/\s+/).map(v => parseFloat(v));
         }
         return [];
     }
 }
+
+export class DCCConformity {
+    constructor(metaDataJson, language) {
+        this.metaDataJson = metaDataJson;
+        this.language = language;
+    }
+
+    getConformityValues() {
+        const confList = this.metaDataJson['dcc:conformityXMLList'];
+        if (confList) {
+            return confList.trim().split(/\s+/);
+        }
+        return [];
+    }
+
+    getLowerLimit() {
+        const data = this.metaDataJson['dcc:data'];
+        if (data && data['dcc:quantity']) {
+            let quantities = data['dcc:quantity'];
+            if (!Array.isArray(quantities)) quantities = [quantities];
+            for (let q of quantities) {
+                if (q.$ && q.$.refType && q.$.refType.includes('basic_toleranceLimitLower')) {
+                    let lowerValue = null;
+                    let lowerUnit = '';
+                    if (q['si:realListXMLList']) {
+                        const realList = q['si:realListXMLList'];
+                        if (realList['si:valueXMLList']) {
+                            lowerValue = parseFloat(realList['si:valueXMLList'].trim());
+                        }
+                        if (realList['si:unitXMLList']) {
+                            const rawUnit = realList['si:unitXMLList'].trim();
+                            const unit = new DSIUnit(rawUnit);
+                            lowerUnit = unit.toHTML({ oneLine: true });
+                        }
+                    }
+                    return { value: lowerValue, unit: lowerUnit, name: this.getQuantityName(q) };
+                }
+            }
+        }
+        return null;
+    }
+
+    getUpperLimit() {
+        const data = this.metaDataJson['dcc:data'];
+        if (data && data['dcc:quantity']) {
+            let quantities = data['dcc:quantity'];
+            if (!Array.isArray(quantities)) quantities = [quantities];
+            for (let q of quantities) {
+                if (q.$ && q.$.refType && q.$.refType.includes('basic_toleranceLimitUpper')) {
+                    let upperValue = null;
+                    let upperUnit = '';
+                    if (q['si:realListXMLList']) {
+                        const realList = q['si:realListXMLList'];
+                        if (realList['si:valueXMLList']) {
+                            upperValue = parseFloat(realList['si:valueXMLList'].trim());
+                        }
+                        if (realList['si:unitXMLList']) {
+                            const rawUnit = realList['si:unitXMLList'].trim();
+                            const unit = new DSIUnit(rawUnit);
+                            upperUnit = unit.toHTML({ oneLine: true });
+                        }
+                    }
+                    return { value: upperValue, unit: upperUnit, name: this.getQuantityName(q) };
+                }
+            }
+        }
+        return null;
+    }
+
+    getQuantityName(q) {
+        if (q['dcc:name'] && q['dcc:name']['dcc:content']) {
+            let content = q['dcc:name']['dcc:content'];
+            if (Array.isArray(content)) {
+                const match = content.find(item => item.$ && item.$.lang === this.language) || content[0];
+                return match._ || match;
+            }
+            return content._ || content;
+        }
+        return '';
+    }
+}
diff --git a/src/renderers/MeasurementRenderer.js b/src/renderers/MeasurementRenderer.js
index 97430b6..0a63c39 100644
--- a/src/renderers/MeasurementRenderer.js
+++ b/src/renderers/MeasurementRenderer.js
@@ -27,6 +27,15 @@ const lightPalette = [
   '#b3ffff'
 ];
 
+const conformityColors = {
+  pass: '#2ca02c',
+  fail: '#d62728',
+  conditionalpass: '#8bc34a',
+  conditionalfail: '#ff9800',
+  nopass: '#9e9e9e',
+  nofail: '#9e9e9e'
+};
+
 export function renderMeasurementResults(measurementResults, language) {
   console.debug('renderMeasurementResults called with:', measurementResults);
   const container = document.getElementById('measurementResults');
@@ -42,9 +51,7 @@ export function renderMeasurementResults(measurementResults, language) {
     console.error("Missing 'dcc:measurementResult' in measurementResults");
     return;
   }
-  if (!Array.isArray(results)) {
-    results = [results];
-  }
+  if (!Array.isArray(results)) { results = [results]; }
   const measurementResult = results[0];
 
   let resultObj = measurementResult['dcc:results'] && measurementResult['dcc:results']['dcc:result'];
@@ -52,9 +59,7 @@ export function renderMeasurementResults(measurementResults, language) {
     console.error("Missing 'dcc:results' or 'dcc:result' in measurementResult:", measurementResult);
     return;
   }
-  if (Array.isArray(resultObj)) {
-    resultObj = resultObj[0];
-  }
+  if (Array.isArray(resultObj)) { resultObj = resultObj[0]; }
 
   let resultName = 'Measurement Result';
   if (measurementResult['dcc:name'] && measurementResult['dcc:name']['dcc:content']) {
@@ -84,11 +89,11 @@ export function renderMeasurementResults(measurementResults, language) {
   }
   if (listData['dcc:measurementMetaData'] && listData['dcc:measurementMetaData']['dcc:metaData']) {
     let metaData = listData['dcc:measurementMetaData']['dcc:metaData'];
-    if (!Array.isArray(metaData)) metaData = [metaData];
+    if (!Array.isArray(metaData)) { metaData = [metaData]; }
     metaData.forEach(md => {
       if (md['dcc:data'] && md['dcc:data']['dcc:quantity']) {
         let qs = md['dcc:data']['dcc:quantity'];
-        if (!Array.isArray(qs)) qs = [qs];
+        if (!Array.isArray(qs)) { qs = [qs]; }
         quantityJSONs = quantityJSONs.concat(qs);
       }
     });
@@ -96,29 +101,36 @@ export function renderMeasurementResults(measurementResults, language) {
 
   const indexQuantities = [];
   const dataQuantities = [];
+  // extraInfo: for each data quantity, store { uncertainty, comments: [{ comment, validArray }], conformity }
   const extraInfo = [];
   quantityJSONs.forEach(q => {
     if (q.$ && q.$.refType && q.$.refType.match(/basic_tableIndex/)) {
       indexQuantities.push(new DCCRealListQuantity(q));
     } else {
       dataQuantities.push(new DCCRealListQuantity(q));
-      let uncertainty = (new DCCRealListQuantity(q)).getUncertainty();
-      let comment = '';
+      const uncertainty = (new DCCRealListQuantity(q)).getUncertainty();
+      let comments = [];
       let conformity = null;
       if (q['dcc:measurementMetaData'] && q['dcc:measurementMetaData']['dcc:metaData']) {
         let md = q['dcc:measurementMetaData']['dcc:metaData'];
         if (!Array.isArray(md)) { md = [md]; }
         md.forEach(item => {
           if (item.$ && item.$.refType && item.$.refType.includes('basic_tableRowComment')) {
+            let commentText = '';
             if (item['dcc:description'] && item['dcc:description']['dcc:content']) {
               let desc = item['dcc:description']['dcc:content'];
               if (Array.isArray(desc)) {
                 const match = desc.find(d => d.$ && d.$.lang === language) || desc[0];
-                comment = match._ || match;
+                commentText = match._ || match;
               } else {
-                comment = desc._ || desc;
+                commentText = desc._ || desc;
               }
             }
+            let validArray = [];
+            if (item['dcc:validXMLList']) {
+              validArray = item['dcc:validXMLList'].trim().split(/\s+/);
+            }
+            comments.push({ comment: commentText, valid: validArray });
           }
           if (item.$ && item.$.refType && item.$.refType.includes('basic_conformity')) {
             if (item['dcc:conformityXMLList']) {
@@ -127,12 +139,12 @@ export function renderMeasurementResults(measurementResults, language) {
           }
         });
       }
-      extraInfo.push({ uncertainty, comment, conformity });
+      extraInfo.push({ uncertainty, comments, conformity });
     }
   });
 
-  // Build headers for data quantities
-  const dataHeaders = dataQuantities.map(q => {
+  // Build data headers array for each data quantity
+  const dataHeaders = dataQuantities.map((q, idx) => {
     let header = q.getName(language);
     let unit = q.getUnit();
     if (!header.toLowerCase().includes(" in ")) {
@@ -141,7 +153,7 @@ export function renderMeasurementResults(measurementResults, language) {
     return header;
   });
 
-  // Create log scaling toggles
+  // Create scaling toggles for log axes
   const scalingContainer = document.createElement('div');
   scalingContainer.innerHTML = '<strong>Scaling:</strong> ';
   const logXToggle = document.createElement('input');
@@ -170,7 +182,7 @@ export function renderMeasurementResults(measurementResults, language) {
     radio.type = 'radio';
     radio.name = 'xAxisSelect';
     radio.value = idx;
-    if (idx === 0) radio.checked = true;
+    if (idx === 0) { radio.checked = true; }
     const label = document.createElement('label');
     label.textContent = nameStr;
     label.style.marginRight = '10px';
@@ -199,10 +211,7 @@ export function renderMeasurementResults(measurementResults, language) {
 
   function updateVisualization() {
     const selectedRadio = document.querySelector('input[name="xAxisSelect"]:checked');
-    if (!selectedRadio) {
-      console.error('No X-Axis selection found.');
-      return;
-    }
+    if (!selectedRadio) { console.error('No X-Axis selection found.'); return; }
     const selectedIndex = selectedRadio.value;
     const xQuantity = indexQuantities[selectedIndex];
     const xValues = xQuantity.getValues();
@@ -211,20 +220,18 @@ export function renderMeasurementResults(measurementResults, language) {
     console.debug('X-Axis unit:', xUnit);
 
     const headers = [];
-    const xHeader = 'X-Axis (selected) (' + xUnit + ')';
-    headers.push(xHeader);
+    headers.push('X-Axis (selected) (' + xUnit + ')');
 
     const dataValues = [];
     const uncertaintiesArray = [];
     const conformityArray = [];
-    dataQuantities.forEach((q, idx) => {
+    extraInfo.forEach((info, idx) => {
       headers.push(dataHeaders[idx]);
-      if (extraInfo[idx] && extraInfo[idx].conformity) {
-        headers.push('Conformity');
-      }
-      dataValues.push(q.getValues());
-      uncertaintiesArray.push(extraInfo[idx] ? extraInfo[idx].uncertainty : []);
-      conformityArray.push(extraInfo[idx] ? extraInfo[idx].conformity : []);
+      // Only include Conformity header if conformity exists for this quantity
+      if (info.conformity) { headers.push('Conformity'); }
+      dataValues.push(dataQuantities[idx].getValues());
+      uncertaintiesArray.push(info.uncertainty || []);
+      conformityArray.push(info.conformity || []);
     });
     headers.push('Comments');
 
@@ -232,20 +239,25 @@ export function renderMeasurementResults(measurementResults, language) {
     for (let i = 0; i < xValues.length; i++) {
       const row = [];
       row.push(xValues[i]);
-      dataValues.forEach((values, idx) => {
-        let cellValue = values[i] !== undefined ? values[i] : '';
+      extraInfo.forEach((info, idx) => {
+        let cellValue = (dataValues[idx][i] !== undefined) ? dataValues[idx][i] : '';
         if (uncertaintiesArray[idx] && uncertaintiesArray[idx][i] !== undefined) {
           cellValue = cellValue + ' ± ' + uncertaintiesArray[idx][i];
         }
         row.push(cellValue);
-        if (extraInfo[idx] && extraInfo[idx].conformity && extraInfo[idx].conformity[i]) {
-          row.push(extraInfo[idx].conformity[i]);
-        } else if (extraInfo[idx] && extraInfo[idx].conformity) {
-          row.push('');
+        if (info.conformity) {
+          row.push(info.conformity[i] || '');
         }
       });
-      let globalComment = extraInfo.map(e => e.comment).filter(c => c).join(' ; ');
-      row.push(globalComment);
+      // For comments, combine all comments valid at this row from all quantities
+      let rowComments = extraInfo.map(info => {
+        if (info.comments) {
+          // info.comments is an array of { comment, valid } objects
+          return info.comments.filter(cObj => cObj.valid[i] === 'true').map(cObj => cObj.comment).join(' ; ');
+        }
+        return '';
+      }).filter(c => c).join(' | ');
+      row.push(rowComments);
       tableData.push(row);
     }
     renderTable(tableData);
@@ -253,9 +265,7 @@ export function renderMeasurementResults(measurementResults, language) {
     const unitGroups = {};
     dataQuantities.forEach((q, idx) => {
       const unit = q.getUnit();
-      if (!unitGroups[unit]) {
-        unitGroups[unit] = [];
-      }
+      if (!unitGroups[unit]) { unitGroups[unit] = []; }
       const header = dataHeaders[idx];
       let values = q.getValues();
       const uncertainty = uncertaintiesArray[idx];
@@ -291,9 +301,7 @@ export function renderMeasurementResults(measurementResults, language) {
           y: trace.y,
           error_y: {
             type: 'data',
-            array: (trace.uncertainty && trace.uncertainty.length === xValues.length)
-                ? trace.uncertainty
-                : new Array(xValues.length).fill(0),
+            array: (trace.uncertainty && trace.uncertainty.length === xValues.length) ? trace.uncertainty : new Array(xValues.length).fill(0),
             visible: true,
             color: trace.defaultColor
           },
@@ -303,9 +311,7 @@ export function renderMeasurementResults(measurementResults, language) {
           marker: { color: trace.markerColor },
           line: { color: trace.defaultColor },
           hovertemplate: tooltip,
-          customdata: (trace.conformity && trace.conformity.length === xValues.length)
-              ? trace.conformity
-              : new Array(xValues.length).fill('')
+          customdata: (trace.conformity && trace.conformity.length === xValues.length) ? trace.conformity : new Array(xValues.length).fill('')
         };
       });
       let xaxisTitle = '';
@@ -366,7 +372,9 @@ export function renderMeasurementResults(measurementResults, language) {
         if (rowIndex === 0) {
           cell.innerHTML = cellData;
           if (cellData !== 'Comments' && cellIndex > 0) {
-            const qtyIndex = Math.floor((cellIndex - 1) / 2);
+            // Use correct header mapping: assume each quantity occupies either 1 (if no conformity) or 2 columns.
+            // We'll compute the index based on dataHeaders length.
+            const qtyIndex = cellIndex % 2 === 1 ? Math.floor(cellIndex / 2) : Math.floor((cellIndex - 1) / 2);
             cell.style.backgroundColor = lightPalette[qtyIndex % lightPalette.length];
           }
         } else {
@@ -397,12 +405,8 @@ export function renderMeasurementResults(measurementResults, language) {
 
   updateVisualization();
   const radios = document.querySelectorAll('input[name="xAxisSelect"]');
-  radios.forEach(radio => {
-    radio.addEventListener('change', updateVisualization);
-  });
+  radios.forEach(radio => { radio.addEventListener('change', updateVisualization); });
   document.getElementById('logXToggle').addEventListener('change', updateVisualization);
   document.getElementById('logYToggle').addEventListener('change', updateVisualization);
-  toleranceToggle.addEventListener('change', () => {
-    console.log('Tolerance toggle:', toleranceToggle.checked);
-  });
+  toleranceToggle.addEventListener('change', () => { console.log('Tolerance toggle:', toleranceToggle.checked); });
 }
-- 
GitLab