lavanya121 commited on
Commit
5b5ee66
·
verified ·
1 Parent(s): be89a3b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -125
app.py CHANGED
@@ -3,115 +3,68 @@ import pandas as pd
3
  from datetime import datetime
4
  import logging
5
  import plotly.express as px
6
- from sklearn.ensemble import IsolationForest # For anomaly detection
7
  from transformers import pipeline
8
- import torch # For GPU availability check
9
- from simple_salesforce import Salesforce # For Salesforce connection
 
 
 
10
 
11
  # Configure logging for debugging
12
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
 
14
- # Salesforce credentials (replace with your actual credentials or use environment variables)
15
- SALESFORCE_USERNAME = "your_username"
16
- SALESFORCE_PASSWORD = "your_password"
17
- SALESFORCE_SECURITY_TOKEN = "your_security_token"
18
- SALESFORCE_DOMAIN = "login" # Use "test" for sandbox, "login" for production
19
-
20
- # Preload Hugging Face summarization model at startup
21
- logging.info("Preloading Hugging Face model...")
22
- try:
23
- device = 0 if torch.cuda.is_available() else -1
24
- summarizer = pipeline("summarization", model="sshleifer/distilbart-cnn-12-6", device=device) # Lighter model
25
- logging.info(f"Hugging Face model preloaded successfully on device: {'GPU' if device == 0 else 'CPU'}")
26
- except Exception as e:
27
- logging.error(f"Failed to preload model: {str(e)}")
28
- raise e
29
-
30
- # Connect to Salesforce
31
  def connect_to_salesforce():
32
  try:
33
  sf = Salesforce(
34
- username=SALESFORCE_USERNAME,
35
- password=SALESFORCE_PASSWORD,
36
- security_token=SALESFORCE_SECURITY_TOKEN,
37
- domain=SALESFORCE_DOMAIN
38
  )
39
- logging.info("Successfully connected to Salesforce")
40
  return sf
41
  except Exception as e:
42
  logging.error(f"Failed to connect to Salesforce: {str(e)}")
43
  raise e
44
 
45
- # Fetch data from Salesforce
46
- def fetch_salesforce_data(sf, row_limit=10000, progress=gr.Progress()):
47
- progress(0.05, "Fetching data from Salesforce...")
48
  try:
49
- # Query Salesforce for LabEquipmentLog__c object
50
- query = """
51
- SELECT Device_ID__c, Log_Type__c, Status__c, Timestamp__c,
52
- Usage_Hours__c, Downtime__c, AMC_Date__c
53
- FROM LabEquipmentLog__c
54
- LIMIT {}
55
- """.format(row_limit)
56
  result = sf.query_all(query)
57
- records = result["records"]
58
-
59
- # Convert to DataFrame
60
  df = pd.DataFrame(records)
 
61
  df = df.rename(columns={
62
- "Device_ID__c": "device_id",
63
- "Log_Type__c": "log_type",
64
- "Status__c": "status",
65
- "Timestamp__c": "timestamp",
66
- "Usage_Hours__c": "usage_hours",
67
- "Downtime__c": "downtime",
68
- "AMC_Date__c": "amc_date"
69
  })
70
-
71
- # Ensure proper data types
72
- df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
73
- df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
74
- df["usage_hours"] = df["usage_hours"].astype("float32", errors='ignore')
75
- df["downtime"] = df["downtime"].astype("float32", errors='ignore')
76
- df["device_id"] = df["device_id"].astype("string")
77
-
78
  logging.info(f"Fetched {len(df)} records from Salesforce")
79
  return df
80
  except Exception as e:
81
  logging.error(f"Failed to fetch Salesforce data: {str(e)}")
82
  raise e
83
 
84
- # Load data from CSV file
85
- def load_csv_data(file_obj, row_limit=10000, progress=gr.Progress()):
86
- progress(0.05, "Loading CSV file...")
87
- try:
88
- file_name = file_obj.name if hasattr(file_obj, 'name') else file_obj
89
- logging.info(f"Processing CSV file: {file_name}")
90
-
91
- if not file_name.endswith(".csv"):
92
- logging.error("Unsupported file format")
93
- raise ValueError("Unsupported file format. Please upload a CSV file.")
94
-
95
- usecols = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
96
- dtypes = {
97
- "device_id": "string",
98
- "log_type": "string",
99
- "status": "string",
100
- "usage_hours": "float32",
101
- "downtime": "float32",
102
- "amc_date": "string"
103
- }
104
- df = pd.read_csv(file_name, usecols=usecols, dtype=dtypes, nrows=row_limit)
105
-
106
- # Convert timestamps
107
- df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
108
- df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
109
-
110
- logging.info(f"File loaded successfully with {len(df)} rows (limited to {row_limit} rows)")
111
- return df
112
- except Exception as e:
113
- logging.error(f"Failed to load CSV: {str(e)}")
114
- raise e
115
 
116
  # Format summary prompt and generate report
117
  def summarize_logs(df, progress=gr.Progress()):
@@ -119,7 +72,6 @@ def summarize_logs(df, progress=gr.Progress()):
119
  try:
120
  total_devices = df["device_id"].nunique()
121
  most_used = df.groupby("device_id")["usage_hours"].sum().idxmax() if not df.empty else "N/A"
122
-
123
  prompt = f"Maintenance logs: {total_devices} devices. Most used: {most_used}."
124
  summary = summarizer(prompt, max_length=50, min_length=10, do_sample=False)[0]["summary_text"]
125
  logging.info("Summary generated successfully")
@@ -128,26 +80,22 @@ def summarize_logs(df, progress=gr.Progress()):
128
  logging.error(f"Summary generation failed: {str(e)}")
129
  return "Failed to generate summary."
130
 
131
- # Anomaly Detection using Isolation Forest with sampling for large datasets
132
  def detect_anomalies(df, progress=gr.Progress()):
133
  progress(0.4, "Detecting anomalies...")
134
  try:
135
  if "usage_hours" not in df.columns or "downtime" not in df.columns:
136
  logging.warning("Required columns for anomaly detection not found")
137
  return "Anomaly detection requires 'usage_hours' and 'downtime' columns."
138
-
139
  if len(df) > 5000:
140
  df = df.sample(n=5000, random_state=42)
141
  logging.info("Sampled data for anomaly detection to 5,000 rows")
142
-
143
  features = df[["usage_hours", "downtime"]].fillna(0)
144
  iso_forest = IsolationForest(contamination=0.1, random_state=42, n_jobs=-1)
145
  df["anomaly"] = iso_forest.fit_predict(features)
146
-
147
  anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
148
  if anomalies.empty:
149
  return "No anomalies detected."
150
-
151
  anomaly_lines = ["**Detected Anomalies:**"]
152
  for idx, row in anomalies.head(5).iterrows():
153
  anomaly_lines.append(f"- Device ID: {row['device_id']}, Usage Hours: {row['usage_hours']}, Downtime: {row['downtime']}, Timestamp: {row['timestamp']}")
@@ -165,16 +113,12 @@ def check_amc_reminders(df, current_date, progress=gr.Progress()):
165
  if "device_id" not in df.columns or "amc_date" not in df.columns:
166
  logging.warning("Required columns for AMC reminders not found")
167
  return "AMC reminders require 'device_id' and 'amc_date' columns."
168
-
169
  df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
170
  current_date = pd.to_datetime(current_date)
171
-
172
  df["days_to_amc"] = (df["amc_date"] - current_date).dt.days
173
  reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "amc_date"]]
174
-
175
  if reminders.empty:
176
  return "No AMC reminders due within the next 30 days."
177
-
178
  reminder_lines = ["**Upcoming AMC Reminders:**"]
179
  for idx, row in reminders.head(5).iterrows():
180
  reminder_lines.append(f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}")
@@ -185,7 +129,7 @@ def check_amc_reminders(df, current_date, progress=gr.Progress()):
185
  logging.error(f"AMC reminder generation failed: {str(e)}")
186
  return f"AMC reminder generation failed: {str(e)}"
187
 
188
- # Dashboard Insights (AI-generated executive-level insights)
189
  def generate_dashboard_insights(df, progress=gr.Progress()):
190
  progress(0.8, "Generating dashboard insights...")
191
  try:
@@ -207,7 +151,6 @@ def create_usage_chart(df, progress=gr.Progress()):
207
  if len(usage_data) > 5:
208
  usage_data = usage_data.nlargest(5, "usage_hours")
209
  logging.info("Limited chart data to top 5 devices")
210
-
211
  custom_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
212
  fig = px.bar(
213
  usage_data,
@@ -230,22 +173,68 @@ def create_usage_chart(df, progress=gr.Progress()):
230
  logging.error(f"Failed to create usage chart: {str(e)}")
231
  return None
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  # Main Gradio function
234
- async def process_logs(file_obj=None, progress=gr.Progress()):
235
  try:
236
- progress(0, "Starting data processing...")
 
 
 
237
 
238
- # Load data: prioritize CSV if uploaded, otherwise fetch from Salesforce
239
- if file_obj:
240
- df = load_csv_data(file_obj, row_limit=10000, progress=progress)
241
- else:
242
- progress(0.05, "No CSV uploaded, fetching from Salesforce...")
243
- sf = connect_to_salesforce()
244
- df = fetch_salesforce_data(sf, row_limit=10000, progress=progress)
245
 
246
  if df.empty:
247
- logging.warning("No data available")
248
- return "No data available.", "No data to preview.", None, "No anomalies detected.", "No AMC reminders.", "No insights generated."
249
 
250
  # Step 1: Summary Report
251
  progress(0.2, "Generating summary...")
@@ -256,12 +245,13 @@ async def process_logs(file_obj=None, progress=gr.Progress()):
256
  if not df.empty:
257
  preview_lines = ["**Step 2: Log Preview (First 5 Rows)**"]
258
  for idx, row in df.head().iterrows():
259
- preview_lines.append(f"**Row {idx + 1}:** Device ID: {row['device_id']}, Timestamp: {row['timestamp']}, Usage Hours: {row['usage_hours']}, Downtime: {row['downtime']}, AMC Date: {row['amc_date']}, Log Type: {row['log_type']}, Status: {row['status']}")
260
  preview = "\n".join(preview_lines)
261
  else:
262
  preview = "**Step 2: Log Preview** \nNo data available."
263
 
264
  # Step 3: Usage Chart
 
265
  chart = create_usage_chart(df, progress)
266
 
267
  # Step 4: Anomaly Detection
@@ -273,13 +263,23 @@ async def process_logs(file_obj=None, progress=gr.Progress()):
273
  # Step 6: Dashboard Insights
274
  insights = f"**Step 5: Dashboard Insights (AI)** \n{generate_dashboard_insights(df, progress)}"
275
 
 
 
 
 
 
 
 
 
 
 
276
  progress(1.0, "Processing complete!")
277
- return summary, preview, chart, anomalies, amc_reminders, insights
278
  except Exception as e:
279
  logging.error(f"Failed to process data: {str(e)}")
280
- return f"Failed to process data: {str(e)}", None, None, None, None, None
281
 
282
- # Gradio Interface with Step-by-Step Layout
283
  try:
284
  logging.info("Initializing Gradio Blocks interface...")
285
  with gr.Blocks(css="""
@@ -291,52 +291,56 @@ try:
291
  .dashboard-section li {margin: 1px 0; line-height: 1.2;}
292
  .dashboard-section ul {margin: 2px 0; padding-left: 20px;}
293
  """) as iface:
294
- gr.Markdown("<h1>LabOps Log Analyzer Dashboard (Salesforce + Hugging Face AI)</h1>")
295
- gr.Markdown("Upload a CSV file or fetch lab equipment logs from Salesforce to analyze usage.")
296
-
297
  with gr.Row():
298
  with gr.Column(scale=1):
299
- file_input = gr.File(label="Upload Logs (CSV)", file_types=[".csv"])
300
- submit_button = gr.Button("Analyze Data", variant="primary")
301
-
302
  with gr.Column(scale=2):
303
  with gr.Group(elem_classes="dashboard-container"):
304
  gr.Markdown("<div class='dashboard-title'>Analysis Results (Step-by-Step)</div>")
305
-
306
  # Step 1: Summary Report
307
  with gr.Group(elem_classes="dashboard-section"):
308
  gr.Markdown("### Step 1: Summary Report")
309
  summary_output = gr.Markdown()
310
-
311
  # Step 2: Log Preview
312
  with gr.Group(elem_classes="dashboard-section"):
313
  gr.Markdown("### Step 2: Log Preview")
314
  preview_output = gr.Markdown()
315
-
316
  # Step 3: Usage Chart
317
  with gr.Group(elem_classes="dashboard-section"):
318
  gr.Markdown("### Step 3: Usage Chart")
319
  chart_output = gr.Plot()
320
-
321
  # Step 4: Anomaly Detection
322
  with gr.Group(elem_classes="dashboard-section"):
323
  gr.Markdown("### Step 4: Anomaly Detection")
324
  anomaly_output = gr.Markdown()
325
-
326
  # Step 5: AMC Reminders
327
  with gr.Group(elem_classes="dashboard-section"):
328
  gr.Markdown("### Step 5: AMC Reminders")
329
  amc_output = gr.Markdown()
330
-
331
  # Step 6: Dashboard Insights
332
  with gr.Group(elem_classes="dashboard-section"):
333
  gr.Markdown("### Step 6: Dashboard Insights (AI)")
334
  insights_output = gr.Markdown()
 
 
 
 
 
335
 
336
  submit_button.click(
337
  fn=process_logs,
338
- inputs=[file_input],
339
- outputs=[summary_output, preview_output, chart_output, anomaly_output, amc_output, insights_output]
340
  )
341
 
342
  logging.info("Gradio interface initialized successfully")
 
3
  from datetime import datetime
4
  import logging
5
  import plotly.express as px
6
+ from sklearn.ensemble import IsolationForest
7
  from transformers import pipeline
8
+ import torch
9
+ from simple_salesforce import Salesforce
10
+ import io
11
+ import base64
12
+ from contextlib import redirect_stdout
13
 
14
  # Configure logging for debugging
15
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
 
17
+ # Salesforce connection configuration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def connect_to_salesforce():
19
  try:
20
  sf = Salesforce(
21
+ username='your_salesforce_username',
22
+ password='your_salesforce_password',
23
+ security_token='your_security_token',
24
+ instance_url='your_instance_url'
25
  )
26
+ logging.info("Connected to Salesforce successfully")
27
  return sf
28
  except Exception as e:
29
  logging.error(f"Failed to connect to Salesforce: {str(e)}")
30
  raise e
31
 
32
+ # Fetch data from Salesforce Smart_Log__c object
33
+ def fetch_salesforce_data(sf, row_limit=10000):
 
34
  try:
35
+ query = f"""
36
+ SELECT Device_ID__c, Log_Type__c, Status__c, Timestamp__c, Usage_Hours__c, Downtime__c, AMC_Date__c
37
+ FROM Smart_Log__c
38
+ LIMIT {row_limit}
39
+ """
 
 
40
  result = sf.query_all(query)
41
+ records = result['records']
 
 
42
  df = pd.DataFrame(records)
43
+ # Rename columns to match existing code
44
  df = df.rename(columns={
45
+ 'Device_ID__c': 'device_id',
46
+ 'Log_Type__c': 'log_type',
47
+ 'Status__c': 'status',
48
+ 'Timestamp__c': 'timestamp',
49
+ 'Usage_Hours__c': 'usage_hours',
50
+ 'Downtime__c': 'downtime',
51
+ 'AMC_Date__c': 'amc_date'
52
  })
 
 
 
 
 
 
 
 
53
  logging.info(f"Fetched {len(df)} records from Salesforce")
54
  return df
55
  except Exception as e:
56
  logging.error(f"Failed to fetch Salesforce data: {str(e)}")
57
  raise e
58
 
59
+ # Preload Hugging Face summarization model at startup
60
+ logging.info("Preloading Hugging Face model...")
61
+ try:
62
+ device = 0 if torch.cuda.is_available() else -1
63
+ summarizer = pipeline("summarization", model="sshleifer/distilbart-cnn-12-6", device=device)
64
+ logging.info(f"Hugging Face model preloaded successfully on device: {'GPU' if device == 0 else 'CPU'}")
65
+ except Exception as e:
66
+ logging.error(f"Failed to preload model: {str(e)}")
67
+ raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  # Format summary prompt and generate report
70
  def summarize_logs(df, progress=gr.Progress()):
 
72
  try:
73
  total_devices = df["device_id"].nunique()
74
  most_used = df.groupby("device_id")["usage_hours"].sum().idxmax() if not df.empty else "N/A"
 
75
  prompt = f"Maintenance logs: {total_devices} devices. Most used: {most_used}."
76
  summary = summarizer(prompt, max_length=50, min_length=10, do_sample=False)[0]["summary_text"]
77
  logging.info("Summary generated successfully")
 
80
  logging.error(f"Summary generation failed: {str(e)}")
81
  return "Failed to generate summary."
82
 
83
+ # Anomaly Detection using Isolation Forest
84
  def detect_anomalies(df, progress=gr.Progress()):
85
  progress(0.4, "Detecting anomalies...")
86
  try:
87
  if "usage_hours" not in df.columns or "downtime" not in df.columns:
88
  logging.warning("Required columns for anomaly detection not found")
89
  return "Anomaly detection requires 'usage_hours' and 'downtime' columns."
 
90
  if len(df) > 5000:
91
  df = df.sample(n=5000, random_state=42)
92
  logging.info("Sampled data for anomaly detection to 5,000 rows")
 
93
  features = df[["usage_hours", "downtime"]].fillna(0)
94
  iso_forest = IsolationForest(contamination=0.1, random_state=42, n_jobs=-1)
95
  df["anomaly"] = iso_forest.fit_predict(features)
 
96
  anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
97
  if anomalies.empty:
98
  return "No anomalies detected."
 
99
  anomaly_lines = ["**Detected Anomalies:**"]
100
  for idx, row in anomalies.head(5).iterrows():
101
  anomaly_lines.append(f"- Device ID: {row['device_id']}, Usage Hours: {row['usage_hours']}, Downtime: {row['downtime']}, Timestamp: {row['timestamp']}")
 
113
  if "device_id" not in df.columns or "amc_date" not in df.columns:
114
  logging.warning("Required columns for AMC reminders not found")
115
  return "AMC reminders require 'device_id' and 'amc_date' columns."
 
116
  df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
117
  current_date = pd.to_datetime(current_date)
 
118
  df["days_to_amc"] = (df["amc_date"] - current_date).dt.days
119
  reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "amc_date"]]
 
120
  if reminders.empty:
121
  return "No AMC reminders due within the next 30 days."
 
122
  reminder_lines = ["**Upcoming AMC Reminders:**"]
123
  for idx, row in reminders.head(5).iterrows():
124
  reminder_lines.append(f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}")
 
129
  logging.error(f"AMC reminder generation failed: {str(e)}")
130
  return f"AMC reminder generation failed: {str(e)}"
131
 
132
+ # Dashboard Insights (AI-generated)
133
  def generate_dashboard_insights(df, progress=gr.Progress()):
134
  progress(0.8, "Generating dashboard insights...")
135
  try:
 
151
  if len(usage_data) > 5:
152
  usage_data = usage_data.nlargest(5, "usage_hours")
153
  logging.info("Limited chart data to top 5 devices")
 
154
  custom_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
155
  fig = px.bar(
156
  usage_data,
 
173
  logging.error(f"Failed to create usage chart: {str(e)}")
174
  return None
175
 
176
+ # Generate LaTeX PDF content
177
+ def generate_pdf_content(summary, preview, anomalies, amc_reminders, insights):
178
+ latex_content = r"""
179
+ \documentclass[a4paper,11pt]{article}
180
+ \usepackage[utf8]{inputenc}
181
+ \usepackage[T1]{fontenc}
182
+ \usepackage{lmodern}
183
+ \usepackage{geometry}
184
+ \geometry{margin=1in}
185
+ \usepackage{enumitem}
186
+ \usepackage{parskip}
187
+ \setlength{\parskip}{0.5em}
188
+ \setlength{\parindent}{0em}
189
+ \usepackage{titling}
190
+ \usepackage{xcolor}
191
+ \usepackage{noto}
192
+
193
+ \title{LabOps Log Analysis Report}
194
+ \author{AI-Powered Analysis}
195
+ \date{\today}
196
+
197
+ \begin{document}
198
+
199
+ \maketitle
200
+
201
+ \section*{Summary Report}
202
+ """ + summary.replace("**", r"\textbf{").replace(":", r":}").replace("\n", r"\\") + r"""
203
+
204
+ \section*{Log Preview}
205
+ """ + preview.replace("**", r"\textbf{").replace(":", r":}").replace("\n", r"\\") + r"""
206
+
207
+ \section*{Anomaly Detection}
208
+ """ + anomalies.replace("**", r"\textbf{").replace(":", r":}").replace("\n", r"\\") + r"""
209
+
210
+ \section*{AMC Reminders}
211
+ """ + amc_reminders.replace("**", r"\textbf{").replace(":", r":}").replace("\n", r"\\") + r"""
212
+
213
+ \section*{Dashboard Insights}
214
+ """ + insights.replace("**", r"\textbf{").replace(":", r":}").replace("\n", r"\\") + r"""
215
+
216
+ \end{document}
217
+ """
218
+ return latex_content
219
+
220
  # Main Gradio function
221
+ async def process_logs(row_limit=10000, progress=gr.Progress()):
222
  try:
223
+ progress(0, "Connecting to Salesforce...")
224
+ sf = connect_to_salesforce()
225
+ progress(0.05, "Fetching data from Salesforce...")
226
+ df = fetch_salesforce_data(sf, row_limit)
227
 
228
+ progress(0.1, "Converting timestamps...")
229
+ try:
230
+ df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
231
+ except Exception as e:
232
+ logging.error(f"Date conversion failed: {str(e)}")
233
+ return f"Failed to convert timestamp to datetime: {str(e)}", None, None, None, None, None, None
 
234
 
235
  if df.empty:
236
+ logging.warning("No data retrieved from Salesforce")
237
+ return "No data available from Salesforce.", "No data to preview.", None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None
238
 
239
  # Step 1: Summary Report
240
  progress(0.2, "Generating summary...")
 
245
  if not df.empty:
246
  preview_lines = ["**Step 2: Log Preview (First 5 Rows)**"]
247
  for idx, row in df.head().iterrows():
248
+ preview_lines.append(f"**Row {idx + 1}:** Device ID: {row['device_id']}, Timestamp: {row['timestamp']}, Usage Hours: {row['usage_hours']}, Downtime: {row['downtime']}, AMC Date: {row['amc_date']}")
249
  preview = "\n".join(preview_lines)
250
  else:
251
  preview = "**Step 2: Log Preview** \nNo data available."
252
 
253
  # Step 3: Usage Chart
254
+ progress(0.9, "Creating usage chart...")
255
  chart = create_usage_chart(df, progress)
256
 
257
  # Step 4: Anomaly Detection
 
263
  # Step 6: Dashboard Insights
264
  insights = f"**Step 5: Dashboard Insights (AI)** \n{generate_dashboard_insights(df, progress)}"
265
 
266
+ # Generate PDF
267
+ progress(0.95, "Generating PDF report...")
268
+ try:
269
+ latex_content = generate_pdf_content(summary, preview, anomalies, amc_reminders, insights)
270
+ pdf_file = gr.File(value=latex_content, file_types=[".pdf"], label="Download Analysis Report")
271
+ logging.info("PDF content generated successfully")
272
+ except Exception as e:
273
+ logging.error(f"Failed to generate PDF: {str(e)}")
274
+ pdf_file = None
275
+
276
  progress(1.0, "Processing complete!")
277
+ return summary, preview, chart, anomalies, amc_reminders, insights, pdf_file
278
  except Exception as e:
279
  logging.error(f"Failed to process data: {str(e)}")
280
+ return f"Failed to process data: {str(e)}", None, None, None, None, None, None
281
 
282
+ # Gradio Interface
283
  try:
284
  logging.info("Initializing Gradio Blocks interface...")
285
  with gr.Blocks(css="""
 
291
  .dashboard-section li {margin: 1px 0; line-height: 1.2;}
292
  .dashboard-section ul {margin: 2px 0; padding-left: 20px;}
293
  """) as iface:
294
+ gr.Markdown("<h1>LabOps Log Analyzer Dashboard (Hugging Face AI + Salesforce)</h1>")
295
+ gr.Markdown("Analyze equipment logs fetched from Salesforce Smart_Log__c object.")
296
+
297
  with gr.Row():
298
  with gr.Column(scale=1):
299
+ row_limit = gr.Number(label="Row Limit (Max Records to Fetch)", value=10000, precision=0)
300
+ submit_button = gr.Button("Fetch and Analyze", variant="primary")
 
301
  with gr.Column(scale=2):
302
  with gr.Group(elem_classes="dashboard-container"):
303
  gr.Markdown("<div class='dashboard-title'>Analysis Results (Step-by-Step)</div>")
304
+
305
  # Step 1: Summary Report
306
  with gr.Group(elem_classes="dashboard-section"):
307
  gr.Markdown("### Step 1: Summary Report")
308
  summary_output = gr.Markdown()
309
+
310
  # Step 2: Log Preview
311
  with gr.Group(elem_classes="dashboard-section"):
312
  gr.Markdown("### Step 2: Log Preview")
313
  preview_output = gr.Markdown()
314
+
315
  # Step 3: Usage Chart
316
  with gr.Group(elem_classes="dashboard-section"):
317
  gr.Markdown("### Step 3: Usage Chart")
318
  chart_output = gr.Plot()
319
+
320
  # Step 4: Anomaly Detection
321
  with gr.Group(elem_classes="dashboard-section"):
322
  gr.Markdown("### Step 4: Anomaly Detection")
323
  anomaly_output = gr.Markdown()
324
+
325
  # Step 5: AMC Reminders
326
  with gr.Group(elem_classes="dashboard-section"):
327
  gr.Markdown("### Step 5: AMC Reminders")
328
  amc_output = gr.Markdown()
329
+
330
  # Step 6: Dashboard Insights
331
  with gr.Group(elem_classes="dashboard-section"):
332
  gr.Markdown("### Step 6: Dashboard Insights (AI)")
333
  insights_output = gr.Markdown()
334
+
335
+ # PDF Download
336
+ with gr.Group(elem_classes="dashboard-section"):
337
+ gr.Markdown("### Download Report")
338
+ pdf_output = gr.File(label="Download Analysis Report as PDF")
339
 
340
  submit_button.click(
341
  fn=process_logs,
342
+ inputs=[row_limit],
343
+ outputs=[summary_output, preview_output, chart_output, anomaly_output, amc_output, insights_output, pdf_output]
344
  )
345
 
346
  logging.info("Gradio interface initialized successfully")