Dineshpopuri commited on
Commit
4613167
·
verified ·
1 Parent(s): 1afdd36

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -222
app.py CHANGED
@@ -14,13 +14,13 @@ def generate_sample_data():
14
  # Generate devices
15
  for i in range(10):
16
  devices.append({
17
- "Device_ID": f"DEV_{i+1}",
18
  "Lab": random.choice(labs),
19
  "Equipment_Type": random.choice(equipment_types),
20
  "Status": random.choice(["Operational", "Down", "Maintenance"])
21
  })
22
 
23
- # Generate logs for a broader date range (Jan 1, 2025 to Jun 30, 2025)
24
  start_date = datetime(2025, 1, 1)
25
  end_date = datetime(2025, 6, 30)
26
  for device in devices:
@@ -29,7 +29,7 @@ def generate_sample_data():
29
  logs.append({
30
  "Device_ID": device["Device_ID"],
31
  "Log_Timestamp": current_date.strftime("%Y-%m-%d %H:%M:%S"),
32
- "Usage_Count": random.randint(0, 100),
33
  "Status": random.choice(["Operational", "Down", "Maintenance"]),
34
  "AMC_Expiry": (current_date + timedelta(days=random.randint(10, 365))).strftime("%Y-%m-%d")
35
  })
@@ -62,9 +62,9 @@ def process_dashboard_data(lab_filter, equipment_type_filter, start_date, end_da
62
  start_date_dt = datetime.strptime(start_date, "%Y-%m-%d")
63
  end_date_dt = datetime.strptime(end_date, "%Y-%m-%d")
64
  if start_date_dt > end_date_dt:
65
- return "Error: Start date must be before end date.", None, None, None, None, None
66
  except ValueError as e:
67
- return f"Error: Invalid date format. Use YYYY-MM-DD (e.g., 2025-05-01). Received: Start={start_date}, End={end_date}", None, None, None, None, None
68
  else:
69
  start_date_dt = datetime(2025, 1, 1)
70
  end_date_dt = datetime(2025, 6, 30)
@@ -78,7 +78,6 @@ def process_dashboard_data(lab_filter, equipment_type_filter, start_date, end_da
78
 
79
  filtered_logs = logs_df[logs_df["Device_ID"].isin(filtered_devices["Device_ID"])]
80
  if start_date_dt and end_date_dt:
81
- # Convert Log_Timestamp to date for filtering
82
  filtered_logs["Log_Date"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.date
83
  start_date_str = start_date_dt.date()
84
  end_date_str = end_date_dt.date()
@@ -95,222 +94,147 @@ def process_dashboard_data(lab_filter, equipment_type_filter, start_date, end_da
95
  usage_count = device_logs["Usage_Count"].sum() if not device_logs.empty else 0
96
  last_log = device_logs["Log_Timestamp"].max() if not device_logs.empty else "No logs"
97
  device_cards += (
98
- f"Device: {device['Device_ID']}, Lab: {device['Lab'] Meet BRD requirements to aggregate logs from multiple objects
99
- # Generate sample data to simulate SmartLog__c, Cell_Analysis__c, etc.
100
- logs = []
101
- for device in devices:
102
- for day in range(30): # Simulate 30 days of logs
103
- log_date = datetime.now() - timedelta(days=30 - day)
104
- logs.append({
105
- "Device_ID": device["Device_ID"],
106
- "Log_Timestamp": log_date.strftime("%Y-%m-%d %H:%M:%S"),
107
- "Usage_Count": random.randint(0, 100),
108
- "Status": random.choice(["Operational", "Down", "Maintenance"]),
109
- "AMC_Expiry": (log_date + timedelta(days=random.randint(10, 365))).strftime("%Y-%m-%d")
110
- })
111
- logs_df = pd.DataFrame(logs)
112
-
113
- return pd.DataFrame(devices), logs_df
114
-
115
- # Process logs to create contact-like records (simulating Contact creation)
116
- def create_contact_from_can(cands_from_trigger):
117
- con_list = []
118
- for candidate in cands_from_trigger:
119
- contact = {
120
- "FirstName": candidate.get("First_Name__c", ""),
121
- "LastName": candidate.get("Last_Name__c", ""),
122
- "Email": candidate.get("Email__c", "")
123
- }
124
- con_list.append(contact)
125
- return con_list
126
-
127
- # Generate sample data
128
- devices_df, logs_df = generate_sample_data()
129
-
130
- # Process dashboard data with filters
131
- def process_dashboard_data(lab_filter, equipment_type_filter, start_date, end_date):
132
- try:
133
- # Validate and parse date inputs
134
- if start_date and end_date:
135
- try:
136
- start_date = start_date.strip() if start_date else ""
137
- end_date = end_date.strip() if end_date else ""
138
- start_date_dt = datetime.strptime(start_date, "%Y-%m-%d")
139
- end_date_dt = datetime.strptime(end_date, "%Y-%m-%d")
140
- if start_date_dt > end_date_dt:
141
- return "Error: Start date must be before end date.", None, None, None, None, None
142
- except ValueError as e:
143
- return f"Error: Invalid date format. Use YYYY-MM-DD (e.g., 2025-05-01). Received: Start={start_date}, End={end_date}", None, None, None, None, None
144
- else:
145
- start_date_dt = datetime(2025, 1, 1)
146
- end_date_dt = datetime(2025, 6, 30)
147
-
148
- # Apply filters
149
- filtered_devices = devices_df.copy()
150
- if lab_filter != "All":
151
- filtered_devices = filtered_devices[filtered_devices["Lab"] == lab_filter]
152
- if equipment_type_filter != "All":
153
- filtered_devices = filtered_devices[filtered_devices["Equipment_Type"] == equipment_type_filter]
154
-
155
- filtered_logs = logs_df[logs_df["Device_ID"].isin(filtered_devices["Device_ID"])]
156
- if start_date_dt and end_date_dt:
157
- filtered_logs["Log_Date"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.date
158
- start_date_str = start_date_dt.date()
159
- end_date_str = end_date_dt.date()
160
- filtered_logs = filtered_logs[
161
- (filtered_logs["Log_Date"] >= start_date_str) &
162
- (filtered_logs["Log_Date"] <= end_date_str)
163
- ]
164
- print(f"Filtered logs count: {len(filtered_logs)}") # Debug log
165
-
166
- # Device Cards
167
- device_cards = "Device Cards:\n"
168
- for _, device in filtered_devices.iterrows():
169
- device_logs = filtered_logs[filtered_logs["Device_ID"] == device["Device_ID"]]
170
- usage_count = device_logs["Usage_Count"].sum() if not device_logs.empty else 0
171
- last_log = device_logs["Log_Timestamp"].max() if not device_logs.empty else "No logs"
172
- device_cards += (
173
- f"Device: {device['Device_ID']}, Lab: {device['Lab']}, Type: {device['Equipment_Type']}, "
174
- f"Status: {device['Status']}, Usage Count: {usage_count}, Last Log: {last_log}\n"
175
- )
176
-
177
- # Daily Log Trends (Matplotlib Plot)
178
- daily_trend_plot = None
179
- if not filtered_logs.empty:
180
- filtered_logs["Date"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.date
181
- daily_trends = filtered_logs.groupby("Date")["Usage_Count"].sum().reset_index()
182
- plt.figure(figsize=(8, 4))
183
- plt.plot(daily_trends["Date"], daily_trends["Usage_Count"], marker="o")
184
- plt.title("Daily Log Trends")
185
- plt.xlabel("Date")
186
- plt.ylabel("Total Usage Count")
187
- plt.xticks(rotation=45)
188
- plt.tight_layout()
189
- daily_trend_plot = plt.gcf()
190
- else:
191
- daily_trend_plot = "No data available for Daily Log Trends in the selected range."
192
-
193
- # Weekly Uptime % (Matplotlib Plot)
194
- uptime_plot = None
195
- if not filtered_logs.empty:
196
- filtered_logs["Week"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.isocalendar().week
197
- uptime_data = filtered_logs.groupby("Week")["Status"].value_counts().unstack(fill_value=0)
198
- uptime_data["Uptime_%"] = uptime_data.get("Operational", 0) / (
199
- uptime_data.get("Operational", 0) + uptime_data.get("Down", 0) + uptime_data.get("Maintenance", 0)
200
- ) * 100
201
- plt.figure(figsize=(8, 4))
202
- plt.bar(uptime_data.index, uptime_data["Uptime_%"])
203
- plt.title("Weekly Uptime %")
204
- plt.xlabel("Week")
205
- plt.ylabel("Uptime %")
206
- plt.tight_layout()
207
- uptime_plot = plt.gcf()
208
- else:
209
- uptime_plot = "No data available for Weekly Uptime % in the selected range."
210
-
211
- # Anomaly Alerts (Usage spikes: >2x average usage)
212
- anomaly_alerts = "Anomaly Alerts:\n"
213
- if not filtered_logs.empty:
214
- avg_usage = filtered_logs["Usage_Count"].mean()
215
- anomalies = filtered_logs[filtered_logs["Usage_Count"] > 2 * avg_usage]
216
- if anomalies.empty:
217
- anomaly_alerts += "No anomalies detected.\n"
218
- for _, log in anomalies.iterrows():
219
- anomaly_alerts += (
220
- f"Device: {log['Device_ID']}, Timestamp: {log['Log_Timestamp']}, "
221
- f"Usage Spike: {log['Usage_Count']} (Avg: {avg_usage:.2f})\n"
222
- )
223
- else:
224
- anomaly_alerts += "No data available for anomaly detection.\n"
225
-
226
- # AMC Reminders (simulated)
227
- report = "LabOps Dashboard Report:\n"
228
- report += f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
229
- report += device_cards + "\n"
230
- report += anomaly_alerts + "\n"
231
- report += "AMC Reminders:\n"
232
- if not filtered_logs.empty:
233
- amc_expiring = filtered_logs[pd.to_datetime(filtered_logs["AMC_Expiry"]) <= (datetime.now() + timedelta(days=14))]
234
- if amc_expiring.empty:
235
- report += "No AMC expiries within the next 14 days.\n"
236
- for _, log in amc_expiring.iterrows():
237
- report += (
238
- f"Device: {log['Device_ID']}, AMC Expiry: {log['AMC_Expiry']}\n"
239
- )
240
- else:
241
- report += "No data available for AMC reminders.\n"
242
-
243
- return device_cards, daily_trend_plot, uptime_plot, anomaly_alerts, report, "labops_report.txt"
244
- except Exception as e:
245
- return f"Error: {str(e)}", None, None, None, None, None
246
-
247
- # Define Gradio interface
248
- with gr.Blocks(title="LabOps Dashboard") as demo:
249
- gr.Markdown("# LabOps Dashboard")
250
- gr.Markdown("Monitor smart lab devices, view usage trends, uptime, anomalies, and export reports.")
251
- gr.Markdown("**Note**: Use the calendar picker to select dates or manually enter in YYYY-MM-DD format (e.g., 2025-05-01). If the calendar picker doesn't appear, enter dates manually.")
252
-
253
- # Filters
254
- gr.Markdown("## Filters")
255
- lab_filter = gr.Dropdown(choices=["All"] + list(devices_df["Lab"].unique()), label="Lab Site")
256
- equipment_type_filter = gr.Dropdown(choices=["All"] + list(devices_df["Equipment_Type"].unique()), label="Equipment Type")
257
- start_date = gr.Textbox(
258
- label="Start Date (YYYY-MM-DD)",
259
- placeholder="Select or enter date (e.g., 2025-05-01)",
260
- elem_id="start_date_picker"
261
  )
262
- end_date = gr.Textbox(
263
- label="End Date (YYYY-MM-DD)",
264
- placeholder="Select or enter date (e.g., 2025-05-30)",
265
- elem_id="end_date_picker"
266
- )
267
-
268
- # Custom JavaScript to enable browser-native date picker
269
- gr.HTML("""
270
- <script>
271
- document.addEventListener('DOMContentLoaded', function() {
272
- const startPicker = document.getElementById('start_date_picker');
273
- const endPicker = document.getElementById('end_date_picker');
274
- if (startPicker && endPicker) {
275
- startPicker.type = 'date';
276
- endPicker.type = 'date';
277
- } else {
278
- console.error('Date picker elements not found');
279
- }
280
- });
281
- </script>
282
- """)
283
-
284
- # Dashboard Components
285
- gr.Markdown("## Device Cards")
286
- device_cards_output = gr.Textbox(label="Device Status", lines=10, interactive=False)
287
- gr.Markdown("## Daily Log Trends")
288
- daily_trend_plot = gr.Plot(label="Daily Usage Trends")
289
- gr.Markdown("## Weekly Uptime %")
290
- uptime_plot = gr.Plot(label="Weekly Uptime")
291
- gr.Markdown("## Anomaly Alerts")
292
- anomaly_alerts_output = gr.Textbox(label="Anomaly Alerts", lines=5, interactive=False)
293
-
294
- # Export Report
295
- gr.Markdown("## Export Report")
296
- report_output = gr.Textbox(label="Report Preview", lines=10, interactive=False)
297
- download_button = gr.File(label="Download Report as Text")
298
-
299
- # Update dashboard on filter change
300
- def update_dashboard(lab, equipment, start_date, end_date):
301
- device_cards, daily_trend, uptime, anomalies, report, report_file = process_dashboard_data(
302
- lab, equipment, start_date, end_date
 
 
 
 
 
303
  )
304
- if report_file:
305
- with open(report_file, "w") as f:
306
- f.write(report)
307
- return device_cards, daily_trend, uptime, anomalies, report, report_file
308
-
309
- gr.Button("Update Dashboard").click(
310
- fn=update_dashboard,
311
- inputs=[lab_filter, equipment_type_filter, start_date, end_date],
312
- outputs=[device_cards_output, daily_trend_plot, uptime_plot, anomaly_alerts_output, report_output, download_button]
313
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
- # Launch the app
316
- demo.launch()
 
14
  # Generate devices
15
  for i in range(10):
16
  devices.append({
17
+ "Device_ID": f"Device_{i+1}",
18
  "Lab": random.choice(labs),
19
  "Equipment_Type": random.choice(equipment_types),
20
  "Status": random.choice(["Operational", "Down", "Maintenance"])
21
  })
22
 
23
+ # Generate logs for a broad date range (Jan 1, 2025 to Jun 30, 2025)
24
  start_date = datetime(2025, 1, 1)
25
  end_date = datetime(2025, 6, 30)
26
  for device in devices:
 
29
  logs.append({
30
  "Device_ID": device["Device_ID"],
31
  "Log_Timestamp": current_date.strftime("%Y-%m-%d %H:%M:%S"),
32
+ "Usage_Count": random.randint(0, 50),
33
  "Status": random.choice(["Operational", "Down", "Maintenance"]),
34
  "AMC_Expiry": (current_date + timedelta(days=random.randint(10, 365))).strftime("%Y-%m-%d")
35
  })
 
62
  start_date_dt = datetime.strptime(start_date, "%Y-%m-%d")
63
  end_date_dt = datetime.strptime(end_date, "%Y-%m-%d")
64
  if start_date_dt > end_date_dt:
65
+ return "Error: Start date must be before end date.", None, None, None, "", None
66
  except ValueError as e:
67
+ return f"Error: Invalid date format. Use YYYY-MM-DD (e.g., 2025-05-01). Received: Start={start_date}, End={end_date}", None, None, None, "", None
68
  else:
69
  start_date_dt = datetime(2025, 1, 1)
70
  end_date_dt = datetime(2025, 6, 30)
 
78
 
79
  filtered_logs = logs_df[logs_df["Device_ID"].isin(filtered_devices["Device_ID"])]
80
  if start_date_dt and end_date_dt:
 
81
  filtered_logs["Log_Date"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.date
82
  start_date_str = start_date_dt.date()
83
  end_date_str = end_date_dt.date()
 
94
  usage_count = device_logs["Usage_Count"].sum() if not device_logs.empty else 0
95
  last_log = device_logs["Log_Timestamp"].max() if not device_logs.empty else "No logs"
96
  device_cards += (
97
+ f"Device: {device['Device_ID']}, Lab: {device['Lab']}, Type: {device['Equipment_Type']}, "
98
+ f"Status: {device['Status']}, Usage Count: {usage_count}, Last Log: {last_log}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  )
100
+
101
+ # Daily Log Trends (Matplotlib Plot)
102
+ daily_trend_plot = None
103
+ if not filtered_logs.empty:
104
+ filtered_logs["Date"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.date
105
+ daily_trends = filtered_logs.groupby("Date")["Usage_Count"].sum().reset_index()
106
+ plt.figure(figsize=(8, 4))
107
+ plt.plot(daily_trends["Date"], daily_trends["Usage_Count"], marker="o", color="#1f77b4")
108
+ plt.title("Daily Log Trends")
109
+ plt.xlabel("Date")
110
+ plt.ylabel("Total Usage Count")
111
+ plt.xticks(rotation=45)
112
+ plt.tight_layout()
113
+ daily_trend_plot = plt.gcf()
114
+ else:
115
+ daily_trend_plot = "No data available for Daily Log Trends in the selected range."
116
+
117
+ # Weekly Uptime % (Matplotlib Plot)
118
+ uptime_plot = None
119
+ if not filtered_logs.empty:
120
+ filtered_logs["Week"] = pd.to_datetime(filtered_logs["Log_Timestamp"]).dt.isocalendar().week
121
+ uptime_data = filtered_logs.groupby("Week")["Status"].value_counts().unstack(fill_value=0)
122
+ uptime_data["Uptime_%"] = uptime_data.get("Operational", 0) / (
123
+ uptime_data.get("Operational", 0) + uptime_data.get("Down", 0) + uptime_data.get("Maintenance", 0)
124
+ ) * 100
125
+ plt.figure(figsize=(8, 4))
126
+ plt.bar(uptime_data.index, uptime_data["Uptime_%"], color="#ff7f0e")
127
+ plt.title("Weekly Uptime %")
128
+ plt.xlabel("Week")
129
+ plt.ylabel("Uptime %")
130
+ plt.tight_layout()
131
+ uptime_plot = plt.gcf()
132
+ else:
133
+ uptime_plot = "No data available for Weekly Uptime % in the selected range."
134
+
135
+ # Anomaly Alerts (Usage spikes: >2x average usage)
136
+ anomaly_alerts = "Anomaly Alerts:\n"
137
+ if not filtered_logs.empty:
138
+ avg_usage = filtered_logs["Usage_Count"].mean()
139
+ anomalies = filtered_logs[filtered_logs["Usage_Count"] > 2 * avg_usage]
140
+ if anomalies.empty:
141
+ anomaly_alerts += "No anomalies detected.\n"
142
+ for _, log in anomalies.iterrows():
143
+ anomaly_alerts += (
144
+ f"Device: {log['Device_ID']}, Timestamp: {log['Log_Timestamp']}, "
145
+ f"Usage Spike: {log['Usage_Count']} (Avg: {avg_usage:.2f})\n"
146
  )
147
+ else:
148
+ anomaly_alerts += "No data available for anomaly detection.\n"
149
+
150
+ # AMC Reminders (simulated)
151
+ report = "LabOps Dashboard Report:\n"
152
+ report += f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
153
+ report += device_cards + "\n"
154
+ report += anomaly_alerts + "\n"
155
+ report += "AMC Reminders:\n"
156
+ if not filtered_logs.empty:
157
+ amc_expiring = filtered_logs[pd.to_datetime(filtered_logs["AMC_Expiry"]) <= (datetime.now() + timedelta(days=14))]
158
+ if amc_expiring.empty:
159
+ report += "No AMC expiries within the next 14 days.\n"
160
+ for _, log in amc_expiring.iterrows():
161
+ report += (
162
+ f"Device: {log['Device_ID']}, AMC Expiry: {log['AMC_Expiry']}\n"
163
+ )
164
+ else:
165
+ report += "No data available for AMC reminders.\n"
166
+
167
+ return device_cards, daily_trend_plot, uptime_plot, anomaly_alerts, report, "labops_report.txt"
168
+ except Exception as e:
169
+ return f"Error: {str(e)}", None, None, None, "", None
170
+
171
+ # Define Gradio interface
172
+ with gr.Blocks(title="LabOps Dashboard") as demo:
173
+ gr.Markdown("# LabOps Dashboard")
174
+ gr.Markdown("Monitor smart lab devices, view usage trends, uptime, anomalies, and export reports.")
175
+ gr.Markdown("**Note**: Use the calendar picker to select dates or manually enter in YYYY-MM-DD format (e.g., 2025-05-01). If the calendar picker doesn't appear, enter dates manually.")
176
+
177
+ # Filters
178
+ gr.Markdown("## Filters")
179
+ lab_filter = gr.Dropdown(choices=["All"] + list(devices_df["Lab"].unique()), label="Lab Site")
180
+ equipment_type_filter = gr.Dropdown(choices=["All"] + list(devices_df["Equipment_Type"].unique()), label="Equipment Type")
181
+ start_date = gr.Textbox(
182
+ label="Start Date (YYYY-MM-DD)",
183
+ placeholder="Select or enter date (e.g., 2025-05-01)",
184
+ elem_id="start_date_picker"
185
+ )
186
+ end_date = gr.Textbox(
187
+ label="End Date (YYYY-MM-DD)",
188
+ placeholder="Select or enter date (e.g., 2025-05-30)",
189
+ elem_id="end_date_picker"
190
+ )
191
+
192
+ # Custom JavaScript to enable browser-native date picker
193
+ gr.HTML("""
194
+ <script>
195
+ document.addEventListener('DOMContentLoaded', function() {
196
+ const startPicker = document.getElementById('start_date_picker');
197
+ const endPicker = document.getElementById('end_date_picker');
198
+ if (startPicker && endPicker) {
199
+ startPicker.type = 'date';
200
+ endPicker.type = 'date';
201
+ } else {
202
+ console.error('Date picker elements not found');
203
+ }
204
+ });
205
+ </script>
206
+ """)
207
+
208
+ # Dashboard Components
209
+ gr.Markdown("## Device Cards")
210
+ device_cards_output = gr.Textbox(label="Device Status", lines=10, interactive=False)
211
+ gr.Markdown("## Daily Log Trends")
212
+ daily_trend_plot = gr.Plot(label="Daily Usage Trends")
213
+ gr.Markdown("## Weekly Uptime %")
214
+ uptime_plot = gr.Plot(label="Weekly Uptime")
215
+ gr.Markdown("## Anomaly Alerts")
216
+ anomaly_alerts_output = gr.Textbox(label="Anomaly Alerts", lines=5, interactive=False)
217
+
218
+ # Export Report
219
+ gr.Markdown("## Export Report")
220
+ report_output = gr.Textbox(label="Report Preview", lines=10, interactive=False)
221
+ download_button = gr.File(label="Download Report as Text")
222
+
223
+ # Update dashboard on filter change
224
+ def update_dashboard(lab, equipment, start_date, end_date):
225
+ device_cards, daily_trend, uptime, anomalies, report, report_file = process_dashboard_data(
226
+ lab, equipment, start_date, end_date
227
+ )
228
+ if report_file:
229
+ with open(report_file, "w") as f:
230
+ f.write(report)
231
+ return device_cards, daily_trend, uptime, anomalies, report, report_file
232
+
233
+ gr.Button("Update Dashboard").click(
234
+ fn=update_dashboard,
235
+ inputs=[lab_filter, equipment_type_filter, start_date, end_date],
236
+ outputs=[device_cards_output, daily_trend_plot, uptime_plot, anomaly_alerts_output, report_output, download_button]
237
+ )
238
 
239
+ # Launch the app
240
+ demo.launch()