VijayPulmamidi's picture
Update app.py
920f674 verified
import pandas as pd
import numpy as np
import gradio as gr
import os
import tempfile
import logging
from simple_salesforce import Salesforce
import plotly.express as px
# Logging setup
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Salesforce credentials
SALESFORCE_USERNAME = "vijaypulmamidi.dev2025@sathkrutha.com"
SALESFORCE_PASSWORD = "Vij@y9100754977"
SALESFORCE_SECURITY_TOKEN = "CaZSEwVmB3EIAiV6G8ukdDp0"
# Connect to Salesforce
sf = Salesforce(username=SALESFORCE_USERNAME, password=SALESFORCE_PASSWORD, security_token=SALESFORCE_SECURITY_TOKEN)
logger.info("Connected to Salesforce.")
def find_salesforce_project(project_name, sf):
try:
query = f"SELECT Id FROM Project__c WHERE Name = '{project_name}' LIMIT 1"
result = sf.query(query)
if result['totalSize'] > 0:
return result['records'][0]['Id']
except Exception as e:
logger.warning(f"Salesforce project lookup failed for '{project_name}': {e}")
return None
def insert_reconciliation_to_salesforce(df, sf):
inserted_count = 0
project_cache = {}
for index, row in df.iterrows():
project_id = None
if 'project_id' in df.columns and pd.notna(row['project_id']):
project_name = row['project_id']
if project_name in project_cache:
project_id = project_cache[project_name]
else:
project_id = find_salesforce_project(project_name, sf)
if project_id:
project_cache[project_name] = project_id
else:
logger.info(f"Project '{project_name}' not found in Salesforce, skipping project ID linkage.")
record = {
'Material_Type__c': row['material_type'],
'Planned_Quantity__c': row['planned_quantity'],
'Received_Quantity__c': row['received_quantity'],
'Used_Quantity__c': row['used_quantity'],
'AI_Suggestion__c': row.get('ai_suggestion', ''),
'Reconciliation_Status__c': row.get('reconciliation_status', '')
}
if project_id:
record['Project_ID__c'] = project_id
try:
sf.Material_Reconciliation_Record__c.create(record)
inserted_count += 1
except Exception as e:
logger.error(f"Failed to insert record for material {row['material_type']}: {e}")
return f"Inserted {inserted_count} records into Salesforce"
def generate_suggestion(row):
if row['deviation'] > 5:
excess = row['used_quantity'] - row['planned_quantity']
return f"Overuse Alert: Reduce future orders by {excess:.0f} units of {row['material_type']}."
elif row['deviation'] < -5:
surplus = abs(row['planned_quantity'] - row['used_quantity'])
return f"Surplus Detected: {surplus:.0f} units unused. Consider reducing future orders."
return "Usage as planned. No action needed."
def reconcile_materials(csv_file):
if isinstance(csv_file, str):
df = pd.read_csv(csv_file)
elif hasattr(csv_file, 'name'):
df = pd.read_csv(csv_file.name)
else:
csv_file.seek(0)
df = pd.read_csv(csv_file)
df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')
col_map = {
'project_id': 'project_id',
'material_type': 'material_type',
'planned_quantity': 'planned_quantity',
'received_quantity': 'received_quantity',
'used_quantity': 'used_quantity',
}
mapped_cols = {}
for expected_col in col_map:
for actual_col in df.columns:
if actual_col == expected_col:
mapped_cols[expected_col] = actual_col
break
df.rename(columns=mapped_cols, inplace=True)
required = ['material_type', 'planned_quantity', 'received_quantity', 'used_quantity']
missing = [col for col in required if col not in df.columns]
if missing:
return None, f"Error: Missing required column(s): {', '.join(missing)}", None, None, None, None
for col in ['planned_quantity', 'received_quantity', 'used_quantity']:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
df['used_quantity'] = df[['used_quantity', 'received_quantity']].min(axis=1)
df['balance_quantity'] = df['received_quantity'] - df['used_quantity']
df['deviation'] = df.apply(
lambda row: ((row['used_quantity'] - row['planned_quantity']) / row['planned_quantity']) * 100
if row['planned_quantity'] != 0 else 0,
axis=1
)
df['anomaly'] = df['deviation'].apply(lambda d: -1 if abs(d) > 5 else 1)
df['ai_suggestion'] = df.apply(generate_suggestion, axis=1)
df['reconciliation_status'] = df['deviation'].apply(lambda d: 'Flagged' if abs(d) > 5 else 'Complete')
salesforce_result = insert_reconciliation_to_salesforce(df, sf)
output_text = f"Material Reconciliation Results\n=============================\n\n"
output_text += f"{salesforce_result}\n\nDetailed Records:\n"
for i, row in df.iterrows():
output_text += f"Record {i + 1}:\n"
if 'project_id' in df.columns and pd.notna(row.get('project_id')):
output_text += f" Project ID: {row['project_id']}\n"
output_text += f" Material Type: {row['material_type']}\n"
output_text += f" Planned Quantity: {row['planned_quantity']}\n"
output_text += f" Received Quantity: {row['received_quantity']}\n"
output_text += f" Used Quantity: {row['used_quantity']}\n"
output_text += f" Balance Quantity: {row['balance_quantity']}\n"
output_text += f" Deviation: {row['deviation']:.2f}%\n"
output_text += f" Anomaly: {'Yes' if row['anomaly'] == -1 else 'No'}\n"
output_text += f" AI Suggestion: {row['ai_suggestion']}\n"
output_text += f" Reconciliation Status: {row['reconciliation_status']}\n"
output_text += "-----------------------------\n"
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
output_file = tmp.name
df.to_csv(output_file, index=False)
bar_fig = px.bar(
df, x='material_type', y='deviation',
color='reconciliation_status',
title='Deviation by Material Type',
labels={'deviation': 'Deviation (%)'},
color_discrete_map={'Flagged': '#FF4B4B', 'Complete': '#36A2EB'}
)
pie_data = df['reconciliation_status'].value_counts().reset_index()
pie_data.columns = ['Reconciliation_Status', 'Count']
pie_fig = px.pie(
pie_data, names='Reconciliation_Status', values='Count',
title='Reconciliation Status Distribution',
color_discrete_map={'Flagged': '#FF4B4B', 'Complete': '#36A2EB'}
)
ai_summary = "\n".join([f"{row['material_type']}: {row['ai_suggestion']}" for _, row in df.iterrows()])
return output_file, output_text, df, bar_fig, pie_fig, ai_summary
# Gradio UI
with gr.Blocks(css='button:has(span:contains("Share via Link")) { display: none !important; }') as interface:
gr.Markdown("# Material Reconciliation Dashboard")
with gr.Row():
with gr.Column(scale=1):
csv_input = gr.File(label="Upload CSV", file_types=[".csv"])
submit_button = gr.Button("Reconcile Materials")
with gr.Column(scale=2):
output_text = gr.Textbox(label="Detailed Results", lines=20)
with gr.Row():
with gr.Column(scale=2):
output_table = gr.Dataframe(label="Reconciled Data")
with gr.Column(scale=1):
ai_summary_output = gr.Textbox(label="AI Suggestions Summary")
with gr.Row():
with gr.Column(scale=1):
bar_plot = gr.Plot(label="Deviation Plot")
with gr.Column(scale=1):
pie_plot = gr.Plot(label="Status Distribution")
output_file = gr.File(label="Download Reconciled CSV")
submit_button.click(
fn=reconcile_materials,
inputs=csv_input,
outputs=[output_file, output_text, output_table, bar_plot, pie_plot, ai_summary_output]
)
interface.launch()