brpuneet898's picture
Update static/index.js (#4)
d111d4d verified
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 = '<tr>' + columns.map(col => `<th class="px-4 py-2 bg-gray-100">${col}</th>`).join('') + '</tr>';
// Update body
tbody.innerHTML = previewData.map(row =>
'<tr>' + columns.map(col => `<td class="border px-4 py-2">${row[col]}</td>`).join('') + '</tr>'
).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 += `
<li>
<span>${col}: ${count} missing values</span>
<select id="method-${col}" class="border rounded p-1 mx-2">
<option value="drop">Drop Rows</option>
<option value="mean">Fill Mean</option>
<option value="median">Fill Median</option>
<option value="mode">Fill Mode</option>
</select>
<button class="btn-learn-more" onclick="fixNulls('${col}')">Fix</button>
</li>
`;
}
}
nullList.innerHTML = html;
// If no nulls left, show a message
if (!hasNulls) {
nullList.innerHTML = '<li class="text-green-700 font-semibold">No missing values remaining!</li>';
}
}
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 = `
<div class="mb-2">
<strong>Model:</strong> ${data.model}
</div>
<div class="mb-2">
<strong>MAPE:</strong> ${data.metrics.MAPE.toFixed(2)}%
<strong class="ml-4">RMSE:</strong> ${data.metrics.RMSE.toFixed(2)}
<strong class="ml-4">R²:</strong> ${data.metrics.R2.toFixed(3)}
</div>
`;
// 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 = '<p>Running prediction...</p>';
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 =
'<div class="grid grid-cols-2 md:grid-cols-4 gap-4">';
for (const [key, value] of Object.entries(data.metrics)) {
metricsHtml += `
<div class="p-4 bg-blue-50 rounded">
<p class="font-semibold">${key}</p>
<p class="text-xl">${value.toFixed(4)}</p>
</div>
`;
}
metricsHtml += "</div>";
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 = `
<table class="min-w-full table-auto">
<thead>
<tr>
<th class="px-4 py-2 bg-gray-100">Feature</th>
<th class="px-4 py-2 bg-gray-100">Importance</th>
</tr>
</thead>
<tbody>
${data.top_features
.map(
(f) =>
`<tr>
<td class="border px-4 py-2">${
f.feature
}</td>
<td class="border px-4 py-2">${f.importance.toFixed(
4
)}</td>
</tr>`
)
.join("")}
</tbody>
</table>
`;
}
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)}%<br>
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 = '<p>Running prediction...</p>';
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 =
'<div class="grid grid-cols-2 md:grid-cols-4 gap-4">';
for (const [key, value] of Object.entries(data.metrics)) {
metricsHtml += `
<div class="p-4 bg-blue-50 rounded">
<p class="font-semibold">${key}</p>
<p class="text-xl">${value.toFixed(4)}</p>
</div>
`;
}
metricsHtml += "</div>";
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 = `
<table class="min-w-full table-auto">
<thead>
<tr>
<th class="px-4 py-2 bg-gray-100">Feature</th>
<th class="px-4 py-2 bg-gray-100">Importance</th>
</tr>
</thead>
<tbody>
${data.top_features
.map(
(f) =>
`<tr>
<td class="border px-4 py-2">${
f.feature
}</td>
<td class="border px-4 py-2">${f.importance.toFixed(
4
)}</td>
</tr>`
)
.join("")}
</tbody>
</table>
`;
}
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)}%<br>
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');
});
};