RathodHarish commited on
Commit
64799ba
·
verified ·
1 Parent(s): 4b07003

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -209
app.py CHANGED
@@ -5,10 +5,6 @@ import logging
5
  import plotly.express as px
6
  import plotly.graph_objects as go
7
  from sklearn.ensemble import IsolationForest
8
- from transformers import pipeline
9
- import torch
10
- from concurrent.futures import ThreadPoolExecutor
11
- from simple_salesforce import Salesforce
12
  import os
13
  import io
14
  import time
@@ -17,19 +13,6 @@ import asyncio
17
  # Configure logging
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
 
20
- # Salesforce configuration
21
- try:
22
- sf = Salesforce(
23
- username='multi-devicelabopsdashboard@sathkrutha.com',
24
- password='Team@1234',
25
- security_token=os.getenv('SF_SECURITY_TOKEN', ''),
26
- domain='login'
27
- )
28
- logging.info("Salesforce connection established")
29
- except Exception as e:
30
- logging.error(f"Failed to connect to Salesforce: {str(e)}")
31
- sf = None
32
-
33
  # Try to import reportlab
34
  try:
35
  from reportlab.lib.pagesizes import letter
@@ -42,120 +25,15 @@ except ImportError:
42
  logging.warning("reportlab module not found. PDF generation disabled.")
43
  reportlab_available = False
44
 
45
- # Preload Hugging Face model with optimization
46
- logging.info("Preloading Hugging Face model...")
47
- try:
48
- device = 0 if torch.cuda.is_available() else -1
49
- summarizer = pipeline(
50
- "summarization",
51
- model="t5-small",
52
- device=device,
53
- max_length=20, # Further reduced for speed
54
- min_length=10,
55
- num_beams=2
56
- )
57
- logging.info(f"Hugging Face model preloaded on {'GPU' if device == 0 else 'CPU'}")
58
- except Exception as e:
59
- logging.error(f"Failed to preload model: {str(e)}")
60
- raise e
61
-
62
- # Cache picklist values at startup
63
- def get_picklist_values(field_name):
64
- if sf is None:
65
- return []
66
- try:
67
- obj_desc = sf.SmartLog__c.describe()
68
- for field in obj_desc['fields']:
69
- if field['name'] == field_name:
70
- return [value['value'] for value in field['picklistValues'] if value['active']]
71
- return []
72
- except Exception as e:
73
- logging.error(f"Failed to fetch picklist values for {field_name}: {str(e)}")
74
- return []
75
-
76
- status_values = get_picklist_values('Status__c') or ["Active", "Inactive", "Pending"]
77
- log_type_values = get_picklist_values('Log_Type__c') or ["Smart Log", "Cell Analysis", "UV Verification"]
78
- logging.info(f"Valid Status__c values: {status_values}")
79
- logging.info(f"Valid Log_Type__c values: {log_type_values}")
80
-
81
- # Map invalid picklist values
82
- picklist_mapping = {
83
- 'Status__c': {
84
- 'normal': 'Active',
85
- 'error': 'Inactive',
86
- 'warning': 'Pending',
87
- 'ok': 'Active',
88
- 'failed': 'Inactive'
89
- },
90
- 'Log_Type__c': {
91
- 'maint': 'Smart Log',
92
- 'error': 'Cell Analysis',
93
- 'ops': 'UV Verification',
94
- 'maintenance': 'Smart Log',
95
- 'cell': 'Cell Analysis',
96
- 'uv': 'UV Verification',
97
- 'weight log': 'Smart Log'
98
- }
99
- }
100
-
101
- # Save to Salesforce
102
- def save_to_salesforce(df, reminders_df):
103
- if sf is None:
104
- return
105
- try:
106
- current_date = datetime.now()
107
- next_30_days = current_date + timedelta(days=30)
108
- records = []
109
- reminder_device_ids = set(reminders_df['device_id']) if not reminders_df.empty else set()
110
-
111
- for _, row in df.iterrows():
112
- status = str(row['status'])
113
- log_type = str(row['log_type'])
114
- status = picklist_mapping['Status__c'].get(status.lower(), status_values[0] if status_values else None)
115
- log_type = picklist_mapping['Log_Type__c'].get(log_type.lower(), log_type_values[0] if log_type_values else None)
116
- if status is None or log_type is None:
117
- continue
118
-
119
- amc_date_str = None
120
- if pd.notna(row['amc_date']):
121
- try:
122
- amc_date = pd.to_datetime(row['amc_date']).strftime('%Y-%m-%d')
123
- amc_date_dt = datetime.strptime(amc_date, '%Y-%m-%d')
124
- if status == "Active" and current_date.date() <= amc_date_dt.date() <= next_30_days.date():
125
- logging.info(f"AMC Reminder for Device ID {row['device_id']}")
126
- amc_date_str = amc_date
127
- except:
128
- amc_date_str = None
129
-
130
- record = {
131
- 'Device_Id__c': str(row['device_id'])[:50],
132
- 'Log_Type__c': log_type,
133
- 'Status__c': status,
134
- 'Timestamp__c': row['timestamp'].isoformat() if pd.notna(row['timestamp']) else None,
135
- 'Usage_Hours__c': float(row['usage_hours']) if pd.notna(row['usage_hours']) else 0.0,
136
- 'Downtime__c': float(row['downtime']) if pd.notna(row['downtime']) else 0.0,
137
- 'AMC_Date__c': amc_date_str
138
- }
139
- if row['device_id'] not in reminder_device_ids:
140
- records.append(record)
141
-
142
- if records:
143
- sf.bulk.SmartLog__c.insert(records)
144
- logging.info(f"Saved {len(records)} records to Salesforce")
145
- except Exception as e:
146
- logging.error(f"Failed to save to Salesforce: {str(e)}")
147
-
148
- # Summarize logs
149
  def summarize_logs(df):
150
  try:
151
  total_devices = df["device_id"].nunique()
152
- most_used = df.groupby("device_id")["usage_hours"].sum().idxmax() if not df.empty else "N/A"
153
- prompt = f"Logs: {total_devices} devices. Most used: {most_used}."
154
- summary = summarizer(prompt, max_length=20, min_length=10, do_sample=False)[0]["summary_text"]
155
- return summary
156
  except Exception as e:
157
  logging.error(f"Summary generation failed: {str(e)}")
158
- return f"Failed to generate summary: {str(e)}"
159
 
160
  # Anomaly detection
161
  def detect_anomalies(df):
@@ -163,8 +41,8 @@ def detect_anomalies(df):
163
  if "usage_hours" not in df.columns or "downtime" not in df.columns:
164
  return "Anomaly detection requires 'usage_hours' and 'downtime' columns.", pd.DataFrame()
165
  features = df[["usage_hours", "downtime"]].fillna(0)
166
- if len(features) > 100: # Further reduced sample size
167
- features = features.sample(n=100, random_state=42)
168
  iso_forest = IsolationForest(contamination=0.1, random_state=42)
169
  df["anomaly"] = iso_forest.fit_predict(features)
170
  anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
@@ -191,17 +69,15 @@ def check_amc_reminders(df, current_date):
191
  logging.error(f"AMC reminder generation failed: {str(e)}")
192
  return f"AMC reminder generation failed: {str(e)}", pd.DataFrame()
193
 
194
- # Dashboard insights
195
  def generate_dashboard_insights(df):
196
  try:
197
  total_devices = df["device_id"].nunique()
198
  avg_usage = df["usage_hours"].mean() if "usage_hours" in df.columns else 0
199
- prompt = f"Insights: {total_devices} devices, avg usage {avg_usage:.2f} hours."
200
- insights = summarizer(prompt, max_length=20, min_length=10, do_sample=False)[0]["summary_text"]
201
- return insights
202
  except Exception as e:
203
  logging.error(f"Dashboard insights generation failed: {str(e)}")
204
- return f"Dashboard insights generation failed: {str(e)}"
205
 
206
  # Placeholder chart for empty data
207
  def create_placeholder_chart(title):
@@ -265,7 +141,7 @@ def create_daily_log_trends_chart(df):
265
  if df.empty or "timestamp" not in df.columns:
266
  logging.warning("Insufficient data for daily log trends chart")
267
  return create_placeholder_chart("Daily Log Trends")
268
- df['date'] = pd.to_datetime(df['timestamp']).dt.date
269
  daily_logs = df.groupby('date').size().reset_index(name='log_count')
270
  if daily_logs.empty:
271
  return create_placeholder_chart("Daily Log Trends")
@@ -288,8 +164,8 @@ def create_weekly_uptime_chart(df):
288
  if df.empty or "timestamp" not in df.columns or "usage_hours" not in df.columns or "downtime" not in df.columns:
289
  logging.warning("Insufficient data for weekly uptime chart")
290
  return create_placeholder_chart("Weekly Uptime Percentage")
291
- df['week'] = pd.to_datetime(df['timestamp']).dt.isocalendar().week
292
- df['year'] = pd.to_datetime(df['timestamp']).dt.year
293
  weekly_data = df.groupby(['year', 'week']).agg({
294
  'usage_hours': 'sum',
295
  'downtime': 'sum'
@@ -317,7 +193,7 @@ def create_anomaly_alerts_chart(anomalies_df):
317
  if anomalies_df is None or anomalies_df.empty or "timestamp" not in anomalies_df.columns:
318
  logging.warning("Insufficient data for anomaly alerts chart")
319
  return create_placeholder_chart("Anomaly Alerts Over Time")
320
- anomalies_df['date'] = pd.to_datetime(anomalies_df['timestamp']).dt.date
321
  anomaly_counts = anomalies_df.groupby('date').size().reset_index(name='anomaly_count')
322
  if anomaly_counts.empty:
323
  return create_placeholder_chart("Anomaly Alerts Over Time")
@@ -438,100 +314,94 @@ def generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights
438
  return None
439
 
440
  # Main processing function
441
- async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range, last_modified_state, cached_df_state):
442
  start_time = time.time()
443
  try:
444
  if not file_obj:
445
- return "No file uploaded.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None, last_modified_state, cached_df_state
446
 
447
  file_path = file_obj.name
448
  current_modified_time = os.path.getmtime(file_path)
449
- if last_modified_state and current_modified_time == last_modified_state and cached_df_state is not None:
450
- df = cached_df_state
451
  else:
452
- logging.info(f"Processing file: {file_path}")
453
- if not file_path.endswith(".csv"):
454
- return "Please upload a CSV file.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, last_modified_state, cached_df_state
455
-
456
- required_columns = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
457
- dtypes = {
458
- "device_id": "string",
459
- "log_type": "string",
460
- "status": "string",
461
- "usage_hours": "float32",
462
- "downtime": "float32",
463
- "amc_date": "string"
464
- }
465
- df = pd.read_csv(file_path, dtype=dtypes)
466
- missing_columns = [col for col in required_columns if col not in df.columns]
467
- if missing_columns:
468
- return f"Missing columns: {missing_columns}", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, cached_df_state
469
-
470
- df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
471
- df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
472
- if df["timestamp"].dt.tz is None:
473
- df["timestamp"] = df["timestamp"].dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata')
474
- if df.empty:
475
- return "No data available.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, cached_df_state
476
-
477
- # Apply filters
478
- filtered_df = df.copy()
479
- if lab_site_filter and lab_site_filter != 'All' and 'lab_site' in filtered_df.columns:
480
- filtered_df = filtered_df[filtered_df['lab_site'] == lab_site_filter]
481
- if equipment_type_filter and equipment_type_filter != 'All' and 'equipment_type' in filtered_df.columns:
482
- filtered_df = filtered_df[filtered_df['equipment_type'] == equipment_type_filter]
483
- if date_range and len(date_range) == 2:
484
- days_start, days_end = date_range
485
- today = pd.to_datetime(datetime.now().date()).tz_localize('Asia/Kolkata')
486
- start_date = today + pd.Timedelta(days=days_start)
487
- end_date = today + pd.Timedelta(days=days_end) + pd.Timedelta(days=1) - pd.Timedelta(seconds=1)
488
- filtered_df = filtered_df[(filtered_df['timestamp'] >= start_date) & (filtered_df['timestamp'] <= end_date)]
489
-
490
- if filtered_df.empty:
491
- return "No data after applying filters.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, df
 
 
 
492
 
493
  # Generate table for preview
494
  preview_df = filtered_df[['device_id', 'log_type', 'status', 'timestamp', 'usage_hours', 'downtime', 'amc_date']].head(5)
495
  preview_html = preview_df.to_html(index=False, classes='table table-striped', border=0)
496
 
497
  # Run critical tasks concurrently
498
- with ThreadPoolExecutor(max_workers=4) as executor: # Reduced workers
499
- future_summary = executor.submit(summarize_logs, filtered_df)
500
  future_anomalies = executor.submit(detect_anomalies, filtered_df)
501
  future_amc = executor.submit(check_amc_reminders, filtered_df, datetime.now())
502
- future_insights = executor.submit(generate_dashboard_insights, filtered_df)
503
- future_usage_chart = executor.submit(create_usage_chart, filtered_df)
504
- future_downtime_chart = executor.submit(create_downtime_chart, filtered_df)
505
- future_daily_log_chart = executor.submit(create_daily_log_chart, filtered_df)
506
- future_weekly_uptime_chart = executor.submit(create_weekly_uptime_chart, filtered_df)
507
- future_device_cards = executor.submit(generate_device_cards, filtered_df)
508
-
509
- summary = f"Step 1: Summary Report\n{future_summary.result()}"
510
  anomalies, anomalies_df = future_anomalies.result()
511
  anomalies = f"Anomaly Detection\n{anomalies}"
512
  amc_reminders, reminders_df = future_amc.result()
513
  amc_reminders = f"AMC Reminders\n{amc_reminders}"
514
- insights = f"Dashboard Insights (AI)\n{future_insights.result()}"
515
- usage_chart = future_usage_chart.result()
516
- downtime_chart = future_downtime_chart.result()
517
- daily_log_chart = future_daily_log_chart.result()
518
- weekly_uptime_chart = future_weekly_uptime_chart.result()
519
- device_cards = future_device_cards.result()
520
-
521
- # Generate anomaly alerts chart after anomalies are computed
522
- anomaly_alerts_chart = create_anomaly_alerts_chart(anomalies_df)
523
 
524
- save_to_salesforce(filtered_df, reminders_df)
 
 
 
 
 
 
525
 
526
  elapsed_time = time.time() - start_time
527
  logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
528
- if elapsed_time > 5: # Stricter threshold
529
- logging.warning(f"Processing time exceeded 5 seconds: {elapsed_time:.2f} seconds")
530
 
531
- return (summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights, None, current_modified_time, df)
532
  except Exception as e:
533
  logging.error(f"Failed to process file: {str(e)}")
534
- return f"Error: {str(e)}", pd.DataFrame(), None, '<p>Error processing data.</p>', None, None, None, None, None, None, None, None, last_modified_state, cached_df_state
535
 
536
  # Generate PDF separately
537
  async def generate_pdf(summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights):
@@ -576,12 +446,13 @@ try:
576
  .table th {background-color: #f2f2f2;}
577
  .table tr:nth-child(even) {background-color: #f9f9f9;}
578
  """) as iface:
579
- gr.Markdown("<h1>LabOps Log Analyzer Dashboard (Hugging Face AI)</h1>")
580
  gr.Markdown("Upload a CSV file to analyze. Click 'Analyze' to refresh the dashboard. Use 'Export PDF' for report download.")
581
 
582
  last_modified_state = gr.State(value=None)
583
  current_file_state = gr.State(value=None)
584
  cached_df_state = gr.State(value=None)
 
585
 
586
  with gr.Row():
587
  with gr.Column(scale=1):
@@ -625,7 +496,7 @@ try:
625
  gr.Markdown("### Step 5: AMC Reminders")
626
  amc_output = gr.Markdown()
627
  with gr.Group(elem_classes="dashboard-section"):
628
- gr.Markdown("### Step 6: Insights (AI)")
629
  insights_output = gr.Markdown()
630
  with gr.Group(elem_classes="dashboard-section"):
631
  gr.Markdown("### Export Report")
@@ -640,8 +511,8 @@ try:
640
 
641
  submit_button.click(
642
  fn=process_logs,
643
- inputs=[file_input, lab_site_filter, equipment_type_filter, date_range_filter, last_modified_state, cached_df_state],
644
- outputs=[summary_output, preview_output, usage_chart_output, device_cards_output, daily_log_trends_output, weekly_uptime_output, anomaly_alerts_output, downtime_chart_output, anomaly_output, amc_output, insights_output, pdf_output, last_modified_state, cached_df_state]
645
  )
646
 
647
  pdf_button.click(
 
5
  import plotly.express as px
6
  import plotly.graph_objects as go
7
  from sklearn.ensemble import IsolationForest
 
 
 
 
8
  import os
9
  import io
10
  import time
 
13
  # Configure logging
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # Try to import reportlab
17
  try:
18
  from reportlab.lib.pagesizes import letter
 
25
  logging.warning("reportlab module not found. PDF generation disabled.")
26
  reportlab_available = False
27
 
28
+ # Summarize logs (without Hugging Face)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def summarize_logs(df):
30
  try:
31
  total_devices = df["device_id"].nunique()
32
+ total_usage = df["usage_hours"].sum() if "usage_hours" in df.columns else 0
33
+ return f"{total_devices} devices processed with {total_usage:.2f} total usage hours."
 
 
34
  except Exception as e:
35
  logging.error(f"Summary generation failed: {str(e)}")
36
+ return "Failed to generate summary."
37
 
38
  # Anomaly detection
39
  def detect_anomalies(df):
 
41
  if "usage_hours" not in df.columns or "downtime" not in df.columns:
42
  return "Anomaly detection requires 'usage_hours' and 'downtime' columns.", pd.DataFrame()
43
  features = df[["usage_hours", "downtime"]].fillna(0)
44
+ if len(features) > 50: # Reduced sample size
45
+ features = features.sample(n=50, random_state=42)
46
  iso_forest = IsolationForest(contamination=0.1, random_state=42)
47
  df["anomaly"] = iso_forest.fit_predict(features)
48
  anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
 
69
  logging.error(f"AMC reminder generation failed: {str(e)}")
70
  return f"AMC reminder generation failed: {str(e)}", pd.DataFrame()
71
 
72
+ # Dashboard insights (without Hugging Face)
73
  def generate_dashboard_insights(df):
74
  try:
75
  total_devices = df["device_id"].nunique()
76
  avg_usage = df["usage_hours"].mean() if "usage_hours" in df.columns else 0
77
+ return f"{total_devices} devices with average usage of {avg_usage:.2f} hours."
 
 
78
  except Exception as e:
79
  logging.error(f"Dashboard insights generation failed: {str(e)}")
80
+ return "Failed to generate insights."
81
 
82
  # Placeholder chart for empty data
83
  def create_placeholder_chart(title):
 
141
  if df.empty or "timestamp" not in df.columns:
142
  logging.warning("Insufficient data for daily log trends chart")
143
  return create_placeholder_chart("Daily Log Trends")
144
+ df['date'] = pd.to_datetime(df['timestamp'], errors='coerce').dt.date
145
  daily_logs = df.groupby('date').size().reset_index(name='log_count')
146
  if daily_logs.empty:
147
  return create_placeholder_chart("Daily Log Trends")
 
164
  if df.empty or "timestamp" not in df.columns or "usage_hours" not in df.columns or "downtime" not in df.columns:
165
  logging.warning("Insufficient data for weekly uptime chart")
166
  return create_placeholder_chart("Weekly Uptime Percentage")
167
+ df['week'] = pd.to_datetime(df['timestamp'], errors='coerce').dt.isocalendar().week
168
+ df['year'] = pd.to_datetime(df['timestamp'], errors='coerce').dt.year
169
  weekly_data = df.groupby(['year', 'week']).agg({
170
  'usage_hours': 'sum',
171
  'downtime': 'sum'
 
193
  if anomalies_df is None or anomalies_df.empty or "timestamp" not in anomalies_df.columns:
194
  logging.warning("Insufficient data for anomaly alerts chart")
195
  return create_placeholder_chart("Anomaly Alerts Over Time")
196
+ anomalies_df['date'] = pd.to_datetime(anomalies_df['timestamp'], errors='coerce').dt.date
197
  anomaly_counts = anomalies_df.groupby('date').size().reset_index(name='anomaly_count')
198
  if anomaly_counts.empty:
199
  return create_placeholder_chart("Anomaly Alerts Over Time")
 
314
  return None
315
 
316
  # Main processing function
317
+ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range, last_modified_state, cached_df_state, cached_filtered_df_state):
318
  start_time = time.time()
319
  try:
320
  if not file_obj:
321
+ return "No file uploaded.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None, last_modified_state, cached_df_state, cached_filtered_df_state
322
 
323
  file_path = file_obj.name
324
  current_modified_time = os.path.getmtime(file_path)
325
+ if last_modified_state and current_modified_time == last_modified_state and cached_filtered_df_state is not None:
326
+ filtered_df = cached_filtered_df_state
327
  else:
328
+ if cached_df_state is None or current_modified_time != last_modified_state:
329
+ logging.info(f"Processing file: {file_path}")
330
+ if not file_path.endswith(".csv"):
331
+ return "Please upload a CSV file.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, last_modified_state, cached_df_state, cached_filtered_df_state
332
+
333
+ required_columns = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
334
+ dtypes = {
335
+ "device_id": "string",
336
+ "log_type": "string",
337
+ "status": "string",
338
+ "usage_hours": "float32",
339
+ "downtime": "float32",
340
+ "amc_date": "string"
341
+ }
342
+ df = pd.read_csv(file_path, dtype=dtypes)
343
+ missing_columns = [col for col in required_columns if col not in df.columns]
344
+ if missing_columns:
345
+ return f"Missing columns: {missing_columns}", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, cached_df_state, cached_filtered_df_state
346
+
347
+ df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
348
+ df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
349
+ if df["timestamp"].dt.tz is None:
350
+ df["timestamp"] = df["timestamp"].dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata')
351
+ if df.empty:
352
+ return "No data available.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, df, cached_filtered_df_state
353
+ else:
354
+ df = cached_df_state
355
+
356
+ # Apply filters
357
+ filtered_df = df.copy()
358
+ if lab_site_filter and lab_site_filter != 'All' and 'lab_site' in filtered_df.columns:
359
+ filtered_df = filtered_df[filtered_df['lab_site'] == lab_site_filter]
360
+ if equipment_type_filter and equipment_type_filter != 'All' and 'equipment_type' in filtered_df.columns:
361
+ filtered_df = filtered_df[filtered_df['equipment_type'] == equipment_type_filter]
362
+ if date_range and len(date_range) == 2:
363
+ days_start, days_end = date_range
364
+ today = pd.to_datetime(datetime.now().date()).tz_localize('Asia/Kolkata')
365
+ start_date = today + pd.Timedelta(days=days_start)
366
+ end_date = today + pd.Timedelta(days=days_end) + pd.Timedelta(days=1) - pd.Timedelta(seconds=1)
367
+ filtered_df = filtered_df[(filtered_df['timestamp'] >= start_date) & (filtered_df['timestamp'] <= end_date)]
368
+
369
+ if filtered_df.empty:
370
+ return "No data after applying filters.", pd.DataFrame(), None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, df, filtered_df
371
 
372
  # Generate table for preview
373
  preview_df = filtered_df[['device_id', 'log_type', 'status', 'timestamp', 'usage_hours', 'downtime', 'amc_date']].head(5)
374
  preview_html = preview_df.to_html(index=False, classes='table table-striped', border=0)
375
 
376
  # Run critical tasks concurrently
377
+ with ThreadPoolExecutor(max_workers=2) as executor: # Reduced workers
 
378
  future_anomalies = executor.submit(detect_anomalies, filtered_df)
379
  future_amc = executor.submit(check_amc_reminders, filtered_df, datetime.now())
380
+
381
+ summary = f"Step 1: Summary Report\n{summarize_logs(filtered_df)}"
 
 
 
 
 
 
382
  anomalies, anomalies_df = future_anomalies.result()
383
  anomalies = f"Anomaly Detection\n{anomalies}"
384
  amc_reminders, reminders_df = future_amc.result()
385
  amc_reminders = f"AMC Reminders\n{amc_reminders}"
386
+ insights = f"Dashboard Insights\n{generate_dashboard_insights(filtered_df)}"
 
 
 
 
 
 
 
 
387
 
388
+ # Generate charts sequentially to avoid contention
389
+ usage_chart = create_usage_chart(filtered_df)
390
+ downtime_chart = create_downtime_chart(filtered_df)
391
+ daily_log_chart = create_daily_log_trends_chart(filtered_df)
392
+ weekly_uptime_chart = create_weekly_uptime_chart(filtered_df)
393
+ anomaly_alerts_chart = create_anomaly_alerts_chart(anomalies_df)
394
+ device_cards = generate_device_cards(filtered_df)
395
 
396
  elapsed_time = time.time() - start_time
397
  logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
398
+ if elapsed_time > 3: # Very strict threshold
399
+ logging.warning(f"Processing time exceeded 3 seconds: {elapsed_time:.2f} seconds")
400
 
401
+ return (summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights, None, current_modified_time, df, filtered_df)
402
  except Exception as e:
403
  logging.error(f"Failed to process file: {str(e)}")
404
+ return f"Error: {str(e)}", pd.DataFrame(), None, '<p>Error processing data.</p>', None, None, None, None, None, None, None, None, last_modified_state, cached_df_state, cached_filtered_df_state
405
 
406
  # Generate PDF separately
407
  async def generate_pdf(summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights):
 
446
  .table th {background-color: #f2f2f2;}
447
  .table tr:nth-child(even) {background-color: #f9f9f9;}
448
  """) as iface:
449
+ gr.Markdown("<h1>LabOps Log Analyzer Dashboard</h1>")
450
  gr.Markdown("Upload a CSV file to analyze. Click 'Analyze' to refresh the dashboard. Use 'Export PDF' for report download.")
451
 
452
  last_modified_state = gr.State(value=None)
453
  current_file_state = gr.State(value=None)
454
  cached_df_state = gr.State(value=None)
455
+ cached_filtered_df_state = gr.State(value=None)
456
 
457
  with gr.Row():
458
  with gr.Column(scale=1):
 
496
  gr.Markdown("### Step 5: AMC Reminders")
497
  amc_output = gr.Markdown()
498
  with gr.Group(elem_classes="dashboard-section"):
499
+ gr.Markdown("### Step 6: Insights")
500
  insights_output = gr.Markdown()
501
  with gr.Group(elem_classes="dashboard-section"):
502
  gr.Markdown("### Export Report")
 
511
 
512
  submit_button.click(
513
  fn=process_logs,
514
+ inputs=[file_input, lab_site_filter, equipment_type_filter, date_range_filter, last_modified_state, cached_df_state, cached_filtered_df_state],
515
+ outputs=[summary_output, preview_output, usage_chart_output, device_cards_output, daily_log_trends_output, weekly_uptime_output, anomaly_alerts_output, downtime_chart_output, anomaly_output, amc_output, insights_output, pdf_output, last_modified_state, cached_df_state, cached_filtered_df_state]
516
  )
517
 
518
  pdf_button.click(