AC02-ML / api /index.html
deropxyz's picture
init
03bcd34
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Predictive Maintenance API - Test Interface</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.header p {
font-size: 1.1em;
opacity: 0.9;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-button {
flex: 1;
padding: 15px;
background: white;
border: none;
border-radius: 10px 10px 0 0;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.tab-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
}
.tab-button.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.tab-content {
display: none;
background: white;
border-radius: 0 0 10px 10px;
padding: 30px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.tab-content.active {
display: block;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
}
label {
font-weight: 600;
margin-bottom: 8px;
color: #333;
display: flex;
align-items: center;
gap: 5px;
}
.info-icon {
font-size: 0.9em;
color: #667eea;
cursor: help;
}
input,
select {
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: all 0.3s;
}
input:focus,
select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.button-group {
display: flex;
gap: 15px;
margin-top: 20px;
}
button {
flex: 1;
padding: 15px 30px;
font-size: 1.1em;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.result-container {
margin-top: 30px;
padding: 25px;
border-radius: 10px;
display: none;
animation: slideIn 0.4s;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.result-container.success {
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
border: 2px solid #667eea;
display: block;
}
.result-container.error {
background: #fff0f0;
border: 2px solid #ff4444;
display: block;
}
.prediction-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #e0e0e0;
}
.prediction-badge {
padding: 10px 20px;
border-radius: 25px;
font-weight: 700;
font-size: 1.2em;
text-transform: uppercase;
}
.badge-healthy {
background: #4caf50;
color: white;
}
.badge-failure {
background: #ff4444;
color: white;
}
.confidence-bar {
flex: 1;
height: 30px;
background: #e0e0e0;
border-radius: 15px;
overflow: hidden;
position: relative;
}
.confidence-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
transition: width 0.5s ease;
}
.diagnostics-grid {
display: grid;
gap: 15px;
}
.diagnostic-item {
padding: 15px;
background: white;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.diagnostic-label {
font-weight: 600;
color: #667eea;
margin-bottom: 5px;
text-transform: uppercase;
font-size: 0.9em;
}
.diagnostic-value {
color: #333;
line-height: 1.6;
}
.severity-badge {
display: inline-block;
padding: 5px 15px;
border-radius: 15px;
font-weight: 600;
font-size: 0.9em;
}
.severity-low {
background: #4caf50;
color: white;
}
.severity-medium {
background: #ff9800;
color: white;
}
.severity-high {
background: #ff5722;
color: white;
}
.severity-critical {
background: #f44336;
color: white;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
.upload-area {
border: 3px dashed #667eea;
border-radius: 10px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: #f8f9ff;
}
.upload-area:hover {
background: #eef1ff;
border-color: #764ba2;
}
.upload-area.dragging {
background: #e0e7ff;
border-color: #4f46e5;
}
.upload-icon {
font-size: 3em;
color: #667eea;
margin-bottom: 15px;
}
.file-name {
margin-top: 15px;
padding: 10px;
background: white;
border-radius: 8px;
font-weight: 600;
color: #667eea;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.loading.active {
display: block;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.preset-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.preset-btn {
padding: 8px 16px;
background: #f0f0f0;
border: 2px solid #667eea;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9em;
font-weight: 600;
color: #667eea;
}
.preset-btn:hover {
background: #667eea;
color: white;
}
.api-status {
position: fixed;
top: 20px;
right: 20px;
padding: 10px 20px;
background: white;
border-radius: 25px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
animation: blink 2s infinite;
}
.status-online {
background: #4caf50;
}
.status-offline {
background: #ff4444;
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
.button-group {
flex-direction: column;
}
.header h1 {
font-size: 1.8em;
}
}
</style>
</head>
<body>
<div class="api-status" id="apiStatus">
<div class="status-dot status-offline" id="statusDot"></div>
<span id="statusText">Checking...</span>
</div>
<div class="container">
<div class="header">
<h1>🔧 Predictive Maintenance</h1>
<p>ML-Powered Equipment Failure Prediction & Diagnostics</p>
</div>
<div class="tabs">
<button class="tab-button active" onclick="switchTab('single')">
📊 Single Prediction
</button>
<button class="tab-button" onclick="switchTab('batch')">
📁 Batch Processing
</button>
</div>
<!-- SINGLE PREDICTION TAB -->
<div id="singleTab" class="tab-content active">
<h2 style="margin-bottom: 20px; color: #333;">🎯 Single Machine Prediction</h2>
<div class="preset-buttons">
<button class="preset-btn" onclick="loadPreset('healthy')">✅ Healthy Machine</button>
<button class="preset-btn" onclick="loadPreset('toolwear')">🔨 Tool Wear Critical</button>
<button class="preset-btn" onclick="loadPreset('overstrain')">⚡ Power Overstrain</button>
<button class="preset-btn" onclick="loadPreset('cooling')">❄️ Cooling Issue</button>
</div>
<form id="predictionForm" onsubmit="handleSinglePredict(event)">
<div class="form-grid">
<div class="form-group">
<label for="machine_id">
Machine ID <span class="info-icon" title="Optional identifier">ℹ️</span>
</label>
<input type="text" id="machine_id" placeholder="e.g., M_L_01">
</div>
<div class="form-group">
<label for="type">
Machine Type <span class="info-icon" title="L=Low, M=Medium, H=High">ℹ️</span>
</label>
<select id="type" required>
<option value="">Select Type</option>
<option value="L">L - Low Quality</option>
<option value="M">M - Medium Quality</option>
<option value="H">H - High Quality</option>
</select>
</div>
<div class="form-group">
<label for="air_temperature">
Air Temperature (K) <span class="info-icon" title="Range: 250-350 K">ℹ️</span>
</label>
<input type="number" id="air_temperature" step="0.1" min="250" max="350" required
placeholder="e.g., 298.1">
</div>
<div class="form-group">
<label for="process_temperature">
Process Temperature (K) <span class="info-icon" title="Range: 250-400 K">ℹ️</span>
</label>
<input type="number" id="process_temperature" step="0.1" min="250" max="400" required
placeholder="e.g., 308.6">
</div>
<div class="form-group">
<label for="rotational_speed">
Rotational Speed (RPM) <span class="info-icon" title="Range: 0-3000 RPM">ℹ️</span>
</label>
<input type="number" id="rotational_speed" min="0" max="3000" required placeholder="e.g., 1551">
</div>
<div class="form-group">
<label for="torque">
Torque (Nm) <span class="info-icon" title="Range: 0-100 Nm">ℹ️</span>
</label>
<input type="number" id="torque" step="0.1" min="0" max="100" required placeholder="e.g., 42.8">
</div>
<div class="form-group">
<label for="tool_wear">
Tool Wear (min) <span class="info-icon" title="Range: 0-300 minutes">ℹ️</span>
</label>
<input type="number" id="tool_wear" min="0" max="300" required placeholder="e.g., 0">
</div>
</div>
<div class="button-group">
<button type="submit" class="btn-primary">🔍 Analyze & Predict</button>
<button type="button" class="btn-secondary" onclick="resetForm()">🔄 Reset Form</button>
</div>
</form>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Analyzing machine data...</p>
</div>
<div id="result" class="result-container"></div>
</div>
<!-- BATCH PREDICTION TAB -->
<div id="batchTab" class="tab-content">
<h2 style="margin-bottom: 20px; color: #333;">📁 Batch Processing</h2>
<div class="upload-area" id="uploadArea" onclick="document.getElementById('csvFile').click()">
<div class="upload-icon">📤</div>
<h3>Click or Drag & Drop CSV File</h3>
<p style="margin-top: 10px; color: #666;">Upload a CSV file with machine data for batch prediction</p>
<input type="file" id="csvFile" accept=".csv" style="display: none;" onchange="handleFileSelect(event)">
<div id="fileName" class="file-name" style="display: none;"></div>
</div>
<div style="margin-top: 20px; padding: 20px; background: #f8f9ff; border-radius: 10px;">
<h4 style="margin-bottom: 10px;">📋 Required CSV Columns:</h4>
<ul style="list-style: none; padding-left: 0;">
<li><code>air_temperature</code> - Air temperature in Kelvin</li>
<li><code>process_temperature</code> - Process temperature in Kelvin</li>
<li><code>rotational_speed</code> - Rotational speed in RPM</li>
<li><code>torque</code> - Torque in Nm</li>
<li><code>tool_wear</code> - Tool wear in minutes</li>
<li><code>type</code> - Machine type (L, M, or H)</li>
<li><code>machine_id</code> - Optional machine identifier</li>
</ul>
</div>
<div class="button-group" style="margin-top: 20px;">
<button class="btn-primary" onclick="uploadBatch()" id="uploadBtn" disabled>📊 Process Batch</button>
<button class="btn-secondary" onclick="downloadTemplate()">⬇️ Download Template CSV</button>
</div>
<div class="loading" id="batchLoading">
<div class="spinner"></div>
<p>Processing batch predictions...</p>
</div>
<div id="batchResult" class="result-container"></div>
</div>
</div>
<script>
const API_URL = 'http://localhost:8000';
let selectedFile = null;
// Check API status on load
async function checkApiStatus() {
try {
const response = await fetch(`${API_URL}/health`);
const data = await response.json();
document.getElementById('statusDot').className = 'status-dot status-online';
document.getElementById('statusText').textContent = 'API Online';
} catch (error) {
document.getElementById('statusDot').className = 'status-dot status-offline';
document.getElementById('statusText').textContent = 'API Offline';
}
}
// Switch between tabs
function switchTab(tab) {
document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
if (tab === 'single') {
document.querySelector('.tab-button:nth-child(1)').classList.add('active');
document.getElementById('singleTab').classList.add('active');
} else {
document.querySelector('.tab-button:nth-child(2)').classList.add('active');
document.getElementById('batchTab').classList.add('active');
}
}
// Load preset values
function loadPreset(type) {
const presets = {
healthy: {
machine_id: 'M_L_01',
type: 'M',
air_temperature: 298.1,
process_temperature: 308.6,
rotational_speed: 1551,
torque: 42.8,
tool_wear: 0
},
toolwear: {
machine_id: 'M_H_03',
type: 'H',
air_temperature: 297.5,
process_temperature: 312.8,
rotational_speed: 1800,
torque: 52.0,
tool_wear: 220
},
overstrain: {
machine_id: 'M_H_05',
type: 'H',
air_temperature: 298.5,
process_temperature: 316.0,
rotational_speed: 1900,
torque: 72.5,
tool_wear: 150
},
cooling: {
machine_id: 'M_M_02',
type: 'M',
air_temperature: 299.8,
process_temperature: 305.2,
rotational_speed: 1650,
torque: 45.0,
tool_wear: 100
}
};
const preset = presets[type];
Object.keys(preset).forEach(key => {
const element = document.getElementById(key);
if (element) element.value = preset[key];
});
}
// Handle single prediction
async function handleSinglePredict(event) {
event.preventDefault();
const formData = {
machine_id: document.getElementById('machine_id').value || null,
type: document.getElementById('type').value,
air_temperature: parseFloat(document.getElementById('air_temperature').value),
process_temperature: parseFloat(document.getElementById('process_temperature').value),
rotational_speed: parseInt(document.getElementById('rotational_speed').value),
torque: parseFloat(document.getElementById('torque').value),
tool_wear: parseInt(document.getElementById('tool_wear').value)
};
document.getElementById('loading').classList.add('active');
document.getElementById('result').style.display = 'none';
try {
console.log('Sending request to API:', formData); // Debug log
const response = await fetch(`${API_URL}/predict`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
console.log('Response status:', response.status); // Debug log
const data = await response.json();
console.log('Response data:', data); // Debug log
if (!response.ok) {
throw new Error(data.detail || 'Prediction failed');
}
displayResult(data);
} catch (error) {
console.error('Error during prediction:', error); // Debug log
displayError(error.message);
} finally {
document.getElementById('loading').classList.remove('active');
}
}
// Display prediction result
function displayResult(data) {
console.log('Displaying result:', data); // Debug log
const resultDiv = document.getElementById('result');
const isFailure = data.prediction === 'FAILURE';
const severityClass = `severity-${data.diagnostics.severity.toLowerCase()}`;
// Calculate probability/confidence percentage
const probability = (data.confidence * 100).toFixed(1);
resultDiv.className = 'result-container success';
resultDiv.style.display = 'block'; // FORCE DISPLAY
resultDiv.innerHTML = `
<h3 style="margin-bottom: 15px;">🔍 Prediction Result</h3>
<!-- Prediction Badge and Confidence Bar -->
<div class="prediction-header">
<span class="prediction-badge ${isFailure ? 'badge-failure' : 'badge-healthy'}">
${data.prediction}
</span>
<div class="confidence-bar">
<div class="confidence-fill" style="width: ${probability}%">
${probability}% Confidence
</div>
</div>
</div>
<!-- Severity and Risk Level -->
<div style="display: flex; gap: 10px; margin: 15px 0;">
<span class="severity-badge ${severityClass}" style="font-size: 14px; padding: 8px 16px;">
${data.diagnostics.severity} SEVERITY
</span>
</div>
<!-- Main Diagnostics Grid -->
<div class="diagnostics-grid">
<div class="diagnostic-item">
<div class="diagnostic-label">🎯 Primary Cause</div>
<div class="diagnostic-value">${data.diagnostics.primary_cause}</div>
</div>
<div class="diagnostic-item">
<div class="diagnostic-label">📊 Sensor Alert</div>
<div class="diagnostic-value">${data.diagnostics.sensor_alert}</div>
</div>
<div class="diagnostic-item">
<div class="diagnostic-label">🔧 Recommended Action</div>
<div class="diagnostic-value">${data.diagnostics.recommended_action}</div>
</div>
${data.machine_id ? `
<div class="diagnostic-item">
<div class="diagnostic-label">🏷️ Machine ID</div>
<div class="diagnostic-value">${data.machine_id}</div>
</div>
` : ''}
<div class="diagnostic-item">
<div class="diagnostic-label">⏰ Timestamp</div>
<div class="diagnostic-value">${new Date(data.timestamp).toLocaleString()}</div>
</div>
</div>
<!-- Engineered Features Section -->
${data.features && Object.keys(data.features).length > 0 ? `
<div style="margin-top: 25px;">
<h4 style="margin-bottom: 15px; color: #667eea;">📊 Engineered Features Analysis</h4>
<div class="feature-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
${Object.entries(data.features).map(([key, value]) => `
<div class="feature-item" style="background: white; padding: 12px; border-radius: 8px; border-left: 3px solid #667eea;">
<div class="feature-label" style="font-size: 11px; color: #6c757d; margin-bottom: 4px; text-transform: uppercase;">
${key.replace(/_/g, ' ')}
</div>
<div class="feature-value" style="font-size: 16px; font-weight: 600; color: #333;">
${typeof value === 'number' ? value.toFixed(2) : value}
</div>
</div>
`).join('')}
</div>
</div>
` : ''}
<!-- Anomaly Detection Section (if available) -->
${data.anomalies && data.anomalies.length > 0 ? `
<div style="margin-top: 25px; padding: 20px; background: #f8f9fa; border-radius: 10px; border-left: 5px solid #667eea;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h4 style="margin: 0; color: #667eea;">🔧 Parameter Anomaly Analysis</h4>
<span style="color: #667eea; font-weight: 600; font-size: 14px;">
${data.anomalies.length} anomalies detected
</span>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px;">
${data.anomalies.map(anomaly => `
<div style="background: white; padding: 15px; border-radius: 10px; border-left: 4px solid ${anomaly.status === 'CRITICAL' ? '#dc3545' : anomaly.status === 'WARNING' ? '#ffc107' : '#28a745'};">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<strong style="font-size: 16px;">${anomaly.parameter}</strong>
<span style="font-size: 12px; padding: 4px 8px; border-radius: 10px; font-weight: 600; background: ${anomaly.status === 'CRITICAL' ? '#f8d7da' : anomaly.status === 'WARNING' ? '#fff3cd' : '#d4edda'}; color: ${anomaly.status === 'CRITICAL' ? '#721c24' : anomaly.status === 'WARNING' ? '#856404' : '#155724'};">
${anomaly.status}
</span>
</div>
<div style="font-size: 18px; font-weight: 600; color: #333; margin: 5px 0;">
Current: ${anomaly.value}
</div>
<div style="font-size: 12px; color: #6c757d; margin: 5px 0;">
Normal range: ${anomaly.normal_range}
</div>
<div style="font-size: 13px; color: #495057; margin-top: 10px; padding: 8px; background: #f1f3f4; border-radius: 6px;">
${anomaly.explanation}
</div>
</div>
`).join('')}
</div>
</div>
` : ''}
<!-- Overall Health Summary (if available) -->
${data.overall_health ? `
<div style="margin-top: 20px; padding: 15px; border-radius: 10px; font-weight: 600; text-align: center; background: ${data.overall_health.includes('EXCELLENT') ? '#d4edda' : data.overall_health.includes('GOOD') ? '#d1ecf1' : data.overall_health.includes('FAIR') ? '#fff3cd' : '#f8d7da'}; color: ${data.overall_health.includes('EXCELLENT') ? '#155724' : data.overall_health.includes('GOOD') ? '#0c5460' : data.overall_health.includes('FAIR') ? '#856404' : '#721c24'}; border: 2px solid ${data.overall_health.includes('EXCELLENT') ? '#28a745' : data.overall_health.includes('GOOD') ? '#17a2b8' : data.overall_health.includes('FAIR') ? '#ffc107' : '#dc3545'};">
🏥 ${data.overall_health}
</div>
` : ''}
`;
}
// Display error
function displayError(message) {
console.error('Displaying error:', message); // Debug log
const resultDiv = document.getElementById('result');
resultDiv.className = 'result-container error';
resultDiv.style.display = 'block'; // FORCE DISPLAY
resultDiv.innerHTML = `
<h3 style="color: #ff4444; margin-bottom: 10px;">❌ Error</h3>
<p>${message}</p>
`;
}
// Reset form
function resetForm() {
document.getElementById('predictionForm').reset();
document.getElementById('result').style.display = 'none';
}
// File upload handlers
const uploadArea = document.getElementById('uploadArea');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
uploadArea.addEventListener(eventName, () => {
uploadArea.classList.add('dragging');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, () => {
uploadArea.classList.remove('dragging');
}, false);
});
uploadArea.addEventListener('drop', (e) => {
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
}, false);
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
handleFile(file);
}
}
function handleFile(file) {
if (!file.name.endsWith('.csv')) {
alert('Please upload a CSV file');
return;
}
selectedFile = file;
document.getElementById('fileName').style.display = 'block';
document.getElementById('fileName').textContent = `📄 ${file.name}`;
document.getElementById('uploadBtn').disabled = false;
}
// Upload batch
async function uploadBatch() {
if (!selectedFile) return;
const formData = new FormData();
formData.append('file', selectedFile);
document.getElementById('batchLoading').classList.add('active');
document.getElementById('batchResult').style.display = 'none';
try {
console.log('Uploading batch file:', selectedFile.name);
const response = await fetch(`${API_URL}/predict/batch/json`, {
method: 'POST',
body: formData
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Batch processing failed');
}
const data = await response.json();
console.log('Batch response:', data);
displayBatchResults(data);
} catch (error) {
console.error('Batch processing error:', error);
const resultDiv = document.getElementById('batchResult');
resultDiv.className = 'result-container error';
resultDiv.style.display = 'block';
resultDiv.innerHTML = `
<h3 style="color: #ff4444; margin-bottom: 10px;">❌ Batch Processing Error</h3>
<p>${error.message}</p>
<p style="margin-top: 10px; font-size: 0.9em; color: #666;">
Make sure your CSV has the required columns: air_temperature, process_temperature,
rotational_speed, torque, tool_wear, type
</p>
`;
} finally {
document.getElementById('batchLoading').classList.remove('active');
}
}
// Display batch results
function displayBatchResults(data) {
const resultDiv = document.getElementById('batchResult');
resultDiv.className = 'result-container success';
resultDiv.style.display = 'block';
const summary = data.summary;
const results = data.results;
let html = `
<h3 style="margin-bottom: 20px;">📊 Batch Processing Results</h3>
<!-- Summary Statistics -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-bottom: 25px;">
<div style="background: white; padding: 20px; border-radius: 10px; text-align: center; border-left: 4px solid #667eea;">
<div style="font-size: 32px; font-weight: 700; color: #667eea;">${summary.total_records}</div>
<div style="font-size: 14px; color: #666; margin-top: 5px;">Total Records</div>
</div>
<div style="background: white; padding: 20px; border-radius: 10px; text-align: center; border-left: 4px solid #4caf50;">
<div style="font-size: 32px; font-weight: 700; color: #4caf50;">${summary.predictions.healthy}</div>
<div style="font-size: 14px; color: #666; margin-top: 5px;">Healthy</div>
</div>
<div style="background: white; padding: 20px; border-radius: 10px; text-align: center; border-left: 4px solid #ff4444;">
<div style="font-size: 32px; font-weight: 700; color: #ff4444;">${summary.predictions.failure}</div>
<div style="font-size: 14px; color: #666; margin-top: 5px;">Failures</div>
</div>
<div style="background: white; padding: 20px; border-radius: 10px; text-align: center; border-left: 4px solid #ff9800;">
<div style="font-size: 32px; font-weight: 700; color: #ff9800;">${summary.failure_rate}%</div>
<div style="font-size: 14px; color: #666; margin-top: 5px;">Failure Rate</div>
</div>
</div>
<!-- Individual Results -->
<h4 style="margin: 25px 0 15px 0; color: #333;">🔍 Detailed Analysis</h4>
<div style="display: flex; flex-direction: column; gap: 20px;">
`;
// Display each result
results.forEach((result, index) => {
const isFailure = result.prediction === 'FAILURE';
const severityClass = result.diagnostics ? `severity-${result.diagnostics.severity.toLowerCase()}` : '';
const probability = (result.confidence * 100).toFixed(1);
html += `
<div style="background: white; padding: 20px; border-radius: 10px; border-left: 5px solid ${isFailure ? '#ff4444' : '#4caf50'};">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 2px solid #e0e0e0;">
<div style="display: flex; align-items: center; gap: 15px;">
<span style="font-size: 14px; font-weight: 600; color: #666;">Row ${result.row_number}</span>
<span style="font-size: 14px; font-weight: 600; color: #667eea;">${result.machine_id}</span>
<span class="prediction-badge ${isFailure ? 'badge-failure' : 'badge-healthy'}" style="font-size: 14px; padding: 6px 14px;">
${result.prediction}
</span>
${result.diagnostics ? `
<span class="severity-badge ${severityClass}" style="font-size: 12px; padding: 4px 10px;">
${result.diagnostics.severity}
</span>
` : ''}
</div>
<div style="background: #e0e0e0; height: 20px; width: 120px; border-radius: 10px; overflow: hidden;">
<div style="height: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); width: ${probability}%; display: flex; align-items: center; justify-content: center; font-size: 11px; color: white; font-weight: 600;">
${probability}%
</div>
</div>
</div>
${result.diagnostics ? `
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 15px; margin-bottom: 15px;">
<div style="padding: 12px; background: #f8f9fa; border-radius: 8px;">
<div style="font-size: 11px; color: #667eea; font-weight: 600; margin-bottom: 5px; text-transform: uppercase;">🎯 Primary Cause</div>
<div style="font-size: 13px; color: #333;">${result.diagnostics.primary_cause}</div>
</div>
<div style="padding: 12px; background: #f8f9fa; border-radius: 8px;">
<div style="font-size: 11px; color: #667eea; font-weight: 600; margin-bottom: 5px; text-transform: uppercase;">🔧 Recommended Action</div>
<div style="font-size: 13px; color: #333;">${result.diagnostics.recommended_action}</div>
</div>
</div>
` : ''}
${result.anomalies && result.anomalies.length > 0 ? `
<div style="margin-top: 15px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
<div style="font-size: 12px; color: #667eea; font-weight: 600; margin-bottom: 10px;">
⚠️ ${result.anomalies.length} Anomalies Detected
</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
${result.anomalies.map(anomaly => `
<span style="font-size: 11px; padding: 4px 8px; border-radius: 12px; background: ${anomaly.status === 'CRITICAL' ? '#f8d7da' : anomaly.status === 'WARNING' ? '#fff3cd' : '#d4edda'}; color: ${anomaly.status === 'CRITICAL' ? '#721c24' : anomaly.status === 'WARNING' ? '#856404' : '#155724'};">
${anomaly.parameter}
</span>
`).join('')}
</div>
</div>
` : ''}
${result.overall_health ? `
<div style="margin-top: 15px; padding: 10px; border-radius: 8px; text-align: center; font-size: 13px; font-weight: 600; background: ${result.overall_health.includes('EXCELLENT') ? '#d4edda' : result.overall_health.includes('GOOD') ? '#d1ecf1' : result.overall_health.includes('FAIR') ? '#fff3cd' : '#f8d7da'}; color: ${result.overall_health.includes('EXCELLENT') ? '#155724' : result.overall_health.includes('GOOD') ? '#0c5460' : result.overall_health.includes('FAIR') ? '#856404' : '#721c24'};">
${result.overall_health}
</div>
` : ''}
${result.error ? `
<div style="margin-top: 10px; padding: 10px; background: #fff0f0; border-radius: 8px; color: #ff4444; font-size: 13px;">
<strong>Error:</strong> ${result.error}
</div>
` : ''}
</div>
`;
});
html += `
</div>
<!-- Download CSV Button -->
<div style="margin-top: 25px; text-align: center;">
<button onclick="downloadBatchCSV()" class="btn-secondary" style="padding: 12px 24px;">
⬇️ Download Results as CSV
</button>
</div>
`;
resultDiv.innerHTML = html;
}
// Download batch results as CSV
async function downloadBatchCSV() {
if (!selectedFile) return;
try {
const formData = new FormData();
formData.append('file', selectedFile);
const response = await fetch(`${API_URL}/predict/batch`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('CSV download failed');
}
// Download the result CSV
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `predictions_${new Date().getTime()}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
alert('Failed to download CSV: ' + error.message);
}
}
// Download template CSV
function downloadTemplate() {
const template = `machine_id,air_temperature,process_temperature,rotational_speed,torque,tool_wear,type
M_L_01,298.1,308.6,1551,42.8,0,M
M_L_02,299.2,310.1,1450,38.5,150,L
M_H_01,297.5,312.8,1800,65.2,220,H`;
const blob = new Blob([template], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'template.csv';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
// Initialize
checkApiStatus();
setInterval(checkApiStatus, 10000); // Check every 10 seconds
</script>
</body>
</html>