Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,6 +11,7 @@ from simple_salesforce import Salesforce
|
|
| 11 |
import os
|
| 12 |
import io
|
| 13 |
import time
|
|
|
|
| 14 |
|
| 15 |
# Configure logging
|
| 16 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
@@ -200,7 +201,7 @@ def save_to_salesforce(df, reminders_df):
|
|
| 200 |
records.append(record)
|
| 201 |
|
| 202 |
if records:
|
| 203 |
-
batch_size =
|
| 204 |
for i in range(0, len(records), batch_size):
|
| 205 |
batch = records[i:i + batch_size]
|
| 206 |
try:
|
|
@@ -237,9 +238,9 @@ def detect_anomalies(df):
|
|
| 237 |
if "usage_hours" not in df.columns or "downtime" not in df.columns:
|
| 238 |
return "Anomaly detection requires 'usage_hours' and 'downtime' columns.", pd.DataFrame()
|
| 239 |
features = df[["usage_hours", "downtime"]].fillna(0)
|
| 240 |
-
if len(features) >
|
| 241 |
-
features = features.sample(n=
|
| 242 |
-
iso_forest = IsolationForest(contamination=0.1, random_state=42)
|
| 243 |
df["anomaly"] = iso_forest.fit_predict(features)
|
| 244 |
anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
|
| 245 |
if anomalies.empty:
|
|
@@ -263,7 +264,7 @@ def check_amc_reminders(df, current_date):
|
|
| 263 |
reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]]
|
| 264 |
if reminders.empty:
|
| 265 |
return "No AMC reminders due within the next 30 days.", reminders
|
| 266 |
-
result = "\n".join([f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}" for _, row in reminders.head(5).iterrows()])
|
| 267 |
logging.info(f"AMC reminders generation took {time.time() - start_time:.2f} seconds")
|
| 268 |
return result, reminders
|
| 269 |
except Exception as e:
|
|
@@ -419,32 +420,12 @@ def generate_device_cards(df):
|
|
| 419 |
logging.error(f"Failed to generate device cards: {str(e)}")
|
| 420 |
return f'<p>Error generating device cards: {str(e)}</p>'
|
| 421 |
|
| 422 |
-
# Generate monthly status
|
| 423 |
-
def generate_monthly_status(df, selected_month):
|
| 424 |
-
try:
|
| 425 |
-
total_devices = df['device_id'].nunique()
|
| 426 |
-
total_usage_hours = df['usage_hours'].sum()
|
| 427 |
-
total_downtime = df['downtime'].sum()
|
| 428 |
-
avg_usage = total_usage_hours / total_devices if total_devices > 0 else 0
|
| 429 |
-
avg_downtime = total_downtime / total_devices if total_devices > 0 else 0
|
| 430 |
-
return f"""
|
| 431 |
-
Monthly Status for {selected_month}:
|
| 432 |
-
- Total Devices: {total_devices}
|
| 433 |
-
- Total Usage Hours: {total_usage_hours:.2f}
|
| 434 |
-
- Total Downtime Hours: {total_downtime:.2f}
|
| 435 |
-
- Average Usage per Device: {avg_usage:.2f} hours
|
| 436 |
-
- Average Downtime per Device: {avg_downtime:.2f} hours
|
| 437 |
-
"""
|
| 438 |
-
except Exception as e:
|
| 439 |
-
logging.error(f"Failed to generate monthly status: {str(e)}")
|
| 440 |
-
return f"Failed to generate monthly status: {str(e)}"
|
| 441 |
-
|
| 442 |
# Generate PDF content
|
| 443 |
-
def generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights, device_cards_html, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart
|
| 444 |
if not reportlab_available:
|
| 445 |
return None
|
| 446 |
try:
|
| 447 |
-
pdf_path = f"
|
| 448 |
doc = SimpleDocTemplate(pdf_path, pagesize=letter)
|
| 449 |
styles = getSampleStyleSheet()
|
| 450 |
story = []
|
|
@@ -452,16 +433,10 @@ def generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights
|
|
| 452 |
def safe_paragraph(text, style):
|
| 453 |
return Paragraph(str(text).replace('\n', '<br/>'), style) if text else Paragraph("", style)
|
| 454 |
|
| 455 |
-
story.append(Paragraph("LabOps
|
| 456 |
story.append(Paragraph(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
|
| 457 |
story.append(Spacer(1, 12))
|
| 458 |
|
| 459 |
-
if selected_month != "All":
|
| 460 |
-
monthly_status = generate_monthly_status(df, selected_month)
|
| 461 |
-
story.append(Paragraph("Monthly Status Summary", styles['Heading2']))
|
| 462 |
-
story.append(safe_paragraph(monthly_status, styles['Normal']))
|
| 463 |
-
story.append(Spacer(1, 12))
|
| 464 |
-
|
| 465 |
story.append(Paragraph("Summary Report", styles['Heading2']))
|
| 466 |
story.append(safe_paragraph(summary, styles['Normal']))
|
| 467 |
story.append(Spacer(1, 12))
|
|
@@ -516,7 +491,7 @@ def generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights
|
|
| 516 |
return None
|
| 517 |
|
| 518 |
# Main processing function
|
| 519 |
-
async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range,
|
| 520 |
start_time = time.time()
|
| 521 |
try:
|
| 522 |
if not file_obj:
|
|
@@ -540,7 +515,7 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 540 |
"downtime": "float32",
|
| 541 |
"amc_date": "string"
|
| 542 |
}
|
| 543 |
-
df = pd.read_csv(file_path, dtype=dtypes)
|
| 544 |
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 545 |
if missing_columns:
|
| 546 |
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
|
|
@@ -564,12 +539,6 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 564 |
start_date = today + pd.Timedelta(days=days_start)
|
| 565 |
end_date = today + pd.Timedelta(days=days_end) + pd.Timedelta(days=1) - pd.Timedelta(seconds=1)
|
| 566 |
filtered_df = filtered_df[(filtered_df['timestamp'] >= start_date) & (filtered_df['timestamp'] <= end_date)]
|
| 567 |
-
if month_filter and month_filter != "All":
|
| 568 |
-
selected_date = pd.to_datetime(month_filter, format="%B %Y")
|
| 569 |
-
filtered_df = filtered_df[
|
| 570 |
-
(filtered_df['timestamp'].dt.year == selected_date.year) &
|
| 571 |
-
(filtered_df['timestamp'].dt.month == selected_date.month)
|
| 572 |
-
]
|
| 573 |
|
| 574 |
if filtered_df.empty:
|
| 575 |
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
|
|
@@ -579,7 +548,7 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 579 |
preview_html = preview_df.to_html(index=False, classes='table table-striped', border=0)
|
| 580 |
|
| 581 |
# Run tasks concurrently
|
| 582 |
-
with ThreadPoolExecutor(max_workers=
|
| 583 |
future_summary = executor.submit(summarize_logs, filtered_df)
|
| 584 |
future_anomalies = executor.submit(detect_anomalies, filtered_df)
|
| 585 |
future_amc = executor.submit(check_amc_reminders, filtered_df, datetime.now())
|
|
@@ -601,16 +570,16 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 601 |
downtime_chart = future_downtime_chart.result()
|
| 602 |
daily_log_chart = future_daily_log_chart.result()
|
| 603 |
weekly_uptime_chart = future_weekly_uptime_chart.result()
|
| 604 |
-
anomaly_alerts_chart = create_anomaly_alerts_chart(anomalies_df)
|
| 605 |
device_cards = future_device_cards.result()
|
| 606 |
|
| 607 |
save_to_salesforce(filtered_df, reminders_df)
|
| 608 |
-
pdf_file = generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart
|
| 609 |
|
| 610 |
elapsed_time = time.time() - start_time
|
| 611 |
logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
|
| 612 |
-
if elapsed_time >
|
| 613 |
-
logging.warning(f"Processing time exceeded
|
| 614 |
|
| 615 |
return (summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights, pdf_file, current_modified_time)
|
| 616 |
except Exception as e:
|
|
@@ -620,7 +589,7 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 620 |
# Update filters
|
| 621 |
def update_filters(file_obj):
|
| 622 |
if not file_obj:
|
| 623 |
-
return gr.update(choices=['All'], value='All'), gr.update(choices=['All'], value='All')
|
| 624 |
try:
|
| 625 |
with open(file_obj.name, 'rb') as f:
|
| 626 |
csv_content = f.read().decode('utf-8')
|
|
@@ -629,12 +598,11 @@ def update_filters(file_obj):
|
|
| 629 |
|
| 630 |
lab_site_options = ['All'] + [site for site in df['lab_site'].dropna().astype(str).unique().tolist() if site.strip()] if 'lab_site' in df.columns else ['All']
|
| 631 |
equipment_type_options = ['All'] + [equip for equip in df['equipment_type'].dropna().astype(str).unique().tolist() if equip.strip()] if 'equipment_type' in df.columns else ['All']
|
| 632 |
-
month_options = ['All'] + sorted(df['timestamp'].dt.strftime('%B %Y').dropna().unique().tolist()) if 'timestamp' in df.columns else ['All']
|
| 633 |
|
| 634 |
-
return gr.update(choices=lab_site_options, value='All'), gr.update(choices=equipment_type_options, value='All')
|
| 635 |
except Exception as e:
|
| 636 |
logging.error(f"Failed to update filters: {str(e)}")
|
| 637 |
-
return gr.update(choices=['All'], value='All'), gr.update(choices=['All'], value='All')
|
| 638 |
|
| 639 |
# Gradio Interface
|
| 640 |
try:
|
|
@@ -664,7 +632,6 @@ try:
|
|
| 664 |
lab_site_filter = gr.Dropdown(label="Lab Site", choices=['All'], value='All', interactive=True)
|
| 665 |
equipment_type_filter = gr.Dropdown(label="Equipment Type", choices=['All'], value='All', interactive=True)
|
| 666 |
date_range_filter = gr.Slider(label="Date Range (Days from Today)", minimum=-365, maximum=0, step=1, value=[-30, 0])
|
| 667 |
-
month_filter = gr.Dropdown(label="Select Month for Report", choices=['All'], value='All', interactive=True)
|
| 668 |
submit_button = gr.Button("Analyze", variant="primary")
|
| 669 |
|
| 670 |
with gr.Column(scale=2):
|
|
@@ -702,19 +669,19 @@ try:
|
|
| 702 |
insights_output = gr.Markdown()
|
| 703 |
with gr.Group(elem_classes="dashboard-section"):
|
| 704 |
gr.Markdown("### Export Report")
|
| 705 |
-
pdf_output = gr.File(label="Download
|
| 706 |
|
| 707 |
file_input.change(
|
| 708 |
fn=update_filters,
|
| 709 |
inputs=[file_input],
|
| 710 |
-
outputs=[lab_site_filter, equipment_type_filter
|
| 711 |
queue=False
|
| 712 |
)
|
| 713 |
|
| 714 |
submit_button.click(
|
| 715 |
fn=process_logs,
|
| 716 |
-
inputs=[file_input, lab_site_filter, equipment_type_filter, date_range_filter,
|
| 717 |
-
outputs=[summary_output, preview_output, usage_chart_output, device_cards_output, daily_log_trends_output, weekly_uptime_output,
|
| 718 |
)
|
| 719 |
|
| 720 |
logging.info("Gradio interface initialized successfully")
|
|
|
|
| 11 |
import os
|
| 12 |
import io
|
| 13 |
import time
|
| 14 |
+
import uuid
|
| 15 |
|
| 16 |
# Configure logging
|
| 17 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
| 201 |
records.append(record)
|
| 202 |
|
| 203 |
if records:
|
| 204 |
+
batch_size = 100 # Reduced batch size for faster processing
|
| 205 |
for i in range(0, len(records), batch_size):
|
| 206 |
batch = records[i:i + batch_size]
|
| 207 |
try:
|
|
|
|
| 238 |
if "usage_hours" not in df.columns or "downtime" not in df.columns:
|
| 239 |
return "Anomaly detection requires 'usage_hours' and 'downtime' columns.", pd.DataFrame()
|
| 240 |
features = df[["usage_hours", "downtime"]].fillna(0)
|
| 241 |
+
if len(features) > 200: # Reduced sample size for faster processing
|
| 242 |
+
features = features.sample(n=200, random_state=42)
|
| 243 |
+
iso_forest = IsolationForest(contamination=0.1, random_state=42, n_estimators=50) # Reduced n_estimators
|
| 244 |
df["anomaly"] = iso_forest.fit_predict(features)
|
| 245 |
anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
|
| 246 |
if anomalies.empty:
|
|
|
|
| 264 |
reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]]
|
| 265 |
if reminders.empty:
|
| 266 |
return "No AMC reminders due within the next 30 days.", reminders
|
| 267 |
+
result = "\n".join([f"- Device ID: {row['device_id']}, Log Type: {row['log_type']}, Status: {row['status']}, Usage: {row['usage_hours']}, Downtime: {row['downtime']}, AMC Date: {row['amc_date']}" for _, row in reminders.head(5).iterrows()])
|
| 268 |
logging.info(f"AMC reminders generation took {time.time() - start_time:.2f} seconds")
|
| 269 |
return result, reminders
|
| 270 |
except Exception as e:
|
|
|
|
| 420 |
logging.error(f"Failed to generate device cards: {str(e)}")
|
| 421 |
return f'<p>Error generating device cards: {str(e)}</p>'
|
| 422 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
# Generate PDF content
|
| 424 |
+
def generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights, device_cards_html, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart):
|
| 425 |
if not reportlab_available:
|
| 426 |
return None
|
| 427 |
try:
|
| 428 |
+
pdf_path = f"status_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
|
| 429 |
doc = SimpleDocTemplate(pdf_path, pagesize=letter)
|
| 430 |
styles = getSampleStyleSheet()
|
| 431 |
story = []
|
|
|
|
| 433 |
def safe_paragraph(text, style):
|
| 434 |
return Paragraph(str(text).replace('\n', '<br/>'), style) if text else Paragraph("", style)
|
| 435 |
|
| 436 |
+
story.append(Paragraph("LabOps Status Report", styles['Title']))
|
| 437 |
story.append(Paragraph(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
|
| 438 |
story.append(Spacer(1, 12))
|
| 439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
story.append(Paragraph("Summary Report", styles['Heading2']))
|
| 441 |
story.append(safe_paragraph(summary, styles['Normal']))
|
| 442 |
story.append(Spacer(1, 12))
|
|
|
|
| 491 |
return None
|
| 492 |
|
| 493 |
# Main processing function
|
| 494 |
+
async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range, last_modified_state):
|
| 495 |
start_time = time.time()
|
| 496 |
try:
|
| 497 |
if not file_obj:
|
|
|
|
| 515 |
"downtime": "float32",
|
| 516 |
"amc_date": "string"
|
| 517 |
}
|
| 518 |
+
df = pd.read_csv(file_path, dtype=dtypes, usecols=required_columns)
|
| 519 |
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 520 |
if missing_columns:
|
| 521 |
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
|
|
|
|
| 539 |
start_date = today + pd.Timedelta(days=days_start)
|
| 540 |
end_date = today + pd.Timedelta(days=days_end) + pd.Timedelta(days=1) - pd.Timedelta(seconds=1)
|
| 541 |
filtered_df = filtered_df[(filtered_df['timestamp'] >= start_date) & (filtered_df['timestamp'] <= end_date)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
|
| 543 |
if filtered_df.empty:
|
| 544 |
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
|
|
|
|
| 548 |
preview_html = preview_df.to_html(index=False, classes='table table-striped', border=0)
|
| 549 |
|
| 550 |
# Run tasks concurrently
|
| 551 |
+
with ThreadPoolExecutor(max_workers=8) as executor: # Increased workers for better parallelization
|
| 552 |
future_summary = executor.submit(summarize_logs, filtered_df)
|
| 553 |
future_anomalies = executor.submit(detect_anomalies, filtered_df)
|
| 554 |
future_amc = executor.submit(check_amc_reminders, filtered_df, datetime.now())
|
|
|
|
| 570 |
downtime_chart = future_downtime_chart.result()
|
| 571 |
daily_log_chart = future_daily_log_chart.result()
|
| 572 |
weekly_uptime_chart = future_weekly_uptime_chart.result()
|
| 573 |
+
anomaly_alerts_chart = create_anomaly_alerts_chart(anomalies_df)
|
| 574 |
device_cards = future_device_cards.result()
|
| 575 |
|
| 576 |
save_to_salesforce(filtered_df, reminders_df)
|
| 577 |
+
pdf_file = generate_pdf_content(summary, preview_df, anomalies, amc_reminders, insights, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart)
|
| 578 |
|
| 579 |
elapsed_time = time.time() - start_time
|
| 580 |
logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
|
| 581 |
+
if elapsed_time > 30:
|
| 582 |
+
logging.warning(f"Processing time exceeded 30 seconds: {elapsed_time:.2f} seconds")
|
| 583 |
|
| 584 |
return (summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights, pdf_file, current_modified_time)
|
| 585 |
except Exception as e:
|
|
|
|
| 589 |
# Update filters
|
| 590 |
def update_filters(file_obj):
|
| 591 |
if not file_obj:
|
| 592 |
+
return gr.update(choices=['All'], value='All'), gr.update(choices=['All'], value='All')
|
| 593 |
try:
|
| 594 |
with open(file_obj.name, 'rb') as f:
|
| 595 |
csv_content = f.read().decode('utf-8')
|
|
|
|
| 598 |
|
| 599 |
lab_site_options = ['All'] + [site for site in df['lab_site'].dropna().astype(str).unique().tolist() if site.strip()] if 'lab_site' in df.columns else ['All']
|
| 600 |
equipment_type_options = ['All'] + [equip for equip in df['equipment_type'].dropna().astype(str).unique().tolist() if equip.strip()] if 'equipment_type' in df.columns else ['All']
|
|
|
|
| 601 |
|
| 602 |
+
return gr.update(choices=lab_site_options, value='All'), gr.update(choices=equipment_type_options, value='All')
|
| 603 |
except Exception as e:
|
| 604 |
logging.error(f"Failed to update filters: {str(e)}")
|
| 605 |
+
return gr.update(choices=['All'], value='All'), gr.update(choices=['All'], value='All')
|
| 606 |
|
| 607 |
# Gradio Interface
|
| 608 |
try:
|
|
|
|
| 632 |
lab_site_filter = gr.Dropdown(label="Lab Site", choices=['All'], value='All', interactive=True)
|
| 633 |
equipment_type_filter = gr.Dropdown(label="Equipment Type", choices=['All'], value='All', interactive=True)
|
| 634 |
date_range_filter = gr.Slider(label="Date Range (Days from Today)", minimum=-365, maximum=0, step=1, value=[-30, 0])
|
|
|
|
| 635 |
submit_button = gr.Button("Analyze", variant="primary")
|
| 636 |
|
| 637 |
with gr.Column(scale=2):
|
|
|
|
| 669 |
insights_output = gr.Markdown()
|
| 670 |
with gr.Group(elem_classes="dashboard-section"):
|
| 671 |
gr.Markdown("### Export Report")
|
| 672 |
+
pdf_output = gr.File(label="Download Status Report as PDF")
|
| 673 |
|
| 674 |
file_input.change(
|
| 675 |
fn=update_filters,
|
| 676 |
inputs=[file_input],
|
| 677 |
+
outputs=[lab_site_filter, equipment_type_filter],
|
| 678 |
queue=False
|
| 679 |
)
|
| 680 |
|
| 681 |
submit_button.click(
|
| 682 |
fn=process_logs,
|
| 683 |
+
inputs=[file_input, lab_site_filter, equipment_type_filter, date_range_filter, last_modified_state],
|
| 684 |
+
outputs=[summary_output, preview_output, usage_chart_output, device_cards_output, daily_log_trends_output, weekly_uptime_output, anomaly_alerts_chart, downtime_chart_output, anomaly_output, amc_output, insights_output, pdf_output, last_modified_state]
|
| 685 |
)
|
| 686 |
|
| 687 |
logging.info("Gradio interface initialized successfully")
|