// ============================================
// 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
| Model |
Type |
Accuracy |
Precision |
Recall |
F1 Score |
FPR |
${results.map(r => `
| ${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)}% |
`).join('')}
ROC Curves
${results.map(r => {
const colorMap = { 'ad-standard-v1': '#a7002b', 'ad-sensitive-v2': '#3b82f6', 'ss-fibres-v1': '#22c55e', 'ss-critical-v1': '#f59e0b' };
return `
`;
}).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
${run.truePositive}
True Positive
${run.falseNegative}
False Negative
${run.falsePositive}
False Positive
${run.trueNegative}
True Negative
`;
// Build image comparison HTML
const buildImageComparison = () => `
`;
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;