Sfoglia il codice sorgente

Merge branch 'master' of https://git.luminad.com/harshit.pathak/content_quality_tool

Harshit Pathak 3 mesi fa
parent
commit
071315d29b

+ 8 - 0
content_quality_tool_public/static/css/attr-extraction.css

@@ -944,3 +944,11 @@ td{
     color: #d9534f;
     font-weight: bold;
 }
+
+
+.highlight-text {
+    background-color: yellow; /* Or any other color you prefer */
+    font-weight: bold;
+    padding: 1px 2px;
+    border-radius: 3px;
+}

+ 403 - 151
content_quality_tool_public/static/js/attr-extraction.js

@@ -226,19 +226,19 @@ function createProductCard(p) {
     // Helper function to create the chip UI for attributes
     function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallback) {
         const wrapper = el('div', 'attribute-chip-group');
-        wrapper.dataset.attrName = attr.attribute_name;
-        wrapper.innerHTML = `<p class="attribute-header">${attr.attribute_name} (${isMandatory ? 'Mandatory' : 'Optional'}):</p>`;
+        wrapper.dataset.attrName = attr?.attribute_name;
+        wrapper.innerHTML = `<p class="attribute-header">${attr?.attribute_name} (${isMandatory ? 'Mandatory' : 'Optional'}):</p>`;
 
         const chipContainer = el('div', 'chips-container');
 
-        attr.possible_values.forEach(value => {
+        attr?.possible_values.forEach(value => {
             const chip = el('label', 'attribute-chip');
             
             // Checkbox input is hidden, but drives the selection state
             const checkbox = document.createElement('input');
             checkbox.type = 'checkbox';
             checkbox.value = value;
-            checkbox.name = `${p.item_id}-${attr.attribute_name}`;
+            checkbox.name = `${p.item_id}-${attr?.attribute_name}`;
             
             // Set initial state
             checkbox.checked = initialSelected.includes(value); 
@@ -311,7 +311,7 @@ if(p.product_type_details.length > 0){
     //     attrContainer.appendChild(manTitle);
 
     //     mandatoryAttributes.forEach(attr => {
-    //         const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+    //         const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
     //         const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
     //         attrContainer.appendChild(chipGroup);
     //     });
@@ -326,7 +326,7 @@ if(p.product_type_details.length > 0){
         attrContainer.appendChild(optTitle);
 
         optionalAttributes.forEach(attr => {
-            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
             const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
             attrContainer.appendChild(chipGroup);
         });
@@ -642,7 +642,7 @@ function generateAttributeUI(p, updateProductState, attrContainer) {
         attrContainer.appendChild(manTitle);
 
         mandatoryAttributes.forEach(attr => {
-            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
             // The createAttributeChips function must be globally available
             const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
             attrContainer.appendChild(chipGroup);
@@ -659,7 +659,7 @@ function generateAttributeUI(p, updateProductState, attrContainer) {
         attrContainer.appendChild(optTitle);
 
         optionalAttributes.forEach(attr => {
-            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
             const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
             attrContainer.appendChild(chipGroup);
         });
@@ -678,15 +678,15 @@ function generateAttributeUI(p, updateProductState, attrContainer) {
  */
 function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallback) {
     const wrapper = el('div', 'attribute-chip-group');
-    wrapper.dataset.attrName = attr.attribute_name;
+    wrapper.dataset.attrName = attr?.attribute_name;
     
     // Determine the header text based on structure preference (e.g., just the name)
     const statusText = isMandatory ? ' (Mandatory)' : ' (Optional)';
-    wrapper.innerHTML = `<p class="attribute-header">${attr.attribute_name}${statusText}:</p>`;
+    wrapper.innerHTML = `<p class="attribute-header">${attr?.attribute_name}${statusText}:</p>`;
 
     const chipContainer = el('div', 'chips-container');
 
-    attr.possible_values.forEach(value => {
+    attr?.possible_values.forEach(value => {
         const chip = el('label', 'attribute-chip');
         
         // Checkbox input is hidden, but drives the selection state
@@ -694,7 +694,7 @@ function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallb
         checkbox.type = 'checkbox';
         checkbox.value = value;
         // Ensure the name is unique per product/attribute group
-        checkbox.name = `${p.item_id}-${attr.attribute_name}`; 
+        checkbox.name = `${p.item_id}-${attr?.attribute_name}`; 
         
         // Set initial state
         checkbox.checked = initialSelected.includes(value); 
@@ -792,7 +792,7 @@ function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallb
 //                 const toggleButton = document.createElement('button');
 //                 toggleButton.textContent = 'Configure';
 //                 toggleButton.classList.add('btn', 'btn-sm', 'btn-info', 'attribute-toggle-btn');
-//                 tdAttr.appendChild(toggleButton);
+//                 tdattr?.appendChild(toggleButton);
 //                 // tr.appendChild(tdAttr);
             
 
@@ -1460,34 +1460,34 @@ function renderInlineForCards() {
 
 //     attributeEntries.forEach(attr => {
 //         // Highlight the entire row in red if the values do not match
-//         const row = el('tr', attr.isMatch ? 'match' : 'mismatch-row'); 
+//         const row = el('tr', attr?.isMatch ? 'match' : 'mismatch-row'); 
         
 //         // 1. Attribute Name
 //         const nameTd = el('td', 'attribute-name');
-//         nameTd.textContent = attr.name.replace(/_/g, ' '); 
+//         nameTd.textContent = attr?.name.replace(/_/g, ' '); 
 //         row.appendChild(nameTd);
 
 //         // 2. Source
 //         const sourceTd = el('td', 'attribute-source');
-//         sourceTd.textContent = formatString(attr.source);
+//         sourceTd.textContent = formatString(attr?.source);
 //         row.appendChild(sourceTd);
 
 
 //         // 3. Existing Value
 //         const originalTd = el('td', 'original-value');
-//         originalTd.textContent = formatString(attr.originalValue);
+//         originalTd.textContent = formatString(attr?.originalValue);
 //         row.appendChild(originalTd);
 
 //         // 4. AI Extracted Value (Highlight if mismatch)
-//         const aiTd = el('td', `ai-value ${attr.aiValue ? '' : 'mismatch-value'}`);
-//         aiTd.textContent = attr.aiValue;
+//         const aiTd = el('td', `ai-value ${attr?.aiValue ? '' : 'mismatch-value'}`);
+//         aiTd.textContent = attr?.aiValue;
 //         row.appendChild(aiTd);
         
 
 //         // 5. Match Status
 //         // const matchTd = el('td', 'match-status');
-//         // const matchPill = el('span', `pill status-pill status-${attr.isMatch ? 'match' : 'mismatch'}`);
-//         // matchPill.textContent = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
+//         // const matchPill = el('span', `pill status-pill status-${attr?.isMatch ? 'match' : 'mismatch'}`);
+//         // matchPill.textContent = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
 //         // matchTd.appendChild(matchPill);
 //         // row.appendChild(matchTd);
 
@@ -1513,41 +1513,78 @@ function renderInlineForCards() {
 function renderMandatoryComparisonTable(attributes, title, productType) {
     const section = el('div', 'attribute-section');
 
-    let attributeEntries = [];
+    // --- 1. Intermediate object for merging values ---
+    let mergedAttributes = {};
 
-    // --- Build attributeEntries ---
+    // --- Build mergedAttributes ---
     Object.keys(attributes).forEach(key => {
         const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
+
+        if (!mergedAttributes[key]) {
+            mergedAttributes[key] = {
+                aiValues: new Set(),
+                originalValues: new Set(),
+                sources: new Set(),
+                reasons: new Set(),
+                // Store the first encountered config for possibleValues lookup
+                firstValue: valuesArray[0]
+            };
+        }
+
         valuesArray.forEach(v => {
             const aiValue = v.value || 'N/A';
             const originalValue = v.original_value || 'N/A';
             const source = v.source || 'N/A';
             const reason = v.reason || 'N/A';
 
-            // Find possible values for this attribute from full data
-            const attrConfig = attributesFullData.find(item => item.attribute_name === key);
-            
-            let possibleValues = [];
-            if (attrConfig && attrConfig.possible_values) {
-                possibleValues = attrConfig.possible_values.split(',').map(s => s.trim());
-            }
+            // Add values to sets for unique collection
+            if (aiValue !== 'N/A') mergedAttributes[key].aiValues.add(aiValue);
+            if (originalValue !== 'N/A') mergedAttributes[key].originalValues.add(originalValue);
+            if (source !== 'N/A') mergedAttributes[key].sources.add(source);
+            if (reason !== 'N/A') mergedAttributes[key].reasons.add(reason);
+        });
+    });
+    
+    // --- 2. Final attributeEntries from merged data ---
+    let attributeEntries = [];
 
-            // Determine match flag
-            const isFoundInPossible = possibleValues.includes(aiValue);
-            const matchFlag = isFoundInPossible ? true : false;
+    Object.keys(mergedAttributes).forEach(key => {
+        const mergedData = mergedAttributes[key];
+        
+        // Find possible values for this attribute from full data using the stored firstValue
+        const attrConfig = attributesFullData.find(item => item.attribute_name === key);
+        
+        let possibleValues = [];
+        if (attrConfig && attrConfig.possible_values) {
+            possibleValues = attrConfig.possible_values.split(',').map(s => s.trim());
+        }
 
-            attributeEntries.push({
-                name: key,
-                aiValue: aiValue,
-                possibleValues: possibleValues,
-                originalValue: originalValue,
-                source: source,
-                isMatch: matchFlag,
-                reason: reason
-            });
+        // Get merged AI Value
+        const aiValueString = Array.from(mergedData.aiValues).join(', ');
+        
+        // Determine match flag: check if ANY of the AI values are in possibleValues
+        const isFoundInPossible = Array.from(mergedData.aiValues).some(aiVal => possibleValues.includes(aiVal));
+        const matchFlag = isFoundInPossible ? true : false;
+        
+        // Get merged Original Value, Source, and Reason strings
+        const originalValueString = Array.from(mergedData.originalValues).join(', ');
+        const sourceString = Array.from(mergedData.sources).join(', ');
+        const reasonString = Array.from(mergedData.reasons).join(' | ');
+
+        attributeEntries.push({
+            name: key,
+            aiValue: aiValueString || 'N/A', // Use merged string
+            possibleValues: possibleValues,
+            originalValue: originalValueString || 'N/A',
+            source: sourceString || 'N/A',
+            isMatch: matchFlag,
+            reason: reasonString || 'N/A'
         });
     });
 
+    // --- Rest of the function (unchanged) ---
+    // ... (Section title, empty check, splitting into two tables, and buildTable helper) ...
+
     // --- Section title ---
     const titleEl = el('div', 'section-title');
     titleEl.innerHTML = `<h3>${title} (${attributeEntries.length})</h3>`;
@@ -1575,7 +1612,8 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
 
         const thead = el('thead');
         const headerRow = el('tr');
-        ['Attribute Name', 'AI Generated Value', 'Action'].forEach(text => {
+        // , 'AI Generated Value'
+        ['Attribute Name', 'Recommended Attribute Value(s)'].forEach(text => {
             const th = el('th');
             th.textContent = text;
             headerRow.appendChild(th);
@@ -1586,80 +1624,47 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
         const tbody = el('tbody');
 
         attrArray.forEach(attr => {
-            const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
+            const row = el('tr', attr?.isMatch ? 'match-row' : 'mismatch-row');
 
             // Attribute name
             const nameTd = el('td', 'attribute-name');
-            nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
+            nameTd.textContent = formatString(attr?.name).replace(/_/g, ' ');
             row.appendChild(nameTd);
 
-            // AI Value display
-            // AI Value cell with info icon
-            const aiValueTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
-
-            // Create a wrapper for text and icon
-            const aiValueWrapper = document.createElement('div');
-            aiValueWrapper.style.display = 'flex';
-            aiValueWrapper.style.alignItems = 'center';
-            aiValueWrapper.style.gap = '6px';
-
-            // Text node
-            const valueText = document.createElement('span');
-            valueText.textContent = formatString(attr.aiValue);
-            aiValueWrapper.appendChild(valueText);
-
-            // Info icon (Bootstrap Icons)
-            if (attr.reason && attr.reason.trim() !== '') {
-                const infoIcon = document.createElement('i');
-                infoIcon.className = 'bi bi-info-circle';
-                infoIcon.style.cursor = 'pointer';
-                infoIcon.setAttribute('title', attr.reason); // Tooltip on hover
-                infoIcon.style.color = '#0d6efd'; // Bootstrap blue
-                aiValueWrapper.appendChild(infoIcon);
-            }
-
-            aiValueTd.appendChild(aiValueWrapper);
-            row.appendChild(aiValueTd);
-
-            // const aiValueTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
-            // aiValueTd.textContent = formatString(attr.aiValue);
-            // row.appendChild(aiValueTd);
-
             // Dropdown
-            // const aiTd = el('td', 'attribute-source');
-            // const select = document.createElement('select');
-            // select.classList.add('select2-dropdown');
-            // select.setAttribute('data-attribute', attr.name);
-            
             const aiTd = el('td', 'attribute-source');
 
             // Create a multi-select dropdown
             const select = document.createElement('select');
             select.classList.add('select2-dropdown');
-            // select.id = 'manually-attributes'; // You may want to make this unique per row
             select.name = 'manuallyUpdatedAttributes[]';
-            // select.setAttribute('aria-labelledby', 'select a attribute for which multiple data required');
             select.setAttribute('multiple', 'multiple');
-            // select.style.width = '100%';
-            select.setAttribute('data-attribute', attr.name);
+            select.setAttribute('data-attribute', attr?.name);
 
+            // The values selected will be the merged AI values
+            const selectedValues = attr?.aiValue.split(', ').filter(v => v !== 'N/A' && v.trim() !== '');
 
             // Populate options
-            attr.possibleValues.forEach(val => {
+            // Add possible values
+            attr?.possibleValues.forEach(val => {
                 const option = document.createElement('option');
                 option.value = val;
                 option.textContent = val;
-                if (val === attr.aiValue) option.selected = true;
+                if (selectedValues.includes(val)) option.selected = true;
                 select.appendChild(option);
             });
+            
+            // Add AI values not found in possibleValues as new selected options
+            // selectedValues.forEach(aiVal => {
+            //     if (!attr?.possibleValues.includes(aiVal)) {
+            //         const newOpt = document.createElement('option');
+            //         newOpt.value = aiVal;
+            //         newOpt.textContent = aiVal;
+            //         newOpt.selected = true;
+            //         select.appendChild(newOpt);
+            //     }
+            // });
 
-            // if (!attr.isMatch && attr.aiValue !== 'N/A') {
-            //     const newOpt = document.createElement('option');
-            //     newOpt.value = attr.aiValue;
-            //     newOpt.textContent = attr.aiValue; // + " (new)";
-            //     newOpt.selected = true;
-            //     select.appendChild(newOpt);
-            // }
 
             aiTd.appendChild(select);
             row.appendChild(aiTd);
@@ -1694,6 +1699,186 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
 
 //     let attributeEntries = [];
 
+//     // --- Build attributeEntries ---
+//     Object.keys(attributes).forEach(key => {
+//         const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
+//         valuesArray.forEach(v => {
+//             const aiValue = v.value || 'N/A';
+//             const originalValue = v.original_value || 'N/A';
+//             const source = v.source || 'N/A';
+//             const reason = v.reason || 'N/A';
+
+//             // Find possible values for this attribute from full data
+//             const attrConfig = attributesFullData.find(item => item.attribute_name === key);
+            
+//             let possibleValues = [];
+//             if (attrConfig && attrConfig.possible_values) {
+//                 possibleValues = attrConfig.possible_values.split(',').map(s => s.trim());
+//             }
+
+//             // Determine match flag
+//             const isFoundInPossible = possibleValues.includes(aiValue);
+//             const matchFlag = isFoundInPossible ? true : false;
+
+//             attributeEntries.push({
+//                 name: key,
+//                 aiValue: aiValue,
+//                 possibleValues: possibleValues,
+//                 originalValue: originalValue,
+//                 source: source,
+//                 isMatch: matchFlag,
+//                 reason: reason
+//             });
+//         });
+//     });
+
+//     // --- Section title ---
+//     const titleEl = el('div', 'section-title');
+//     titleEl.innerHTML = `<h3>${title} (${attributeEntries.length})</h3>`;
+//     section.appendChild(titleEl);
+
+//     if (attributeEntries.length === 0) {
+//         const msg = el('p', 'no-attributes-message');
+//         msg.textContent = `No ${title.toLowerCase()} found.`;
+//         section.appendChild(msg);
+//         return section;
+//     }
+
+//     // --- Split attributes in half ---
+//     const midIndex = Math.ceil(attributeEntries.length / 2);
+//     const leftAttributes = attributeEntries.slice(0, midIndex);
+//     const rightAttributes = attributeEntries.slice(midIndex);
+
+//     // --- Create a container for two tables ---
+//     const tableContainer = el('div', 'two-column-table-container');
+
+//     // Helper function to build a table
+//     function buildTable(attrArray) {
+//         const scrollWrapper = el('div', 'attribute-scroll-wrapper');
+//         const table = el('table', 'attribute-detail-table comparison-table');
+
+//         const thead = el('thead');
+//         const headerRow = el('tr');
+//         // , 'AI Generated Value'
+//         ['Attribute Name', 'Recommended Attribute Value(s)'].forEach(text => {
+//             const th = el('th');
+//             th.textContent = text;
+//             headerRow.appendChild(th);
+//         });
+//         thead.appendChild(headerRow);
+//         table.appendChild(thead);
+
+//         const tbody = el('tbody');
+
+//         attrArray.forEach(attr => {
+//             const row = el('tr', attr?.isMatch ? 'match-row' : 'mismatch-row');
+
+//             // Attribute name
+//             const nameTd = el('td', 'attribute-name');
+//             nameTd.textContent = formatString(attr?.name).replace(/_/g, ' ');
+//             row.appendChild(nameTd);
+
+//             // AI Value display
+//             // AI Value cell with info icon
+//             // const aiValueTd = el('td', `ai-value ${attr?.isMatch ? '' : 'mismatch-value'}`);
+
+//             // // Create a wrapper for text and icon
+//             // const aiValueWrapper = document.createElement('div');
+//             // aiValueWrapper.style.display = 'flex';
+//             // aiValueWrapper.style.alignItems = 'center';
+//             // aiValueWrapper.style.gap = '6px';
+
+//             // // Text node
+//             // const valueText = document.createElement('span');
+//             // valueText.textContent = formatString(attr?.aiValue);
+//             // aiValueWrapper.appendChild(valueText);
+
+//             // // Info icon (Bootstrap Icons)
+//             // if (attr?.reason && attr?.reason.trim() !== '') {
+//             //     const infoIcon = document.createElement('i');
+//             //     infoIcon.className = 'bi bi-info-circle';
+//             //     infoIcon.style.cursor = 'pointer';
+//             //     infoIcon.setAttribute('title', attr?.reason); // Tooltip on hover
+//             //     infoIcon.style.color = '#0d6efd'; // Bootstrap blue
+//             //     aiValueWrapper.appendChild(infoIcon);
+//             // }
+
+//             // aiValueTd.appendChild(aiValueWrapper);
+//             // row.appendChild(aiValueTd);
+
+//             // const aiValueTd = el('td', `ai-value ${attr?.isMatch ? '' : 'mismatch-value'}`);
+//             // aiValueTd.textContent = formatString(attr?.aiValue);
+//             // row.appendChild(aiValueTd);
+
+//             // Dropdown
+//             // const aiTd = el('td', 'attribute-source');
+//             // const select = document.createElement('select');
+//             // select.classList.add('select2-dropdown');
+//             // select.setAttribute('data-attribute', attr?.name);
+            
+//             const aiTd = el('td', 'attribute-source');
+
+//             // Create a multi-select dropdown
+//             const select = document.createElement('select');
+//             select.classList.add('select2-dropdown');
+//             // select.id = 'manually-attributes'; // You may want to make this unique per row
+//             select.name = 'manuallyUpdatedAttributes[]';
+//             // select.setAttribute('aria-labelledby', 'select a attribute for which multiple data required');
+//             select.setAttribute('multiple', 'multiple');
+//             // select.style.width = '100%';
+//             select.setAttribute('data-attribute', attr?.name);
+
+
+//             // Populate options
+//             attr?.possibleValues.forEach(val => {
+//                 const option = document.createElement('option');
+//                 option.value = val;
+//                 option.textContent = val;
+//                 if (val === attr?.aiValue) option.selected = true;
+//                 select.appendChild(option);
+//             });
+
+//             // if (!attr?.isMatch && attr?.aiValue !== 'N/A') {
+//             //     const newOpt = document.createElement('option');
+//             //     newOpt.value = attr?.aiValue;
+//             //     newOpt.textContent = attr?.aiValue; // + " (new)";
+//             //     newOpt.selected = true;
+//             //     select.appendChild(newOpt);
+//             // }
+
+//             aiTd.appendChild(select);
+//             row.appendChild(aiTd);
+
+//             tbody.appendChild(row);
+
+//             // Initialize Select2
+//             jQuery(select).select2({
+//                 tags: true,
+//                 width: 'resolve'
+//             });
+//         });
+
+//         table.appendChild(tbody);
+//         scrollWrapper.appendChild(table);
+//         return scrollWrapper;
+//     }
+
+//     // --- Build and append both tables ---
+//     const leftTable = buildTable(leftAttributes);
+//     const rightTable = buildTable(rightAttributes);
+
+//     tableContainer.appendChild(leftTable);
+//     tableContainer.appendChild(rightTable);
+
+//     section.appendChild(tableContainer);
+//     return section;
+// }
+
+// function renderMandatoryComparisonTable(attributes, title, productType) {
+//     const section = el('div', 'attribute-section');
+
+//     let attributeEntries = [];
+
 //     Object.keys(attributes).forEach(key => {
 //         const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
 //         valuesArray.forEach(v => {
@@ -1754,16 +1939,16 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
 //     const tbody = el('tbody');
 
 //     attributeEntries.forEach(attr => {
-//         const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
+//         const row = el('tr', attr?.isMatch ? 'match-row' : 'mismatch-row');
 
 //         // 1. Attribute Name
 //         const nameTd = el('td', 'attribute-name');
-//         nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
+//         nameTd.textContent = formatString(attr?.name).replace(/_/g, ' ');
 //         row.appendChild(nameTd);
 
 //         // 3. Source Column
-//         const sourceTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
-//         sourceTd.textContent = formatString(attr.aiValue);
+//         const sourceTd = el('td', `ai-value ${attr?.isMatch ? '' : 'mismatch-value'}`);
+//         sourceTd.textContent = formatString(attr?.aiValue);
 //         row.appendChild(sourceTd);
 
 
@@ -1772,22 +1957,22 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
 
 //         const select = document.createElement('select');
 //         select.classList.add('select2-dropdown');
-//         select.setAttribute('data-attribute', attr.name);
+//         select.setAttribute('data-attribute', attr?.name);
 
 //         // Populate possible values
-//         attr.possibleValues.forEach(val => {
+//         attr?.possibleValues.forEach(val => {
 //             const option = document.createElement('option');
 //             option.value = val;
 //             option.textContent = val;
-//             if (val === attr.aiValue) option.selected = true;
+//             if (val === attr?.aiValue) option.selected = true;
 //             select.appendChild(option);
 //         });
 
 //         // If not found in possible values, add it as a new option
-//         if (!attr.isMatch && attr.aiValue !== 'N/A') {
+//         if (!attr?.isMatch && attr?.aiValue !== 'N/A') {
 //             const newOpt = document.createElement('option');
-//             newOpt.value = attr.aiValue;
-//             newOpt.textContent = attr.aiValue + " (new)";
+//             newOpt.value = attr?.aiValue;
+//             newOpt.textContent = attr?.aiValue + " (new)";
 //             newOpt.selected = true;
 //             select.appendChild(newOpt);
 //         }
@@ -1868,22 +2053,22 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
 
 //     attributeEntries.forEach(attr => {
 //         // Highlight the entire row in red if the values do not match
-//         const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row'); 
+//         const row = el('tr', attr?.isMatch ? 'match-row' : 'mismatch-row'); 
         
 //         // 1. Attribute Name
 //         const nameTd = el('td', 'attribute-name');
-//         nameTd.textContent = formatString(attr.name).replace(/_/g, ' '); 
+//         nameTd.textContent = formatString(attr?.name).replace(/_/g, ' '); 
 //         row.appendChild(nameTd);
 
 
 //         // 3. AI Extracted Value
-//         const aiTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
-//         aiTd.textContent = attr.aiValue;
+//         const aiTd = el('td', `ai-value ${attr?.isMatch ? '' : 'mismatch-value'}`);
+//         aiTd.textContent = attr?.aiValue;
 //         row.appendChild(aiTd);
         
 //         // 4. Source
 //         const sourceTd = el('td', 'attribute-source');
-//         sourceTd.textContent = formatString(attr.source);
+//         sourceTd.textContent = formatString(attr?.source);
 //         row.appendChild(sourceTd);
 
 //         tbody.appendChild(row);
@@ -1991,7 +2176,7 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
         
         if (hasMandatoryBaseline) {
             // Normalize the current attribute's value
-            const currentValueNormalized = String(attr.value).trim().toLowerCase();
+            const currentValueNormalized = String(attr?.value).trim().toLowerCase();
             
             // Check if the current value exists ANYWHERE in the mandatory original values set
             if (mandatoryOriginalValuesSet.has(currentValueNormalized)) {
@@ -2003,11 +2188,11 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
         // --- END CORE COMPARISON LOGIC ---
         
         const nameTd = el('td', 'attribute-name');
-        nameTd.textContent = formatString(attr.name).replace(/_/g, ' '); 
+        nameTd.textContent = formatString(attr?.name).replace(/_/g, ' '); 
         row.appendChild(nameTd);
 
         const valueTd = el('td', 'attribute-value');
-        const displayValue = formatString(attr.value) + ' (' + formatString(attr.source) + ')';
+        const displayValue = formatString(attr?.value) + ' (' + formatString(attr?.source) + ')';
         
         valueTd.textContent = displayValue || 'N/A';
         
@@ -2088,18 +2273,18 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
 //         const row = el('tr'); 
         
 //         const nameTd = el('td', 'attribute-name');
-//         nameTd.textContent = formatString(attr.name).replace(/_/g, ' '); 
+//         nameTd.textContent = formatString(attr?.name).replace(/_/g, ' '); 
 //         row.appendChild(nameTd);
 
 //         const valueTd = el('td', 'attribute-value');
-//         const displayValue = Array.isArray(attr.value) 
-//             ? (attr.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr.source)+ ')' 
-//             : formatString(attr.value) +'('+ formatString(attr.source)+ ')';
+//         const displayValue = Array.isArray(attr?.value) 
+//             ? (attr?.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr?.source)+ ')' 
+//             : formatString(attr?.value) +'('+ formatString(attr?.source)+ ')';
 //         valueTd.textContent = displayValue || 'N/A';
 //         row.appendChild(valueTd);
 
 //         // const sourceTd = el('td', 'attribute-source');
-//         // sourceTd.textContent = attr.source || 'Unknown';
+//         // sourceTd.textContent = attr?.source || 'Unknown';
 //         // row.appendChild(sourceTd);
 
 //         tbody.appendChild(row);
@@ -2215,6 +2400,18 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
 //     if (apiSummary) apiSummary.style.display = 'block';
 // }
 
+function highlightMatches(text, keywords) {
+    if (!text) return '—';
+    let highlighted = text;
+    keywords.forEach(keyword => {
+        const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape regex
+        const regex = new RegExp(`(${escapedKeyword})`, 'gi');
+        highlighted = highlighted.replace(regex, '<span class="highlight-text">$1</span>');
+    });
+    return highlighted;
+}
+
+// WOrking ONe
 function renderInlineForTable() {
     const api = API_RESPONSE_AI;
     const table = $('#tableContainer');
@@ -2244,7 +2441,7 @@ function renderInlineForTable() {
         if (Object.keys(mandatoryData).length > 0) {
             const mandatoryTable = renderMandatoryComparisonTable(
                 mandatoryData, 
-                'Mandatory Attributes Comparison'
+                'Attributes'
             );
             content.appendChild(mandatoryTable);
         }
@@ -2312,12 +2509,53 @@ function renderInlineForTable() {
         const c3 = el('span', 'pill secondary'); c3.textContent = `OCR Keys: ${ocrExtractedCount}`;
         const c4 = el('span', 'pill secondary'); c4.textContent = `Visual Keys: ${visualExtractedCount}`;
         counts.appendChild(c1); counts.appendChild(c2); counts.appendChild(c3); counts.appendChild(c4);
-        content.appendChild(counts);
+        // content.appendChild(counts);
         
         // Final assembly and insertion
         td.appendChild(content); 
         detail.appendChild(td);
         baseRow.insertAdjacentElement('afterend', detail);
+
+        
+        // Extract mandatory values for highlighting
+        // const mandatoryValues = Object.values(mandatoryData)
+        //     .map(v => v?.value?.toString()?.trim())
+        //     .filter(Boolean);
+
+        const mandatoryValues = Object.values(mandatoryData)
+            // 1. Use flatMap to iterate over the outer array AND flatten the inner arrays.
+            //    'arr' here is the array like [ { value: 'Pullover', ... } ]
+            .flatMap(arr => 
+                // 2. Map the inner array. We safely assume the first element [0] 
+                //    holds the value we want.
+                arr.map(item => item?.value?.toString()?.trim())
+            )
+            // 3. Filter out any potential undefined/null/empty string results.
+            .filter(Boolean);
+
+        // Highlight in Product Name
+        const nameCell = baseRow.querySelector('td:nth-child(3)');
+        if (nameCell) {
+            nameCell.innerHTML = highlightMatches(nameCell.textContent, mandatoryValues);
+        }
+
+        // Highlight in Short Description
+        const descCell = baseRow.querySelector('td:nth-child(6)');
+        if (descCell) {
+            const shortDescDiv = descCell.querySelector('.short-desc');
+            if (shortDescDiv) {
+                shortDescDiv.innerHTML = highlightMatches(shortDescDiv.innerHTML, mandatoryValues);
+            }
+        }
+
+        const desclongCell = baseRow.querySelector('td:nth-child(6)');
+        if (desclongCell) {
+            const longDescDiv = desclongCell.querySelector('.long-desc');
+            if (longDescDiv) {
+                longDescDiv.innerHTML = highlightMatches(longDescDiv.innerHTML, mandatoryValues);
+            }
+        }
+
     });
 
     // Update summary statistics
@@ -2327,7 +2565,21 @@ function renderInlineForTable() {
     const apiSummary = $('#api-summary');
     if (apiSummary) apiSummary.style.display = 'block';
 }
+// working one
+function applyHighlightingToTableRow(baseRow, mandatoryValues) {
+    const nameCell = baseRow.querySelector('td:nth-child(3)');
+    if (nameCell) {
+        nameCell.innerHTML = highlightMatches(nameCell.textContent, mandatoryValues);
+    }
 
+    const descCell = baseRow.querySelector('td:nth-child(6)');
+    if (descCell) {
+        const shortDescDiv = descCell.querySelector('.short-desc');
+        if (shortDescDiv) {
+            shortDescDiv.innerHTML = highlightMatches(shortDescDiv.innerHTML, mandatoryValues);
+        }
+    }
+}
 
 function renderInlineAttributes() {
     if (layoutMode === 'cards') renderInlineForCards(); else renderInlineForTable();
@@ -2695,8 +2947,8 @@ function getAtributeList(){
                  // Step 1: Extract unique mandatory attribute names
                 const mandatoryAttributes = [...new Set(
                     attributesData
-                        .filter(attr => attr.is_mandatory === "Yes")
-                        .map(attr => attr.attribute_name)
+                        .filter(attr => attr?.is_mandatory === "Yes")
+                        .map(attr => attr?.attribute_name)
                 )];
 
                 // Step 2: Populate the select element
@@ -2795,11 +3047,11 @@ function renderMandatoryComparisonCards(attributes, title) {
 
     attributeEntries.forEach(attr => {
         // Main Card Element
-        const card = el('div', `comparison-card ${attr.isMatch ? 'match' : 'mismatch-card'}`);
+        const card = el('div', `comparison-card ${attr?.isMatch ? 'match' : 'mismatch-card'}`);
 
         // Card Header (Attribute Name)
         const header = el('div', 'card-header');
-        header.textContent = attr.name.replace(/_/g, ' ');
+        header.textContent = attr?.name.replace(/_/g, ' ');
         card.appendChild(header);
 
         // Content Wrapper
@@ -2809,25 +3061,25 @@ function renderMandatoryComparisonCards(attributes, title) {
         const originalBox = el('div', 'value-box original-box');
         originalBox.innerHTML = `
             <div class="value-label">Manually Identified Value</div>
-            <div class="value-text">${attr.originalValue}</div>
+            <div class="value-text">${attr?.originalValue}</div>
         `;
         content.appendChild(originalBox);
 
         // AI Value Box
-        const aiBox = el('div', `value-box ai-box ${attr.isMatch ? 'found-value' : 'mismatch-value'}`);
+        const aiBox = el('div', `value-box ai-box ${attr?.isMatch ? 'found-value' : 'mismatch-value'}`);
         aiBox.innerHTML = `
-            <div class="value-label">AI Generated Value <span class="value-source">(${attr.source})</span></div>
-            <div class="value-text">${attr.aiValue}</div>
+            <div class="value-label">AI Generated Value <span class="value-source">(${attr?.source})</span></div>
+            <div class="value-text">${attr?.aiValue}</div>
         `;
         content.appendChild(aiBox);
 
         card.appendChild(content);
 
         // Mismatch Indicator (only visible on mismatch-card via CSS)
-        if (!attr.isMatch) {
+        if (!attr?.isMatch) {
             const indicator = el('div', 'mismatch-indicator');
             // indicator.innerHTML = '❌ MISMATCH';
-            indicator.innerHTML = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
+            indicator.innerHTML = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
             card.appendChild(indicator);
         }
 
@@ -2911,10 +3163,10 @@ function renderMandatoryComparisonCards(attributes, title) {
 //     const cardsContainer = el('div', 'comparison-cards-container');
 
 //     attributeEntries.forEach(attr => {
-//         const card = el('div', `comparison-card ${attr.isMatch ? 'match' : 'mismatch-card'}`);
+//         const card = el('div', `comparison-card ${attr?.isMatch ? 'match' : 'mismatch-card'}`);
 
 //         const header = el('div', 'card-header');
-//         header.textContent = attr.name.replace(/_/g, ' ');
+//         header.textContent = attr?.name.replace(/_/g, ' ');
 //         card.appendChild(header);
 
 //         const content = el('div', 'card-content');
@@ -2923,25 +3175,25 @@ function renderMandatoryComparisonCards(attributes, title) {
 //         const originalBox = el('div', 'value-box original-box');
 //         originalBox.innerHTML = `
 //             <div class="value-label">Manually Identified Value</div>
-//             <div class="value-text">${attr.originalValue}</div>
+//             <div class="value-text">${attr?.originalValue}</div>
 //         `;
 //         content.appendChild(originalBox);
 
 //         // AI Value Box
-//         const aiBox = el('div', `value-box ai-box ${attr.isMatch ? 'found-value' : 'mismatch-value'}`);
+//         const aiBox = el('div', `value-box ai-box ${attr?.isMatch ? 'found-value' : 'mismatch-value'}`);
 //         aiBox.innerHTML = `
-//             <div class="value-label">AI Generated Value <span class="value-source">(${attr.source})</span></div>
-//             <div class="value-text">${attr.aiValue}</div>
+//             <div class="value-label">AI Generated Value <span class="value-source">(${attr?.source})</span></div>
+//             <div class="value-text">${attr?.aiValue}</div>
 //         `;
 //         content.appendChild(aiBox);
 
 //         card.appendChild(content);
 
 //         // Mismatch Indicator
-//         if (!attr.isMatch) {
+//         if (!attr?.isMatch) {
 //             const indicator = el('div', 'mismatch-indicator');
 //             // indicator.innerHTML = '❌ MISMATCH';
-//             indicator.innerHTML = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
+//             indicator.innerHTML = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
 //             card.appendChild(indicator);
 //         }
 
@@ -2993,11 +3245,11 @@ function renderMandatoryComparisonCards(attributes, title) {
 
     attributeEntries.forEach(attr => {
         // --- CHANGE 1: Apply 'match-card' or 'mismatch-card' explicitly ---
-        const cardClass = attr.isMatch ? 'match-card' : 'mismatch-card';
+        const cardClass = attr?.isMatch ? 'match-card' : 'mismatch-card';
         const card = el('div', `comparison-card ${cardClass}`);
 
         const header = el('div', 'card-header');
-        header.textContent = attr.name.replace(/_/g, ' ');
+        header.textContent = attr?.name.replace(/_/g, ' ');
         card.appendChild(header);
 
         const content = el('div', 'card-content');
@@ -3006,16 +3258,16 @@ function renderMandatoryComparisonCards(attributes, title) {
         const originalBox = el('div', 'value-box original-box');
         originalBox.innerHTML = `
             <div class="value-label">Manually Identified Value</div>
-            <div class="value-text">${attr.originalValue}</div>
+            <div class="value-text">${attr?.originalValue}</div>
         `;
         content.appendChild(originalBox);
 
         // AI Value Box
         // Removed 'found-value' class here as styling should rely on the parent card class
-        const aiBox = el('div', `value-box ai-box ${attr.isMatch ? '' : 'mismatch-value'}`); 
+        const aiBox = el('div', `value-box ai-box ${attr?.isMatch ? '' : 'mismatch-value'}`); 
         aiBox.innerHTML = `
-            <div class="value-label">AI Generated Value <span class="value-source">(${attr.source})</span></div>
-            <div class="value-text">${attr.aiValue}</div>
+            <div class="value-label">AI Generated Value <span class="value-source">(${attr?.source})</span></div>
+            <div class="value-text">${attr?.aiValue}</div>
         `;
         content.appendChild(aiBox);
 
@@ -3023,7 +3275,7 @@ function renderMandatoryComparisonCards(attributes, title) {
 
         // --- CHANGE 2: Display the indicator for ALL cards, controlling color via CSS ---
         const indicator = el('div', 'match-status-indicator');
-        indicator.innerHTML = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
+        indicator.innerHTML = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
         card.appendChild(indicator);
 
         cardsContainer.appendChild(card);
@@ -3092,7 +3344,7 @@ function renderSimpleAttributeCards(attributes, title) {
 
         // Card Header (Attribute Name)
         const header = el('div', 'card-header');
-        header.textContent = formatString(attr.name).replace(/_/g, ' ');
+        header.textContent = formatString(attr?.name).replace(/_/g, ' ');
         card.appendChild(header);
 
         // Content Wrapper
@@ -3100,13 +3352,13 @@ function renderSimpleAttributeCards(attributes, title) {
         
         // Value Box
         const valueBox = el('div', 'value-box single-value-box');
-        const displayValue = Array.isArray(attr.value) 
-            ? (attr.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr.source)+ ')' 
-            : formatString(attr.value) +'('+ formatString(attr.source)+ ')'; 
-            // : attr.value;
+        const displayValue = Array.isArray(attr?.value) 
+            ? (attr?.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr?.source)+ ')' 
+            : formatString(attr?.value) +'('+ formatString(attr?.source)+ ')'; 
+            // : attr?.value;
             
         valueBox.innerHTML = `
-            <div class="value-label">Extracted Value <span class="value-source">(${formatString(attr.source)})</span></div>
+            <div class="value-label">Extracted Value <span class="value-source">(${formatString(attr?.source)})</span></div>
             <div class="value-text">${displayValue || 'N/A'}</div>
         `;
         content.appendChild(valueBox);

+ 2 - 2
content_quality_tool_public/templates/attr-extraction.html

@@ -81,8 +81,8 @@
                     </div>
                     <div class="actions">
                         <!-- Checkbox: Extract Additional -->
-                        <div class="form-check me-3"  style="padding: 10px;">
-                            <input class="form-check-input" checked type="checkbox" id="extract_additional" name="extract_additional">
+                        <div class="form-check me-3"  style="padding: 10px;display: none;">
+                            <input class="form-check-input"  type="checkbox" id="extract_additional" name="extract_additional">
                             <label class="form-check-label" for="extract_additional">
                                &nbsp; Extract Additional
                             </label>

+ 2 - 2
content_quality_tool_public/templates/product-performance-analysis.html

@@ -606,8 +606,8 @@ border-left: 5px solid #e20303; /* Green */
                 apiresults = JSON.parse(apiresults_stringify);
                 const scores = apiresults.map(item => item.breakdown.title_quality);
                 console.log("** scores **",scores);
-                const scores2 = apiresults.map(item => item.image_score);
-                console.log("** scores 2 **",scores);
+                const scores2 = apiresults.map(item => item.ai_suggestions.content.quality_score_prediction);
+                console.log("** scores 21 **",scores2);
                 // apiresults = [];
 
                 // Define performance buckets