/** * Diabetes Health Predictor - AI Doctor Portal * JavaScript for Dynamic Form Handling & API Integration */ // ======================================== // GLOBAL VARIABLES // ======================================== let currentPredictionData = null; let currentReportFilename = null; let currentComparisonAnalysis = null; let comparisonInitialized = false; // ======================================== // DOM ELEMENTS // ======================================== const patientForm = document.getElementById('patientForm'); const medicalForm = document.getElementById('medicalForm'); const registrationCard = document.getElementById('registrationCard'); const medicalTestCard = document.getElementById('medicalTestCard'); const resultsCard = document.getElementById('resultsCard'); const resultContainer = document.getElementById('resultContainer'); const reportModal = document.getElementById('reportModal'); const reportContent = document.getElementById('reportContent'); const reportFooter = document.getElementById('reportFooter'); const loadingOverlay = document.getElementById('loadingOverlay'); const predictBtn = document.getElementById('predictBtn'); const patientSummary = document.getElementById('patientSummary'); const reportTabPane = document.getElementById('reportTabPane'); const comparisonTabPane = document.getElementById('comparisonTabPane'); const modalTabButtons = document.querySelectorAll('.modal-tab'); const pastPredictionsSelect = document.getElementById('pastPredictionsSelect'); const comparePredictionsBtn = document.getElementById('comparePredictionsBtn'); const comparisonStatus = document.getElementById('comparisonStatus'); const currentVsNormalImg = document.getElementById('currentVsNormalImg'); const comparisonGraphImg = document.getElementById('comparisonGraphImg'); const historicalGraphCard = document.getElementById('historicalGraphCard'); const groqExplanationCard = document.getElementById('groqExplanationCard'); const groqExplanation = document.getElementById('groqExplanation'); const downloadComparisonPdfBtn = document.getElementById('downloadComparisonPdf'); const MAX_COMPARISON_SELECTION = 3; // Store patient data let patientData = {}; // ======================================== // FORM VALIDATION & SUBMISSION // ======================================== // Handle Patient Registration Form Submission patientForm.addEventListener('submit', (e) => { e.preventDefault(); // Collect patient data const name = document.getElementById('patientName').value.trim(); const age = document.getElementById('patientAge').value; const sex = document.getElementById('patientSex').value; const contact = document.getElementById('patientContact').value.trim(); const address = document.getElementById('patientAddress').value.trim(); // Validate if (!name || !age || !sex || !contact) { showAlert('Please fill in all required fields!', 'error'); return; } // Validate contact number if (contact.length !== 10 || !/^\d+$/.test(contact)) { showAlert('Please enter a valid 10-digit contact number!', 'error'); return; } // Store patient data patientData = { name: name, age: parseInt(age), sex: sex, contact: contact, address: address }; // Show patient summary in medical test card displayPatientSummary(); // Hide registration, show medical test card registrationCard.style.display = 'none'; medicalTestCard.style.display = 'block'; medicalTestCard.scrollIntoView({ behavior: 'smooth' }); showAlert('Patient registered successfully! Please enter health details.', 'success'); }); // Display patient summary function displayPatientSummary() { patientSummary.innerHTML = `

Patient Information

Name: ${patientData.name}
Age: ${patientData.age} years
Sex: ${patientData.sex}
Contact: ${patientData.contact}
`; } // Go back to registration function goBackToRegistration() { medicalTestCard.style.display = 'none'; registrationCard.style.display = 'block'; registrationCard.scrollIntoView({ behavior: 'smooth' }); } // Handle Medical Form Submission medicalForm.addEventListener('submit', async (e) => { e.preventDefault(); // Collect all form data const formData = { // Patient Information ...patientData, // Medical Test Parameters pregnancies: parseFloat(document.getElementById('pregnancies').value) || 0, glucose: parseFloat(document.getElementById('glucose').value), bloodPressure: parseFloat(document.getElementById('bloodPressure').value), skinThickness: parseFloat(document.getElementById('skinThickness').value) || 20, insulin: parseFloat(document.getElementById('insulin').value) || 79, bmi: parseFloat(document.getElementById('bmi').value), diabetesPedigreeFunction: parseFloat(document.getElementById('diabetesPedigreeFunction').value), }; // Validate required medical fields if (!formData.glucose || !formData.bloodPressure || !formData.bmi || !formData.diabetesPedigreeFunction) { showAlert('Please fill in all required medical test parameters marked with *', 'error'); return; } // Make prediction await makePrediction(formData); }); // ======================================== // API CALLS // ======================================== /** * Make prediction API call */ async function makePrediction(data) { showLoading(true); predictBtn.disabled = true; try { const response = await fetch('/predict', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { currentPredictionData = result; displayResults(result); resultsCard.style.display = 'block'; resultsCard.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Play sound based on risk level playPredictionSound(result.risk_level); resetComparisonUI(); } else { showAlert(`Prediction Error: ${result.error}`, 'error'); } } catch (error) { console.error('Prediction error:', error); showAlert('Failed to connect to the server. Please try again.', 'error'); } finally { showLoading(false); predictBtn.disabled = false; } } /** * Generate AI doctor report */ async function generateReport() { if (!currentPredictionData) { showAlert('No prediction data available. Please run prediction first.', 'error'); return; } // Show modal with loading setReportModalTab('report'); reportModal.classList.add('show'); reportContent.innerHTML = `

Generating comprehensive medical report...

This may take a few moments

`; reportFooter.style.display = 'none'; try { const response = await fetch('/report', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(currentPredictionData) }); const result = await response.json(); if (result.success) { currentReportFilename = result.report_file; displayReport(result.report); if (reportFooter) { const activeTab = document.querySelector('.modal-tab.active'); const comparisonActive = activeTab && activeTab.dataset.tab === 'comparison'; if (comparisonActive) { reportFooter.dataset.restoreDisplay = 'flex'; reportFooter.style.display = 'none'; } else { reportFooter.style.display = 'flex'; delete reportFooter.dataset.restoreDisplay; } } } else { reportContent.innerHTML = `

Report Generation Failed

${result.error}

`; } } catch (error) { console.error('Report generation error:', error); reportContent.innerHTML = `

Connection Error

Failed to connect to the AI report generator.

`; } } /** * Download the generated report */ function downloadReport() { if (currentReportFilename) { window.location.href = `/download_report/${currentReportFilename}`; showAlert('Report downloaded successfully!', 'success'); } else { showAlert('No report file available to download.', 'error'); } } // ======================================== // UI DISPLAY FUNCTIONS // ======================================== /** * Display prediction results */ function displayResults(data) { const riskClass = data.risk_level === 'high' ? 'high-risk' : 'low-risk'; const icon = data.risk_level === 'high' ? '' : ''; resultContainer.innerHTML = `
${icon} ${data.prediction}
Confidence: ${data.confidence}%

Patient Information

Name: ${data.patient_info.name}
Age: ${data.patient_info.age} years
Sex: ${data.patient_info.sex}
Contact: ${data.patient_info.contact}

Medical Test Results

${Object.entries(data.medical_data).map(([key, value]) => `
${key}: ${typeof value === 'number' ? value.toFixed(2) : value}
`).join('')}
Recommendation:
${data.recommendation}
`; } function resetComparisonUI() { comparisonInitialized = false; currentComparisonAnalysis = null; if (pastPredictionsSelect) { pastPredictionsSelect.innerHTML = ''; } if (comparisonStatus) { comparisonStatus.textContent = ''; } if (comparePredictionsBtn) { comparePredictionsBtn.disabled = true; } if (currentVsNormalImg) { currentVsNormalImg.removeAttribute('src'); } if (comparisonGraphImg) { comparisonGraphImg.removeAttribute('src'); } if (historicalGraphCard) { historicalGraphCard.style.display = 'none'; } if (groqExplanation) { groqExplanation.innerHTML = ''; } if (groqExplanationCard) { groqExplanationCard.style.display = 'none'; } if (downloadComparisonPdfBtn) { downloadComparisonPdfBtn.style.display = 'none'; downloadComparisonPdfBtn.dataset.downloadUrl = ''; } } function updateCurrentVsNormalImage() { if (!currentVsNormalImg) { return; } const graphUrl = (currentComparisonAnalysis && currentComparisonAnalysis.current_vs_normal_graph_url) || currentPredictionData?.graphs?.current_vs_normal?.url || currentPredictionData?.current_vs_normal_graph_url; if (graphUrl) { currentVsNormalImg.src = appendCacheBuster(graphUrl); } else { currentVsNormalImg.removeAttribute('src'); } } async function prepareComparisonTab(forceReload = false) { if (!comparisonTabPane) { return; } if (!currentPredictionData || !currentPredictionData.firebase_id) { resetComparisonUI(); if (comparisonStatus) { comparisonStatus.textContent = 'Run a prediction before using trend comparison.'; } return; } updateCurrentVsNormalImage(); if (!comparisonInitialized || forceReload) { if (comparisonStatus) { comparisonStatus.textContent = 'Loading history...'; } if (comparePredictionsBtn) { comparePredictionsBtn.disabled = true; } await populatePastPredictions(currentPredictionData.firebase_id); comparisonInitialized = true; } else if (comparisonStatus && !comparisonStatus.textContent) { comparisonStatus.textContent = 'Select two or three predictions for trend analysis.'; } if (currentComparisonAnalysis) { renderComparisonAnalysis(currentComparisonAnalysis); } } function setReportModalTab(targetTab) { if (!modalTabButtons || modalTabButtons.length === 0) { return; } modalTabButtons.forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === targetTab); }); if (targetTab === 'comparison') { if (reportTabPane) { reportTabPane.style.display = 'none'; } if (comparisonTabPane) { comparisonTabPane.style.display = 'block'; } if (reportFooter && !reportFooter.dataset.restoreDisplay) { reportFooter.dataset.restoreDisplay = reportFooter.style.display || ''; } if (reportFooter) { reportFooter.style.display = 'none'; } prepareComparisonTab().catch(err => console.error('Comparison prep error:', err)); } else { if (reportTabPane) { reportTabPane.style.display = 'block'; } if (comparisonTabPane) { comparisonTabPane.style.display = 'none'; } if (reportFooter && reportFooter.dataset.restoreDisplay !== undefined) { reportFooter.style.display = reportFooter.dataset.restoreDisplay; delete reportFooter.dataset.restoreDisplay; } } } async function populatePastPredictions(currentPredictionId) { if (!pastPredictionsSelect) { return; } pastPredictionsSelect.innerHTML = ''; if (comparisonStatus) { comparisonStatus.textContent = 'Loading history...'; } try { const response = await fetch('/patient_history?limit=25'); const data = await response.json(); if (!data.success || !Array.isArray(data.history)) { if (comparisonStatus) { comparisonStatus.textContent = 'Unable to load prediction history.'; } return; } const filteredHistory = data.history.filter(item => item.id && item.id !== currentPredictionId); filteredHistory.sort((a, b) => parseHistoryDate(a) - parseHistoryDate(b)); if (filteredHistory.length < 2) { if (comparisonStatus) { comparisonStatus.textContent = 'Need at least two past predictions to run a comparison.'; } if (comparePredictionsBtn) { comparePredictionsBtn.disabled = true; } return; } filteredHistory.forEach(item => { const option = document.createElement('option'); option.value = item.id; option.textContent = formatPredictionOption(item); pastPredictionsSelect.appendChild(option); }); if (comparisonStatus) { comparisonStatus.textContent = 'Select two or three predictions for trend analysis.'; } if (comparePredictionsBtn) { comparePredictionsBtn.disabled = false; } } catch (error) { console.error('History fetch error:', error); if (comparisonStatus) { comparisonStatus.textContent = 'Failed to load prediction history.'; } if (comparePredictionsBtn) { comparePredictionsBtn.disabled = true; } } } function parseHistoryDate(item) { if (!item) { return 0; } if (item.timestamp) { const date = new Date(item.timestamp); if (!Number.isNaN(date.getTime())) { return date.getTime(); } } if (item.created_at) { const created = Number(item.created_at); if (!Number.isNaN(created)) { return created * 1000; } } if (item.date) { const formatted = `${item.date}T${item.time || '00:00:00'}`; const date = new Date(formatted); if (!Number.isNaN(date.getTime())) { return date.getTime(); } } return 0; } function formatPredictionOption(item) { const dateValue = parseHistoryDate(item); const dateObj = new Date(dateValue); const hasValidDate = !Number.isNaN(dateObj.getTime()); const dateLabel = hasValidDate ? `${dateObj.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} ${dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : 'Previous Visit'; const resultText = item.result || item.prediction || item.risk_level || 'Unknown Risk'; const confidenceValue = item.confidence !== undefined && item.confidence !== null ? Number(item.confidence).toFixed(1) : null; const confidenceText = confidenceValue ? ` (${confidenceValue}%)` : ''; return `${dateLabel} - ${resultText}${confidenceText}`; } function appendCacheBuster(url) { if (!url) { return ''; } const separator = url.includes('?') ? '&' : '?'; return `${url}${separator}t=${Date.now()}`; } function formatExplanation(text) { if (!text) { return '

No explanation available.

'; } return text .split('\n') .map(line => line.trim()) .filter(Boolean) .map(line => `

${escapeHtml(line)}

`) .join(''); } function renderComparisonAnalysis(result) { if (!result) { if (comparisonGraphImg) { comparisonGraphImg.removeAttribute('src'); } if (historicalGraphCard) { historicalGraphCard.style.display = 'none'; } if (groqExplanation) { groqExplanation.innerHTML = ''; } if (groqExplanationCard) { groqExplanationCard.style.display = 'none'; } if (downloadComparisonPdfBtn) { downloadComparisonPdfBtn.style.display = 'none'; downloadComparisonPdfBtn.dataset.downloadUrl = ''; } return; } const comparisonGraphUrl = result.comparison_graph_url; if (comparisonGraphImg) { if (comparisonGraphUrl) { comparisonGraphImg.src = appendCacheBuster(comparisonGraphUrl); if (historicalGraphCard) { historicalGraphCard.style.display = 'block'; } } else { comparisonGraphImg.removeAttribute('src'); if (historicalGraphCard) { historicalGraphCard.style.display = 'none'; } } } if (groqExplanation) { groqExplanation.innerHTML = formatExplanation(result.explanation); } if (groqExplanationCard) { groqExplanationCard.style.display = 'block'; } if (downloadComparisonPdfBtn) { if (result.report_download_url) { downloadComparisonPdfBtn.dataset.downloadUrl = result.report_download_url; downloadComparisonPdfBtn.style.display = 'inline-flex'; } else { downloadComparisonPdfBtn.style.display = 'none'; downloadComparisonPdfBtn.dataset.downloadUrl = ''; } } } async function compareWithPast() { if (!pastPredictionsSelect || !currentPredictionData || !currentPredictionData.firebase_id) { showAlert('Run a prediction before requesting comparisons.', 'warning'); return; } const selected = Array.from(pastPredictionsSelect.selectedOptions).map(option => option.value); if (selected.length < 2 || selected.length > MAX_COMPARISON_SELECTION) { showAlert(`Please select between 2 and ${MAX_COMPARISON_SELECTION} past predictions.`, 'warning'); return; } if (comparisonStatus) { comparisonStatus.textContent = 'Generating comparison analysis...'; } if (comparePredictionsBtn) { comparePredictionsBtn.disabled = true; } try { const response = await fetch('/prediction/analysis', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current_prediction_id: currentPredictionData.firebase_id, past_prediction_ids: selected }) }); const result = await response.json(); if (!result.success) { showAlert(`Comparison Error: ${result.error}`, 'error'); if (comparisonStatus) { comparisonStatus.textContent = result.error || 'Comparison failed.'; } return; } currentComparisonAnalysis = result; updateCurrentVsNormalImage(); renderComparisonAnalysis(result); if (comparisonStatus) { comparisonStatus.textContent = 'Comparison updated successfully.'; } } catch (error) { console.error('Comparison analysis error:', error); showAlert('Failed to analyze past predictions. Please try again later.', 'error'); if (comparisonStatus) { comparisonStatus.textContent = 'Comparison failed. Try again later.'; } } finally { if (comparePredictionsBtn) { comparePredictionsBtn.disabled = false; } } } function downloadComparisonReport() { if (!downloadComparisonPdfBtn) { return; } const downloadUrl = downloadComparisonPdfBtn.dataset.downloadUrl; if (!downloadUrl) { showAlert('Generate a comparison before downloading the report.', 'warning'); return; } window.location.href = downloadUrl; showAlert('Preparing comparison report download...', 'info'); } /** * Display AI-generated report in modal */ function displayReport(reportText) { reportContent.innerHTML = `
${escapeHtml(reportText)}
`; } /** * Show/hide loading overlay */ function showLoading(show) { if (show) { loadingOverlay.classList.add('show'); } else { loadingOverlay.classList.remove('show'); } } /** * Show alert message */ function showAlert(message, type = 'info') { const alertColors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; const alertDiv = document.createElement('div'); alertDiv.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${alertColors[type]}; color: white; padding: 1rem 1.5rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 4000; animation: slideInRight 0.3s ease; max-width: 400px; font-weight: 500; `; alertDiv.innerHTML = `
${message}
`; document.body.appendChild(alertDiv); setTimeout(() => { alertDiv.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => alertDiv.remove(), 300); }, 4000); } // ======================================== // MODAL FUNCTIONS // ======================================== /** * Close the report modal */ function closeModal() { setReportModalTab('report'); reportModal.classList.remove('show'); } // Close modal when clicking outside reportModal.addEventListener('click', (e) => { if (e.target === reportModal) { closeModal(); } }); // Close modal with Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && reportModal.classList.contains('show')) { closeModal(); } }); // ======================================== // UTILITY FUNCTIONS // ======================================== /** * Reset all forms */ function resetForms() { if (confirm('Are you sure you want to start a new assessment?')) { patientForm.reset(); medicalForm.reset(); registrationCard.style.display = 'block'; medicalTestCard.style.display = 'none'; resultsCard.style.display = 'none'; currentPredictionData = null; currentReportFilename = null; currentComparisonAnalysis = null; comparisonInitialized = false; patientData = {}; resetComparisonUI(); window.scrollTo({ top: 0, behavior: 'smooth' }); showAlert('Starting new patient assessment', 'info'); } } /** * Start a new prediction (reset and scroll to top) */ function newPrediction() { resetForms(); } /** * Play sound alert based on prediction result */ function playPredictionSound(riskLevel) { try { if (riskLevel === 'high') { // Play alert sound for high risk (diabetes detected) const alertSound = document.getElementById('alertSound'); if (alertSound) { alertSound.volume = 0.5; alertSound.play().catch(err => console.log('Audio play prevented:', err)); } // Show visual alert showAlert('⚠️ HIGH RISK DETECTED! Please consult a doctor immediately.', 'warning'); } else { // Play success sound for low risk (safe, no diabetes) const successSound = document.getElementById('successSound'); if (successSound) { successSound.volume = 0.5; successSound.play().catch(err => console.log('Audio play prevented:', err)); } // Show visual success message showAlert('✅ Good News! Low risk detected. Maintain healthy lifestyle.', 'success'); } } catch (error) { console.log('Sound playback error:', error); } } /** * Escape HTML to prevent XSS */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Validate contact number in real-time */ document.getElementById('patientContact').addEventListener('input', function(e) { this.value = this.value.replace(/\D/g, ''); if (this.value.length > 10) { this.value = this.value.slice(0, 10); } }); if (pastPredictionsSelect) { pastPredictionsSelect.addEventListener('change', () => { const selected = Array.from(pastPredictionsSelect.selectedOptions); if (selected.length > MAX_COMPARISON_SELECTION) { selected[selected.length - 1].selected = false; showAlert(`You can compare up to ${MAX_COMPARISON_SELECTION} predictions at a time.`, 'warning'); } }); } if (modalTabButtons && modalTabButtons.length > 0) { modalTabButtons.forEach(btn => { btn.addEventListener('click', () => setReportModalTab(btn.dataset.tab)); }); } if (comparePredictionsBtn) { comparePredictionsBtn.addEventListener('click', compareWithPast); } if (downloadComparisonPdfBtn) { downloadComparisonPdfBtn.addEventListener('click', downloadComparisonReport); } // ======================================== // ANIMATIONS FOR CSS // ======================================== // Add slide animation styles const style = document.createElement('style'); style.textContent = ` @keyframes slideInRight { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(400px); opacity: 0; } } `; document.head.appendChild(style); // ======================================== // INITIALIZATION // ======================================== console.log('🏥 Diabetes Health Predictor - AI Doctor Portal initialized'); console.log('✅ All systems ready');