RathodHarish commited on
Commit
e01af3c
·
verified ·
1 Parent(s): 21cb5fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +576 -95
app.py CHANGED
@@ -1,112 +1,593 @@
1
-
 
 
2
  import gradio as gr
3
  import pandas as pd
4
- import plotly.express as px
5
- import numpy as np
6
  import logging
 
7
  from sklearn.ensemble import IsolationForest
8
- from simple_salesforce import Salesforce
9
  from transformers import pipeline
10
- from datetime import datetime
 
 
11
  import os
12
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
13
- from reportlab.lib.pagesizes import letter
14
- from reportlab.lib.styles import getSampleStyleSheet
15
  import smtplib
16
  from email.mime.text import MIMEText
17
  from email.mime.multipart import MIMEMultipart
18
 
19
- # Setup logging
20
- logging.basicConfig(level=logging.INFO)
21
 
22
- # Environment Variables
23
- SF_USERNAME = os.getenv("SF_USERNAME")
24
- SF_PASSWORD = os.getenv("SF_PASSWORD")
25
- SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
26
- SMTP_USERNAME = os.getenv('SMTP_USERNAME', 'harishkumarr@sathkrutha.com')
27
- SMTP_PASSWORD = os.getenv('SMTP_PASSWORD', 'your_app_password_here')
28
- EMAIL_FROM = SMTP_USERNAME
29
- EMAIL_TO = os.getenv('EMAIL_TO', 'sanjaybhargavneela@sathkrutha.com')
 
 
 
 
30
 
31
- # Hugging Face Pipeline
32
- summarizer = pipeline("summarization")
 
 
 
 
 
33
 
34
- # Connect to Salesforce
35
- sf = Salesforce(username=SF_USERNAME, password=SF_PASSWORD, security_token=SF_SECURITY_TOKEN)
 
 
 
 
 
 
 
 
36
 
37
- def detect_anomalies(df):
38
- df['Usage_Hours__c'] = pd.to_numeric(df['Usage_Hours__c'], errors='coerce').fillna(0)
39
- model = IsolationForest(contamination=0.1)
40
- df['anomaly'] = model.fit_predict(df[['Usage_Hours__c']])
41
- df['anomaly_label'] = df['anomaly'].apply(lambda x: 'Anomaly' if x == -1 else 'Normal')
42
- return df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def summarize_logs(df):
45
- joined_text = "\n".join([f"Device {row['Device_Id__c']} used for {row['Usage_Hours__c']} hrs with status {row['Status__c']}" for _, row in df.iterrows()])
46
- summary = summarizer(joined_text, max_length=100, min_length=30, do_sample=False)[0]['summary_text']
47
- return summary
48
-
49
- def save_to_salesforce(df):
50
- records = []
51
- for _, row in df.iterrows():
52
- record = {
53
- "Device_Id__c": row.get("Device_Id__c"),
54
- "Usage_Hours__c": float(row.get("Usage_Hours__c", 0)),
55
- "Downtime__c": float(row.get("Downtime__c", 0)),
56
- "Timestamp__c": row.get("Timestamp__c"),
57
- "Status__c": row.get("Status__c"),
58
- "Log_Type__c": row.get("Log_Type__c"),
59
- "AMC_Date__c": row.get("AMC_Date__c"),
60
- "Name": row.get("Device_Id__c") + "-" + datetime.now().strftime("%Y%m%d%H%M%S")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
- records.append(record)
63
- result = sf.bulk.SmartLog__c.insert(records)
64
- logging.info(f"Inserted {len(result)} records into Salesforce")
65
- return result
66
-
67
- def generate_pdf(summary):
68
- filename = "/tmp/audit_summary.pdf"
69
- doc = SimpleDocTemplate(filename, pagesize=letter)
70
- styles = getSampleStyleSheet()
71
- elements = [Paragraph("Smart Audit Log Summary", styles['Title']), Spacer(1, 12)]
72
- elements.append(Paragraph(summary, styles['BodyText']))
73
- doc.build(elements)
74
- return filename
75
-
76
- def send_email_alert(subject, body):
77
- msg = MIMEMultipart()
78
- msg['From'] = EMAIL_FROM
79
- msg['To'] = EMAIL_TO
80
- msg['Subject'] = subject
81
- msg.attach(MIMEText(body, 'plain'))
82
-
83
- with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
84
- server.login(SMTP_USERNAME, SMTP_PASSWORD)
85
- server.send_message(msg)
86
-
87
- def process_logs(file):
88
- df = pd.read_csv(file)
89
- df = detect_anomalies(df)
90
- summary = summarize_logs(df)
91
- pdf_path = generate_pdf(summary)
92
- save_to_salesforce(df)
93
-
94
- anomalies = df[df['anomaly_label'] == 'Anomaly']
95
- if not anomalies.empty:
96
- alert_msg = f"Anomalies Detected in Devices:\n" + "\n".join(anomalies['Device_Id__c'].values)
97
- send_email_alert("Anomaly Alert", alert_msg)
98
-
99
- fig = px.scatter(df, x="Device_Id__c", y="Usage_Hours__c", color="anomaly_label", title="Device Usage with Anomalies")
100
- return df, summary, fig, pdf_path
101
-
102
- with gr.Blocks() as app:
103
- gr.Markdown("### LabOps Log Analyzer Dashboard")
104
- file_input = gr.File(label="Upload LabOps CSV", file_types=[".csv"])
105
- df_output = gr.Dataframe()
106
- summary_output = gr.Textbox(label="Summary")
107
- graph_output = gr.Plot()
108
- pdf_download = gr.File(label="Download PDF Summary")
109
-
110
- file_input.change(fn=process_logs, inputs=file_input, outputs=[df_output, summary_output, graph_output, pdf_download])
111
-
112
- app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LabOps Log Analyzer Dashboard with CSV file upload, PDF generation, Salesforce integration, and AMC reminder email alerts
3
+ """
4
  import gradio as gr
5
  import pandas as pd
6
+ from datetime import datetime, timedelta
 
7
  import logging
8
+ import plotly.express as px
9
  from sklearn.ensemble import IsolationForest
 
10
  from transformers import pipeline
11
+ import torch
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ from simple_salesforce import Salesforce
14
  import os
15
+ import json
 
 
16
  import smtplib
17
  from email.mime.text import MIMEText
18
  from email.mime.multipart import MIMEMultipart
19
 
20
+ # Configure logging
21
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
22
 
23
+ # Salesforce configuration
24
+ try:
25
+ sf = Salesforce(
26
+ username='multi-devicelabopsdashboard@sathkrutha.com',
27
+ password='Team@1234',
28
+ security_token=os.getenv('SF_SECURITY_TOKEN', ''),
29
+ domain='login'
30
+ )
31
+ logging.info("Salesforce connection established")
32
+ except Exception as e:
33
+ logging.error(f"Failed to connect to Salesforce: {str(e)}")
34
+ sf = None
35
 
36
+ # Email configuration
37
+ SMTP_SERVER = 'smtp.gmail.com'
38
+ SMTP_PORT = 587
39
+ SMTP_USERNAME = 'harishkumarr@sathkrutha.com'
40
+ SMTP_PASSWORD = 'Harish@048' # Ensure this is an app-specific password
41
+ EMAIL_FROM = "harishkumarr@sathkrutha.com"
42
+ EMAIL_TO = "sanjaybhargavneela@sathkrutha.com"
43
 
44
+ # Try to import reportlab
45
+ try:
46
+ from reportlab.lib.pagesizes import letter
47
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
48
+ from reportlab.lib.styles import getSampleStyleSheet
49
+ reportlab_available = True
50
+ logging.info("reportlab module successfully imported")
51
+ except ImportError:
52
+ logging.warning("reportlab module not found. PDF generation disabled.")
53
+ reportlab_available = False
54
 
55
+ # Preload Hugging Face model with optimization
56
+ logging.info("Preloading Hugging Face model...")
57
+ try:
58
+ device = 0 if torch.cuda.is_available() else -1
59
+ summarizer = pipeline(
60
+ "summarization",
61
+ model="facebook/bart-large-cnn",
62
+ device=device,
63
+ max_length=50,
64
+ min_length=10,
65
+ num_beams=4
66
+ )
67
+ logging.info(f"Hugging Face model preloaded on {'GPU' if device == 0 else 'CPU'}")
68
+ except Exception as e:
69
+ logging.error(f"Failed to preload model: {str(e)}")
70
+ raise e
71
+
72
+ # Fetch valid picklist values from Salesforce
73
+ def get_picklist_values(field_name):
74
+ if sf is None:
75
+ return []
76
+ try:
77
+ obj_desc = sf.SmartLog__c.describe()
78
+ for field in obj_desc['fields']:
79
+ if field['name'] == field_name:
80
+ return [value['value'] for value in field['picklistValues'] if value['active']]
81
+ return []
82
+ except Exception as e:
83
+ logging.error(f"Failed to fetch picklist values for {field_name}: {str(e)}")
84
+ return []
85
+
86
+ # Cache picklist values at startup
87
+ status_values = get_picklist_values('Status__c') or ["Active", "Inactive", "Pending"]
88
+ log_type_values = get_picklist_values('Log_Type__c') or ["Smart Log", "Cell Analysis", "UV Verification"]
89
+ logging.info(f"Valid Status__c values: {status_values}")
90
+ logging.info(f"Valid Log_Type__c values: {log_type_values}")
91
+
92
+ # Map invalid picklist values to valid ones
93
+ picklist_mapping = {
94
+ 'Status__c': {
95
+ 'normal': 'Active',
96
+ 'error': 'Inactive',
97
+ 'warning': 'Pending',
98
+ 'ok': 'Active',
99
+ 'failed': 'Inactive'
100
+ },
101
+ 'Log_Type__c': {
102
+ 'maint': 'Smart Log',
103
+ 'error': 'Cell Analysis',
104
+ 'ops': 'UV Verification',
105
+ 'maintenance': 'Smart Log',
106
+ 'cell': 'Cell Analysis',
107
+ 'uv': 'UV Verification'
108
+ }
109
+ }
110
+
111
+ # Fetch folder ID for "LabOps Reports"
112
+ def get_folder_id(folder_name):
113
+ if sf is None:
114
+ return None
115
+ try:
116
+ query = f"SELECT Id FROM Folder WHERE Name = '{folder_name}' AND Type = 'Report'"
117
+ result = sf.query(query)
118
+ if result['totalSize'] > 0:
119
+ folder_id = result['records'][0]['Id']
120
+ logging.info(f"Found folder ID for '{folder_name}': {folder_id}")
121
+ return folder_id
122
+ else:
123
+ logging.error(f"Folder '{folder_name}' not found in Salesforce.")
124
+ return None
125
+ except Exception as e:
126
+ logging.error(f"Failed to fetch folder ID for '{folder_name}': {str(e)}")
127
+ return None
128
+
129
+ # Cache the folder ID at startup
130
+ LABOPS_REPORTS_FOLDER_ID = get_folder_id('LabOps Reports')
131
+
132
+ # Send AMC reminder emails
133
+ def send_amc_reminder_emails(reminders_df):
134
+ if reminders_df.empty:
135
+ logging.info("No AMC reminders to send via email.")
136
+ return "No AMC reminder emails sent (no reminders found)."
137
+
138
+ try:
139
+ # Set up the SMTP server
140
+ server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
141
+ server.starttls()
142
+ server.login(SMTP_USERNAME, SMTP_PASSWORD)
143
+
144
+ email_results = []
145
+ for _, row in reminders_df.iterrows():
146
+ device_id = row['device_id']
147
+ amc_date = row['amc_date'].strftime('%Y-%m-%d')
148
 
149
+ # Create the email
150
+ msg = MIMEMultipart()
151
+ msg['From'] = EMAIL_FROM
152
+ msg['To'] = EMAIL_TO
153
+ msg['Subject'] = f"AMC Reminder for Device {device_id}"
154
+
155
+ body = f"""
156
+ Dear Sanjay Bhargav Neela,
157
+ This is a reminder that the Annual Maintenance Contract (AMC) for the following device is due:
158
+ - Device ID: {device_id}
159
+ - AMC Date: {amc_date}
160
+ Please schedule the maintenance at your earliest convenience.
161
+ Best regards,
162
+ Harish Kumar
163
+ LabOps Team
164
+ """
165
+ msg.attach(MIMEText(body, 'plain'))
166
+
167
+ # Send the email
168
+ server.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string())
169
+ logging.info(f"AMC reminder email sent for Device ID {device_id} to {EMAIL_TO}")
170
+ email_results.append(f"Sent AMC reminder for Device ID {device_id}")
171
+
172
+ server.quit()
173
+ return "\n".join(email_results) if email_results else "No emails sent."
174
+ except Exception as e:
175
+ logging.error(f"Failed to send AMC reminder emails: {str(e)}")
176
+ return f"Failed to send AMC reminder emails: {str(e)}"
177
+
178
+ # Simplified Salesforce report creation
179
+ def create_salesforce_reports(df):
180
+ if sf is None:
181
+ return "Salesforce connection not available."
182
+ if not LABOPS_REPORTS_FOLDER_ID:
183
+ return "Cannot create reports: 'LabOps Reports' folder not found in Salesforce."
184
+
185
+ try:
186
+ # Simplified Usage Report
187
+ usage_report_metadata = {
188
+ "reportMetadata": {
189
+ "name": f"SmartLog_Usage_Report_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
190
+ "reportType": "SmartLog__c",
191
+ "reportFormat": "SUMMARY",
192
+ "reportColumns": [
193
+ {"field": "Device_Id__c"},
194
+ {"field": "Usage_Hours__c", "aggregateTypes": ["Sum"]}
195
+ ],
196
+ "groupingsDown": [
197
+ {"field": "Device_Id__c", "sortOrder": "Asc"}
198
+ ],
199
+ "folderId": LABOPS_REPORTS_FOLDER_ID
200
+ }
201
+ }
202
+ usage_result = sf.restful('analytics/reports', method='POST', json=usage_report_metadata)
203
+ usage_report_id = usage_result['id']
204
+ logging.info(f"Usage Report created: {usage_report_id}")
205
+
206
+ # Simplified AMC Reminders Report
207
+ amc_report_metadata = {
208
+ "reportMetadata": {
209
+ "name": f"SmartLog_AMC_Reminders_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
210
+ "reportType": "SmartLog__c",
211
+ "reportFormat": "TABULAR",
212
+ "reportColumns": [
213
+ {"field": "Device_Id__c"},
214
+ {"field": "AMC_Date__c"}
215
+ ],
216
+ "folderId": LABOPS_REPORTS_FOLDER_ID
217
+ }
218
+ }
219
+ amc_result = sf.restful('analytics/reports', method='POST', json=amc_report_metadata)
220
+ amc_report_id = amc_result['id']
221
+ logging.info(f"AMC Reminders Report created: {amc_report_id}")
222
+
223
+ return f"Usage Report ID: {usage_report_id}, AMC Reminders Report ID: {amc_report_id}"
224
+ except Exception as e:
225
+ logging.error(f"Failed to create Salesforce reports: {str(e)}")
226
+ return f"Failed to create reports: {str(e)}"
227
+
228
+ # Save results to Salesforce SmartLog__c with better AMC_Date__c handling
229
+ def save_to_salesforce(df, summary, anomalies, amc_reminders, insights):
230
+ if sf is None:
231
+ return "Salesforce connection not available."
232
+ try:
233
+ records = []
234
+ current_date = datetime.now()
235
+ next_30_days = current_date + timedelta(days=30)
236
+ for _, row in df.head(100).iterrows():
237
+ # Validate and map picklist values
238
+ status = str(row['status'])
239
+ log_type = str(row['log_type'])
240
+
241
+ # Map Status__c
242
+ if status not in status_values:
243
+ status = picklist_mapping['Status__c'].get(status.lower(), status_values[0] if status_values else None)
244
+ if status is None:
245
+ logging.warning(f"Skipping record with invalid Status__c: {row['status']}")
246
+ continue
247
+
248
+ # Map Log_Type__c
249
+ if log_type not in log_type_values:
250
+ log_type = picklist_mapping['Log_Type__c'].get(log_type.lower(), log_type_values[0] if log_type_values else None)
251
+ if log_type is None:
252
+ logging.warning(f"Skipping record with invalid Log_Type__c: {row['log_type']}")
253
+ continue
254
+
255
+ # Handle AMC_Date__c with proper formatting
256
+ amc_date_str = None
257
+ if pd.notna(row['amc_date']):
258
+ try:
259
+ amc_date = pd.to_datetime(row['amc_date']).strftime('%Y-%m-%d')
260
+ amc_date_str = amc_date
261
+ amc_date_dt = datetime.strptime(amc_date, '%Y-%m-%d')
262
+ if status == "Active" and current_date.date() <= amc_date_dt.date() <= next_30_days.date():
263
+ logging.info(f"Record qualifies for AMC Reminders: Device ID {row['device_id']}, AMC Date {amc_date_str}")
264
+ except Exception as e:
265
+ logging.error(f"Failed to parse AMC Date for Device ID {row['device_id']}: {str(e)}")
266
+ amc_date_str = None
267
+
268
+ record = {
269
+ 'Device_Id__c': str(row['device_id'])[:50],
270
+ 'Log_Type__c': log_type,
271
+ 'Status__c': status,
272
+ 'Timestamp__c': row['timestamp'].isoformat() if pd.notna(row['timestamp']) else None,
273
+ 'Usage_Hours__c': float(row['usage_hours']) if pd.notna(row['usage_hours']) else 0.0,
274
+ 'Downtime__c': float(row['downtime']) if pd.notna(row['downtime']) else 0.0,
275
+ 'AMC_Date__c': amc_date_str
276
+ }
277
+ records.append(record)
278
+
279
+ # Bulk insert to reduce API calls
280
+ if records:
281
+ sf.bulk.SmartLog__c.insert(records)
282
+ logging.info(f"Saved {len(records)} records to Salesforce")
283
+ return f"Saved {len(records)} records to Salesforce."
284
+ except Exception as e:
285
+ logging.error(f"Failed to save to Salesforce: {str(e)}")
286
+ return f"Failed to save to Salesforce: {str(e)}"
287
+
288
+ # Summarize logs
289
  def summarize_logs(df):
290
+ try:
291
+ total_devices = df["device_id"].nunique()
292
+ most_used = df.groupby("device_id")["usage_hours"].sum().idxmax() if not df.empty else "N/A"
293
+ prompt = f"Maintenance logs: {total_devices} devices. Most used: {most_used}."
294
+ summary = summarizer(prompt, max_length=50, min_length=10, do_sample=False)[0]["summary_text"]
295
+ logging.info("Summary generated successfully")
296
+ return summary
297
+ except Exception as e:
298
+ logging.error(f"Summary generation failed: {str(e)}")
299
+ return f"Failed to generate summary: {str(e)}"
300
+
301
+ # Anomaly detection
302
+ def detect_anomalies(df):
303
+ try:
304
+ if "usage_hours" not in df.columns or "downtime" not in df.columns:
305
+ return "Anomaly detection requires 'usage_hours' and 'downtime' columns."
306
+ if len(df) > 1000:
307
+ df = df.sample(n=1000, random_state=42)
308
+ features = df[["usage_hours", "downtime"]].fillna(0)
309
+ iso_forest = IsolationForest(contamination=0.1, random_state=42, n_jobs=-1)
310
+ df["anomaly"] = iso_forest.fit_predict(features)
311
+ anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
312
+ if anomalies.empty:
313
+ return "No anomalies detected."
314
+ anomaly_lines = ["Detected Anomalies:"]
315
+ for _, row in anomalies.head(5).iterrows():
316
+ anomaly_lines.append(
317
+ f"- Device ID: {row['device_id']}, Usage Hours: {row['usage_hours']}, "
318
+ f"Downtime: {row['downtime']}, Timestamp: {row['timestamp']}"
319
+ )
320
+ return "\n".join(anomaly_lines)
321
+ except Exception as e:
322
+ logging.error(f"Anomaly detection failed: {str(e)}")
323
+ return f"Anomaly detection failed: {str(e)}"
324
+
325
+ # AMC reminders
326
+ def check_amc_reminders(df, current_date):
327
+ try:
328
+ if "device_id" not in df.columns or "amc_date" not in df.columns:
329
+ return "AMC reminders require 'device_id' and 'amc_date' columns.", pd.DataFrame()
330
+ df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
331
+ current_date = pd.to_datetime(current_date)
332
+ df["days_to_amc"] = (df["amc_date"] - current_date).dt.days
333
+ reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "amc_date"]]
334
+ if reminders.empty:
335
+ return "No AMC reminders due within the next 30 days.", reminders
336
+ reminder_lines = ["Upcoming AMC Reminders:"]
337
+ for _, row in reminders.head(5).iterrows():
338
+ reminder_lines.append(f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}")
339
+ return "\n".join(reminder_lines), reminders
340
+ except Exception as e:
341
+ logging.error(f"AMC reminder generation failed: {str(e)}")
342
+ return f"AMC reminder generation failed: {str(e)}", pd.DataFrame()
343
+
344
+ # Dashboard insights
345
+ def generate_dashboard_insights(df):
346
+ try:
347
+ total_devices = df["device_id"].nunique()
348
+ avg_usage = df["usage_hours"].mean() if "usage_hours" in df.columns else 0
349
+ prompt = f"Insights: {total_devices} devices, avg usage {avg_usage:.2f} hours."
350
+ insights = summarizer(prompt, max_length=50, min_length=10, do_sample=False)[0]["summary_text"]
351
+ return insights
352
+ except Exception as e:
353
+ logging.error(f"Dashboard insights generation failed: {str(e)}")
354
+ return f"Dashboard insights generation failed: {str(e)}"
355
+
356
+ # Create usage chart
357
+ def create_usage_chart(df):
358
+ try:
359
+ usage_data = df.groupby("device_id")["usage_hours"].sum().reset_index()
360
+ if len(usage_data) > 5:
361
+ usage_data = usage_data.nlargest(5, "usage_hours")
362
+ custom_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
363
+ fig = px.bar(
364
+ usage_data,
365
+ x="device_id",
366
+ y="usage_hours",
367
+ title="Usage Hours per Device",
368
+ labels={"device_id": "Device ID", "usage_hours": "Usage Hours"},
369
+ color="device_id",
370
+ color_discrete_sequence=custom_colors
371
+ )
372
+ fig.update_layout(
373
+ title_font_size=16,
374
+ margin=dict(l=20, r=20, t=40, b=20),
375
+ plot_bgcolor="white",
376
+ paper_bgcolor="white",
377
+ font=dict(size=12)
378
+ )
379
+ return fig
380
+ except Exception as e:
381
+ logging.error(f"Failed to create usage chart: {str(e)}")
382
+ return None
383
+
384
+ # Generate PDF content
385
+ def generate_pdf_content(summary, preview, anomalies, amc_reminders, insights, email_status):
386
+ if not reportlab_available:
387
+ return None
388
+ try:
389
+ pdf_path = f"analysis_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
390
+ doc = SimpleDocTemplate(pdf_path, pagesize=letter)
391
+ styles = getSampleStyleSheet()
392
+ story = []
393
+
394
+ def safe_paragraph(text, style):
395
+ return Paragraph(str(text).replace('\n', '<br/>'), style) if text else Paragraph("", style)
396
+
397
+ story.append(Paragraph("LabOps Log Analysis Report", styles['Title']))
398
+ story.append(Paragraph(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
399
+ story.append(Spacer(1, 12))
400
+
401
+ story.append(Paragraph("Summary Report", styles['Heading2']))
402
+ story.append(safe_paragraph(summary or "No summary available.", styles['Normal']))
403
+ story.append(Spacer(1, 12))
404
+
405
+ story.append(Paragraph("Log Preview", styles['Heading2']))
406
+ story.append(safe_paragraph(preview or "No preview available.", styles['Normal']))
407
+ story.append(Spacer(1, 12))
408
+
409
+ story.append(Paragraph("Anomaly Detection", styles['Heading2']))
410
+ story.append(safe_paragraph(anomalies or "No anomalies detected.", styles['Normal']))
411
+ story.append(Spacer(1, 12))
412
+
413
+ story.append(Paragraph("AMC Reminders", styles['Heading2']))
414
+ story.append(safe_paragraph(amc_reminders or "No AMC reminders.", styles['Normal']))
415
+ story.append(Spacer(1, 12))
416
+
417
+ story.append(Paragraph("Email Notification Status", styles['Heading2']))
418
+ story.append(safe_paragraph(email_status or "No emails sent.", styles['Normal']))
419
+ story.append(Spacer(1, 12))
420
+
421
+ story.append(Paragraph("Dashboard Insights", styles['Heading2']))
422
+ story.append(safe_paragraph(insights or "No insights generated.", styles['Normal']))
423
+
424
+ doc.build(story)
425
+ logging.info(f"PDF generated at {pdf_path}")
426
+ return pdf_path
427
+ except Exception as e:
428
+ logging.error(f"Failed to generate PDF: {str(e)}")
429
+ return None
430
+
431
+ # Main Gradio function with optimized performance
432
+ async def process_logs(file_obj):
433
+ try:
434
+ start_time = datetime.now()
435
+ if not file_obj:
436
+ return "No file uploaded.", "No data to preview.", None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None, "No Salesforce data saved.", "No report created.", "No emails sent."
437
+
438
+ file_name = file_obj.name
439
+ logging.info(f"Processing file: {file_name}")
440
+
441
+ if not file_name.endswith(".csv"):
442
+ return "Please upload a CSV file.", "", None, "", "", "", None, "", "", ""
443
+
444
+ required_columns = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
445
+ dtypes = {
446
+ "device_id": "string",
447
+ "log_type": "string",
448
+ "status": "string",
449
+ "usage_hours": "float32",
450
+ "downtime": "float32",
451
+ "amc_date": "string"
452
  }
453
+ df = pd.read_csv(file_obj, dtype=dtypes)
454
+ missing_columns = [col for col in required_columns if col not in df.columns]
455
+ if missing_columns:
456
+ return f"Missing columns: {missing_columns}", None, None, None, None, None, None, None, None, None
457
+ df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
458
+ df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
459
+ if df.empty:
460
+ return "No data available.", None, None, None, None, None, None, None, None, None
461
+
462
+ # Run tasks concurrently but simplify execution
463
+ with ThreadPoolExecutor(max_workers=4) as executor:
464
+ future_summary = executor.submit(summarize_logs, df)
465
+ future_anomalies = executor.submit(detect_anomalies, df)
466
+ future_amc = executor.submit(check_amc_reminders, df, datetime.now())
467
+ future_insights = executor.submit(generate_dashboard_insights, df)
468
+ future_chart = executor.submit(create_usage_chart, df)
469
+ future_reports = executor.submit(create_salesforce_reports, df)
470
+
471
+ summary = f"Step 1: Summary Report\n{future_summary.result()}"
472
+ anomalies = f"Anomaly Detection\n{future_anomalies.result()}"
473
+ amc_reminders, reminders_df = future_amc.result()
474
+ amc_reminders = f"AMC Reminders\n{amc_reminders}"
475
+ insights = f"Dashboard Insights (AI)\n{future_insights.result()}"
476
+ chart = future_chart.result()
477
+ report_result = future_reports.result()
478
+
479
+ preview_lines = ["Step 2: Log Preview (First 5 Rows)"]
480
+ for idx, row in df.head(5).iterrows():
481
+ preview_lines.append(
482
+ f"Row {idx + 1}: Device ID: {row['device_id']}, "
483
+ f"Log Type: {row['log_type']}, Status: {row['status']}, "
484
+ f"Timestamp: {row['timestamp']}, Usage Hours: {row['usage_hours']}, "
485
+ f"Downtime: {row['downtime']}, AMC Date: {row['amc_date']}"
486
+ )
487
+ preview = "\n".join(preview_lines)
488
+
489
+ salesforce_result = save_to_salesforce(df, summary, anomalies, amc_reminders, insights)
490
+ email_status = send_amc_reminder_emails(reminders_df)
491
+ pdf_file = generate_pdf_content(summary, preview, anomalies, amc_reminders, insights, email_status)
492
+
493
+ elapsed_time = (datetime.now() - start_time).total_seconds()
494
+ logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
495
+ return summary, preview, chart, anomalies, amc_reminders, insights, pdf_file, salesforce_result, report_result, email_status
496
+ except Exception as e:
497
+ logging.error(f"Failed to process file: {str(e)}")
498
+ return f"Error: {str(e)}", None, None, None, None, None, None, None, None, None
499
+
500
+ # Gradio Interface
501
+ try:
502
+ logging.info("Initializing Gradio interface...")
503
+ with gr.Blocks(css="""
504
+ .dashboard-container {border: 1px solid #e0e0e0; padding: 10px; border-radius: 5px;}
505
+ .dashboard-title {font-size: 24px; font-weight: bold; margin-bottom: 5px;}
506
+ .dashboard-section {margin-bottom: 20px;}
507
+ .dashboard-section h3 {font-size: 18px; margin-bottom: 2px;}
508
+ .dashboard-section p {margin: 1px 0; line-height: 1.2;}
509
+ .dashboard-section ul {margin: 2px 0; padding-left: 20px;}
510
+ """) as iface:
511
+ gr.Markdown("<h1>LabOps Log Analyzer Dashboard (Hugging Face AI)</h1>")
512
+ gr.Markdown("Upload a CSV file to analyze, generate Salesforce reports, and send AMC reminder emails.")
513
+
514
+ with gr.Row():
515
+ with gr.Column(scale=1):
516
+ file_input = gr.File(label="Upload Logs (CSV)", file_types=[".csv"])
517
+ submit_button = gr.Button("Analyze", variant="primary")
518
+
519
+ with gr.Column(scale=2):
520
+ with gr.Group(elem_classes="dashboard-container"):
521
+ gr.Markdown("<div class='dashboard-title'>Analysis Results</div>")
522
+
523
+ with gr.Group(elem_classes="dashboard-section"):
524
+ gr.Markdown("### Step 1: Summary Report")
525
+ summary_output = gr.Markdown()
526
+
527
+ with gr.Group(elem_classes="dashboard-section"):
528
+ gr.Markdown("### Step 2: Log Preview")
529
+ preview_output = gr.Markdown()
530
+
531
+ with gr.Group(elem_classes="dashboard-section"):
532
+ gr.Markdown("### Step 3: Usage Chart")
533
+ chart_output = gr.Plot()
534
+
535
+ with gr.Group(elem_classes="dashboard-section"):
536
+ gr.Markdown("### Step 4: Anomaly Detection")
537
+ anomaly_output = gr.Markdown()
538
+
539
+ with gr.Group(elem_classes="dashboard-section"):
540
+ gr.Markdown("### Step 5: AMC Reminders")
541
+ amc_output = gr.Markdown()
542
+
543
+ with gr.Group(elem_classes="dashboard-section"):
544
+ gr.Markdown("### Step 6: Insights (AI)")
545
+ insights_output = gr.Markdown()
546
+
547
+ with gr.Group(elem_classes="dashboard-section"):
548
+ gr.Markdown("### Step 7: Email Notification Status")
549
+ email_output = gr.Markdown()
550
+
551
+ with gr.Group(elem_classes="dashboard-section"):
552
+ gr.Markdown("### Salesforce Integration")
553
+ salesforce_output = gr.Markdown()
554
+
555
+ with gr.Group(elem_classes="dashboard-section"):
556
+ gr.Markdown("### Salesforce Reports")
557
+ report_output = gr.Markdown()
558
+
559
+ with gr.Group(elem_classes="dashboard-section"):
560
+ gr.Markdown("### Download Report")
561
+ pdf_output = gr.File(label="Download Analysis Report as PDF")
562
+
563
+ submit_button.click(
564
+ fn=process_logs,
565
+ inputs=[file_input],
566
+ outputs=[
567
+ summary_output,
568
+ preview_output,
569
+ chart_output,
570
+ anomaly_output,
571
+ amc_output,
572
+ insights_output,
573
+ pdf_output,
574
+ salesforce_output,
575
+ report_output,
576
+ email_output
577
+ ]
578
+ )
579
+
580
+ logging.info("Gradio interface initialized successfully")
581
+ except Exception as e:
582
+ logging.error(f"Failed to initialize Gradio interface: {str(e)}")
583
+ raise e
584
+
585
+ if __name__ == "__main__":
586
+ try:
587
+ logging.info("Launching Gradio interface...")
588
+ iface.launch(server_name="0.0.0.0", server_port=7860, debug=True, share=False)
589
+ logging.info("Gradio interface launched successfully")
590
+ except Exception as e:
591
+ logging.error(f"Failed to launch Gradio interface: {str(e)}")
592
+ print(f"Error launching app: {str(e)}")
593
+ raise e