VijayPulmamidi commited on
Commit
48502df
·
verified ·
1 Parent(s): 778f792

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -127
app.py CHANGED
@@ -1,38 +1,41 @@
1
  import pandas as pd
2
  import numpy as np
3
  from sklearn.ensemble import IsolationForest
4
- import streamlit as st
 
 
5
  import plotly.express as px
6
  import plotly.graph_objects as go
7
- from simple_salesforce import Salesforce
8
  import logging
9
  import os
10
  import tempfile
11
- from uuid import uuid4
12
 
13
  # Set up logging
14
- logging.basicConfig(level=logging.INFO)
15
  logger = logging.getLogger(__name__)
16
 
17
- # Salesforce credentials
18
  SALESFORCE_USERNAME = "vijaypulmamidi.dev2025@sathkrutha.com"
19
  SALESFORCE_PASSWORD = "Vij@y9100754977"
20
  SALESFORCE_SECURITY_TOKEN = "CaZSEwVmB3EIAiV6G8ukdDp0"
21
 
22
- def get_salesforce_connection():
23
- """Establish connection to Salesforce using provided credentials."""
 
 
 
 
24
  try:
25
  sf = Salesforce(
26
  username=SALESFORCE_USERNAME,
27
  password=SALESFORCE_PASSWORD,
28
  security_token=SALESFORCE_SECURITY_TOKEN
29
  )
30
- logger.info("Successfully connected to Salesforce")
31
  return sf
32
  except Exception as e:
33
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
34
- st.error("Failed to connect to Salesforce. Please check credentials.")
35
- return None
36
 
37
  def find_salesforce_project(project_name, sf):
38
  """Find an existing Project__c record by name and return its ID."""
@@ -41,50 +44,51 @@ def find_salesforce_project(project_name, sf):
41
  result = sf.query(query)
42
  if result['totalSize'] > 0:
43
  project_id = result['records'][0]['Id']
44
- logger.info(f"Found Project__c with Name: {project_name}, ID: {project_id}")
45
  return project_id
46
- logger.info(f"No Project__c found with Name: {project_name}")
47
  return None
48
  except Exception as e:
49
- logger.error(f"Error querying Project__c: {str(e)}")
50
  return None
51
 
52
  def insert_reconciliation_to_salesforce(df, sf):
53
- """Insert reconciliation records into Salesforce."""
54
  inserted_count = 0
55
  project_cache = {}
56
 
57
  for index, row in df.iterrows():
58
- project_id = None
59
- if 'Project_ID' in df.columns and pd.notna(row['Project_ID']):
60
- project_name = row['Project_ID']
61
- if project_name in project_cache:
62
- project_id = project_cache[project_name]
63
- else:
64
- project_id = find_salesforce_project(project_name, sf)
65
- if project_id:
66
- project_cache[project_name] = project_id
67
-
68
- reconciliation_record = {
69
- 'Material_Type__c': row['Material_Type'],
70
- 'Planned_Quantity__c': row['Planned_Quantity'],
71
- 'Received_Quantity__c': row['Received_Quantity'],
72
- 'Used_Quantity__c': row['Used_Quantity'],
73
- 'AI_Suggestion__c': row['AI_Suggestion'],
74
- 'Reconciliation_Status__c': row['Reconciliation_Status']
75
- }
76
- if project_id:
77
- reconciliation_record['Project_ID__c'] = project_id
78
-
79
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  sf.Material_Reconciliation_Record__c.create(reconciliation_record)
81
  inserted_count += 1
82
- logger.info(f"Inserted record: {reconciliation_record}")
83
  except Exception as e:
84
- logger.error(f"Error inserting record: {str(e)}")
 
85
 
86
- logger.info(f"Inserted {inserted_count} of {len(df)} records successfully")
87
- return inserted_count
88
 
89
  def generate_suggestion(row):
90
  """Generate AI suggestions based on reconciliation data."""
@@ -98,16 +102,17 @@ def generate_suggestion(row):
98
  return f"Shortage: Order {abs(row['Used_Quantity'] - row['Planned_Quantity'])} more units of {row['Material_Type']}."
99
  return "No action needed."
100
  except Exception as e:
101
- logger.error(f"Error generating suggestion: {str(e)}")
102
  return "Error generating suggestion"
103
 
104
- def process_csv_data(file):
105
- """Process uploaded CSV file and perform reconciliation."""
106
  try:
107
- df = pd.read_csv(file)
108
- logger.info(f"CSV read successfully. Columns: {df.columns.tolist()}")
 
109
 
110
- # Validate CSV columns
111
  column_mapping = {
112
  'Material_Type': 'Material_Type',
113
  'Planned_Quantity': ['Planned_Quantity', 'Planned_Qty'],
@@ -157,100 +162,169 @@ def process_csv_data(file):
157
  return df
158
  except Exception as e:
159
  logger.error(f"Error processing CSV: {str(e)}")
160
- st.error(f"Error processing CSV: {str(e)}")
161
- return None
162
 
163
  def create_visualizations(df):
164
  """Create visualizations for the dashboard."""
165
- # Bar chart for Deviation
166
- bar_fig = px.bar(
167
- df,
168
- x='Material_Type',
169
- y='Deviation',
170
- color='Reconciliation_Status',
171
- title='Deviation by Material Type',
172
- labels={'Deviation': 'Deviation (%)'},
173
- color_discrete_map={'Flagged': '#FF4B4B', 'Complete': '#36A2EB'}
174
- )
175
- bar_fig.update_layout(xaxis_title="Material Type", yaxis_title="Deviation (%)")
 
176
 
177
- # Pie chart for Reconciliation Status
178
- status_counts = df['Reconciliation_Status'].value_counts().reset_index()
179
- status_counts.columns = ['Reconciliation_Status', 'Count']
180
- pie_fig = px.pie(
181
- status_counts,
182
- names='Reconciliation_Status',
183
- values='Count',
184
- title='Reconciliation Status Distribution',
185
- color_discrete_map={'Flagged': '#FF4B4B', 'Complete': '#36A2EB'}
186
- )
187
 
188
- # AI Suggestions summary
189
- ai_summary = "\n".join([f"{row['Material_Type']}: {row['AI_Suggestion']}" for _, row in df.iterrows()])
190
 
191
- return bar_fig, pie_fig, ai_summary
 
 
 
192
 
193
- def main():
194
- """Main function to set up Streamlit dashboard."""
195
- st.set_page_config(page_title="Material Reconciliation Dashboard", layout="wide")
196
- st.title("Material Reconciliation Dashboard")
 
 
 
 
 
 
 
 
 
197
 
198
- # Initialize Salesforce connection
199
- sf = get_salesforce_connection()
200
-
201
- # File upload
202
- st.subheader("Upload CSV File")
203
- uploaded_file = st.file_uploader("Choose a CSV file", type=["csv"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- if uploaded_file:
206
- with st.spinner("Processing data..."):
207
- df = process_csv_data(uploaded_file)
208
- if df is not None:
209
- # Save results to temporary file
210
- with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp_file:
211
- output_file = tmp_file.name
212
- df.to_csv(output_file, index=False)
213
- logger.info(f"Output CSV saved to: {output_file}")
 
 
 
 
 
 
214
 
215
- # Insert to Salesforce if connection is available
216
- if sf:
217
- inserted_count = insert_reconciliation_to_salesforce(df, sf)
218
- st.success(f"Inserted {inserted_count} records into Salesforce")
219
 
220
- # Create visualizations
221
- bar_fig, pie_fig, ai_summary = create_visualizations(df)
 
 
 
222
 
223
- # Display results
224
- st.subheader("Reconciliation Results")
225
- col1, col2 = st.columns([2, 1])
226
-
227
- with col1:
228
- st.subheader("Data Table")
229
- st.dataframe(df, use_container_width=True)
230
-
231
- with col2:
232
- st.subheader("AI Suggestions")
233
- st.text_area("Suggestions", ai_summary, height=200)
 
 
 
 
 
234
 
235
- # Visualizations
236
- st.subheader("Visualizations")
237
- col3, col4 = st.columns(2)
238
-
239
- with col3:
240
- st.plotly_chart(bar_fig, use_container_width=True)
241
-
242
- with col4:
243
- st.plotly_chart(pie_fig, use_container_width=True)
244
 
245
- # Download link
246
- st.subheader("Download Results")
247
- with open(output_file, "rb") as file:
248
- st.download_button(
249
- label="Download Reconciled CSV",
250
- data=file,
251
- file_name="reconciled_data.csv",
252
- mime="text/csv"
253
- )
 
 
 
 
 
 
254
 
255
- if __name__ == "__main__":
256
- main()
 
1
  import pandas as pd
2
  import numpy as np
3
  from sklearn.ensemble import IsolationForest
4
+ import dash
5
+ from dash import dcc, html, Input, Output
6
+ from simple_salesforce import Salesforce
7
  import plotly.express as px
8
  import plotly.graph_objects as go
 
9
  import logging
10
  import os
11
  import tempfile
 
12
 
13
  # Set up logging
14
+ logging.basicConfig(level=logging.DEBUG)
15
  logger = logging.getLogger(__name__)
16
 
17
+ # Salesforce credentials (should be stored in environment variables in production)
18
  SALESFORCE_USERNAME = "vijaypulmamidi.dev2025@sathkrutha.com"
19
  SALESFORCE_PASSWORD = "Vij@y9100754977"
20
  SALESFORCE_SECURITY_TOKEN = "CaZSEwVmB3EIAiV6G8ukdDp0"
21
 
22
+ # Initialize Dash app
23
+ app = dash.Dash(__name__, title="Material Reconciliation Dashboard")
24
+ server = app.server # Required for Hugging Face deployment
25
+
26
+ def connect_to_salesforce():
27
+ """Connect to Salesforce with error handling."""
28
  try:
29
  sf = Salesforce(
30
  username=SALESFORCE_USERNAME,
31
  password=SALESFORCE_PASSWORD,
32
  security_token=SALESFORCE_SECURITY_TOKEN
33
  )
34
+ logger.debug("Successfully connected to Salesforce")
35
  return sf
36
  except Exception as e:
37
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
38
+ raise Exception(f"Salesforce connection failed: {str(e)}")
 
39
 
40
  def find_salesforce_project(project_name, sf):
41
  """Find an existing Project__c record by name and return its ID."""
 
44
  result = sf.query(query)
45
  if result['totalSize'] > 0:
46
  project_id = result['records'][0]['Id']
47
+ logger.debug(f"Found Project__c with Name: {project_name}, ID: {project_id}")
48
  return project_id
49
+ logger.debug(f"No Project__c found with Name: {project_name}")
50
  return None
51
  except Exception as e:
52
+ logger.error(f"Error finding project {project_name}: {str(e)}")
53
  return None
54
 
55
  def insert_reconciliation_to_salesforce(df, sf):
56
+ """Inserts reconciliation records into Salesforce."""
57
  inserted_count = 0
58
  project_cache = {}
59
 
60
  for index, row in df.iterrows():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  try:
62
+ project_id = None
63
+ if 'Project_ID' in df.columns and pd.notna(row['Project_ID']):
64
+ project_name = row['Project_ID']
65
+ if project_name in project_cache:
66
+ project_id = project_cache[project_name]
67
+ else:
68
+ project_id = find_salesforce_project(project_name, sf)
69
+ if project_id:
70
+ project_cache[project_name] = project_id
71
+
72
+ reconciliation_record = {
73
+ 'Material_Type__c': row['Material_Type'],
74
+ 'Planned_Quantity__c': row['Planned_Quantity'],
75
+ 'Received_Quantity__c': row['Received_Quantity'],
76
+ 'Used_Quantity__c': row['Used_Quantity'],
77
+ 'AI_Suggestion__c': row['AI_Suggestion'],
78
+ 'Reconciliation_Status__c': row['Reconciliation_Status']
79
+ }
80
+ if project_id:
81
+ reconciliation_record['Project_ID__c'] = project_id
82
+
83
+ logger.debug(f"Inserting record: {reconciliation_record}")
84
  sf.Material_Reconciliation_Record__c.create(reconciliation_record)
85
  inserted_count += 1
 
86
  except Exception as e:
87
+ logger.error(f"Error inserting record {index}: {str(e)}")
88
+ continue
89
 
90
+ logger.debug(f"Inserted {inserted_count} of {len(df)} records successfully")
91
+ return f"Inserted {inserted_count} records into Salesforce"
92
 
93
  def generate_suggestion(row):
94
  """Generate AI suggestions based on reconciliation data."""
 
102
  return f"Shortage: Order {abs(row['Used_Quantity'] - row['Planned_Quantity'])} more units of {row['Material_Type']}."
103
  return "No action needed."
104
  except Exception as e:
105
+ logger.error(f"Error generating suggestion for row: {str(e)}")
106
  return "Error generating suggestion"
107
 
108
+ def process_csv(file_path):
109
+ """Process CSV file and perform reconciliation."""
110
  try:
111
+ # Read CSV
112
+ df = pd.read_csv(file_path)
113
+ logger.debug(f"CSV read successfully. Columns: {df.columns.tolist()}")
114
 
115
+ # Validate columns
116
  column_mapping = {
117
  'Material_Type': 'Material_Type',
118
  'Planned_Quantity': ['Planned_Quantity', 'Planned_Qty'],
 
162
  return df
163
  except Exception as e:
164
  logger.error(f"Error processing CSV: {str(e)}")
165
+ raise
 
166
 
167
  def create_visualizations(df):
168
  """Create visualizations for the dashboard."""
169
+ try:
170
+ # Bar chart for Deviation
171
+ bar_fig = px.bar(
172
+ df,
173
+ x='Material_Type',
174
+ y='Deviation',
175
+ color='Reconciliation_Status',
176
+ title='Deviation by Material Type',
177
+ labels={'Deviation': 'Deviation (%)'},
178
+ color_discrete_map={'Flagged': '#FF4B4B', 'Complete': '#36A2EB'}
179
+ )
180
+ bar_fig.update_layout(xaxis_title="Material Type", yaxis_title="Deviation (%)")
181
 
182
+ # Pie chart for Reconciliation Status
183
+ status_counts = df['Reconciliation_Status'].value_counts().reset_index()
184
+ status_counts.columns = ['Reconciliation_Status', 'Count']
185
+ pie_fig = px.pie(
186
+ status_counts,
187
+ names='Reconciliation_Status',
188
+ values='Count',
189
+ title='Reconciliation Status Distribution',
190
+ color_discrete_map={'Flagged': '#FF4B4B', 'Complete': '#36A2EB'}
191
+ )
192
 
193
+ # AI Suggestions summary
194
+ ai_summary = "\n".join([f"{row['Material_Type']}: {row['AI_Suggestion']}" for _, row in df.iterrows()])
195
 
196
+ return bar_fig, pie_fig, ai_summary
197
+ except Exception as e:
198
+ logger.error(f"Error creating visualizations: {str(e)}")
199
+ raise
200
 
201
+ # Dash layout
202
+ app.layout = html.Div([
203
+ html.H1("Material Reconciliation Dashboard", className="text-3xl font-bold mb-4"),
204
+ html.Div([
205
+ html.H2("Upload CSV File", className="text-xl font-semibold mb-2"),
206
+ dcc.Upload(
207
+ id='upload-data',
208
+ children=html.Button('Upload CSV', className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"),
209
+ multiple=False,
210
+ accept='.csv'
211
+ ),
212
+ html.Div(id='upload-status', className="mt-2 text-gray-600")
213
+ ], className="mb-6"),
214
 
215
+ html.Div(id='output-container', children=[
216
+ html.H2("Reconciliation Results", className="text-xl font-semibold mb-2"),
217
+ html.Div(id='output-text', className="bg-gray-100 p-4 rounded mb-4"),
218
+
219
+ html.H2("Data Table", className="text-xl font-semibold mb-2"),
220
+ dcc.Graph(id='data-table'),
221
+
222
+ html.H2("AI Suggestions", className="text-xl font-semibold mb-2"),
223
+ html.Div(id='ai-suggestions', className="bg-gray-100 p-4 rounded mb-4"),
224
+
225
+ html.H2("Visualizations", className="text-xl font-semibold mb-2"),
226
+ html.Div([
227
+ html.Div([
228
+ html.H3("Deviation by Material", className="text-lg font-medium mb-2"),
229
+ dcc.Graph(id='bar-plot')
230
+ ], className="w-full md:w-1/2 p-2"),
231
+ html.Div([
232
+ html.H3("Reconciliation Status Distribution", className="text-lg font-medium mb-2"),
233
+ dcc.Graph(id='pie-plot')
234
+ ], className="w-full md:w-1/2 p-2")
235
+ ], className="flex flex-wrap"),
236
+
237
+ html.A(
238
+ "Download Reconciled CSV",
239
+ id='download-link',
240
+ download="reconciled_data.csv",
241
+ href="",
242
+ className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded mt-4 inline-block"
243
+ )
244
+ ], className="container mx-auto p-4")
245
+ ], className="p-6 bg-gray-50 min-h-screen")
246
+
247
+ @app.callback(
248
+ [
249
+ Output('upload-status', 'children'),
250
+ Output('output-text', 'children'),
251
+ Output('data-table', 'figure'),
252
+ Output('bar-plot', 'figure'),
253
+ Output('pie-plot', 'figure'),
254
+ Output('ai-suggestions', 'children'),
255
+ Output('download-link', 'href')
256
+ ],
257
+ [Input('upload-data', 'contents')],
258
+ [Input('upload-data', 'filename')]
259
+ )
260
+ def update_output(uploaded_file, filename):
261
+ """Callback to process uploaded CSV and update dashboard."""
262
+ if uploaded_file is None:
263
+ return "No file uploaded.", "", {}, {}, {}, "", ""
264
 
265
+ try:
266
+ # Save uploaded file temporarily
267
+ content_type, content_string = uploaded_file.split(',')
268
+ import base64
269
+ decoded = base64.b64decode(content_string)
270
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp_file:
271
+ tmp_file.write(decoded)
272
+ tmp_file_path = tmp_file.name
273
+
274
+ # Process CSV
275
+ df = process_csv(tmp_file_path)
276
+
277
+ # Connect to Salesforce and insert records
278
+ sf = connect_to_salesforce()
279
+ salesforce_result = insert_reconciliation_to_salesforce(df, sf)
280
 
281
+ # Generate visualizations
282
+ bar_fig, pie_fig, ai_summary = create_visualizations(df)
 
 
283
 
284
+ # Create table figure
285
+ table_fig = go.Figure(data=[go.Table(
286
+ header=dict(values=list(df.columns), fill_color='paleturquoise', align='left'),
287
+ cells=dict(values=[df[col] for col in df.columns], fill_color='lavender', align='left')
288
+ )])
289
 
290
+ # Generate text output
291
+ text_output = f"Material Reconciliation Results\n{'='*30}\n\n{salesforce_result}\n\nDetailed Records:\n"
292
+ for index, row in df.iterrows():
293
+ text_output += f"Record {index + 1}:\n"
294
+ if 'Project_ID' in df.columns and pd.notna(row['Project_ID']):
295
+ text_output += f" Project ID: {row['Project_ID']}\n"
296
+ text_output += f" Material Type: {row['Material_Type']}\n"
297
+ text_output += f" Planned Quantity: {row['Planned_Quantity']}\n"
298
+ text_output += f" Received Quantity: {row['Received_Quantity']}\n"
299
+ text_output += f" Used Quantity: {row['Used_Quantity']}\n"
300
+ text_output += f" Balance Quantity: {row['Balance_Quantity']}\n"
301
+ text_output += f" Deviation: {row['Deviation']:.2f}%\n"
302
+ text_output += f" Anomaly: {'Yes' if row['Anomaly'] == -1 else 'No'}\n"
303
+ text_output += f" AI Suggestion: {row['AI_Suggestion']}\n"
304
+ text_output += f" Reconciliation Status: {row['Reconciliation_Status']}\n"
305
+ text_output += f"{'-'*30}\n"
306
 
307
+ # Create download link
308
+ csv_string = df.to_csv(index=False)
309
+ csv_string = "data:text/csv;charset=utf-8," + csv_string
310
+ import urllib.parse
311
+ csv_string = urllib.parse.quote(csv_string, safe=':,')
 
 
 
 
312
 
313
+ # Clean up temporary file
314
+ os.unlink(tmp_file_path)
315
+
316
+ return (
317
+ f"File {filename} processed successfully.",
318
+ text_output,
319
+ table_fig,
320
+ bar_fig,
321
+ pie_fig,
322
+ ai_summary,
323
+ csv_string
324
+ )
325
+ except Exception as e:
326
+ logger.error(f"Error in callback: {str(e)}")
327
+ return f"Error processing file: {str(e)}", "", {}, {}, {}, "", ""
328
 
329
+ if __name__ == '__main__':
330
+ app.run_server(debug=True)