document.addEventListener('DOMContentLoaded', () => { const navLinks = document.querySelectorAll('header nav a'); navLinks.forEach(link => { link.addEventListener('click', function(event) { event.stopImmediatePropagation(); window.location.href = this.href; }, true); }); console.log('Landing page loaded.'); // Animate elements on scroll using Intersection Observer const animatedElements = document.querySelectorAll('.animate-on-scroll'); const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-fadeIn'); observer.unobserve(entry.target); } }); }, { threshold: 0.2 }); animatedElements.forEach(el => observer.observe(el)); // Plotly initial plot (if present) const initialPlotDiv = document.getElementById('initialPlot'); if (initialPlotDiv) { const plotData = JSON.parse(initialPlotDiv.dataset.plot); Plotly.newPlot('plotDiv', plotData.data, plotData.layout); } // Initialize toast notifications if (!document.getElementById('toast-container')) { const toastContainer = document.createElement('div'); toastContainer.id = 'toast-container'; toastContainer.className = 'fixed top-4 right-4 z-50'; document.body.appendChild(toastContainer); } }); function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `p-4 mb-4 rounded shadow-lg ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`; toast.textContent = message; const container = document.getElementById('toast-container'); container.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } window.createPlot = function() { const column = document.getElementById('plotColumn').value; const plotType = document.getElementById('plotType').value; fetch('/forecast/sales/plot', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `column=${column}&plot_type=${plotType}` }) .then(response => response.json()) .then(data => { if (data.success) { const plotData = JSON.parse(data.plot); Plotly.newPlot('plotDiv', plotData.data, plotData.layout); showToast(`Generated ${plotType} for ${column}`); } else { showToast(data.error, 'error'); } }) .catch(error => { console.error('Error:', error); showToast(error.message, 'error'); }); } window.fixNulls = function(column) { const method = document.getElementById('method-' + column).value; fetch('/forecast/sales/fix_nulls', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `column=${column}&method=${method}` }) .then(response => response.json()) .then(data => { const msgDiv = document.getElementById('null-fix-message'); if (data.success) { msgDiv.textContent = data.message; msgDiv.classList.remove('text-red-700'); msgDiv.classList.add('text-green-700'); showToast(data.message); // Update summary stats updateSummaryStats(data.summary_stats); // Update preview table updatePreviewTable(data.columns, data.preview_data); // Update nulls UI updateNullsUI(data.summary_stats.missing_values); } else { msgDiv.textContent = data.error; msgDiv.classList.remove('text-green-700'); msgDiv.classList.add('text-red-700'); showToast(data.error, 'error'); } }); } // Helper to update summary stats function updateSummaryStats(stats) { document.querySelectorAll('.summary-total-rows').forEach(el => el.textContent = stats.total_rows); document.querySelectorAll('.summary-total-cols').forEach(el => el.textContent = stats.total_columns); document.querySelectorAll('.summary-numeric-cols').forEach(el => el.textContent = stats.numeric_columns.join(', ')); document.querySelectorAll('.summary-categorical-cols').forEach(el => el.textContent = stats.categorical_columns.join(', ')); } // Helper to update preview table function updatePreviewTable(columns, previewData) { const table = document.getElementById('preview-table'); if (!table) return; // Update header let thead = table.querySelector('thead'); let tbody = table.querySelector('tbody'); thead.innerHTML = '' + columns.map(col => `${col}`).join('') + ''; // Update body tbody.innerHTML = previewData.map(row => '' + columns.map(col => `${row[col]}`).join('') + '' ).join(''); } // Helper to update nulls UI function updateNullsUI(missingValues) { const nullList = document.getElementById('null-list'); if (!nullList) return; let html = ''; let hasNulls = false; for (const [col, count] of Object.entries(missingValues)) { if (count > 0) { hasNulls = true; html += `
  • ${col}: ${count} missing values
  • `; } } nullList.innerHTML = html; // If no nulls left, show a message if (!hasNulls) { nullList.innerHTML = '
  • No missing values remaining!
  • '; } } window.runForecast = function() { const dateCol = document.getElementById('forecastDateCol').value; const targetCol = document.getElementById('forecastTargetCol').value; const model = document.getElementById('forecastModel').value; const horizon = document.getElementById('forecastHorizon').value; const metricsDiv = document.getElementById('forecast-metrics'); const plotDiv = document.getElementById('forecast-plot'); const errorDiv = document.getElementById('forecast-error'); metricsDiv.innerHTML = ''; plotDiv.innerHTML = ''; errorDiv.textContent = ''; fetch('/forecast/sales/run_forecast', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `date_col=${dateCol}&target_col=${targetCol}&model=${model}&horizon=${horizon}` }) .then(response => response.json()) .then(data => { if (data.success) { // Show metrics metricsDiv.innerHTML = `
    Model: ${data.model}
    MAPE: ${data.metrics.MAPE.toFixed(2)}% RMSE: ${data.metrics.RMSE.toFixed(2)} R²: ${data.metrics.R2.toFixed(3)}
    `; // Plot forecast with confidence intervals const trace = { x: data.dates, y: data.forecast, mode: 'lines+markers', name: 'Forecast' }; const lower = data.conf_int.map(ci => ci[0]); const upper = data.conf_int.map(ci => ci[1]); const ciTrace = { x: [...data.dates, ...data.dates.slice().reverse()], y: [...upper, ...lower.reverse()], fill: 'toself', fillcolor: 'rgba(0,100,80,0.2)', line: {color: 'transparent'}, name: 'Confidence Interval', showlegend: true, type: 'scatter' }; const layout = { title: 'Forecasted Sales', xaxis: {title: 'Date'}, yaxis: {title: 'Forecast'}, showlegend: true }; Plotly.newPlot(plotDiv, [trace, ciTrace], layout); showToast('Forecast generated!'); } else { errorDiv.textContent = data.error; showToast(data.error, 'error'); } }) .catch(error => { errorDiv.textContent = error.message; showToast(error.message, 'error'); }); } // Machine Failure Prediction function runPrediction() { // Show loading state const metricsDiv = document.getElementById('predict-metrics'); const errorDiv = document.getElementById('predict-error'); const singlePredictionSection = document.getElementById('single-prediction-section'); metricsDiv.innerHTML = '

    Running prediction...

    '; errorDiv.textContent = ''; singlePredictionSection.classList.add('hidden'); // Hide form until model is ready // Get selected values const targetCol = document.getElementById('predictTargetCol').value; const model = document.getElementById('predictModel').value; // Model is currently fixed to RF in backend, but UI allows selection // Create form data const formData = new FormData(); formData.append('target_col', targetCol); formData.append('model', model); // Make API call fetch('/predict/machine_failure/run_prediction', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { // Display metrics in a grid let metricsHtml = '
    '; for (const [key, value] of Object.entries(data.metrics)) { metricsHtml += `

    ${key}

    ${value.toFixed(4)}

    `; } metricsHtml += "
    "; metricsDiv.innerHTML = metricsHtml; // --- Feature Importance --- if (data.top_features && Array.isArray(data.top_features)) { const fiDiv = document.getElementById("feature-importance"); const fiList = document.getElementById("feature-importance-list"); fiDiv.classList.remove("hidden"); fiList.innerHTML = ` ${data.top_features .map( (f) => `` ) .join("")}
    Feature Importance
    ${ f.feature } ${f.importance.toFixed( 4 )}
    `; } showToast("Model trained successfully"); // Now that the model is trained, fetch form data and display the single prediction form fetchSinglePredictionForm(); } else { errorDiv.textContent = data.error || 'An error occurred'; showToast(data.error || 'An error occurred', 'error'); } }) .catch(error => { errorDiv.textContent = 'Error: ' + error.message; showToast('Error: ' + error.message, 'error'); }); } window.runPrediction = runPrediction; // Function to fetch data for and generate the single prediction form function fetchSinglePredictionForm() { fetch('/predict/machine_failure/get_form_data') .then(response => response.json()) .then(data => { if (data.success) { generatePredictionForm(data.form_fields); document.getElementById('single-prediction-section').classList.remove('hidden'); } else { showToast(data.error, 'error'); document.getElementById('single-prediction-error').textContent = data.error; } }) .catch(error => { console.error('Error fetching form data:', error); showToast('Error fetching form data: ' + error.message, 'error'); document.getElementById('single-prediction-error').textContent = 'Error fetching form data: ' + error.message; }); } // Function to dynamically generate the prediction form function generatePredictionForm(formFields) { const formContainer = document.getElementById('single-prediction-form'); formContainer.innerHTML = ''; // Clear previous fields formFields.forEach(field => { const div = document.createElement('div'); div.className = 'mb-4'; const label = document.createElement('label'); label.className = 'block text-gray-700 text-sm font-bold mb-2'; label.textContent = field.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); // Capitalize and replace underscores div.appendChild(label); if (field.type === 'select') { const select = document.createElement('select'); select.id = `input-${field.name}`; select.name = field.name; select.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'; field.options.forEach(option => { const optionElement = document.createElement('option'); optionElement.value = option; optionElement.textContent = option; // Set default selected option if (field.default_value !== undefined && String(field.default_value) === String(option)) { // Compare as strings optionElement.selected = true; } select.appendChild(optionElement); }); div.appendChild(select); } else if (field.type === 'number') { const input = document.createElement('input'); input.id = `input-${field.name}`; input.name = field.name; input.type = 'number'; input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'; input.placeholder = `Enter ${field.name.replace(/_/g, ' ')}`; if (field.default_value !== undefined) { input.value = field.default_value; } div.appendChild(input); } else { // Default to text for other types, including timestamps const input = document.createElement('input'); input.id = `input-${field.name}`; input.name = field.name; input.type = 'text'; input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'; input.placeholder = field.placeholder || `Enter ${field.name.replace(/_/g, ' ')}`; if (field.default_value !== undefined) { input.value = field.default_value; } div.appendChild(input); } formContainer.appendChild(div); }); } // Function to handle single instance prediction submission window.predictSingleInstance = function() { const form = document.getElementById('single-prediction-form'); const formData = {}; const inputs = form.querySelectorAll('input, select'); inputs.forEach(input => { formData[input.name] = input.value; }); const resultDiv = document.getElementById('single-prediction-result'); const predictionOutput = document.getElementById('prediction-output'); const probabilityOutput = document.getElementById('probability-output'); const errorDiv = document.getElementById('single-prediction-error'); resultDiv.classList.add('hidden'); errorDiv.textContent = ''; predictionOutput.textContent = 'Predicting...'; probabilityOutput.innerHTML = ''; fetch('/predict/machine_failure/predict_single', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }) .then(response => response.json()) .then(data => { if (data.success) { let displayPrediction = 'Unknown'; if (data.prediction === 0 || data.prediction === '0') { displayPrediction = 'No Failure'; } else if (data.prediction === 1 || data.prediction === '1') { displayPrediction = 'Failure'; } else { displayPrediction = data.prediction; // Fallback for other values if backend sends different strings } predictionOutput.textContent = displayPrediction; if (data.probability && Array.isArray(data.probability)) { if (data.probability.length === 2) { // Binary classification const probNoFailure = data.probability[0]; const probFailure = data.probability[1]; probabilityOutput.innerHTML = `Probability of No Failure: ${(probNoFailure * 100).toFixed(2)}%
    Probability of Failure: ${(probFailure * 100).toFixed(2)}%`; } else { // Multi-class classification probabilityOutput.innerHTML = 'Probabilities: ' + data.probability.map((p, i) => `Class ${i}: ${(p * 100).toFixed(2)}%`).join(', '); } } else { probabilityOutput.innerHTML = 'Probability: N/A (not a classification model)'; } resultDiv.classList.remove('hidden'); showToast('Single prediction successful!'); } else { errorDiv.textContent = data.error || 'An error occurred during single prediction.'; showToast(data.error || 'An error occurred', 'error'); } }) .catch(error => { console.error('Error during single prediction:', error); errorDiv.textContent = 'Error: ' + error.message; showToast('Error: ' + error.message, 'error'); }); }; // Supply Failure Prediction (New functions similar to Machine Failure) window.runSupplyPrediction = function() { const metricsDiv = document.getElementById('predict-metrics-supply'); const errorDiv = document.getElementById('predict-error-supply'); const singlePredictionSection = document.getElementById('single-prediction-section-supply'); metricsDiv.innerHTML = '

    Running prediction...

    '; errorDiv.textContent = ''; singlePredictionSection.classList.add('hidden'); // Target column is fixed to 'failure_flag' const targetCol = 'failure_flag'; const model = document.getElementById('predictModelSupply').value; const formData = new FormData(); formData.append('target_col', targetCol); formData.append('model', model); fetch('/predict/supply_failure/run_prediction', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { let metricsHtml = '
    '; for (const [key, value] of Object.entries(data.metrics)) { metricsHtml += `

    ${key}

    ${value.toFixed(4)}

    `; } metricsHtml += "
    "; metricsDiv.innerHTML = metricsHtml; // --- Feature Importance --- if (data.top_features && Array.isArray(data.top_features)) { const fiDiv = document.getElementById("feature-importance"); const fiList = document.getElementById("feature-importance-list"); fiDiv.classList.remove("hidden"); fiList.innerHTML = ` ${data.top_features .map( (f) => `` ) .join("")}
    Feature Importance
    ${ f.feature } ${f.importance.toFixed( 4 )}
    `; } showToast("Supply Model trained successfully"); fetchSupplySinglePredictionForm(); } else { errorDiv.textContent = data.error || 'An error occurred'; showToast(data.error || 'An error occurred', 'error'); } }) .catch(error => { errorDiv.textContent = 'Error: ' + error.message; showToast('Error: ' + error.message, 'error'); }); } function fetchSupplySinglePredictionForm() { fetch('/predict/supply_failure/get_form_data') .then(response => response.json()) .then(data => { if (data.success) { generateSupplyPredictionForm(data.form_fields); document.getElementById('single-prediction-section-supply').classList.remove('hidden'); } else { showToast(data.error, 'error'); document.getElementById('single-prediction-error-supply').textContent = data.error; } }) .catch(error => { console.error('Error fetching supply form data:', error); showToast('Error fetching supply form data: ' + error.message, 'error'); document.getElementById('single-prediction-error-supply').textContent = 'Error fetching supply form data: ' + error.message; }); } function generateSupplyPredictionForm(formFields) { const formContainer = document.getElementById('single-prediction-form-supply'); formContainer.innerHTML = ''; formFields.forEach(field => { const div = document.createElement('div'); div.className = 'mb-4'; const label = document.createElement('label'); label.className = 'block text-gray-700 text-sm font-bold mb-2'; label.textContent = field.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); div.appendChild(label); if (field.type === 'select') { const select = document.createElement('select'); select.id = `input-supply-${field.name}`; select.name = field.name; select.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'; field.options.forEach(option => { const optionElement = document.createElement('option'); optionElement.value = option; optionElement.textContent = option; if (field.default_value !== undefined && String(field.default_value) === String(option)) { optionElement.selected = true; } select.appendChild(optionElement); }); div.appendChild(select); } else if (field.type === 'number') { const input = document.createElement('input'); input.id = `input-supply-${field.name}`; input.name = field.name; input.type = 'number'; input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'; input.placeholder = `Enter ${field.name.replace(/_/g, ' ')}`; if (field.default_value !== undefined) { input.value = field.default_value; } div.appendChild(input); } else { // Default to text for other types, including timestamps const input = document.createElement('input'); input.id = `input-supply-${field.name}`; input.name = field.name; input.type = 'text'; input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'; input.placeholder = field.placeholder || `Enter ${field.name.replace(/_/g, ' ')}`; if (field.default_value !== undefined) { input.value = field.default_value; } div.appendChild(input); } formContainer.appendChild(div); }); } window.predictSupplySingleInstance = function() { const form = document.getElementById('single-prediction-form-supply'); const formData = {}; const inputs = form.querySelectorAll('input, select'); inputs.forEach(input => { formData[input.name] = input.value; }); const resultDiv = document.getElementById('single-prediction-result-supply'); const predictionOutput = document.getElementById('prediction-output-supply'); const probabilityOutput = document.getElementById('probability-output-supply'); const errorDiv = document.getElementById('single-prediction-error-supply'); resultDiv.classList.add('hidden'); errorDiv.textContent = ''; predictionOutput.textContent = 'Predicting...'; probabilityOutput.innerHTML = ''; fetch('/predict/supply_failure/predict_single', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }) .then(response => response.json()) .then(data => { if (data.success) { // User-friendly mapping for Supply Failure let displayPrediction = 'Unknown'; if (data.prediction === "Delivery Successful") { // Backend sends "Delivery Successful" or "Delivery Failed" displayPrediction = 'Delivery Successful'; } else if (data.prediction === "Delivery Failed") { displayPrediction = 'Delivery Failed'; } else { displayPrediction = data.prediction; // Fallback } predictionOutput.textContent = displayPrediction; if (data.probability && Array.isArray(data.probability)) { if (data.probability.length === 2) { // Assuming data.probability[0] corresponds to 'Delivery Successful' and data.probability[1] to 'Delivery Failed' const probSuccessful = data.probability[0]; const probFailed = data.probability[1]; probabilityOutput.innerHTML = `Probability of Delivery Successful: ${(probSuccessful * 100).toFixed(2)}%
    Probability of Delivery Failed: ${(probFailed * 100).toFixed(2)}%`; } else { probabilityOutput.innerHTML = 'Probabilities: ' + data.probability.map((p, i) => `Class ${i}: ${(p * 100).toFixed(2)}%`).join(', '); } } else { probabilityOutput.innerHTML = 'Probability: N/A (not a classification model)'; } resultDiv.classList.remove('hidden'); showToast('Single prediction successful!'); } else { errorDiv.textContent = data.error || 'An error occurred during single prediction.'; showToast(data.error || 'An error occurred', 'error'); } }) .catch(error => { console.error('Error during single prediction:', error); errorDiv.textContent = 'Error: ' + error.message; showToast('Error: ' + error.message, 'error'); }); };