Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
import pandas as pd
|
| 2 |
import numpy as np
|
| 3 |
-
from sklearn.ensemble import IsolationForest
|
| 4 |
import gradio as gr
|
| 5 |
import os
|
| 6 |
import tempfile
|
|
@@ -68,16 +67,15 @@ def insert_reconciliation_to_salesforce(df, sf):
|
|
| 68 |
return f"Inserted {inserted_count} records into Salesforce"
|
| 69 |
|
| 70 |
def generate_suggestion(row):
|
| 71 |
-
if row['deviation'] >
|
| 72 |
excess = row['used_quantity'] - row['planned_quantity']
|
| 73 |
return f"Overuse Alert: Reduce future orders by {excess:.0f} units of {row['material_type']}."
|
| 74 |
-
elif row['deviation'] < -
|
| 75 |
surplus = abs(row['planned_quantity'] - row['used_quantity'])
|
| 76 |
return f"Surplus Detected: {surplus:.0f} units unused. Consider reducing future orders."
|
| 77 |
return "Usage as planned. No action needed."
|
| 78 |
|
| 79 |
def reconcile_materials(csv_file):
|
| 80 |
-
# Read CSV
|
| 81 |
if isinstance(csv_file, str):
|
| 82 |
df = pd.read_csv(csv_file)
|
| 83 |
elif hasattr(csv_file, 'name'):
|
|
@@ -86,10 +84,8 @@ def reconcile_materials(csv_file):
|
|
| 86 |
csv_file.seek(0)
|
| 87 |
df = pd.read_csv(csv_file)
|
| 88 |
|
| 89 |
-
# Normalize column names: strip spaces, lowercase
|
| 90 |
df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')
|
| 91 |
|
| 92 |
-
# Map common columns - all lowercase with underscores
|
| 93 |
col_map = {
|
| 94 |
'project_id': 'project_id',
|
| 95 |
'material_type': 'material_type',
|
|
@@ -97,27 +93,25 @@ def reconcile_materials(csv_file):
|
|
| 97 |
'received_quantity': 'received_quantity',
|
| 98 |
'used_quantity': 'used_quantity',
|
| 99 |
}
|
| 100 |
-
|
| 101 |
mapped_cols = {}
|
| 102 |
for expected_col in col_map:
|
| 103 |
for actual_col in df.columns:
|
| 104 |
if actual_col == expected_col:
|
| 105 |
mapped_cols[expected_col] = actual_col
|
| 106 |
break
|
| 107 |
-
|
| 108 |
df.rename(columns=mapped_cols, inplace=True)
|
| 109 |
|
| 110 |
-
# Required columns
|
| 111 |
required = ['material_type', 'planned_quantity', 'received_quantity', 'used_quantity']
|
| 112 |
missing = [col for col in required if col not in df.columns]
|
| 113 |
if missing:
|
| 114 |
return None, f"Error: Missing required column(s): {', '.join(missing)}", None, None, None, None
|
| 115 |
|
| 116 |
-
# Convert quantities to numeric, fill missing with 0
|
| 117 |
for col in ['planned_quantity', 'received_quantity', 'used_quantity']:
|
| 118 |
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
|
| 119 |
|
| 120 |
-
|
| 121 |
df['balance_quantity'] = df['received_quantity'] - df['used_quantity']
|
| 122 |
df['deviation'] = df.apply(
|
| 123 |
lambda row: ((row['used_quantity'] - row['planned_quantity']) / row['planned_quantity']) * 100
|
|
@@ -125,25 +119,12 @@ def reconcile_materials(csv_file):
|
|
| 125 |
axis=1
|
| 126 |
)
|
| 127 |
|
| 128 |
-
|
| 129 |
-
features = df[['planned_quantity', 'received_quantity', 'used_quantity', 'deviation']]
|
| 130 |
-
iso_forest = IsolationForest(contamination=0.1, random_state=42)
|
| 131 |
-
df['anomaly'] = iso_forest.fit_predict(features)
|
| 132 |
-
|
| 133 |
-
# Enforce anomaly for deviation beyond 15%
|
| 134 |
-
df.loc[df['deviation'].abs() > 15, 'anomaly'] = -1
|
| 135 |
-
|
| 136 |
-
# AI Suggestions & Status
|
| 137 |
df['ai_suggestion'] = df.apply(generate_suggestion, axis=1)
|
| 138 |
-
df['reconciliation_status'] = df.apply(
|
| 139 |
-
lambda row: 'Flagged' if abs(row['deviation']) > 15 else 'Complete',
|
| 140 |
-
axis=1
|
| 141 |
-
)
|
| 142 |
|
| 143 |
-
# Insert into Salesforce (handle None for output)
|
| 144 |
salesforce_result = insert_reconciliation_to_salesforce(df, sf)
|
| 145 |
|
| 146 |
-
# Prepare output text summary
|
| 147 |
output_text = f"Material Reconciliation Results\n=============================\n\n"
|
| 148 |
output_text += f"{salesforce_result}\n\nDetailed Records:\n"
|
| 149 |
for i, row in df.iterrows():
|
|
@@ -161,12 +142,10 @@ def reconcile_materials(csv_file):
|
|
| 161 |
output_text += f" Reconciliation Status: {row['reconciliation_status']}\n"
|
| 162 |
output_text += "-----------------------------\n"
|
| 163 |
|
| 164 |
-
# Save file
|
| 165 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
| 166 |
output_file = tmp.name
|
| 167 |
df.to_csv(output_file, index=False)
|
| 168 |
|
| 169 |
-
# Charts
|
| 170 |
bar_fig = px.bar(
|
| 171 |
df, x='material_type', y='deviation',
|
| 172 |
color='reconciliation_status',
|
|
@@ -186,7 +165,6 @@ def reconcile_materials(csv_file):
|
|
| 186 |
ai_summary = "\n".join([f"{row['material_type']}: {row['ai_suggestion']}" for _, row in df.iterrows()])
|
| 187 |
return output_file, output_text, df, bar_fig, pie_fig, ai_summary
|
| 188 |
|
| 189 |
-
|
| 190 |
# Gradio UI
|
| 191 |
with gr.Blocks(css='button:has(span:contains("Share via Link")) { display: none !important; }') as interface:
|
| 192 |
gr.Markdown("# Material Reconciliation Dashboard")
|
|
|
|
| 1 |
import pandas as pd
|
| 2 |
import numpy as np
|
|
|
|
| 3 |
import gradio as gr
|
| 4 |
import os
|
| 5 |
import tempfile
|
|
|
|
| 67 |
return f"Inserted {inserted_count} records into Salesforce"
|
| 68 |
|
| 69 |
def generate_suggestion(row):
|
| 70 |
+
if row['deviation'] > 5:
|
| 71 |
excess = row['used_quantity'] - row['planned_quantity']
|
| 72 |
return f"Overuse Alert: Reduce future orders by {excess:.0f} units of {row['material_type']}."
|
| 73 |
+
elif row['deviation'] < -5:
|
| 74 |
surplus = abs(row['planned_quantity'] - row['used_quantity'])
|
| 75 |
return f"Surplus Detected: {surplus:.0f} units unused. Consider reducing future orders."
|
| 76 |
return "Usage as planned. No action needed."
|
| 77 |
|
| 78 |
def reconcile_materials(csv_file):
|
|
|
|
| 79 |
if isinstance(csv_file, str):
|
| 80 |
df = pd.read_csv(csv_file)
|
| 81 |
elif hasattr(csv_file, 'name'):
|
|
|
|
| 84 |
csv_file.seek(0)
|
| 85 |
df = pd.read_csv(csv_file)
|
| 86 |
|
|
|
|
| 87 |
df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')
|
| 88 |
|
|
|
|
| 89 |
col_map = {
|
| 90 |
'project_id': 'project_id',
|
| 91 |
'material_type': 'material_type',
|
|
|
|
| 93 |
'received_quantity': 'received_quantity',
|
| 94 |
'used_quantity': 'used_quantity',
|
| 95 |
}
|
| 96 |
+
|
| 97 |
mapped_cols = {}
|
| 98 |
for expected_col in col_map:
|
| 99 |
for actual_col in df.columns:
|
| 100 |
if actual_col == expected_col:
|
| 101 |
mapped_cols[expected_col] = actual_col
|
| 102 |
break
|
| 103 |
+
|
| 104 |
df.rename(columns=mapped_cols, inplace=True)
|
| 105 |
|
|
|
|
| 106 |
required = ['material_type', 'planned_quantity', 'received_quantity', 'used_quantity']
|
| 107 |
missing = [col for col in required if col not in df.columns]
|
| 108 |
if missing:
|
| 109 |
return None, f"Error: Missing required column(s): {', '.join(missing)}", None, None, None, None
|
| 110 |
|
|
|
|
| 111 |
for col in ['planned_quantity', 'received_quantity', 'used_quantity']:
|
| 112 |
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
|
| 113 |
|
| 114 |
+
df['used_quantity'] = df[['used_quantity', 'received_quantity']].min(axis=1)
|
| 115 |
df['balance_quantity'] = df['received_quantity'] - df['used_quantity']
|
| 116 |
df['deviation'] = df.apply(
|
| 117 |
lambda row: ((row['used_quantity'] - row['planned_quantity']) / row['planned_quantity']) * 100
|
|
|
|
| 119 |
axis=1
|
| 120 |
)
|
| 121 |
|
| 122 |
+
df['anomaly'] = df['deviation'].apply(lambda d: -1 if abs(d) > 5 else 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
df['ai_suggestion'] = df.apply(generate_suggestion, axis=1)
|
| 124 |
+
df['reconciliation_status'] = df['deviation'].apply(lambda d: 'Flagged' if abs(d) > 5 else 'Complete')
|
|
|
|
|
|
|
|
|
|
| 125 |
|
|
|
|
| 126 |
salesforce_result = insert_reconciliation_to_salesforce(df, sf)
|
| 127 |
|
|
|
|
| 128 |
output_text = f"Material Reconciliation Results\n=============================\n\n"
|
| 129 |
output_text += f"{salesforce_result}\n\nDetailed Records:\n"
|
| 130 |
for i, row in df.iterrows():
|
|
|
|
| 142 |
output_text += f" Reconciliation Status: {row['reconciliation_status']}\n"
|
| 143 |
output_text += "-----------------------------\n"
|
| 144 |
|
|
|
|
| 145 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
| 146 |
output_file = tmp.name
|
| 147 |
df.to_csv(output_file, index=False)
|
| 148 |
|
|
|
|
| 149 |
bar_fig = px.bar(
|
| 150 |
df, x='material_type', y='deviation',
|
| 151 |
color='reconciliation_status',
|
|
|
|
| 165 |
ai_summary = "\n".join([f"{row['material_type']}: {row['ai_suggestion']}" for _, row in df.iterrows()])
|
| 166 |
return output_file, output_text, df, bar_fig, pie_fig, ai_summary
|
| 167 |
|
|
|
|
| 168 |
# Gradio UI
|
| 169 |
with gr.Blocks(css='button:has(span:contains("Share via Link")) { display: none !important; }') as interface:
|
| 170 |
gr.Markdown("# Material Reconciliation Dashboard")
|