MaheshP98 commited on
Commit
7037721
·
verified ·
1 Parent(s): 9a6ae7c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -28
app.py CHANGED
@@ -13,7 +13,7 @@ import tempfile
13
  # Configure logging to match the log format
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s,%(msecs)03d - %(levelname)s - %(message)s')
15
 
16
- # CSS styling for the Gradio interface with a dark theme and high-contrast text
17
  css = """
18
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
19
  @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
@@ -34,7 +34,7 @@ h1 {
34
  }
35
 
36
  .gr-button {
37
- background-color: #2DD4BF;
38
  color: #1F2937;
39
  border: none;
40
  border-radius: 8px;
@@ -44,7 +44,7 @@ h1 {
44
  }
45
 
46
  .gr-button:hover {
47
- background-color: #26A69A;
48
  }
49
 
50
  .dashboard-container {
@@ -226,21 +226,24 @@ def validate_csv(df):
226
  Validate that the CSV has the required columns.
227
  Returns True if valid, False otherwise with an error message.
228
  """
229
- required_columns = ['equipment', 'usage_count', 'status', 'amc_expiry']
230
  missing_columns = [col for col in required_columns if col not in df.columns]
231
  if missing_columns:
232
  return False, f"Missing required columns: {', '.join(missing_columns)}"
233
  # Validate data types
234
  try:
235
- df['usage_count'] = pd.to_numeric(df['usage_count'], errors='raise')
236
- df['amc_expiry'] = pd.to_datetime(df['amc_expiry'], errors='raise')
 
 
 
237
  except Exception as e:
238
  return False, f"Invalid data types: {str(e)}"
239
  return True, ""
240
 
241
  def generate_device_cards(df, anomaly_df):
242
  """
243
- Generate HTML for device cards showing health, usage count, and status.
244
  Returns an HTML string.
245
  """
246
  if anomaly_df is not None:
@@ -249,15 +252,21 @@ def generate_device_cards(df, anomaly_df):
249
  df['anomaly'] = "Unknown"
250
 
251
  html = []
252
- for equipment in df['equipment'].unique():
253
- device_data = df[df['equipment'] == equipment].iloc[-1] # Latest record
254
  anomaly_class = "anomaly-unusual" if device_data['anomaly'] == "Unusual" else "anomaly-normal"
 
 
 
255
  html.append(f"""
256
  <div class="card device-card">
257
- <h2><i class="fas fa-microchip"></i> {equipment}</h2>
258
  <p><strong>Status:</strong> {device_data['status']}</p>
259
- <p><strong>Usage Count:</strong> {device_data['usage_count']}</p>
 
260
  <p><strong>Activity:</strong> <span class="anomaly-badge {anomaly_class}">{device_data['anomaly']}</span></p>
 
 
261
  <p><strong>AMC Expiry:</strong> {device_data['amc_expiry'].strftime('%Y-%m-%d')}</p>
262
  </div>
263
  """)
@@ -265,7 +274,7 @@ def generate_device_cards(df, anomaly_df):
265
 
266
  def generate_summary(combined_df, anomaly_df, amc_df, plot_path, pdf_path):
267
  """
268
- Generate a detailed and easy-to-understand summary of the processing results.
269
  Returns a markdown string for display in the Gradio interface.
270
  """
271
  summary = []
@@ -274,7 +283,9 @@ def generate_summary(combined_df, anomaly_df, amc_df, plot_path, pdf_path):
274
  summary.append("## Overview")
275
  total_records = len(combined_df)
276
  unique_devices = combined_df['equipment'].unique()
 
277
  summary.append(f"We processed **{total_records} log entries** for **{len(unique_devices)} devices** ({', '.join(unique_devices)}).")
 
278
  summary.append("This dashboard provides real-time insights into device health, usage patterns, and maintenance needs.\n")
279
 
280
  # Downtime Insights (Anomalies)
@@ -283,9 +294,10 @@ def generate_summary(combined_df, anomaly_df, amc_df, plot_path, pdf_path):
283
  num_anomalies = sum(anomaly_df['anomaly'] == -1)
284
  if num_anomalies > 0:
285
  summary.append(f"**{num_anomalies} potential downtime risks** detected:")
286
- anomaly_records = anomaly_df[anomaly_df['anomaly'] == -1][['equipment', 'usage_count', 'status']]
287
  for _, row in anomaly_records.iterrows():
288
- summary.append(f"- **{row['equipment']}** (Usage: {row['usage_count']}, Status: {row['status']}) - Indicates possible overuse or underuse.")
 
289
  else:
290
  summary.append("No potential downtime risks detected. All devices are operating within expected patterns.")
291
  else:
@@ -321,8 +333,8 @@ def generate_flowchart_html():
321
  """
322
  steps = [
323
  ("Upload CSV File(s)", "User uploads log files in CSV format."),
324
- ("Validate Data", "Checks for required columns (equipment, usage_count, status, amc_expiry) and correct data types."),
325
- ("Generate Usage Chart", "Creates a bar chart showing usage counts by device and status (e.g., Active, Inactive)."),
326
  ("Detect Downtime Risks", "Uses Local Outlier Factor to identify devices with unusual usage patterns (e.g., too high or too low)."),
327
  ("Check Maintenance Dates", "Identifies devices with AMC expiries within 7 days from 2025-06-05."),
328
  ("Create PDF Report", "Generates a detailed PDF with data tables, insights, and this flowchart.")
@@ -360,6 +372,12 @@ def process_files(uploaded_files):
360
  try:
361
  df = pd.read_csv(file.name)
362
  logging.info(f"Loaded {len(df)} records from {file.name}")
 
 
 
 
 
 
363
  # Validate CSV structure
364
  is_valid, error_msg = validate_csv(df)
365
  if not is_valid:
@@ -437,7 +455,7 @@ def generate_usage_plot(df):
437
  try:
438
  plt.figure(figsize=(12, 6))
439
  # Define colors for statuses (adjusted for dark theme visibility)
440
- status_colors = {'Active': '#2DD4BF', 'Inactive': '#F87171', 'Down': '#FBBF24', 'Online': '#10B981'}
441
  for status in df['status'].unique():
442
  subset = df[df['status'] == status]
443
  plt.bar(
@@ -447,7 +465,7 @@ def generate_usage_plot(df):
447
  color=status_colors.get(status, '#6B7280')
448
  )
449
  plt.xlabel("Equipment (Status)", fontsize=12, color='#D1D5DB')
450
- plt.ylabel("Usage Count", fontsize=12, color='#D1D5DB')
451
  plt.title("Device Usage Overview", fontsize=14, color='#FFFFFF')
452
  plt.legend(title="Status")
453
  plt.xticks(rotation=45, ha='right', color='#D1D5DB')
@@ -541,13 +559,16 @@ def generate_pdf_report(original_df, anomaly_df, amc_df):
541
  c.drawString(50, y, f"Total Records: {len(original_df)}")
542
  y -= 20
543
  c.drawString(50, y, f"Unique Devices: {', '.join(original_df['equipment'].unique())}")
 
 
 
544
  y -= 40
545
 
546
  # Device Log Details
547
  y = draw_section_title("Device Log Details", y)
548
  c.setFont("Helvetica-Bold", 10)
549
- headers = ["Equipment", "Usage Count", "Status", "AMC Expiry", "Activity"]
550
- x_positions = [50, 150, 250, 350, 450]
551
  for i, header in enumerate(headers):
552
  c.drawString(x_positions[i], y, header)
553
  c.line(50, y - 5, width - 50, y - 5)
@@ -559,10 +580,13 @@ def generate_pdf_report(original_df, anomaly_df, amc_df):
559
  output_df['anomaly'] = anomaly_df['anomaly'].map({1: "Normal", -1: "Unusual"})
560
  for _, row in output_df.iterrows():
561
  c.drawString(50, y, str(row['equipment']))
562
- c.drawString(150, y, str(row['usage_count']))
563
- c.drawString(250, y, str(row['status']))
564
- c.drawString(350, y, str(row['amc_expiry'].strftime('%Y-%m-%d')))
565
- c.drawString(450, y, str(row['anomaly']))
 
 
 
566
  y -= 20
567
  if y < 50:
568
  c.showPage()
@@ -578,12 +602,13 @@ def generate_pdf_report(original_df, anomaly_df, amc_df):
578
  c.drawString(50, y, f"Potential Downtime Risks Detected: {num_anomalies}")
579
  y -= 20
580
  if num_anomalies > 0:
581
- anomaly_records = anomaly_df[anomaly_df['anomaly'] == -1][['equipment', 'usage_count', 'status']]
582
  c.drawString(50, y, "Details:")
583
  y -= 20
584
  c.setFont("Helvetica-Oblique", 10)
585
  for _, row in anomaly_records.iterrows():
586
- c.drawString(50, y, f"{row['equipment']}: Usage Count = {row['usage_count']}, Status = {row['status']}")
 
587
  y -= 20
588
  c.drawString(70, y, "Note: This device’s usage is significantly higher or lower than others, which may indicate overuse or underuse.")
589
  y -= 20
@@ -642,8 +667,8 @@ def generate_pdf_report(original_df, anomaly_df, amc_df):
642
  c.setFont("Helvetica", 10)
643
  flowchart = [
644
  ("1. Upload CSV File(s)", "User uploads log files in CSV format containing device usage data."),
645
- ("2. Validate Data", "Ensures all required columns (equipment, usage_count, status, amc_expiry) are present and data types are correct (e.g., usage_count as numeric, amc_expiry as date)."),
646
- ("3. Generate Usage Chart", "Creates a bar chart showing usage counts by device and status (e.g., Active, Inactive) to visualize usage patterns."),
647
  ("4. Detect Downtime Risks", "Uses Local Outlier Factor (LOF) algorithm to identify devices with unusual usage patterns by comparing local density of usage counts (contamination=0.1, n_neighbors=5)."),
648
  ("5. Check Maintenance Dates", "Identifies devices with AMC expiries within 7 days from 2025-06-05, calculating days left and urgency (urgent if ≤3 days)."),
649
  ("6. Create PDF Report", "Generates this PDF with a data table, downtime insights, maintenance alerts, and this detailed flowchart.")
 
13
  # Configure logging to match the log format
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s,%(msecs)03d - %(levelname)s - %(message)s')
15
 
16
+ # CSS styling for the Gradio interface with a dark theme and blue button
17
  css = """
18
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
19
  @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
 
34
  }
35
 
36
  .gr-button {
37
+ background-color: #3B82F6;
38
  color: #1F2937;
39
  border: none;
40
  border-radius: 8px;
 
44
  }
45
 
46
  .gr-button:hover {
47
+ background-color: #2563EB;
48
  }
49
 
50
  .dashboard-container {
 
226
  Validate that the CSV has the required columns.
227
  Returns True if valid, False otherwise with an error message.
228
  """
229
+ required_columns = ['device_id', 'usage_hours', 'amc_date', 'status']
230
  missing_columns = [col for col in required_columns if col not in df.columns]
231
  if missing_columns:
232
  return False, f"Missing required columns: {', '.join(missing_columns)}"
233
  # Validate data types
234
  try:
235
+ df['usage_hours'] = pd.to_numeric(df['usage_hours'], errors='raise')
236
+ df['amc_date'] = pd.to_datetime(df['amc_date'], errors='raise')
237
+ # Handle 'downtime' if present
238
+ if 'downtime' in df.columns:
239
+ df['downtime'] = pd.to_numeric(df['downtime'], errors='raise')
240
  except Exception as e:
241
  return False, f"Invalid data types: {str(e)}"
242
  return True, ""
243
 
244
  def generate_device_cards(df, anomaly_df):
245
  """
246
+ Generate HTML for device cards showing health, usage hours, downtime, status, log type, and timestamp.
247
  Returns an HTML string.
248
  """
249
  if anomaly_df is not None:
 
252
  df['anomaly'] = "Unknown"
253
 
254
  html = []
255
+ for device_id in df['equipment'].unique():
256
+ device_data = df[df['equipment'] == device_id].iloc[-1] # Latest record
257
  anomaly_class = "anomaly-unusual" if device_data['anomaly'] == "Unusual" else "anomaly-normal"
258
+ downtime = device_data.get('downtime', 'N/A')
259
+ log_type = device_data.get('log_type', 'N/A')
260
+ timestamp = device_data.get('timestamp', 'N/A')
261
  html.append(f"""
262
  <div class="card device-card">
263
+ <h2><i class="fas fa-microchip"></i> {device_id}</h2>
264
  <p><strong>Status:</strong> {device_data['status']}</p>
265
+ <p><strong>Usage Hours:</strong> {device_data['usage_count']}</p>
266
+ <p><strong>Downtime (hrs):</strong> {downtime}</p>
267
  <p><strong>Activity:</strong> <span class="anomaly-badge {anomaly_class}">{device_data['anomaly']}</span></p>
268
+ <p><strong>Log Type:</strong> {log_type}</p>
269
+ <p><strong>Last Log:</strong> {timestamp}</p>
270
  <p><strong>AMC Expiry:</strong> {device_data['amc_expiry'].strftime('%Y-%m-%d')}</p>
271
  </div>
272
  """)
 
274
 
275
  def generate_summary(combined_df, anomaly_df, amc_df, plot_path, pdf_path):
276
  """
277
+ Generate a detailed and easy-to-understand summary of the processing results, including downtime.
278
  Returns a markdown string for display in the Gradio interface.
279
  """
280
  summary = []
 
283
  summary.append("## Overview")
284
  total_records = len(combined_df)
285
  unique_devices = combined_df['equipment'].unique()
286
+ total_downtime = combined_df['downtime'].sum() if 'downtime' in combined_df.columns else 0
287
  summary.append(f"We processed **{total_records} log entries** for **{len(unique_devices)} devices** ({', '.join(unique_devices)}).")
288
+ summary.append(f"Total downtime recorded: **{total_downtime} hours**.")
289
  summary.append("This dashboard provides real-time insights into device health, usage patterns, and maintenance needs.\n")
290
 
291
  # Downtime Insights (Anomalies)
 
294
  num_anomalies = sum(anomaly_df['anomaly'] == -1)
295
  if num_anomalies > 0:
296
  summary.append(f"**{num_anomalies} potential downtime risks** detected:")
297
+ anomaly_records = anomaly_df[anomaly_df['anomaly'] == -1][['equipment', 'usage_count', 'status', 'downtime']]
298
  for _, row in anomaly_records.iterrows():
299
+ downtime = row['downtime'] if 'downtime' in row else 'N/A'
300
+ summary.append(f"- **{row['equipment']}** (Usage: {row['usage_count']}, Status: {row['status']}, Downtime: {downtime} hrs) - Indicates possible overuse or underuse.")
301
  else:
302
  summary.append("No potential downtime risks detected. All devices are operating within expected patterns.")
303
  else:
 
333
  """
334
  steps = [
335
  ("Upload CSV File(s)", "User uploads log files in CSV format."),
336
+ ("Validate Data", "Checks for required columns (device_id, usage_hours, amc_date, status) and correct data types."),
337
+ ("Generate Usage Chart", "Creates a bar chart showing usage hours by device and status (e.g., ok, warning)."),
338
  ("Detect Downtime Risks", "Uses Local Outlier Factor to identify devices with unusual usage patterns (e.g., too high or too low)."),
339
  ("Check Maintenance Dates", "Identifies devices with AMC expiries within 7 days from 2025-06-05."),
340
  ("Create PDF Report", "Generates a detailed PDF with data tables, insights, and this flowchart.")
 
372
  try:
373
  df = pd.read_csv(file.name)
374
  logging.info(f"Loaded {len(df)} records from {file.name}")
375
+ # Rename columns to match expected names
376
+ df = df.rename(columns={
377
+ 'device_id': 'equipment',
378
+ 'usage_hours': 'usage_count',
379
+ 'amc_date': 'amc_expiry'
380
+ })
381
  # Validate CSV structure
382
  is_valid, error_msg = validate_csv(df)
383
  if not is_valid:
 
455
  try:
456
  plt.figure(figsize=(12, 6))
457
  # Define colors for statuses (adjusted for dark theme visibility)
458
+ status_colors = {'ok': '#2DD4BF', 'warning': '#F87171', 'normal': '#10B981', 'down': '#FBBF24'}
459
  for status in df['status'].unique():
460
  subset = df[df['status'] == status]
461
  plt.bar(
 
465
  color=status_colors.get(status, '#6B7280')
466
  )
467
  plt.xlabel("Equipment (Status)", fontsize=12, color='#D1D5DB')
468
+ plt.ylabel("Usage Hours", fontsize=12, color='#D1D5DB')
469
  plt.title("Device Usage Overview", fontsize=14, color='#FFFFFF')
470
  plt.legend(title="Status")
471
  plt.xticks(rotation=45, ha='right', color='#D1D5DB')
 
559
  c.drawString(50, y, f"Total Records: {len(original_df)}")
560
  y -= 20
561
  c.drawString(50, y, f"Unique Devices: {', '.join(original_df['equipment'].unique())}")
562
+ y -= 20
563
+ total_downtime = original_df['downtime'].sum() if 'downtime' in original_df.columns else 0
564
+ c.drawString(50, y, f"Total Downtime: {total_downtime} hours")
565
  y -= 40
566
 
567
  # Device Log Details
568
  y = draw_section_title("Device Log Details", y)
569
  c.setFont("Helvetica-Bold", 10)
570
+ headers = ["Equipment", "Timestamp", "Usage Hours", "Downtime (hrs)", "Status", "Log Type", "AMC Expiry", "Activity"]
571
+ x_positions = [50, 110, 190, 260, 320, 370, 430, 490]
572
  for i, header in enumerate(headers):
573
  c.drawString(x_positions[i], y, header)
574
  c.line(50, y - 5, width - 50, y - 5)
 
580
  output_df['anomaly'] = anomaly_df['anomaly'].map({1: "Normal", -1: "Unusual"})
581
  for _, row in output_df.iterrows():
582
  c.drawString(50, y, str(row['equipment']))
583
+ c.drawString(110, y, str(row.get('timestamp', 'N/A')))
584
+ c.drawString(190, y, str(row['usage_count']))
585
+ c.drawString(260, y, str(row.get('downtime', 'N/A')))
586
+ c.drawString(320, y, str(row['status']))
587
+ c.drawString(370, y, str(row.get('log_type', 'N/A')))
588
+ c.drawString(430, y, str(row['amc_expiry'].strftime('%Y-%m-%d')))
589
+ c.drawString(490, y, str(row['anomaly']))
590
  y -= 20
591
  if y < 50:
592
  c.showPage()
 
602
  c.drawString(50, y, f"Potential Downtime Risks Detected: {num_anomalies}")
603
  y -= 20
604
  if num_anomalies > 0:
605
+ anomaly_records = anomaly_df[anomaly_df['anomaly'] == -1][['equipment', 'usage_count', 'status', 'downtime']]
606
  c.drawString(50, y, "Details:")
607
  y -= 20
608
  c.setFont("Helvetica-Oblique", 10)
609
  for _, row in anomaly_records.iterrows():
610
+ downtime = row['downtime'] if 'downtime' in row else 'N/A'
611
+ c.drawString(50, y, f"{row['equipment']}: Usage Hours = {row['usage_count']}, Status = {row['status']}, Downtime = {downtime} hrs")
612
  y -= 20
613
  c.drawString(70, y, "Note: This device’s usage is significantly higher or lower than others, which may indicate overuse or underuse.")
614
  y -= 20
 
667
  c.setFont("Helvetica", 10)
668
  flowchart = [
669
  ("1. Upload CSV File(s)", "User uploads log files in CSV format containing device usage data."),
670
+ ("2. Validate Data", "Ensures all required columns (device_id, usage_hours, amc_date, status) are present and data types are correct (e.g., usage_hours as numeric, amc_date as date)."),
671
+ ("3. Generate Usage Chart", "Creates a bar chart showing usage hours by device and status (e.g., ok, warning) to visualize usage patterns."),
672
  ("4. Detect Downtime Risks", "Uses Local Outlier Factor (LOF) algorithm to identify devices with unusual usage patterns by comparing local density of usage counts (contamination=0.1, n_neighbors=5)."),
673
  ("5. Check Maintenance Dates", "Identifies devices with AMC expiries within 7 days from 2025-06-05, calculating days left and urgency (urgent if ≤3 days)."),
674
  ("6. Create PDF Report", "Generates this PDF with a data table, downtime insights, maintenance alerts, and this detailed flowchart.")