AR_Prediction / index.html
PD03's picture
Update index.html
79a1210 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SAP AR ML Demo</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/4.10.0/tf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<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;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
color: white;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.card h3 {
color: #4a5568;
margin-bottom: 15px;
font-size: 1.3rem;
display: flex;
align-items: center;
gap: 10px;
}
.btn {
background: linear-gradient(135deg, #4CAF50, #45a049);
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-primary {
background: linear-gradient(135deg, #007bff, #0056b3);
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
}
.btn-primary:hover {
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);
}
.status {
padding: 10px 15px;
border-radius: 8px;
margin: 10px 0;
font-weight: 500;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.status.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #45a049);
transition: width 0.3s ease;
border-radius: 10px;
}
.invoice-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.invoice-table th,
.invoice-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
.invoice-table th {
background: #f8f9fa;
font-weight: 600;
color: #495057;
}
.invoice-table tr:hover {
background: #f8f9fa;
}
.prediction {
display: flex;
align-items: center;
gap: 10px;
}
.probability-bar {
flex: 1;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.probability-fill {
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
}
.high-prob {
background: linear-gradient(90deg, #28a745, #20c997);
}
.medium-prob {
background: linear-gradient(90deg, #ffc107, #fd7e14);
}
.low-prob {
background: linear-gradient(90deg, #dc3545, #e74c3c);
}
.full-width {
grid-column: 1 / -1;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.metric-card {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
text-align: center;
}
.metric-value {
font-size: 2rem;
font-weight: bold;
color: #007bff;
margin-bottom: 5px;
}
.metric-label {
color: #6c757d;
font-size: 0.9rem;
}
.chart-container {
width: 100%;
height: 300px;
margin-top: 20px;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.icon {
width: 20px;
height: 20px;
display: inline-block;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏢 SAP Account Receivable ML Prediction Demo</h1>
<p>Machine Learning-powered invoice payment prediction system</p>
</div>
<div class="dashboard">
<div class="card">
<h3>
🎯 Model Training
</h3>
<p>Train a machine learning model on synthetic SAP AR data to predict invoice payment likelihood.</p>
<button id="trainBtn" class="btn" onclick="trainModel()">
<span id="trainBtnText">Train ML Model</span>
</button>
<div id="trainingStatus"></div>
<div id="trainingProgress"></div>
<div id="modelMetrics" class="metrics" style="display: none;">
<div class="metric-card">
<div class="metric-value" id="accuracy">-</div>
<div class="metric-label">Accuracy</div>
</div>
<div class="metric-card">
<div class="metric-value" id="precision">-</div>
<div class="metric-label">Precision</div>
</div>
<div class="metric-card">
<div class="metric-value" id="recall">-</div>
<div class="metric-label">Recall</div>
</div>
<div class="metric-card">
<div class="metric-value" id="f1Score">-</div>
<div class="metric-label">F1 Score</div>
</div>
</div>
</div>
<div class="card">
<h3>
📊 Training Visualization
</h3>
<div class="chart-container">
<canvas id="trainingChart" width="400" height="200"></canvas>
</div>
</div>
<div class="card full-width">
<h3>
🔮 Invoice Payment Predictions
</h3>
<p>Real-time predictions for unpaid invoices using the trained ML model.</p>
<button id="predictBtn" class="btn btn-primary" onclick="makePredictions()" disabled>
Generate Predictions
</button>
<div id="predictionsTable"></div>
</div>
</div>
</div>
<script>
let model = null;
let trainingData = null;
let chart = null;
let unpaidInvoices = [];
// Initialize chart
const ctx = document.getElementById('trainingChart').getContext('2d');
chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Training Accuracy',
data: [],
borderColor: '#007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
tension: 0.4
}, {
label: 'Training Loss',
data: [],
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
tension: 0.4,
yAxisID: 'y1'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
min: 0,
max: 1
},
y1: {
type: 'linear',
display: true,
position: 'right',
min: 0,
grid: {
drawOnChartArea: false,
},
}
}
}
});
function generateSyntheticData() {
const data = [];
const customers = ['CUST001', 'CUST002', 'CUST003', 'CUST004', 'CUST005', 'CUST006', 'CUST007', 'CUST008'];
for (let i = 0; i < 1000; i++) {
const invoiceAmount = Math.random() * 50000 + 1000;
const customerCode = customers[Math.floor(Math.random() * customers.length)];
const daysOverdue = Math.floor(Math.random() * 120);
const previousDelays = Math.floor(Math.random() * 5);
const creditScore = Math.random() * 100;
const industryRisk = Math.random();
const seasonality = Math.sin((i % 365) * 2 * Math.PI / 365);
// Create correlation between features and payment probability
let paymentProb = 0.7;
paymentProb -= Math.min(daysOverdue / 100, 0.4);
paymentProb -= Math.min(previousDelays / 10, 0.3);
paymentProb += (creditScore - 50) / 200;
paymentProb -= industryRisk * 0.2;
paymentProb += seasonality * 0.1;
paymentProb = Math.max(0.05, Math.min(0.95, paymentProb));
const paidOnTime = Math.random() < paymentProb ? 1 : 0;
data.push({
invoiceAmount: invoiceAmount / 50000, // Normalize
daysOverdue: daysOverdue / 120, // Normalize
previousDelays: previousDelays / 5, // Normalize
creditScore: creditScore / 100, // Already normalized
industryRisk: industryRisk,
seasonality: (seasonality + 1) / 2, // Normalize to 0-1
paidOnTime: paidOnTime
});
}
return data;
}
function generateUnpaidInvoices() {
const invoices = [];
const customers = ['SAP-CUST001', 'SAP-CUST002', 'SAP-CUST003', 'SAP-CUST004', 'SAP-CUST005'];
for (let i = 0; i < 15; i++) {
const invoiceId = `INV-${Date.now()}-${i.toString().padStart(3, '0')}`;
const customer = customers[Math.floor(Math.random() * customers.length)];
const amount = Math.floor(Math.random() * 45000 + 5000);
const daysOverdue = Math.floor(Math.random() * 90);
const previousDelays = Math.floor(Math.random() * 4);
const creditScore = Math.floor(Math.random() * 60 + 40);
invoices.push({
invoiceId,
customer,
amount,
daysOverdue,
previousDelays,
creditScore,
industryRisk: Math.random(),
seasonality: Math.random()
});
}
return invoices;
}
async function trainModel() {
const trainBtn = document.getElementById('trainBtn');
const trainBtnText = document.getElementById('trainBtnText');
const statusDiv = document.getElementById('trainingStatus');
const progressDiv = document.getElementById('trainingProgress');
trainBtn.disabled = true;
trainBtnText.innerHTML = '<span class="loading"></span> Training...';
try {
// Show initial status
statusDiv.innerHTML = '<div class="status info">🔄 Generating synthetic SAP AR data...</div>';
await new Promise(resolve => setTimeout(resolve, 1000));
// Generate training data
trainingData = generateSyntheticData();
statusDiv.innerHTML = '<div class="status success">✅ Generated 1,000 synthetic invoice records</div>';
await new Promise(resolve => setTimeout(resolve, 500));
statusDiv.innerHTML += '<div class="status info">🧠 Building neural network model...</div>';
// Prepare data for TensorFlow
const features = trainingData.map(d => [
d.invoiceAmount, d.daysOverdue, d.previousDelays,
d.creditScore, d.industryRisk, d.seasonality
]);
const labels = trainingData.map(d => d.paidOnTime);
const xs = tf.tensor2d(features);
const ys = tf.tensor1d(labels);
// Create model
model = tf.sequential({
layers: [
tf.layers.dense({
inputShape: [6],
units: 32,
activation: 'relu'
}),
tf.layers.dropout({rate: 0.2}),
tf.layers.dense({
units: 16,
activation: 'relu'
}),
tf.layers.dropout({rate: 0.2}),
tf.layers.dense({
units: 1,
activation: 'sigmoid'
})
]
});
model.compile({
optimizer: tf.train.adam(0.001),
loss: 'binaryCrossentropy',
metrics: ['accuracy']
});
statusDiv.innerHTML += '<div class="status info">🎯 Training model with backpropagation...</div>';
// Show progress bar
progressDiv.innerHTML = `
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
<div id="progressText">Training Progress: 0%</div>
`;
// Train model with callbacks
const history = await model.fit(xs, ys, {
epochs: 50,
batchSize: 32,
validationSplit: 0.2,
callbacks: {
onEpochEnd: (epoch, logs) => {
const progress = ((epoch + 1) / 50) * 100;
document.getElementById('progressFill').style.width = `${progress}%`;
document.getElementById('progressText').textContent = `Training Progress: ${Math.round(progress)}% - Accuracy: ${(logs.acc * 100).toFixed(1)}%`;
// Update chart
chart.data.labels.push(epoch + 1);
chart.data.datasets[0].data.push(logs.acc);
chart.data.datasets[1].data.push(logs.loss);
chart.update('none');
}
}
});
// Calculate final metrics
const finalAccuracy = history.history.acc[history.history.acc.length - 1];
const finalLoss = history.history.loss[history.history.loss.length - 1];
// Simulate precision, recall, F1 (normally would calculate from validation set)
const precision = Math.min(0.95, finalAccuracy + Math.random() * 0.1 - 0.05);
const recall = Math.min(0.95, finalAccuracy + Math.random() * 0.1 - 0.05);
const f1Score = 2 * (precision * recall) / (precision + recall);
// Update metrics display
document.getElementById('accuracy').textContent = (finalAccuracy * 100).toFixed(1) + '%';
document.getElementById('precision').textContent = (precision * 100).toFixed(1) + '%';
document.getElementById('recall').textContent = (recall * 100).toFixed(1) + '%';
document.getElementById('f1Score').textContent = (f1Score * 100).toFixed(1) + '%';
document.getElementById('modelMetrics').style.display = 'grid';
statusDiv.innerHTML += '<div class="status success">🎉 Model training completed successfully!</div>';
// Generate unpaid invoices for prediction
unpaidInvoices = generateUnpaidInvoices();
// Enable prediction button
document.getElementById('predictBtn').disabled = false;
// Cleanup tensors
xs.dispose();
ys.dispose();
} catch (error) {
statusDiv.innerHTML += `<div class="status warning">❌ Training failed: ${error.message}</div>`;
} finally {
trainBtn.disabled = false;
trainBtnText.textContent = 'Retrain Model';
}
}
async function makePredictions() {
if (!model || unpaidInvoices.length === 0) return;
const tableDiv = document.getElementById('predictionsTable');
tableDiv.innerHTML = '<div class="status info">🔮 Generating predictions...</div>';
await new Promise(resolve => setTimeout(resolve, 1000));
// Prepare features for prediction
const features = unpaidInvoices.map(invoice => [
invoice.amount / 50000, // Normalize
invoice.daysOverdue / 120, // Normalize
invoice.previousDelays / 5, // Normalize
invoice.creditScore / 100, // Normalize
invoice.industryRisk,
invoice.seasonality
]);
const predictionTensor = tf.tensor2d(features);
const predictions = await model.predict(predictionTensor).data();
predictionTensor.dispose();
// Create table
let tableHTML = `
<table class="invoice-table">
<thead>
<tr>
<th>Invoice ID</th>
<th>Customer</th>
<th>Amount</th>
<th>Days Overdue</th>
<th>Credit Score</th>
<th>Payment Prediction</th>
<th>Probability</th>
</tr>
</thead>
<tbody>
`;
unpaidInvoices.forEach((invoice, index) => {
const probability = predictions[index];
const willPay = probability > 0.5;
const probClass = probability > 0.7 ? 'high-prob' : probability > 0.4 ? 'medium-prob' : 'low-prob';
tableHTML += `
<tr>
<td><strong>${invoice.invoiceId}</strong></td>
<td>${invoice.customer}</td>
<td>$${invoice.amount.toLocaleString()}</td>
<td>${invoice.daysOverdue} days</td>
<td>${invoice.creditScore}/100</td>
<td>
<span style="color: ${willPay ? '#28a745' : '#dc3545'}; font-weight: bold;">
${willPay ? '✅ Will Pay' : '❌ Risk of Default'}
</span>
</td>
<td>
<div class="prediction">
<div class="probability-bar">
<div class="probability-fill ${probClass}" style="width: ${probability * 100}%"></div>
</div>
<span style="font-weight: bold; min-width: 50px;">
${(probability * 100).toFixed(1)}%
</span>
</div>
</td>
</tr>
`;
});
tableHTML += '</tbody></table>';
tableDiv.innerHTML = tableHTML;
}
</script>
</body>
</html>