RathodHarish commited on
Commit
d2060b3
·
verified ·
1 Parent(s): ff91e98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -64
app.py CHANGED
@@ -13,19 +13,20 @@ import numpy as np
13
  import torch
14
  import tempfile
15
 
16
- # Set up logging
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
- # Log dependency versions
21
  logger.info(f"NumPy version: {np.__version__}")
22
  logger.info(f"Pandas version: {pd.__version__}")
23
  logger.info(f"PyTorch version: {torch.__version__}")
24
  logger.info(f"Gradio version: {gr.__version__}")
25
 
26
- # Store logs for UI display
27
  log_messages = []
28
 
 
29
  class UILogHandler(logging.Handler):
30
  def emit(self, record):
31
  log_entry = self.format(record)
@@ -35,12 +36,12 @@ ui_handler = UILogHandler()
35
  ui_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
36
  logger.addHandler(ui_handler)
37
 
38
- # Initialize Hugging Face summarization pipeline
39
  try:
40
  summarizer = pipeline(
41
  "summarization",
42
  model="facebook/bart-large-cnn",
43
- device=0 if torch.cuda.is_available() else -1,
44
  framework="pt"
45
  )
46
  logger.info("Hugging Face summarization pipeline initialized")
@@ -48,29 +49,32 @@ except Exception as e:
48
  logger.error(f"Failed to initialize Hugging Face pipeline: {e}")
49
  summarizer = None
50
 
51
- # Global variable to store logs data
52
  logs = pd.DataFrame()
53
 
54
- # Load logs from uploaded CSV file
55
  def load_logs(logs_file):
56
  global logs
57
  try:
58
  if logs_file is None:
59
  logger.error("No logs CSV file uploaded")
60
  return pd.DataFrame()
61
- # Read CSV from uploaded file
62
  with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
63
  tmp.write(logs_file.read())
64
  tmp_path = tmp.name
65
  logs = pd.read_csv(tmp_path)
66
- os.unlink(tmp_path) # Clean up temporary file
67
-
68
- # Validate required columns
69
  required_columns = ['device_id', 'lab_id', 'timestamp', 'status', 'usage_count', 'type']
70
  if not all(col in logs.columns for col in required_columns):
71
  logger.error(f"Missing required columns in logs: {required_columns}")
72
  return pd.DataFrame()
73
-
 
 
 
 
74
  logs['timestamp'] = pd.to_datetime(logs['timestamp'], errors='coerce')
75
  if logs['timestamp'].isna().any():
76
  logger.error("Invalid timestamps detected in logs")
@@ -81,11 +85,10 @@ def load_logs(logs_file):
81
  logger.error(f"Error loading logs: {e}")
82
  return pd.DataFrame()
83
 
84
- # Update dropdowns and validate uploads
85
  def update_dropdowns(logs_file):
86
  global logs
87
  logs = load_logs(logs_file)
88
-
89
  if logs.empty:
90
  return (
91
  gr.Dropdown(choices=[], value=None, label="Select Lab"),
@@ -93,11 +96,10 @@ def update_dropdowns(logs_file):
93
  "<p style='color: red;'>Please upload a valid logs CSV file to proceed.</p>",
94
  False # Disable Generate Dashboard button
95
  )
96
-
97
- lab_choices = sorted(logs['lab_id'].unique().tolist()) # Sort for better UX
98
- equipment_choices = sorted(logs['type'].unique().tolist()) # Sort for better UX
99
  upload_status = "<p style='color: green;'>Logs loaded successfully.</p>"
100
-
101
  return (
102
  gr.Dropdown(choices=lab_choices, value=lab_choices[0] if lab_choices else None, label="Select Lab"),
103
  gr.Dropdown(choices=equipment_choices, value=equipment_choices[0] if equipment_choices else None, label="Select Equipment Type"),
@@ -105,7 +107,7 @@ def update_dropdowns(logs_file):
105
  True # Enable Generate Dashboard button
106
  )
107
 
108
- # Anomaly detection
109
  def detect_anomalies(logs):
110
  try:
111
  if logs.empty:
@@ -120,14 +122,15 @@ def detect_anomalies(logs):
120
  logger.error(f"Error in anomaly detection: {e}")
121
  return logs
122
 
123
- # Generate text summary using Hugging Face
124
  def generate_text_summary(logs):
125
  if summarizer is None or logs.empty:
126
  return "No summary available due to missing data or model initialization."
127
  try:
128
  log_text = "\n".join(
129
  f"Device {row['device_id']} in lab {row['lab_id']} on {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')} "
130
- f"was {row['status']} with usage count {row['usage_count']} (Type: {row['type']})."
 
131
  for _, row in logs.iterrows()
132
  )
133
  summary = summarizer(log_text, max_length=150, min_length=40, do_sample=False)[0]['summary_text']
@@ -137,28 +140,28 @@ def generate_text_summary(logs):
137
  logger.error(f"Error generating text summary: {e}")
138
  return f"Error generating summary: {str(e)}"
139
 
140
- # Generate executive insights
141
  def generate_executive_insights(logs, anomalies):
142
  if summarizer is None or logs.empty:
143
  return "No executive insights available due to missing data or model initialization."
144
  try:
145
  log_text = "\n".join(
146
  f"Device {row['device_id']} in lab {row['lab_id']} on {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')} "
147
- f"was {row['status']} with usage count {row['usage_count']} (Type: {row['type']})."
 
148
  for _, row in logs.iterrows()
149
  )
150
  anomaly_text = "\n".join(
151
- f"Device {row['device_id']} showed anomalous usage count {row['usage_count']} on {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}."
 
152
  for _, row in anomalies.iterrows()
153
  ) if not anomalies.empty else "No anomalies detected."
154
-
155
  prompt = (
156
  f"Summarize the following lab operations data into concise executive insights:\n\n"
157
  f"Logs:\n{log_text}\n\n"
158
  f"Anomalies:\n{anomaly_text}\n\n"
159
- f"Provide high-level insights for lab managers, focusing on operational status and issues."
160
  )
161
-
162
  insights = summarizer(prompt, max_length=200, min_length=50, do_sample=False)[0]['summary_text']
163
  logger.info("Executive insights generated successfully")
164
  return insights
@@ -166,7 +169,7 @@ def generate_executive_insights(logs, anomalies):
166
  logger.error(f"Error generating executive insights: {e}")
167
  return f"Error generating insights: {str(e)}"
168
 
169
- # Generate PDF report with summary and insights
170
  def generate_pdf(lab, equipment_type, filtered_logs, summary, insights):
171
  try:
172
  pdf_file = f"labops_report_{lab}_{equipment_type}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
@@ -200,13 +203,14 @@ def generate_pdf(lab, equipment_type, filtered_logs, summary, insights):
200
  c.showPage()
201
  y = 750
202
 
203
- # Add device status
204
  c.setFont("Helvetica-Bold", 14)
205
  c.drawString(100, y, "Device Status Summary")
206
  c.setFont("Helvetica", 12)
207
  y -= 20
208
  for _, row in filtered_logs.iterrows():
209
- c.drawString(100, y, f"Device: {row['device_id']}, Status: {row['status']}, Usage: {row['usage_count']}")
 
210
  y -= 20
211
  if y < 100:
212
  c.showPage()
@@ -222,7 +226,7 @@ def generate_pdf(lab, equipment_type, filtered_logs, summary, insights):
222
  logger.error(f"Error generating PDF: {e}")
223
  return None
224
 
225
- # Validate date format
226
  def validate_date(date_str):
227
  pattern = r'^\d{4}-\d{2}-\d{2}$'
228
  if not isinstance(date_str, str) or not re.match(pattern, date_str):
@@ -233,11 +237,11 @@ def validate_date(date_str):
233
  except ValueError:
234
  return False
235
 
236
- # Dashboard rendering
237
  def render_dashboard(lab, equipment_type, start_date, end_date):
238
  try:
239
- logger.info(f"Rendering dashboard with filters: lab={lab}, equipment_type={equipment_type}, start_date={start_date}, end_date={end_date}")
240
-
241
  # Validate inputs
242
  if not lab or not equipment_type:
243
  return (
@@ -246,29 +250,24 @@ def render_dashboard(lab, equipment_type, start_date, end_date):
246
  None, "No summary available.", "No insights available.",
247
  "\n".join(log_messages[-10:]) or "No logs available."
248
  )
249
-
250
- # Validate dates
251
  if not validate_date(start_date):
252
  logger.warning(f"Invalid start_date format: {start_date}. Using default 2025-05-01.")
253
  start_date = "2025-05-01"
254
  if not validate_date(end_date):
255
  logger.warning(f"Invalid end_date format: {end_date}. Using default 2025-05-30.")
256
  end_date = "2025-05-30"
257
-
258
  start_dt = pd.to_datetime(start_date, format='%Y-%m-%d')
259
  end_dt = pd.to_datetime(end_date, format='%Y-%m-%d')
260
-
261
  if start_dt > end_dt:
262
  logger.warning("start_date is after end_date. Swapping dates.")
263
  start_dt, end_dt = end_dt, start_dt
264
-
265
- # Filter logs
266
  filtered_logs = logs[(logs['lab_id'] == lab) & (logs['type'] == equipment_type)]
267
  filtered_logs = filtered_logs[
268
  (filtered_logs['timestamp'] >= start_dt) &
269
  (filtered_logs['timestamp'] <= end_dt)
270
  ]
271
-
272
  if filtered_logs.empty:
273
  logger.warning("No data available for the selected filters")
274
  return (
@@ -277,11 +276,9 @@ def render_dashboard(lab, equipment_type, start_date, end_date):
277
  None, "No summary available.", "No insights available.",
278
  "\n".join(log_messages[-10:]) or "No logs available."
279
  )
280
-
281
  # Apply anomaly detection
282
  filtered_logs = detect_anomalies(filtered_logs)
283
-
284
- # Device Cards
285
  device_cards = "<div style='display: flex; flex-wrap: wrap; gap: 20px;'>"
286
  for _, row in filtered_logs.iterrows():
287
  status_color = "green" if row['status'] == "active" else "red"
@@ -290,12 +287,12 @@ def render_dashboard(lab, equipment_type, start_date, end_date):
290
  <h3 style='margin: 0; font-size: 18px;'>Device: {row['device_id']}</h3>
291
  <p style='color: {status_color};'>Status: {row['status'].capitalize()}</p>
292
  <p>Usage Count: {row['usage_count']}</p>
 
293
  <p>Last Log: {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}</p>
294
  </div>
295
  """
296
  device_cards += "</div>"
297
-
298
- # Usage Trend Chart
299
  fig_usage = px.line(
300
  filtered_logs,
301
  x='timestamp',
@@ -308,8 +305,7 @@ def render_dashboard(lab, equipment_type, start_date, end_date):
308
  yaxis_title="Usage Count",
309
  margin=dict(l=20, r=20, t=40, b=20)
310
  )
311
-
312
- # Uptime Chart
313
  uptime = filtered_logs.groupby('device_id')['status'].apply(lambda x: (x == 'active').mean() * 100)
314
  fig_uptime = px.bar(
315
  uptime,
@@ -322,25 +318,20 @@ def render_dashboard(lab, equipment_type, start_date, end_date):
322
  yaxis_title="Uptime %",
323
  margin=dict(l=20, r=20, t=40, b=20)
324
  )
325
-
326
- # Anomaly Alerts
327
  anomalies = filtered_logs[filtered_logs['anomaly'] == -1] if 'anomaly' in filtered_logs.columns else pd.DataFrame()
328
- anomaly_table = anomalies[['device_id', 'timestamp', 'usage_count']].to_html(
329
  index=False,
330
  classes="table table-striped",
331
  border=0
332
  ) if not anomalies.empty else "<p>No anomalies detected.</p>"
333
-
334
  # Generate summary and insights
335
  summary = generate_text_summary(filtered_logs)
336
  insights = generate_executive_insights(filtered_logs, anomalies)
337
-
338
- # PDF Download
339
  pdf_data = generate_pdf(lab, equipment_type, filtered_logs, summary, insights)
340
-
341
- # Logs for display
342
  logs_output = "\n".join(log_messages[-10:]) or "No logs available."
343
-
344
  logger.info("Dashboard rendered successfully")
345
  return (
346
  device_cards,
@@ -366,7 +357,7 @@ def render_dashboard(lab, equipment_type, start_date, end_date):
366
  logs_output
367
  )
368
 
369
- # Gradio Interface
370
  with gr.Blocks(
371
  css="""
372
  .gradio-container {max-width: 1200px; margin: auto; padding: 20px;}
@@ -379,7 +370,7 @@ with gr.Blocks(
379
  ) as demo:
380
  gr.Markdown("## LabOps Central Dashboard")
381
 
382
- # File Upload
383
  with gr.Group():
384
  gr.Markdown("### Upload Data File")
385
  gr.Markdown("**Note**: Please upload a logs.csv file to proceed.")
@@ -387,7 +378,7 @@ with gr.Blocks(
387
  upload_btn = gr.Button("Load Data", variant="primary", elem_classes="submit-btn")
388
  upload_status = gr.HTML(label="Upload Status")
389
 
390
- # Filters
391
  with gr.Group():
392
  gr.Markdown("### Filter Options")
393
  with gr.Row():
@@ -418,7 +409,7 @@ with gr.Blocks(
418
  )
419
  submit_btn = gr.Button("Generate Dashboard", variant="primary", elem_classes="submit-btn", interactive=False)
420
 
421
- # Outputs
422
  with gr.Group():
423
  gr.Markdown("### Dashboard Results")
424
  with gr.Tabs():
@@ -437,14 +428,13 @@ with gr.Blocks(
437
  pdf_download = gr.File(label="Download PDF Report")
438
  log_display = gr.Textbox(label="Debug Logs", lines=10, interactive=False)
439
 
440
- # Update dropdowns on file upload
441
  upload_btn.click(
442
  fn=update_dropdowns,
443
  inputs=[logs_file],
444
  outputs=[lab, equipment_type, upload_status, submit_btn]
445
  )
446
-
447
- # Update dashboard on submit
448
  inputs = [lab, equipment_type, start_date, end_date]
449
  outputs = [device_cards, usage_chart, uptime_chart, anomaly_table, pdf_download, summary_output, insights_output, log_display]
450
  submit_btn.click(render_dashboard, inputs=inputs, outputs=outputs)
 
13
  import torch
14
  import tempfile
15
 
16
+ # Configure logging to track application events and errors
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
+ # Log versions of key dependencies for debugging
21
  logger.info(f"NumPy version: {np.__version__}")
22
  logger.info(f"Pandas version: {pd.__version__}")
23
  logger.info(f"PyTorch version: {torch.__version__}")
24
  logger.info(f"Gradio version: {gr.__version__}")
25
 
26
+ # Initialize list to store log messages for UI display
27
  log_messages = []
28
 
29
+ # Custom logging handler to capture logs for Gradio UI
30
  class UILogHandler(logging.Handler):
31
  def emit(self, record):
32
  log_entry = self.format(record)
 
36
  ui_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
37
  logger.addHandler(ui_handler)
38
 
39
+ # Initialize Hugging Face summarization model (BART) for generating summaries
40
  try:
41
  summarizer = pipeline(
42
  "summarization",
43
  model="facebook/bart-large-cnn",
44
+ device=0 if torch.cuda.is_available() else -1, # Use GPU if available
45
  framework="pt"
46
  )
47
  logger.info("Hugging Face summarization pipeline initialized")
 
49
  logger.error(f"Failed to initialize Hugging Face pipeline: {e}")
50
  summarizer = None
51
 
52
+ # Global DataFrame to store logs data
53
  logs = pd.DataFrame()
54
 
55
+ # Load and validate logs from uploaded CSV file
56
  def load_logs(logs_file):
57
  global logs
58
  try:
59
  if logs_file is None:
60
  logger.error("No logs CSV file uploaded")
61
  return pd.DataFrame()
62
+ # Write uploaded file to a temporary CSV
63
  with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
64
  tmp.write(logs_file.read())
65
  tmp_path = tmp.name
66
  logs = pd.read_csv(tmp_path)
67
+ os.unlink(tmp_path) # Remove temporary file
68
+ # Define required columns; 'comments' is optional
 
69
  required_columns = ['device_id', 'lab_id', 'timestamp', 'status', 'usage_count', 'type']
70
  if not all(col in logs.columns for col in required_columns):
71
  logger.error(f"Missing required columns in logs: {required_columns}")
72
  return pd.DataFrame()
73
+ # Add 'comments' column if missing, filling with 'No comment'
74
+ if 'comments' not in logs.columns:
75
+ logs['comments'] = 'No comment'
76
+ logger.info("Added missing 'comments' column with default value 'No comment'")
77
+ # Convert timestamp to datetime and check for invalid entries
78
  logs['timestamp'] = pd.to_datetime(logs['timestamp'], errors='coerce')
79
  if logs['timestamp'].isna().any():
80
  logger.error("Invalid timestamps detected in logs")
 
85
  logger.error(f"Error loading logs: {e}")
86
  return pd.DataFrame()
87
 
88
+ # Update dropdown menus based on uploaded CSV data
89
  def update_dropdowns(logs_file):
90
  global logs
91
  logs = load_logs(logs_file)
 
92
  if logs.empty:
93
  return (
94
  gr.Dropdown(choices=[], value=None, label="Select Lab"),
 
96
  "<p style='color: red;'>Please upload a valid logs CSV file to proceed.</p>",
97
  False # Disable Generate Dashboard button
98
  )
99
+ # Extract unique labs and equipment types, sorted for better UX
100
+ lab_choices = sorted(logs['lab_id'].unique().tolist())
101
+ equipment_choices = sorted(logs['type'].unique().tolist())
102
  upload_status = "<p style='color: green;'>Logs loaded successfully.</p>"
 
103
  return (
104
  gr.Dropdown(choices=lab_choices, value=lab_choices[0] if lab_choices else None, label="Select Lab"),
105
  gr.Dropdown(choices=equipment_choices, value=equipment_choices[0] if equipment_choices else None, label="Select Equipment Type"),
 
107
  True # Enable Generate Dashboard button
108
  )
109
 
110
+ # Detect anomalies using Isolation Forest
111
  def detect_anomalies(logs):
112
  try:
113
  if logs.empty:
 
122
  logger.error(f"Error in anomaly detection: {e}")
123
  return logs
124
 
125
+ # Generate text summary including comments
126
  def generate_text_summary(logs):
127
  if summarizer is None or logs.empty:
128
  return "No summary available due to missing data or model initialization."
129
  try:
130
  log_text = "\n".join(
131
  f"Device {row['device_id']} in lab {row['lab_id']} on {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')} "
132
+ f"was {row['status']} with usage count {row['usage_count']} (Type: {row['type']}). "
133
+ f"Comment: {row['comments']}"
134
  for _, row in logs.iterrows()
135
  )
136
  summary = summarizer(log_text, max_length=150, min_length=40, do_sample=False)[0]['summary_text']
 
140
  logger.error(f"Error generating text summary: {e}")
141
  return f"Error generating summary: {str(e)}"
142
 
143
+ # Generate executive insights including comments
144
  def generate_executive_insights(logs, anomalies):
145
  if summarizer is None or logs.empty:
146
  return "No executive insights available due to missing data or model initialization."
147
  try:
148
  log_text = "\n".join(
149
  f"Device {row['device_id']} in lab {row['lab_id']} on {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')} "
150
+ f"was {row['status']} with usage count {row['usage_count']} (Type: {row['type']}). "
151
+ f"Comment: {row['comments']}"
152
  for _, row in logs.iterrows()
153
  )
154
  anomaly_text = "\n".join(
155
+ f"Device {row['device_id']} showed anomalous usage count {row['usage_count']} on "
156
+ f"{row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}. Comment: {row['comments']}"
157
  for _, row in anomalies.iterrows()
158
  ) if not anomalies.empty else "No anomalies detected."
 
159
  prompt = (
160
  f"Summarize the following lab operations data into concise executive insights:\n\n"
161
  f"Logs:\n{log_text}\n\n"
162
  f"Anomalies:\n{anomaly_text}\n\n"
163
+ f"Provide high-level insights for lab managers, focusing on operational status, issues, and comments."
164
  )
 
165
  insights = summarizer(prompt, max_length=200, min_length=50, do_sample=False)[0]['summary_text']
166
  logger.info("Executive insights generated successfully")
167
  return insights
 
169
  logger.error(f"Error generating executive insights: {e}")
170
  return f"Error generating insights: {str(e)}"
171
 
172
+ # Generate PDF report with summary, insights, and comments
173
  def generate_pdf(lab, equipment_type, filtered_logs, summary, insights):
174
  try:
175
  pdf_file = f"labops_report_{lab}_{equipment_type}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
 
203
  c.showPage()
204
  y = 750
205
 
206
+ # Add device status with comments
207
  c.setFont("Helvetica-Bold", 14)
208
  c.drawString(100, y, "Device Status Summary")
209
  c.setFont("Helvetica", 12)
210
  y -= 20
211
  for _, row in filtered_logs.iterrows():
212
+ c.drawString(100, y, f"Device: {row['device_id']}, Status: {row['status']}, "
213
+ f"Usage: {row['usage_count']}, Comment: {row['comments'][:30]}")
214
  y -= 20
215
  if y < 100:
216
  c.showPage()
 
226
  logger.error(f"Error generating PDF: {e}")
227
  return None
228
 
229
+ # Validate date format (YYYY-MM-DD)
230
  def validate_date(date_str):
231
  pattern = r'^\d{4}-\d{2}-\d{2}$'
232
  if not isinstance(date_str, str) or not re.match(pattern, date_str):
 
237
  except ValueError:
238
  return False
239
 
240
+ # Render the dashboard with filtered data
241
  def render_dashboard(lab, equipment_type, start_date, end_date):
242
  try:
243
+ logger.info(f"Rendering dashboard with filters: lab={lab}, equipment_type={equipment_type}, "
244
+ f"start_date={start_date}, end_date={end_date}")
245
  # Validate inputs
246
  if not lab or not equipment_type:
247
  return (
 
250
  None, "No summary available.", "No insights available.",
251
  "\n".join(log_messages[-10:]) or "No logs available."
252
  )
253
+ # Validate and adjust dates
 
254
  if not validate_date(start_date):
255
  logger.warning(f"Invalid start_date format: {start_date}. Using default 2025-05-01.")
256
  start_date = "2025-05-01"
257
  if not validate_date(end_date):
258
  logger.warning(f"Invalid end_date format: {end_date}. Using default 2025-05-30.")
259
  end_date = "2025-05-30"
 
260
  start_dt = pd.to_datetime(start_date, format='%Y-%m-%d')
261
  end_dt = pd.to_datetime(end_date, format='%Y-%m-%d')
 
262
  if start_dt > end_dt:
263
  logger.warning("start_date is after end_date. Swapping dates.")
264
  start_dt, end_dt = end_dt, start_dt
265
+ # Filter logs by lab, equipment type, and date range
 
266
  filtered_logs = logs[(logs['lab_id'] == lab) & (logs['type'] == equipment_type)]
267
  filtered_logs = filtered_logs[
268
  (filtered_logs['timestamp'] >= start_dt) &
269
  (filtered_logs['timestamp'] <= end_dt)
270
  ]
 
271
  if filtered_logs.empty:
272
  logger.warning("No data available for the selected filters")
273
  return (
 
276
  None, "No summary available.", "No insights available.",
277
  "\n".join(log_messages[-10:]) or "No logs available."
278
  )
 
279
  # Apply anomaly detection
280
  filtered_logs = detect_anomalies(filtered_logs)
281
+ # Generate device cards for display
 
282
  device_cards = "<div style='display: flex; flex-wrap: wrap; gap: 20px;'>"
283
  for _, row in filtered_logs.iterrows():
284
  status_color = "green" if row['status'] == "active" else "red"
 
287
  <h3 style='margin: 0; font-size: 18px;'>Device: {row['device_id']}</h3>
288
  <p style='color: {status_color};'>Status: {row['status'].capitalize()}</p>
289
  <p>Usage Count: {row['usage_count']}</p>
290
+ <p>Comment: {row['comments'][:30]}</p>
291
  <p>Last Log: {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}</p>
292
  </div>
293
  """
294
  device_cards += "</div>"
295
+ # Generate usage trend chart
 
296
  fig_usage = px.line(
297
  filtered_logs,
298
  x='timestamp',
 
305
  yaxis_title="Usage Count",
306
  margin=dict(l=20, r=20, t=40, b=20)
307
  )
308
+ # Generate uptime chart
 
309
  uptime = filtered_logs.groupby('device_id')['status'].apply(lambda x: (x == 'active').mean() * 100)
310
  fig_uptime = px.bar(
311
  uptime,
 
318
  yaxis_title="Uptime %",
319
  margin=dict(l=20, r=20, t=40, b=20)
320
  )
321
+ # Generate anomaly table
 
322
  anomalies = filtered_logs[filtered_logs['anomaly'] == -1] if 'anomaly' in filtered_logs.columns else pd.DataFrame()
323
+ anomaly_table = anomalies[['device_id', 'timestamp', 'usage_count', 'comments']].to_html(
324
  index=False,
325
  classes="table table-striped",
326
  border=0
327
  ) if not anomalies.empty else "<p>No anomalies detected.</p>"
 
328
  # Generate summary and insights
329
  summary = generate_text_summary(filtered_logs)
330
  insights = generate_executive_insights(filtered_logs, anomalies)
331
+ # Generate PDF report
 
332
  pdf_data = generate_pdf(lab, equipment_type, filtered_logs, summary, insights)
333
+ # Display recent logs
 
334
  logs_output = "\n".join(log_messages[-10:]) or "No logs available."
 
335
  logger.info("Dashboard rendered successfully")
336
  return (
337
  device_cards,
 
357
  logs_output
358
  )
359
 
360
+ # Define Gradio interface
361
  with gr.Blocks(
362
  css="""
363
  .gradio-container {max-width: 1200px; margin: auto; padding: 20px;}
 
370
  ) as demo:
371
  gr.Markdown("## LabOps Central Dashboard")
372
 
373
+ # File Upload Section
374
  with gr.Group():
375
  gr.Markdown("### Upload Data File")
376
  gr.Markdown("**Note**: Please upload a logs.csv file to proceed.")
 
378
  upload_btn = gr.Button("Load Data", variant="primary", elem_classes="submit-btn")
379
  upload_status = gr.HTML(label="Upload Status")
380
 
381
+ # Filter Options
382
  with gr.Group():
383
  gr.Markdown("### Filter Options")
384
  with gr.Row():
 
409
  )
410
  submit_btn = gr.Button("Generate Dashboard", variant="primary", elem_classes="submit-btn", interactive=False)
411
 
412
+ # Output Section
413
  with gr.Group():
414
  gr.Markdown("### Dashboard Results")
415
  with gr.Tabs():
 
428
  pdf_download = gr.File(label="Download PDF Report")
429
  log_display = gr.Textbox(label="Debug Logs", lines=10, interactive=False)
430
 
431
+ # Connect upload button to dropdown update function
432
  upload_btn.click(
433
  fn=update_dropdowns,
434
  inputs=[logs_file],
435
  outputs=[lab, equipment_type, upload_status, submit_btn]
436
  )
437
+ # Connect submit button to dashboard rendering
 
438
  inputs = [lab, equipment_type, start_date, end_date]
439
  outputs = [device_cards, usage_chart, uptime_chart, anomaly_table, pdf_download, summary_output, insights_output, log_display]
440
  submit_btn.click(render_dashboard, inputs=inputs, outputs=outputs)