| document.addEventListener('DOMContentLoaded', async () => {
|
| await loadStats();
|
|
|
| const biasForm = document.getElementById('biasForm');
|
| biasForm.addEventListener('submit', async (e) => {
|
| e.preventDefault();
|
| const data = {
|
| job: document.getElementById('job').value,
|
| years: document.getElementById('years').value,
|
| edu: document.getElementById('edu').value,
|
| skill: document.getElementById('skill').value
|
| };
|
|
|
| const response = await fetch('/api/predict', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify(data)
|
| });
|
|
|
| const result = await response.json();
|
| const resultDiv = document.getElementById('predictionResult');
|
| resultDiv.style.display = 'block';
|
|
|
| if (result.decision === 1) {
|
| resultDiv.innerHTML = `<span style="color: #10b981; font-weight:700">SUGGESTED HIRE</span><br><small>Confidence: ${(result.probability * 100).toFixed(1)}%</small>`;
|
| resultDiv.style.background = 'rgba(16, 185, 129, 0.1)';
|
| } else {
|
| resultDiv.innerHTML = `<span style="color: #ef4444; font-weight:700">REJECTION LIKELY</span><br><small>Confidence: ${((1 - result.probability) * 100).toFixed(1)}%</small>`;
|
| resultDiv.style.background = 'rgba(239, 68, 68, 0.1)';
|
| }
|
| });
|
| });
|
|
|
| async function loadStats() {
|
| try {
|
| const response = await fetch('/api/stats');
|
| const data = await response.json();
|
|
|
|
|
| document.getElementById('totalCandidates').innerText = data.overview.total_candidates.toLocaleString();
|
| document.getElementById('aiApprovalRate').innerText = ((data.overview.ai_hired / data.overview.total_candidates) * 100).toFixed(1) + '%';
|
| document.getElementById('humanApprovalRate').innerText = ((data.overview.human_hired / data.overview.total_candidates) * 100).toFixed(1) + '%';
|
| document.getElementById('agreementRate').innerText = data.overview.agreement_rate.toFixed(1) + '%';
|
|
|
| renderChart('genderChart', data.gender_bias.map(m => m.group), data.gender_bias.map(m => m.selection_rate), 'Selection Rate by Gender');
|
| renderTable('genderTableBody', data.gender_bias);
|
|
|
| renderChart('raceChart', data.race_bias.map(m => m.group), data.race_bias.map(m => m.selection_rate), 'Selection Rate by Race');
|
| renderTable('raceTableBody', data.race_bias);
|
| renderIntersectionalTable('intersectionalTableBody', data.intersectional);
|
|
|
|
|
| const mitigateBtn = document.getElementById('mitigateBtn');
|
| mitigateBtn.addEventListener('click', async () => {
|
| mitigateBtn.innerText = 'Analyzing & Optimizing...';
|
| mitigateBtn.disabled = true;
|
|
|
| const res = await fetch('/api/mitigate', { method: 'POST' });
|
| const result = await res.json();
|
|
|
| const statusDiv = document.getElementById('mitigationStatus');
|
| statusDiv.style.display = 'block';
|
| statusDiv.innerHTML = `
|
| <div style="color: var(--success); font-weight:600">${result.status}</div>
|
| <div style="color: var(--text-muted)">Strategy: ${result.strategy}</div>
|
| <div style="color: var(--primary)">Result: ${result.improvement}</div>
|
| `;
|
|
|
| mitigateBtn.innerText = 'Mitigation Applied';
|
| });
|
|
|
| } catch (error) {
|
| console.error('Error loading stats:', error);
|
| }
|
| }
|
|
|
| function renderChart(id, labels, data, label) {
|
| const ctx = document.getElementById(id).getContext('2d');
|
| new Chart(ctx, {
|
| type: 'bar',
|
| data: {
|
| labels: labels,
|
| datasets: [{
|
| label: label,
|
| data: data,
|
| backgroundColor: 'rgba(99, 102, 241, 0.6)',
|
| borderColor: '#6366f1',
|
| borderWidth: 2,
|
| borderRadius: 5
|
| }]
|
| },
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: false,
|
| scales: {
|
| y: { beginAtZero: true, max: 1, grid: { color: 'rgba(255,255,255,0.05)' } },
|
| x: { grid: { display: false } }
|
| },
|
| plugins: {
|
| legend: { display: false }
|
| }
|
| }
|
| });
|
| }
|
|
|
| function renderTable(bodyId, data) {
|
| const body = document.getElementById(bodyId);
|
| body.innerHTML = '';
|
| data.forEach(item => {
|
| const di = item.disparate_impact;
|
| let status = 'Good';
|
| let cls = 'impact-good';
|
|
|
| if (di < 0.8) {
|
| status = 'Alert';
|
| cls = 'impact-bad';
|
| } else if (di < 0.9) {
|
| status = 'Warning';
|
| cls = 'impact-warn';
|
| }
|
|
|
| const row = `
|
| <tr>
|
| <td>${item.group}</td>
|
| <td>${(item.selection_rate * 100).toFixed(1)}%</td>
|
| <td>${di.toFixed(2)}</td>
|
| <td><span class="impact-badge ${cls}">${status}</span></td>
|
| </tr>
|
| `;
|
| body.innerHTML += row;
|
| });
|
| }
|
|
|
| function renderIntersectionalTable(bodyId, data) {
|
| const body = document.getElementById(bodyId);
|
| body.innerHTML = '';
|
| data.sort((a, b) => b.rate - a.rate);
|
| data.forEach(item => {
|
| const row = `
|
| <tr>
|
| <td style="font-size: 0.8rem">${item.group}</td>
|
| <td>${(item.rate * 100).toFixed(1)}%</td>
|
| </tr>
|
| `;
|
| body.innerHTML += row;
|
| });
|
| }
|
|
|