// ============================================ // Model Comparison — AI Model Analysis & Validation // ============================================ const ModelComparison = { selectedModels: new Set(['ad-standard-v1']), activeMode: 'models', // 'models', 'validation', 'datasets' isRunning: false, validationResults: [], bestRunIndex: null, init() { this.renderModelSelectors(); this.renderDatasetSelectors(); this.bindEvents(); this.loadValidationHistory(); }, bindEvents() { // Mode toggle Helpers.$$('.analysis-mode-btn').forEach(btn => { btn.addEventListener('click', () => { Helpers.$$('.analysis-mode-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); this.setMode(btn.dataset.mode); }); }); // Threshold slider Helpers.$('#validation-threshold')?.addEventListener('input', (e) => { const val = (e.target.value / 100).toFixed(2); Helpers.$('#threshold-value').textContent = val; }); // Sensitivity preset changes threshold Helpers.$('#validation-sensitivity')?.addEventListener('change', (e) => { const thresholdMap = { low: 30, medium: 75, high: 90 }; const threshold = thresholdMap[e.target.value] || 75; Helpers.$('#validation-threshold').value = threshold; Helpers.$('#threshold-value').textContent = (threshold / 100).toFixed(2); }); // Run comparison Helpers.$('#btn-run-model-comparison')?.addEventListener('click', () => this.runComparison()); }, setMode(mode) { this.activeMode = mode; const modelSelectors = Helpers.$('#model-selectors-container'); const validationSettings = Helpers.$('#validation-settings'); const exportBtns = Helpers.$('#validation-export'); if (mode === 'models') { if (modelSelectors) modelSelectors.style.display = ''; if (validationSettings) validationSettings.style.display = 'none'; if (exportBtns) exportBtns.style.display = 'none'; } else if (mode === 'validation') { if (modelSelectors) modelSelectors.style.display = 'none'; if (validationSettings) validationSettings.style.display = ''; if (exportBtns) exportBtns.style.display = ''; this.renderValidationResults(); } else if (mode === 'datasets') { if (modelSelectors) modelSelectors.style.display = ''; if (validationSettings) validationSettings.style.display = 'none'; if (exportBtns) exportBtns.style.display = 'none'; } }, renderModelSelectors() { const container = Helpers.$('#model-selectors'); if (!container) return; container.innerHTML = MockData.models.map(model => `
${model.name}
${model.description}
${model.type}
`).join(''); // Bind model selection container.querySelectorAll('.model-selector').forEach(el => { el.addEventListener('click', () => { const id = el.dataset.modelId; if (this.selectedModels.has(id)) { if (this.selectedModels.size > 1) { this.selectedModels.delete(id); el.classList.remove('model-selector--selected'); } else { Toast.show('At least one model must be selected', 'warning'); } } else { if (this.selectedModels.size >= 3) { Toast.show('Maximum 3 models can be compared', 'warning'); return; } this.selectedModels.add(id); el.classList.add('model-selector--selected'); } }); }); }, renderDatasetSelectors() { const select = Helpers.$('#model-compare-image-set'); if (!select) return; select.innerHTML = MockData.datasets.map(ds => `` ).join(''); }, loadValidationHistory() { // Mock validation run history this.validationResults = MockData.validationRuns || []; }, runComparison() { if (this.activeMode === 'validation') { this.runValidation(); } else { this.runModelComparison(); } }, runModelComparison() { if (this.selectedModels.size === 0) { Toast.show('Select at least one model', 'warning'); return; } this.isRunning = true; const btn = Helpers.$('#btn-run-model-comparison'); btn.disabled = true; btn.innerHTML = ' Running...'; setTimeout(() => { this.isRunning = false; btn.disabled = false; btn.innerHTML = ' Run Comparison'; this.renderComparisonResults(); Toast.show('Model comparison complete!', 'success'); }, 2000); }, runValidation() { const btn = Helpers.$('#btn-run-model-comparison'); btn.disabled = true; btn.innerHTML = ' Validating...'; const threshold = parseInt(Helpers.$('#validation-threshold')?.value || 75) / 100; const sensitivity = Helpers.$('#validation-sensitivity')?.value || 'medium'; const defectClass = Helpers.$('#validation-defect-class')?.value || 'all'; const datasetId = Helpers.$('#model-compare-image-set')?.value || 'west2257'; setTimeout(() => { btn.disabled = false; btn.innerHTML = ' Run Validation'; // Create new validation result const newResult = { id: Date.now(), date: new Date().toISOString(), dataset: datasetId, model: 'ad-standard-v1', threshold: threshold, sensitivity: sensitivity, defectClass: defectClass, accuracy: 0.92 + Math.random() * 0.07, precision: 0.88 + Math.random() * 0.10, recall: 0.85 + Math.random() * 0.12, f1: 0.86 + Math.random() * 0.11, falsePositiveRate: 0.02 + Math.random() * 0.08, falseNegativeRate: 0.03 + Math.random() * 0.10, truePositive: Math.floor(15 + Math.random() * 10), trueNegative: Math.floor(50 + Math.random() * 30), falsePositive: Math.floor(2 + Math.random() * 8), falseNegative: Math.floor(1 + Math.random() * 6), rocData: this.generateMockROCData(), status: 'passed' }; this.validationResults.unshift(newResult); this.bestRunIndex = 0; this.setMode('validation'); Toast.show('Validation complete!', 'success'); }, 1500); }, generateMockROCData() { const data = []; for (let tpr = 0; tpr <= 100; tpr += 5) { data.push({ tpr: tpr / 100, fpr: Math.max(0, (tpr / 100 - 0.05) * 0.9 + Math.random() * 0.05) }); } return data; }, renderComparisonResults() { const container = Helpers.$('#model-comparison-results'); if (!container) return; const modelIds = [...this.selectedModels]; const results = modelIds.map(id => ({ ...MockData.models.find(m => m.id === id), ...MockData.validationResults[id] })); const bestF1 = Math.max(...results.map(r => r.f1)); const bestPrecision = Math.max(...results.map(r => r.precision)); const bestRecall = Math.max(...results.map(r => r.recall)); const lowestFpr = Math.min(...results.map(r => r.falsePositiveRate)); container.innerHTML = `
${results.map(r => `
${r.name}
${(r.f1 * 100).toFixed(1)}%
F1 Score
`).join('')}

Performance Comparison

${results.map(r => ` `).join('')}
Model Type Accuracy Precision Recall F1 Score FPR
${r.name} ${r.type} ${(r.accuracy * 100).toFixed(1)}% ${(r.precision * 100).toFixed(1)}% ${(r.recall * 100).toFixed(1)}% ${(r.f1 * 100).toFixed(1)}% ${(r.falsePositiveRate * 100).toFixed(1)}%

ROC Curves

${results.map(r => { const colorMap = { 'ad-standard-v1': '#a7002b', 'ad-sensitive-v2': '#3b82f6', 'ss-fibres-v1': '#22c55e', 'ss-critical-v1': '#f59e0b' }; return `
${r.name}
`; }).join('')}
`; // Draw ROC curves requestAnimationFrame(() => { results.forEach(r => { const rocData = r.rocData || []; const colorMap = { 'ad-standard-v1': '#a7002b', 'ad-sensitive-v2': '#3b82f6', 'ss-fibres-v1': '#22c55e', 'ss-critical-v1': '#f59e0b' }; Helpers.drawROCCanvas('roc-' + r.id, { rocData, color: colorMap[r.id] }); }); }); }, renderValidationResults() { const container = Helpers.$('#model-comparison-results'); if (!container) return; if (this.validationResults.length === 0) { container.innerHTML = `

No Validation Runs Yet

Run a validation to see results here

`; return; } // Find best run by F1 score const bestRun = this.validationResults.reduce((best, run, i) => run.f1 > best.value ? { value: run.f1, index: i } : best , { value: 0, index: 0 }); const latestRun = this.validationResults[0]; // Build confusion matrix HTML const buildConfusionMatrix = (run) => `
Confusion Matrix
Predicted Positive
Predicted Negative
Actual Positive
${run.truePositive} True Positive
${run.falseNegative} False Negative
Actual Negative
${run.falsePositive} False Positive
${run.trueNegative} True Negative
`; // Build image comparison HTML const buildImageComparison = () => `

Classic vs AI Comparison

Classic Vision
AI Anomaly Detection
`; container.innerHTML = `

Validation History

${this.validationResults.map((run, i) => `
${run.model} — Threshold: ${run.threshold.toFixed(2)} ${i === bestRun.index ? ' Best' : ''}
${Helpers.formatDate(run.date)} • ${run.sensitivity} sensitivity • ${run.defectClass === 'all' ? 'All defects' : run.defectClass}
${(run.f1 * 100).toFixed(1)}%
F1 Score
`).join('')}

Latest Validation Results

Accuracy
${(latestRun.accuracy * 100).toFixed(1)}%
Precision
${(latestRun.precision * 100).toFixed(1)}%
Recall
${(latestRun.recall * 100).toFixed(1)}%
F1 Score
${(latestRun.f1 * 100).toFixed(1)}%
${buildConfusionMatrix(latestRun)} ${buildImageComparison()}

ROC Curve

Run Metadata
Dataset: ${latestRun.dataset} Model: ${latestRun.model} Threshold: ${latestRun.threshold.toFixed(2)} Operator: Engineer (simulated) Status: ${latestRun.status === 'passed' ? 'PASSED' : 'FAILED'}
`; requestAnimationFrame(() => { Helpers.drawROCCanvas('roc-validation', { rocData: latestRun.rocData || [], color: '#a7002b' }); }); } }; window.ModelComparison = ModelComparison;