Harshit Pathak 3 月之前
父节点
当前提交
078f2e5989

+ 13 - 2
content_quality_tool_public/static/css/attr-extraction.css

@@ -74,7 +74,8 @@ h1 { font-size: 1.6rem; margin: 0; letter-spacing: .3px; }
 
 
 /* Cards layout */
 /* Cards layout */
 .list { display: grid; gap: 12px; }
 .list { display: grid; gap: 12px; }
-.product { display: grid; grid-template-columns: 64px 1fr 1fr auto; gap: 12px; align-items: start; padding: 10px; border-radius: 12px; background: #ffffff; border: 1px solid var(--border); cursor: pointer; }
+.product { display: grid; grid-template-columns: 64px 1fr auto; gap: 12px; align-items: start; padding: 10px; border-radius: 12px; background: #ffffff; border: 1px solid var(--border); cursor: pointer; }
+/* .product { display: grid; grid-template-columns: 64px 1fr 1fr auto; gap: 12px; align-items: start; padding: 10px; border-radius: 12px; background: #ffffff; border: 1px solid var(--border); cursor: pointer; } */
 .product:hover { border-color: var(--row-hover); }
 .product:hover { border-color: var(--row-hover); }
 .product.selected { outline: 2px solid var(--brand-2); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
 .product.selected { outline: 2px solid var(--brand-2); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
 
 
@@ -895,4 +896,14 @@ td{
 /* THE MAGIC: Show the full-image-hover when the parent container is hovered */
 /* THE MAGIC: Show the full-image-hover when the parent container is hovered */
 .mini-thumb-container:hover .full-image-hover {
 .mini-thumb-container:hover .full-image-hover {
     display: block;
     display: block;
-}
+}
+
+.green-text {
+    color: green;
+    font-weight: bold; /* Optional: helps it stand out */
+}
+
+/* .red-text {
+    color: red;
+    font-weight: bold; 
+} */

+ 326 - 89
content_quality_tool_public/static/js/attr-extraction.js

@@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', () => {
         renderProducts();
         renderProducts();
         getAtributeList();
         getAtributeList();
         document.getElementById('btnSubmit').addEventListener('click', submitAttributes);
         document.getElementById('btnSubmit').addEventListener('click', submitAttributes);
-        document.getElementById('btnReset').addEventListener('click', resetAll);
+        // document.getElementById('btnReset').addEventListener('click', resetAll);
         // document.getElementById('btnSelectAll').addEventListener('click', () => {
         // document.getElementById('btnSelectAll').addEventListener('click', () => {
         // if (selectedIds.size === PRODUCT_BASE.length) { selectedIds.clear(); } else { selectedIds = new Set(PRODUCT_BASE.map(p => p.id)); }
         // if (selectedIds.size === PRODUCT_BASE.length) { selectedIds.clear(); } else { selectedIds = new Set(PRODUCT_BASE.map(p => p.id)); }
         // // renderProducts();
         // // renderProducts();
@@ -304,17 +304,17 @@ if(p.product_type_details.length > 0){
     });
     });
     
     
     // --- Render Mandatory Attributes ---
     // --- Render Mandatory Attributes ---
-    if (mandatoryAttributes.length > 0) {
-        const manTitle = el('p', "pSelectRight mandatory-title");
-        manTitle.innerHTML = "Mandatory Attributes:";
-        attrContainer.appendChild(manTitle);
-
-        mandatoryAttributes.forEach(attr => {
-            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
-            const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
-            attrContainer.appendChild(chipGroup);
-        });
-    }
+    // if (mandatoryAttributes.length > 0) {
+    //     const manTitle = el('p', "pSelectRight mandatory-title");
+    //     manTitle.innerHTML = "Mandatory Attributes:";
+    //     attrContainer.appendChild(manTitle);
+
+    //     mandatoryAttributes.forEach(attr => {
+    //         const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+    //         const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
+    //         attrContainer.appendChild(chipGroup);
+    //     });
+    // }
 
 
     // --- Render Optional Attributes ---
     // --- Render Optional Attributes ---
     if (optionalAttributes.length > 0) {
     if (optionalAttributes.length > 0) {
@@ -348,7 +348,7 @@ if(p.product_type_details.length > 0){
     row.appendChild(container); row.appendChild(mid);
     row.appendChild(container); row.appendChild(mid);
     if(p.product_type_details.length > 0){
     if(p.product_type_details.length > 0){
         console.log("IN ");
         console.log("IN ");
-        row.appendChild(attrContainer); // Append the new attribute selectors container
+        // row.appendChild(attrContainer); // Append the new attribute selectors container
     }    
     }    
     row.appendChild(right);
     row.appendChild(right);
     // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) { 
     // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) { 
@@ -724,7 +724,7 @@ function renderProductsTable(items = getCurrentSlice()) {
     const trh = document.createElement('tr');
     const trh = document.createElement('tr');
     
     
     // Table Headers
     // Table Headers
-    ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description', 'Attributes'].forEach(h => {
+    ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
         const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
         const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
     });
     });
     thead.appendChild(trh); table.appendChild(thead);
     thead.appendChild(trh); table.appendChild(thead);
@@ -757,7 +757,7 @@ function renderProductsTable(items = getCurrentSlice()) {
             const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
             const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
             const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
             const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
             const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
             const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
-            const tdDesc = document.createElement('td'); tdDesc.textContent = p.product_short_description || ''; tr.appendChild(tdDesc);
+            const tdDesc = document.createElement('td'); tdDesc.innerHTML = p.product_short_description || ''; tr.appendChild(tdDesc);
 
 
             // ---------------------------------------------
             // ---------------------------------------------
             // --- ATTRIBUTE SELECTION IMPLEMENTATION ---
             // --- ATTRIBUTE SELECTION IMPLEMENTATION ---
@@ -770,7 +770,7 @@ function renderProductsTable(items = getCurrentSlice()) {
             detailRow.id = `detail-row-${p.id}`;
             detailRow.id = `detail-row-${p.id}`;
             
             
             const detailCell = document.createElement('td');
             const detailCell = document.createElement('td');
-            detailCell.colSpan = 7; // Must span all columns
+            detailCell.colSpan = 6; // Must span all columns
             
             
             const attrContainer = document.createElement('div');
             const attrContainer = document.createElement('div');
             attrContainer.id = `attr-container-${p.item_id}`; // Unique ID for targeting by updateProductState
             attrContainer.id = `attr-container-${p.item_id}`; // Unique ID for targeting by updateProductState
@@ -792,7 +792,7 @@ function renderProductsTable(items = getCurrentSlice()) {
                 toggleButton.textContent = 'Configure';
                 toggleButton.textContent = 'Configure';
                 toggleButton.classList.add('btn', 'btn-sm', 'btn-info', 'attribute-toggle-btn');
                 toggleButton.classList.add('btn', 'btn-sm', 'btn-info', 'attribute-toggle-btn');
                 tdAttr.appendChild(toggleButton);
                 tdAttr.appendChild(toggleButton);
-                tr.appendChild(tdAttr);
+                // tr.appendChild(tdAttr);
             
             
 
 
 
 
@@ -850,7 +850,7 @@ function renderProductsTable(items = getCurrentSlice()) {
     } else {
     } else {
         const tr = el('tr'); 
         const tr = el('tr'); 
         const tdName = el('td');
         const tdName = el('td');
-        tdName.colSpan = 7; 
+        tdName.colSpan = 6; 
         tdName.innerHTML = "No Products Found.";
         tdName.innerHTML = "No Products Found.";
         tr.appendChild(tdName);
         tr.appendChild(tdName);
         tbody.appendChild(tr);
         tbody.appendChild(tr);
@@ -1416,10 +1416,23 @@ function renderMandatoryComparisonTable(attributes, title) {
  * @param {string} title - The title for the table section.
  * @param {string} title - The title for the table section.
  * @returns {HTMLElement} A div containing the table.
  * @returns {HTMLElement} A div containing the table.
  */
  */
-function renderAttributesAsTable(attributes, title) {
+function renderAttributesAsTable(attributes, title, mandatoryData = null) {
     const section = el('div', 'attribute-section');
     const section = el('div', 'attribute-section');
     let attributeEntries = [];
     let attributeEntries = [];
 
 
+    // --- STEP 1: Create a Set of all Mandatory Original Values (Normalized) ---
+    // A Set is used for fast lookups. Values are normalized (trimmed, lowercase)
+    const mandatoryOriginalValuesSet = new Set();
+    if (mandatoryData) {
+        Object.values(mandatoryData).forEach(attrArray => {
+            const originalValue = attrArray[0]?.original_value;
+            if (originalValue) {
+                mandatoryOriginalValuesSet.add(String(originalValue).trim().toLowerCase());
+            }
+        });
+    }
+
+    // Helper to extract attribute entries consistently
     const processAttribute = (key, values) => {
     const processAttribute = (key, values) => {
         const valuesArray = Array.isArray(values) ? values : [values]; 
         const valuesArray = Array.isArray(values) ? values : [values]; 
         valuesArray.forEach(v => {
         valuesArray.forEach(v => {
@@ -1431,19 +1444,29 @@ function renderAttributesAsTable(attributes, title) {
         });
         });
     };
     };
 
 
+    // Iterate through attributes (OCR/Visual/Additional) and flatten them
     Object.keys(attributes).forEach(key => {
     Object.keys(attributes).forEach(key => {
         const attribute = attributes[key];
         const attribute = attributes[key];
         
         
         if (Array.isArray(attribute)) {
         if (Array.isArray(attribute)) {
             processAttribute(key, attribute);
             processAttribute(key, attribute);
         } else if (typeof attribute === 'object' && attribute !== null) {
         } else if (typeof attribute === 'object' && attribute !== null) {
-            Object.keys(attribute).forEach(subKey => {
-                const subAttribute = attribute[subKey];
-                if (Array.isArray(subAttribute)) {
-                    // Combines parent key (e.g., 'size') and sub-key (e.g., 'waist_size')
-                    processAttribute(`${key} (${subKey.replace(/_/g, ' ')})`, subAttribute);
-                }
-            });
+            // Handle simple { "key": { "value": "X", "source": "Y" } } structure
+            if (attribute.value !== undefined) {
+                 attributeEntries.push({
+                    name: key,
+                    value: attribute.value,
+                    source: attribute.source || 'N/A'
+                });
+            } else {
+                // Handle nested objects
+                Object.keys(attribute).forEach(subKey => {
+                    const subAttribute = attribute[subKey];
+                    if (Array.isArray(subAttribute)) {
+                        processAttribute(`${key} (${subKey.replace(/_/g, ' ')})`, subAttribute);
+                    }
+                });
+            }
         }
         }
     });
     });
 
 
@@ -1458,7 +1481,6 @@ function renderAttributesAsTable(attributes, title) {
         return section;
         return section;
     }
     }
 
 
-    // --- SCROLL WRAPPER ADDITION ---
     const scrollWrapper = el('div', 'attribute-scroll-wrapper');
     const scrollWrapper = el('div', 'attribute-scroll-wrapper');
     const table = el('table', 'attribute-detail-table');
     const table = el('table', 'attribute-detail-table');
 
 
@@ -1478,34 +1500,236 @@ function renderAttributesAsTable(attributes, title) {
     attributeEntries.forEach(attr => {
     attributeEntries.forEach(attr => {
         const row = el('tr'); 
         const row = el('tr'); 
         
         
+        // --- CORE COMPARISON LOGIC (Global Check) ---
+        let colorClass = '';
+        const hasMandatoryBaseline = mandatoryOriginalValuesSet.size > 0;
+        
+        if (hasMandatoryBaseline) {
+            // Normalize the current attribute's value
+            const currentValueNormalized = String(attr.value).trim().toLowerCase();
+            
+            // Check if the current value exists ANYWHERE in the mandatory original values set
+            if (mandatoryOriginalValuesSet.has(currentValueNormalized)) {
+                colorClass = 'green-text'; // Found a match in the global mandatory set
+            } else {
+                colorClass = 'red-text'; // Did NOT find a match
+            }
+        }
+        // --- END CORE COMPARISON LOGIC ---
+        
         const nameTd = el('td', '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);
         row.appendChild(nameTd);
 
 
         const valueTd = el('td', 'attribute-value');
         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 = formatString(attr.value) + ' (' + formatString(attr.source) + ')';
+        
         valueTd.textContent = displayValue || 'N/A';
         valueTd.textContent = displayValue || 'N/A';
-        row.appendChild(valueTd);
-
-        // const sourceTd = el('td', 'attribute-source');
-        // sourceTd.textContent = attr.source || 'Unknown';
-        // row.appendChild(sourceTd);
+        
+        // Apply the determined color class to the value cell
+        if (colorClass) {
+            valueTd.classList.add(colorClass);
+        }
 
 
+        row.appendChild(valueTd);
         tbody.appendChild(row);
         tbody.appendChild(row);
     });
     });
 
 
     table.appendChild(tbody);
     table.appendChild(tbody);
-    scrollWrapper.appendChild(table); // Append table to wrapper
-    section.appendChild(scrollWrapper); // Append wrapper to section
+    scrollWrapper.appendChild(table); 
+    section.appendChild(scrollWrapper); 
     return section;
     return section;
 }
 }
+// function renderAttributesAsTable(attributes, title, mandatoryData = null) {
+//     const section = el('div', 'attribute-section');
+//     let attributeEntries = [];
+
+//     const processAttribute = (key, values) => {
+//         const valuesArray = Array.isArray(values) ? values : [values]; 
+//         valuesArray.forEach(v => {
+//             attributeEntries.push({
+//                 name: key,
+//                 value: v.value,
+//                 source: v.source || 'N/A'
+//             });
+//         });
+//     };
+
+//     Object.keys(attributes).forEach(key => {
+//         const attribute = attributes[key];
+        
+//         if (Array.isArray(attribute)) {
+//             processAttribute(key, attribute);
+//         } else if (typeof attribute === 'object' && attribute !== null) {
+//             Object.keys(attribute).forEach(subKey => {
+//                 const subAttribute = attribute[subKey];
+//                 if (Array.isArray(subAttribute)) {
+//                     // Combines parent key (e.g., 'size') and sub-key (e.g., 'waist_size')
+//                     processAttribute(`${key} (${subKey.replace(/_/g, ' ')})`, subAttribute);
+//                 }
+//             });
+//         }
+//     });
+
+//     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;
+//     }
+
+//     // --- SCROLL WRAPPER ADDITION ---
+//     const scrollWrapper = el('div', 'attribute-scroll-wrapper');
+//     const table = el('table', 'attribute-detail-table');
+
+//     const thead = el('thead');
+//     const headerRow = el('tr');
+    
+//     ['Attribute Name', 'Value'].forEach(text => {
+//         const th = el('th');
+//         th.textContent = text;
+//         headerRow.appendChild(th);
+//     });
+//     thead.appendChild(headerRow);
+//     table.appendChild(thead);
+
+//     const tbody = el('tbody');
+
+//     attributeEntries.forEach(attr => {
+//         const row = el('tr'); 
+        
+//         const nameTd = el('td', 'attribute-name');
+//         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)+ ')';
+//         valueTd.textContent = displayValue || 'N/A';
+//         row.appendChild(valueTd);
+
+//         // const sourceTd = el('td', 'attribute-source');
+//         // sourceTd.textContent = attr.source || 'Unknown';
+//         // row.appendChild(sourceTd);
+
+//         tbody.appendChild(row);
+//     });
+
+//     table.appendChild(tbody);
+//     scrollWrapper.appendChild(table); // Append table to wrapper
+//     section.appendChild(scrollWrapper); // Append wrapper to section
+//     return section;
+// }
 
 
 // ------------------------------------------------------------------
 // ------------------------------------------------------------------
 // --- 3. MAIN RENDER FUNCTION (REPLACEMENT) ---
 // --- 3. MAIN RENDER FUNCTION (REPLACEMENT) ---
 // ------------------------------------------------------------------
 // ------------------------------------------------------------------
 
 
+// function renderInlineForTable() {
+//     const api = API_RESPONSE_AI;
+//     const table = $('#tableContainer');
+//     if (!table) return;
+
+//     // Remove existing detail rows
+//     table.querySelectorAll('tr.detail-row').forEach(r => r.remove());
+
+//     PRODUCT_BASE.forEach((p, idx) => {
+//         if (!isProductSelected(p.item_id)) return;
+        
+//         const res = findApiResultForProduct(p, idx, api);
+        
+//         const tbody = table.querySelector('tbody');
+//         const baseRow = tbody ? tbody.querySelector(`#row-${p.id}`) : null; 
+//         if (!baseRow) return;
+
+//         // --- Detail Row Construction ---
+//         const detail = el('tr', 'detail-row');
+//         // td.colSpan must match the number of columns in your main table
+//         const td = el('td'); td.colSpan = 7; 
+//         const content = el('div', 'detail-content-tables');
+
+
+//         // // 1. MANDATORY Attributes Table (NOW USES CARD COMPARISON)
+//         // const mandatorySection = renderMandatoryComparisonCards( // <-- NEW FUNCTION NAME
+//         //     res?.mandatory || {}, 
+//         //     'Mandatory Attributes Comparison'
+//         // );
+//         // content.appendChild(mandatorySection);
+
+//         // // 2. COMBINED Attributes Section (Additional, OCR, Visuals) - REMAINS THE SAME
+//         // content.appendChild(el('hr', 'section-separator'));
+
+//         // 1. MANDATORY Attributes Table (USES COMPARISON FUNCTION)
+//         const mandatoryTable = renderMandatoryComparisonTable(
+//             res?.mandatory || {}, 
+//             'Mandatory Attributes Comparison'
+//         );
+//         content.appendChild(mandatoryTable);
+
+//         // 2. COMBINED Attributes Section (Additional, OCR, Visuals)
+//         content.appendChild(el('hr', 'section-separator')); 
+        
+//         const combinedTitle = el('div', 'section-title');
+//         combinedTitle.innerHTML = '<h2>Additional & AI-Driven Attributes</h2>';
+//         content.appendChild(combinedTitle);
+        
+//         const combinedAttributesContainer = el('div', 'combined-attributes-container');
+        
+//         // Use the general renderer for these sections
+//         const additionalTable = renderAttributesAsTable(
+//             res?.additional || {}, 
+//             'Additional Attributes'
+//         );
+        
+//         const ocrTable = renderAttributesAsTable(
+//             res?.ocr_results?.extracted_attributes || {}, 
+//             'OCR Results'
+//         );
+        
+//         const visualsTable = renderAttributesAsTable(
+//             res?.visual_results?.visual_attributes || {}, 
+//             'Visual Results'
+//         );
+
+//         // Append all sections to the combined container
+//         combinedAttributesContainer.appendChild(additionalTable);
+//         combinedAttributesContainer.appendChild(ocrTable);
+//         combinedAttributesContainer.appendChild(visualsTable);
+//         content.appendChild(combinedAttributesContainer);
+        
+//         // --- Summary Counts ---
+//         const mandCount = Object.keys(res?.mandatory || {}).length;
+//         const addCount = Object.keys(res?.additional || {}).length;
+//         const ocrExtractedCount = Object.keys(res?.ocr_results?.extracted_attributes || {}).length;
+//         const visualExtractedCount = Object.keys(res?.visual_results?.visual_attributes || {}).length;
+        
+//         const counts = el('div', 'attribute-summary-pills');
+//         const c1 = el('span', 'pill primary'); c1.textContent = `Mandatory: ${mandCount}`;
+//         const c2 = el('span', 'pill secondary'); c2.textContent = `Additional: ${addCount}`;
+//         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);
+        
+//         // Final assembly and insertion
+//         td.appendChild(content); 
+//         detail.appendChild(td);
+//         baseRow.insertAdjacentElement('afterend', detail);
+//     });
+
+//     // Update summary statistics
+//     $('#statTotal').textContent = api.total_products ?? 0;
+//     $('#statOk').textContent = api.successful ?? 0;
+//     $('#statKo').textContent = api.failed ?? 0;
+//     const apiSummary = $('#api-summary');
+//     if (apiSummary) apiSummary.style.display = 'block';
+// }
+
 function renderInlineForTable() {
 function renderInlineForTable() {
     const api = API_RESPONSE_AI;
     const api = API_RESPONSE_AI;
     const table = $('#tableContainer');
     const table = $('#tableContainer');
@@ -1526,63 +1750,76 @@ function renderInlineForTable() {
         // --- Detail Row Construction ---
         // --- Detail Row Construction ---
         const detail = el('tr', 'detail-row');
         const detail = el('tr', 'detail-row');
         // td.colSpan must match the number of columns in your main table
         // td.colSpan must match the number of columns in your main table
-        const td = el('td'); td.colSpan = 7; 
+        const td = el('td'); td.colSpan = 6; 
         const content = el('div', 'detail-content-tables');
         const content = el('div', 'detail-content-tables');
 
 
-
-        // // 1. MANDATORY Attributes Table (NOW USES CARD COMPARISON)
-        // const mandatorySection = renderMandatoryComparisonCards( // <-- NEW FUNCTION NAME
-        //     res?.mandatory || {}, 
-        //     'Mandatory Attributes Comparison'
-        // );
-        // content.appendChild(mandatorySection);
-
-        // // 2. COMBINED Attributes Section (Additional, OCR, Visuals) - REMAINS THE SAME
-        // content.appendChild(el('hr', 'section-separator'));
-
-        // 1. MANDATORY Attributes Table (USES COMPARISON FUNCTION)
-        const mandatoryTable = renderMandatoryComparisonTable(
-            res?.mandatory || {}, 
-            'Mandatory Attributes Comparison'
-        );
-        content.appendChild(mandatoryTable);
+        // 1. MANDATORY Attributes Table
+        // 🚨 Note: The color check (Requirement #2) must be implemented inside renderMandatoryComparisonTable. 
+        const mandatoryData = res?.mandatory || {};
+        if (Object.keys(mandatoryData).length > 0) {
+            const mandatoryTable = renderMandatoryComparisonTable(
+                mandatoryData, 
+                'Mandatory Attributes Comparison'
+            );
+            content.appendChild(mandatoryTable);
+        }
 
 
         // 2. COMBINED Attributes Section (Additional, OCR, Visuals)
         // 2. COMBINED Attributes Section (Additional, OCR, Visuals)
-        content.appendChild(el('hr', 'section-separator')); 
-        
-        const combinedTitle = el('div', 'section-title');
-        combinedTitle.innerHTML = '<h2>Additional & AI-Driven Attributes</h2>';
-        content.appendChild(combinedTitle);
+        const additionalData = res?.additional || {};
+        const ocrData = res?.ocr_results?.extracted_attributes || {};
+        const visualsData = res?.visual_results?.visual_attributes || {};
         
         
-        const combinedAttributesContainer = el('div', 'combined-attributes-container');
-        
-        // Use the general renderer for these sections
-        const additionalTable = renderAttributesAsTable(
-            res?.additional || {}, 
-            'Additional Attributes'
-        );
-        
-        const ocrTable = renderAttributesAsTable(
-            res?.ocr_results?.extracted_attributes || {}, 
-            'OCR Results'
-        );
-        
-        const visualsTable = renderAttributesAsTable(
-            res?.visual_results?.visual_attributes || {}, 
-            'Visual Results'
-        );
+        const hasCombinedData = Object.keys(additionalData).length > 0 || 
+                                Object.keys(ocrData).length > 0 || 
+                                Object.keys(visualsData).length > 0;
 
 
-        // Append all sections to the combined container
-        combinedAttributesContainer.appendChild(additionalTable);
-        combinedAttributesContainer.appendChild(ocrTable);
-        combinedAttributesContainer.appendChild(visualsTable);
-        content.appendChild(combinedAttributesContainer);
+        if (hasCombinedData) {
+            content.appendChild(el('hr', 'section-separator')); 
+            
+            const combinedTitle = el('div', 'section-title');
+            combinedTitle.innerHTML = '<h2>Additional & AI-Driven Attributes</h2>';
+            content.appendChild(combinedTitle);
+            
+            const combinedAttributesContainer = el('div', 'combined-attributes-container');
+            
+            // Render Additional Table (Conditional based on data existence)
+            if (Object.keys(additionalData).length > 0) {
+                const additionalTable = renderAttributesAsTable(
+                    additionalData, 
+                    'Additional Attributes',
+                    mandatoryData
+                );
+                combinedAttributesContainer.appendChild(additionalTable);
+            }
+            
+            // Render OCR Table (Conditional based on data existence)
+            if (Object.keys(ocrData).length > 0) {
+                const ocrTable = renderAttributesAsTable(
+                    ocrData, 
+                    'OCR Results',
+                    mandatoryData
+                );
+                combinedAttributesContainer.appendChild(ocrTable);
+            }
+            
+            // Render Visuals Table (Conditional based on data existence)
+            if (Object.keys(visualsData).length > 0) {
+                const visualsTable = renderAttributesAsTable(
+                    visualsData, 
+                    'Visual Results',
+                    mandatoryData
+                );
+                combinedAttributesContainer.appendChild(visualsTable);
+            }
+
+            content.appendChild(combinedAttributesContainer);
+        }
         
         
         // --- Summary Counts ---
         // --- Summary Counts ---
-        const mandCount = Object.keys(res?.mandatory || {}).length;
-        const addCount = Object.keys(res?.additional || {}).length;
-        const ocrExtractedCount = Object.keys(res?.ocr_results?.extracted_attributes || {}).length;
-        const visualExtractedCount = Object.keys(res?.visual_results?.visual_attributes || {}).length;
+        const mandCount = Object.keys(mandatoryData).length;
+        const addCount = Object.keys(additionalData).length;
+        const ocrExtractedCount = Object.keys(ocrData).length;
+        const visualExtractedCount = Object.keys(visualsData).length;
         
         
         const counts = el('div', 'attribute-summary-pills');
         const counts = el('div', 'attribute-summary-pills');
         const c1 = el('span', 'pill primary'); c1.textContent = `Mandatory: ${mandCount}`;
         const c1 = el('span', 'pill primary'); c1.textContent = `Mandatory: ${mandCount}`;
@@ -1685,7 +1922,7 @@ function submitAttributes() {
               "extract_additional": extractAdditional,
               "extract_additional": extractAdditional,
               "process_image": processImage,
               "process_image": processImage,
               "multiple": selectedValues,
               "multiple": selectedValues,
-              "threshold_abs": threshold,  // Lower threshold to be more permissive
+              "threshold_abs": 0.6,  // Lower threshold to be more permissive
             //   "margin": 0.3,  // Larger margin to include more candidates
             //   "margin": 0.3,  // Larger margin to include more candidates
             //   "use_adaptive_margin": true,
             //   "use_adaptive_margin": true,
             //   "use_semantic_clustering": true
             //   "use_semantic_clustering": true
@@ -1726,9 +1963,9 @@ function resetAll() {
     const thresholdInput = document.getElementById('thresholdRange');
     const thresholdInput = document.getElementById('thresholdRange');
     const thresholdDisplay = document.getElementById('thresholdValue');
     const thresholdDisplay = document.getElementById('thresholdValue');
 
 
-    thresholdInput.value = '0.5'; // or any default value you prefer
+    thresholdInput.value = '0.65'; // or any default value you prefer
     if (thresholdDisplay) {
     if (thresholdDisplay) {
-        thresholdDisplay.textContent = '0.5';
+        thresholdDisplay.textContent = '0.65';
     }
     }
 }
 }
 
 

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

@@ -87,15 +87,15 @@
                         </div>
                         </div>
                         <!-- <input type="checkbox" id="extract_additional" name="extract_additional"/>
                         <!-- <input type="checkbox" id="extract_additional" name="extract_additional"/>
                         <input type="checkbox" id="process_image" name="process_image"/> -->
                         <input type="checkbox" id="process_image" name="process_image"/> -->
-                        <button id="btnSubmit" class="btn btn-primary" title="AI Suggested">View Attributes</button>
-                        <button id="btnReset" class="btn btn-secondary">Reset</button>
+                        <button id="btnSubmit" class="btn btn-primary" title="AI Suggested">View AI Suggested Attributes</button>
+                        <!-- <button id="btnReset" class="btn btn-secondary">Reset</button> -->
                         <button id="btnSelectAll" class="btn btn-primary">Select all</button>
                         <button id="btnSelectAll" class="btn btn-primary">Select all</button>
                         <!-- Upload trigger button -->
                         <!-- Upload trigger button -->
                         <button id="btnUpload" type="button"
                         <button id="btnUpload" type="button"
                                 class="btn btn-info"
                                 class="btn btn-info"
                                 data-bs-toggle="modal"
                                 data-bs-toggle="modal"
                                 data-bs-target="#uploadModal">
                                 data-bs-target="#uploadModal">
-                                Upload
+                                Upload Products
                         </button>
                         </button>
                     </div>
                     </div>
                     </header>
                     </header>
@@ -155,8 +155,8 @@
                         <div><p>Select multiple :<select id="mandatory-attributes" name="mandatoryAttributes[]" aria-labelledby="select a attribute for which multiple data required" multiple="multiple" style="width: 100%;"></select></p></div>
                         <div><p>Select multiple :<select id="mandatory-attributes" name="mandatoryAttributes[]" aria-labelledby="select a attribute for which multiple data required" multiple="multiple" style="width: 100%;"></select></p></div>
                         <div style="display: none;">
                         <div style="display: none;">
                             <label for="thresholdRange">Threshold:</label>
                             <label for="thresholdRange">Threshold:</label>
-                            <input type="range" id="thresholdRange" min="0" max="1" step="0.01" value="0.5">
-                            <span id="thresholdValue">0.5</span>
+                            <input type="range" id="thresholdRange" min="0" max="1" step="0.01" value="0.65">
+                            <span id="thresholdValue">0.65</span>
                         </div>
                         </div>
                         </div>
                         </div>
                         <div class="card-body">
                         <div class="card-body">