|
import gradio as gr |
|
import numpy as np |
|
import pandas as pd |
|
import tensorflow as tf |
|
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score |
|
from sklearn.model_selection import train_test_split |
|
import plotly.graph_objects as go |
|
import plotly.express as px |
|
from datetime import datetime, timedelta |
|
import random |
|
import time |
|
|
|
class SAPARPredictor: |
|
def __init__(self): |
|
self.model = None |
|
self.training_history = None |
|
self.is_trained = False |
|
|
|
def generate_synthetic_data(self, n_samples=1000): |
|
"""Generate synthetic SAP AR data""" |
|
np.random.seed(42) |
|
|
|
customers = ['CUST001', 'CUST002', 'CUST003', 'CUST004', 'CUST005', 'CUST006', 'CUST007', 'CUST008'] |
|
|
|
data = [] |
|
for i in range(n_samples): |
|
invoice_amount = np.random.uniform(1000, 51000) |
|
customer_code = np.random.choice(customers) |
|
days_overdue = np.random.randint(0, 120) |
|
previous_delays = np.random.randint(0, 5) |
|
credit_score = np.random.uniform(0, 100) |
|
industry_risk = np.random.uniform(0, 1) |
|
seasonality = np.sin((i % 365) * 2 * np.pi / 365) |
|
|
|
|
|
payment_prob = 0.7 |
|
payment_prob -= min(days_overdue / 100, 0.4) |
|
payment_prob -= min(previous_delays / 10, 0.3) |
|
payment_prob += (credit_score - 50) / 200 |
|
payment_prob -= industry_risk * 0.2 |
|
payment_prob += seasonality * 0.1 |
|
payment_prob = max(0.05, min(0.95, payment_prob)) |
|
|
|
paid_on_time = 1 if np.random.random() < payment_prob else 0 |
|
|
|
data.append({ |
|
'invoice_amount': invoice_amount / 50000, |
|
'days_overdue': days_overdue / 120, |
|
'previous_delays': previous_delays / 5, |
|
'credit_score': credit_score / 100, |
|
'industry_risk': industry_risk, |
|
'seasonality': (seasonality + 1) / 2, |
|
'paid_on_time': paid_on_time |
|
}) |
|
|
|
return pd.DataFrame(data) |
|
|
|
def train_model(self, progress=gr.Progress()): |
|
"""Train the ML model with progress tracking""" |
|
try: |
|
progress(0, desc="🔄 Generating synthetic data...") |
|
|
|
|
|
df = self.generate_synthetic_data(1000) |
|
time.sleep(0.5) |
|
|
|
progress(0.2, desc="📊 Preparing features and labels...") |
|
|
|
|
|
feature_columns = ['invoice_amount', 'days_overdue', 'previous_delays', |
|
'credit_score', 'industry_risk', 'seasonality'] |
|
X = df[feature_columns].values |
|
y = df['paid_on_time'].values |
|
|
|
|
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) |
|
|
|
progress(0.3, desc="🧠 Building neural network...") |
|
|
|
|
|
self.model = tf.keras.Sequential([ |
|
tf.keras.layers.Dense(32, activation='relu', input_shape=(6,)), |
|
tf.keras.layers.Dropout(0.2), |
|
tf.keras.layers.Dense(16, activation='relu'), |
|
tf.keras.layers.Dropout(0.2), |
|
tf.keras.layers.Dense(1, activation='sigmoid') |
|
]) |
|
|
|
self.model.compile( |
|
optimizer=tf.keras.optimizers.Adam(0.001), |
|
loss='binary_crossentropy', |
|
metrics=['accuracy'] |
|
) |
|
|
|
progress(0.4, desc="🎯 Training model (50 epochs)...") |
|
|
|
|
|
history = self.model.fit( |
|
X_train, y_train, |
|
epochs=50, |
|
batch_size=32, |
|
validation_split=0.2, |
|
verbose=0 |
|
) |
|
|
|
progress(0.8, desc="📈 Evaluating model performance...") |
|
|
|
|
|
y_pred_proba = self.model.predict(X_test, verbose=0) |
|
y_pred = (y_pred_proba > 0.5).astype(int) |
|
|
|
|
|
accuracy = accuracy_score(y_test, y_pred) |
|
precision = precision_score(y_test, y_pred) |
|
recall = recall_score(y_test, y_pred) |
|
f1 = f1_score(y_test, y_pred) |
|
|
|
self.training_history = history.history |
|
self.is_trained = True |
|
|
|
progress(1.0, desc="✅ Training completed successfully!") |
|
|
|
|
|
fig = go.Figure() |
|
|
|
epochs = list(range(1, len(history.history['accuracy']) + 1)) |
|
|
|
fig.add_trace(go.Scatter( |
|
x=epochs, |
|
y=history.history['accuracy'], |
|
mode='lines+markers', |
|
name='Training Accuracy', |
|
line=dict(color='#007bff', width=4), |
|
marker=dict(size=8) |
|
)) |
|
|
|
fig.add_trace(go.Scatter( |
|
x=epochs, |
|
y=history.history['val_accuracy'], |
|
mode='lines+markers', |
|
name='Validation Accuracy', |
|
line=dict(color='#28a745', width=4), |
|
marker=dict(size=8) |
|
)) |
|
|
|
fig.update_layout( |
|
title={ |
|
'text': '📊 Model Training Progress', |
|
'x': 0.5, |
|
'font': {'size': 20} |
|
}, |
|
xaxis_title='Epoch', |
|
yaxis_title='Accuracy', |
|
template='plotly_white', |
|
|
|
hovermode='x unified', |
|
legend=dict( |
|
yanchor="bottom", |
|
y=0.02, |
|
xanchor="right", |
|
x=0.98 |
|
) |
|
) |
|
|
|
|
|
metrics_html = f""" |
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin: 20px 0;"> |
|
<div style="background: linear-gradient(135deg, #007bff, #0056b3); color: white; padding: 20px; border-radius: 15px; text-align: center; box-shadow: 0 4px 15px rgba(0,123,255,0.3);"> |
|
<div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 5px;">{accuracy:.1%}</div> |
|
<div style="font-size: 1.1rem;">🎯 Accuracy</div> |
|
</div> |
|
<div style="background: linear-gradient(135deg, #28a745, #20c997); color: white; padding: 20px; border-radius: 15px; text-align: center; box-shadow: 0 4px 15px rgba(40,167,69,0.3);"> |
|
<div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 5px;">{precision:.1%}</div> |
|
<div style="font-size: 1.1rem;">🎯 Precision</div> |
|
</div> |
|
<div style="background: linear-gradient(135deg, #ffc107, #fd7e14); color: white; padding: 20px; border-radius: 15px; text-align: center; box-shadow: 0 4px 15px rgba(255,193,7,0.3);"> |
|
<div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 5px;">{recall:.1%}</div> |
|
<div style="font-size: 1.1rem;">📊 Recall</div> |
|
</div> |
|
<div style="background: linear-gradient(135deg, #17a2b8, #138496); color: white; padding: 20px; border-radius: 15px; text-align: center; box-shadow: 0 4px 15px rgba(23,162,184,0.3);"> |
|
<div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 5px;">{f1:.1%}</div> |
|
<div style="font-size: 1.1rem">⚖️ F1 Score</div> |
|
</div> |
|
</div> |
|
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 15px; border-radius: 10px; margin-top: 15px; text-align: center;"> |
|
<strong>✅ Model trained successfully on 1,000 synthetic SAP AR records!</strong><br> |
|
<em>The model is now ready to make predictions on unpaid invoices.</em> |
|
</div> |
|
""" |
|
|
|
return fig, metrics_html, gr.update(interactive=True, variant="primary") |
|
|
|
except Exception as e: |
|
error_html = f""" |
|
<div style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 15px; border-radius: 10px; text-align: center;"> |
|
<strong>❌ Training failed:</strong> {str(e)} |
|
</div> |
|
""" |
|
return None, error_html, gr.update(interactive=False) |
|
|
|
def generate_unpaid_invoices(self): |
|
"""Generate sample unpaid invoices for prediction""" |
|
customers = ['SAP-CUST001', 'SAP-CUST002', 'SAP-CUST003', 'SAP-CUST004', 'SAP-CUST005'] |
|
|
|
invoices = [] |
|
for i in range(12): |
|
invoice_id = f"INV-{datetime.now().strftime('%Y%m%d')}-{i:03d}" |
|
customer = random.choice(customers) |
|
amount = random.randint(5000, 50000) |
|
days_overdue = random.randint(0, 90) |
|
previous_delays = random.randint(0, 4) |
|
credit_score = random.randint(40, 100) |
|
|
|
invoices.append({ |
|
'Invoice ID': invoice_id, |
|
'Customer': customer, |
|
'Amount ($)': amount, |
|
'Days Overdue': days_overdue, |
|
'Previous Delays': previous_delays, |
|
'Credit Score': credit_score, |
|
'Industry Risk': round(random.random(), 3), |
|
'Seasonality': round(random.random(), 3) |
|
}) |
|
|
|
return pd.DataFrame(invoices) |
|
|
|
def make_predictions(self): |
|
"""Make predictions on unpaid invoices""" |
|
if not self.is_trained: |
|
error_msg = """ |
|
<div style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 15px; border-radius: 10px; text-align: center;"> |
|
<strong>❌ Please train the model first!</strong><br> |
|
<em>Go to the Model Training tab and click "Train ML Model"</em> |
|
</div> |
|
""" |
|
return None, error_msg, None |
|
|
|
try: |
|
|
|
df = self.generate_unpaid_invoices() |
|
|
|
|
|
features = [] |
|
for _, row in df.iterrows(): |
|
features.append([ |
|
row['Amount ($)'] / 50000, |
|
row['Days Overdue'] / 120, |
|
row['Previous Delays'] / 5, |
|
row['Credit Score'] / 100, |
|
row['Industry Risk'], |
|
row['Seasonality'] |
|
]) |
|
|
|
|
|
predictions = self.model.predict(np.array(features), verbose=0) |
|
|
|
|
|
results_df = df.copy() |
|
prob_values = [p[0] for p in predictions] |
|
|
|
|
|
results_df['Payment Probability'] = [f"{p:.1%}" for p in prob_values] |
|
results_df['Prediction'] = ['✅ Will Pay' if p > 0.5 else '❌ Risk of Default' for p in prob_values] |
|
results_df['Risk Level'] = ['🟢 Low Risk' if p > 0.7 else '🟡 Medium Risk' if p > 0.4 else '🔴 High Risk' for p in prob_values] |
|
|
|
|
|
results_df['Amount ($)'] = results_df['Amount ($)'].apply(lambda x: f"${x:,}") |
|
|
|
|
|
column_order = ['Invoice ID', 'Customer', 'Amount ($)', 'Days Overdue', 'Credit Score', |
|
'Payment Probability', 'Prediction', 'Risk Level'] |
|
results_df = results_df[column_order] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
fig.add_trace(go.Histogram( |
|
x=prob_values, |
|
nbinsx=15, |
|
marker_color='rgba(0, 123, 255, 0.7)', |
|
marker_line_color='rgba(0, 123, 255, 1)', |
|
marker_line_width=2, |
|
name='Payment Probability' |
|
)) |
|
|
|
|
|
fig.add_vline(x=0.4, line_dash="dash", line_color="orange", |
|
annotation_text="Medium Risk Threshold") |
|
fig.add_vline(x=0.7, line_dash="dash", line_color="green", |
|
annotation_text="Low Risk Threshold") |
|
|
|
fig.update_layout( |
|
title={ |
|
'text': '📊 Distribution of Payment Probabilities', |
|
'x': 0.5, |
|
'font': {'size': 18} |
|
}, |
|
xaxis_title='Payment Probability', |
|
yaxis_title='Number of Invoices', |
|
template='plotly_white', |
|
|
|
showlegend=False |
|
) |
|
|
|
|
|
will_pay = sum(1 for p in prob_values if p > 0.5) |
|
risk_default = len(prob_values) - will_pay |
|
high_risk = sum(1 for p in prob_values if p <= 0.4) |
|
|
|
success_msg = f""" |
|
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 20px; border-radius: 10px; margin: 15px 0;"> |
|
<div style="text-align: center; margin-bottom: 15px;"> |
|
<strong style="font-size: 1.2rem;">🔮 Prediction Results Generated Successfully!</strong> |
|
</div> |
|
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; text-align: center;"> |
|
<div style="background: rgba(40, 167, 69, 0.1); padding: 15px; border-radius: 8px; border: 2px solid #28a745;"> |
|
<div style="font-size: 2rem; font-weight: bold; color: #28a745;">{will_pay}</div> |
|
<div style="font-weight: bold;">✅ Will Pay</div> |
|
</div> |
|
<div style="background: rgba(220, 53, 69, 0.1); padding: 15px; border-radius: 8px; border: 2px solid #dc3545;"> |
|
<div style="font-size: 2rem; font-weight: bold; color: #dc3545;">{risk_default}</div> |
|
<div style="font-weight: bold;">❌ Risk of Default</div> |
|
</div> |
|
<div style="background: rgba(255, 193, 7, 0.1); padding: 15px; border-radius: 8px; border: 2px solid #ffc107;"> |
|
<div style="font-size: 2rem; font-weight: bold; color: #856404;">{high_risk}</div> |
|
<div style="font-weight: bold;">🔴 High Risk</div> |
|
</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
return results_df, success_msg, fig |
|
|
|
except Exception as e: |
|
error_msg = f""" |
|
<div style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 15px; border-radius: 10px; text-align: center;"> |
|
<strong>❌ Prediction failed:</strong> {str(e)} |
|
</div> |
|
""" |
|
return None, error_msg, None |
|
|
|
|
|
predictor = SAPARPredictor() |
|
|
|
|
|
with gr.Blocks( |
|
theme=gr.themes.Soft( |
|
primary_hue="blue", |
|
secondary_hue="green", |
|
neutral_hue="slate" |
|
), |
|
title="SAP AR ML Prediction Demo", |
|
css=""" |
|
.gradio-container { |
|
max-width: 1400px !important; |
|
margin: 0 auto !important; |
|
} |
|
.main-header { |
|
text-align: center; |
|
margin-bottom: 2rem; |
|
padding: 2rem; |
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); |
|
border-radius: 15px; |
|
color: white; |
|
margin-bottom: 30px; |
|
box-shadow: 0 8px 32px rgba(0,0,0,0.2); |
|
} |
|
.tab-nav { |
|
margin-bottom: 20px; |
|
} |
|
""" |
|
) as demo: |
|
|
|
gr.HTML(""" |
|
<div class="main-header"> |
|
<h1 style="font-size: 2.5rem; margin-bottom: 15px; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); color: white; font-weight: bold;"> |
|
🏢 SAP Account Receivable ML Prediction Demo |
|
</h1> |
|
<p style="font-size: 1.2rem; color: rgba(255,255,255,0.9); margin: 0; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);"> |
|
Machine Learning-powered invoice payment prediction system using TensorFlow |
|
</p> |
|
</div> |
|
""") |
|
|
|
with gr.Tabs() as tabs: |
|
|
|
with gr.Tab("🎯 Model Training", id=0): |
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
gr.Markdown(""" |
|
### 🚀 Train Your ML Model |
|
|
|
This will create a neural network trained on **1,000 synthetic SAP AR records** to predict invoice payment likelihood. |
|
The model analyzes multiple factors including invoice amount, days overdue, customer credit score, and payment history. |
|
""") |
|
|
|
train_btn = gr.Button( |
|
"🚀 Train ML Model", |
|
variant="primary", |
|
size="lg", |
|
scale=1 |
|
) |
|
|
|
with gr.Column(scale=1): |
|
gr.Markdown(""" |
|
### 📋 Model Features |
|
- Invoice Amount |
|
- Days Overdue |
|
- Previous Delays |
|
- Credit Score |
|
- Industry Risk |
|
- Seasonality |
|
""") |
|
|
|
metrics_display = gr.HTML() |
|
|
|
with gr.Row(): |
|
training_plot = gr.Plot(label="📈 Training Progress") |
|
|
|
with gr.Row(): |
|
predict_btn = gr.Button( |
|
"🔮 Generate Predictions", |
|
variant="secondary", |
|
interactive=False, |
|
size="lg" |
|
) |
|
|
|
with gr.Tab("📊 Invoice Predictions", id=1): |
|
gr.Markdown(""" |
|
### 🔮 Real-time Payment Predictions |
|
View ML-powered predictions for unpaid invoices with probability scores and risk assessments. |
|
""") |
|
|
|
prediction_status = gr.HTML() |
|
|
|
|
|
with gr.Column(): |
|
predictions_df = gr.Dataframe( |
|
label="📋 Invoice Predictions", |
|
interactive=False, |
|
wrap=True, |
|
|
|
) |
|
|
|
probability_plot = gr.Plot(label="📊 Probability Distribution") |
|
|
|
|
|
train_btn.click( |
|
fn=predictor.train_model, |
|
outputs=[training_plot, metrics_display, predict_btn], |
|
show_progress=True |
|
) |
|
|
|
predict_btn.click( |
|
fn=predictor.make_predictions, |
|
outputs=[predictions_df, prediction_status, probability_plot] |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch(share=True) |