Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -469,6 +469,10 @@ def generate_device_cards(df):
|
|
| 469 |
counts = df_clean.groupby('device_id').size().reset_index(name='count')
|
| 470 |
device_stats = device_stats.merge(counts, on='device_id')
|
| 471 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
device_stats['health'] = device_stats['status'].map({
|
| 473 |
'Active': 'Healthy',
|
| 474 |
'Inactive': 'Unhealthy',
|
|
@@ -595,34 +599,24 @@ def generate_pdf_content(summary, preview, anomalies, amc_reminders, insights, d
|
|
| 595 |
logging.error(f"Failed to generate PDF: {str(e)}", exc_info=True)
|
| 596 |
return None
|
| 597 |
|
| 598 |
-
# Validate inputs before generating PDF
|
| 599 |
-
def validate_and_generate_pdf(summary, preview, anomalies, amc_reminders, insights, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, df, month_filter):
|
| 600 |
-
if not all([summary, preview, anomalies, amc_reminders, insights, device_cards, df is not None]):
|
| 601 |
-
logging.error("One or more required inputs for PDF generation are missing.")
|
| 602 |
-
return None, "Please click 'Analyze' to process the data before generating a PDF."
|
| 603 |
-
pdf_path = generate_pdf_content(summary, preview, anomalies, amc_reminders, insights, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, df, month_filter)
|
| 604 |
-
if pdf_path is None:
|
| 605 |
-
return None, "Failed to generate PDF. Check logs for details."
|
| 606 |
-
return pdf_path, "PDF generated successfully."
|
| 607 |
-
|
| 608 |
# Main Gradio function
|
| 609 |
async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range, month_filter, last_modified_state):
|
| 610 |
try:
|
| 611 |
start_time = datetime.now()
|
| 612 |
|
| 613 |
if not file_obj:
|
| 614 |
-
return "No file uploaded.", "No data to preview.", None, '<p>No device cards available.</p>', None, None, None, None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None, last_modified_state, None, None, None, None, None, None,
|
| 615 |
|
| 616 |
file_path = file_obj.name
|
| 617 |
current_modified_time = os.path.getmtime(file_path)
|
| 618 |
|
| 619 |
if last_modified_state and current_modified_time == last_modified_state:
|
| 620 |
-
return None, None, None, None, None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None,
|
| 621 |
|
| 622 |
logging.info(f"Processing file: {file_path}, last modified: {current_modified_time}")
|
| 623 |
|
| 624 |
if not file_path.endswith(".csv"):
|
| 625 |
-
return "Please upload a CSV file.", "", None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, last_modified_state, None, None, None, None, None, None,
|
| 626 |
|
| 627 |
required_columns = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
|
| 628 |
dtypes = {
|
|
@@ -634,9 +628,13 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 634 |
"amc_date": "string"
|
| 635 |
}
|
| 636 |
df = pd.read_csv(file_path, dtype=dtypes)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 638 |
if missing_columns:
|
| 639 |
-
return f"Missing columns: {missing_columns}", None, None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None,
|
| 640 |
|
| 641 |
df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
|
| 642 |
df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
|
|
@@ -644,7 +642,7 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 644 |
logging.info("Localizing naive timestamps to IST")
|
| 645 |
df["timestamp"] = df["timestamp"].dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata')
|
| 646 |
if df.empty:
|
| 647 |
-
return "No data available.", None, None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None,
|
| 648 |
|
| 649 |
logging.info(f"DataFrame before filtering:\n{df.head().to_string()}")
|
| 650 |
|
|
@@ -679,7 +677,7 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 679 |
|
| 680 |
if filtered_df.empty:
|
| 681 |
logging.warning("Filtered DataFrame is empty after applying filters.")
|
| 682 |
-
return "No data after applying filters.", None, None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None,
|
| 683 |
|
| 684 |
logging.info(f"Filtered DataFrame:\n{filtered_df.head().to_string()}")
|
| 685 |
|
|
@@ -788,24 +786,31 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 788 |
)
|
| 789 |
preview_text = "\n".join(preview_lines)
|
| 790 |
|
| 791 |
-
|
| 792 |
-
executor.submit(save_to_salesforce, filtered_df, reminders_df, summary, anomalies, amc_reminders, insights)
|
| 793 |
-
executor.submit(create_salesforce_reports, filtered_df)
|
| 794 |
-
|
| 795 |
pdf_file = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 796 |
|
| 797 |
elapsed_time = (datetime.now() - start_time).total_seconds()
|
| 798 |
logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
|
| 799 |
if elapsed_time > 10:
|
| 800 |
logging.warning(f"Processing time exceeded 10 seconds: {elapsed_time:.2f} seconds")
|
| 801 |
|
| 802 |
-
|
| 803 |
-
logging.info(f"Setting state variables: summary={summary}, preview={preview_text}, anomalies={anomalies}, amc_reminders={amc_reminders}, insights={insights}, device_cards={device_cards[:50]}..., df={'set' if df is not None else 'None'}")
|
| 804 |
-
|
| 805 |
-
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, summary, preview_text, anomalies, amc_reminders, insights, device_cards, filtered_df, True, "Analysis completed successfully. You can now generate the PDF report.")
|
| 806 |
except Exception as e:
|
| 807 |
logging.error(f"Failed to process file: {str(e)}")
|
| 808 |
-
return f"Error: {str(e)}", None, None, '<p>Error processing data.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None,
|
| 809 |
|
| 810 |
# Update filter options
|
| 811 |
def update_filters(file_obj):
|
|
@@ -863,7 +868,7 @@ try:
|
|
| 863 |
.dashboard-section ul {margin: 2px 0; padding-left: 20px;}
|
| 864 |
""") as iface:
|
| 865 |
gr.Markdown("<h1>LabOps Log Analyzer Dashboard</h1>")
|
| 866 |
-
gr.Markdown("Upload a CSV file to analyze. Click 'Analyze' to refresh the dashboard with the latest data.")
|
| 867 |
|
| 868 |
last_modified_state = gr.State(value=None)
|
| 869 |
summary_state = gr.State()
|
|
@@ -873,7 +878,6 @@ try:
|
|
| 873 |
insights_state = gr.State()
|
| 874 |
device_cards_state = gr.State()
|
| 875 |
df_state = gr.State()
|
| 876 |
-
is_analyzed_state = gr.State(value=False)
|
| 877 |
|
| 878 |
with gr.Row():
|
| 879 |
with gr.Column(scale=1):
|
|
@@ -957,7 +961,6 @@ try:
|
|
| 957 |
|
| 958 |
with gr.Group(elem_classes="dashboard-section"):
|
| 959 |
gr.Markdown("### Export Report")
|
| 960 |
-
pdf_button = gr.Button("Generate PDF Report", interactive=False) # Disabled by default
|
| 961 |
pdf_output = gr.File(label="Download Monthly Status Report as PDF")
|
| 962 |
|
| 963 |
file_input.change(
|
|
@@ -998,40 +1001,10 @@ try:
|
|
| 998 |
insights_state,
|
| 999 |
device_cards_state,
|
| 1000 |
df_state,
|
| 1001 |
-
is_analyzed_state,
|
| 1002 |
status_message
|
| 1003 |
]
|
| 1004 |
)
|
| 1005 |
|
| 1006 |
-
def update_pdf_button_interactivity(is_analyzed):
|
| 1007 |
-
return gr.update(interactive=is_analyzed)
|
| 1008 |
-
|
| 1009 |
-
is_analyzed_state.change(
|
| 1010 |
-
fn=update_pdf_button_interactivity,
|
| 1011 |
-
inputs=[is_analyzed_state],
|
| 1012 |
-
outputs=[pdf_button],
|
| 1013 |
-
queue=False
|
| 1014 |
-
)
|
| 1015 |
-
|
| 1016 |
-
pdf_button.click(
|
| 1017 |
-
fn=validate_and_generate_pdf,
|
| 1018 |
-
inputs=[
|
| 1019 |
-
summary_state,
|
| 1020 |
-
preview_state,
|
| 1021 |
-
anomalies_state,
|
| 1022 |
-
amc_reminders_state,
|
| 1023 |
-
insights_state,
|
| 1024 |
-
device_cards_state,
|
| 1025 |
-
daily_log_trends_output,
|
| 1026 |
-
weekly_uptime_output,
|
| 1027 |
-
anomaly_alerts_output,
|
| 1028 |
-
downtime_chart_output,
|
| 1029 |
-
df_state,
|
| 1030 |
-
month_filter
|
| 1031 |
-
],
|
| 1032 |
-
outputs=[pdf_output, status_message]
|
| 1033 |
-
)
|
| 1034 |
-
|
| 1035 |
logging.info("Gradio interface initialized successfully")
|
| 1036 |
except Exception as e:
|
| 1037 |
logging.error(f"Failed to initialize Gradio interface: {str(e)}")
|
|
|
|
| 469 |
counts = df_clean.groupby('device_id').size().reset_index(name='count')
|
| 470 |
device_stats = device_stats.merge(counts, on='device_id')
|
| 471 |
|
| 472 |
+
# Limit to top 10 devices by count
|
| 473 |
+
device_stats = device_stats.nlargest(10, 'count')
|
| 474 |
+
logging.info(f"Limited device cards to top {len(device_stats)} devices by usage count.")
|
| 475 |
+
|
| 476 |
device_stats['health'] = device_stats['status'].map({
|
| 477 |
'Active': 'Healthy',
|
| 478 |
'Inactive': 'Unhealthy',
|
|
|
|
| 599 |
logging.error(f"Failed to generate PDF: {str(e)}", exc_info=True)
|
| 600 |
return None
|
| 601 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
# Main Gradio function
|
| 603 |
async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range, month_filter, last_modified_state):
|
| 604 |
try:
|
| 605 |
start_time = datetime.now()
|
| 606 |
|
| 607 |
if not file_obj:
|
| 608 |
+
return "No file uploaded.", "No data to preview.", None, '<p>No device cards available.</p>', None, None, None, None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None, last_modified_state, None, None, None, None, None, None, "Please upload a CSV file to analyze."
|
| 609 |
|
| 610 |
file_path = file_obj.name
|
| 611 |
current_modified_time = os.path.getmtime(file_path)
|
| 612 |
|
| 613 |
if last_modified_state and current_modified_time == last_modified_state:
|
| 614 |
+
return None, None, None, None, None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None, "No changes detected in the file."
|
| 615 |
|
| 616 |
logging.info(f"Processing file: {file_path}, last modified: {current_modified_time}")
|
| 617 |
|
| 618 |
if not file_path.endswith(".csv"):
|
| 619 |
+
return "Please upload a CSV file.", "", None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, last_modified_state, None, None, None, None, None, None, "Invalid file format. Please upload a CSV file."
|
| 620 |
|
| 621 |
required_columns = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
|
| 622 |
dtypes = {
|
|
|
|
| 628 |
"amc_date": "string"
|
| 629 |
}
|
| 630 |
df = pd.read_csv(file_path, dtype=dtypes)
|
| 631 |
+
# Downsample early if dataset is too large
|
| 632 |
+
if len(df) > 10000:
|
| 633 |
+
df = df.sample(n=10000, random_state=42)
|
| 634 |
+
logging.info(f"Downsampled DataFrame to 10,000 rows immediately after loading.")
|
| 635 |
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 636 |
if missing_columns:
|
| 637 |
+
return f"Missing columns: {missing_columns}", None, None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None, f"Missing required columns: {missing_columns}"
|
| 638 |
|
| 639 |
df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
|
| 640 |
df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
|
|
|
|
| 642 |
logging.info("Localizing naive timestamps to IST")
|
| 643 |
df["timestamp"] = df["timestamp"].dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata')
|
| 644 |
if df.empty:
|
| 645 |
+
return "No data available.", None, None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None, "No data available in the uploaded file."
|
| 646 |
|
| 647 |
logging.info(f"DataFrame before filtering:\n{df.head().to_string()}")
|
| 648 |
|
|
|
|
| 677 |
|
| 678 |
if filtered_df.empty:
|
| 679 |
logging.warning("Filtered DataFrame is empty after applying filters.")
|
| 680 |
+
return "No data after applying filters.", None, None, '<p>No device cards available.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None, "No data available after applying filters."
|
| 681 |
|
| 682 |
logging.info(f"Filtered DataFrame:\n{filtered_df.head().to_string()}")
|
| 683 |
|
|
|
|
| 786 |
)
|
| 787 |
preview_text = "\n".join(preview_lines)
|
| 788 |
|
| 789 |
+
# Auto-generate PDF after analysis
|
|
|
|
|
|
|
|
|
|
| 790 |
pdf_file = None
|
| 791 |
+
status_msg = "Analysis completed successfully."
|
| 792 |
+
if all([summary, preview_text, anomalies, amc_reminders, insights, device_cards, filtered_df is not None]):
|
| 793 |
+
pdf_file = generate_pdf_content(
|
| 794 |
+
summary, preview_text, anomalies, amc_reminders, insights, device_cards,
|
| 795 |
+
daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart,
|
| 796 |
+
filtered_df, month_filter
|
| 797 |
+
)
|
| 798 |
+
if pdf_file:
|
| 799 |
+
status_msg = "Analysis completed successfully. PDF report generated and available for download."
|
| 800 |
+
else:
|
| 801 |
+
status_msg = "Analysis completed successfully, but failed to generate PDF. Check logs for details."
|
| 802 |
+
else:
|
| 803 |
+
status_msg = "Analysis completed, but some data is missing for PDF generation."
|
| 804 |
|
| 805 |
elapsed_time = (datetime.now() - start_time).total_seconds()
|
| 806 |
logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
|
| 807 |
if elapsed_time > 10:
|
| 808 |
logging.warning(f"Processing time exceeded 10 seconds: {elapsed_time:.2f} seconds")
|
| 809 |
|
| 810 |
+
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, summary, preview_text, anomalies, amc_reminders, insights, device_cards, filtered_df, status_msg)
|
|
|
|
|
|
|
|
|
|
| 811 |
except Exception as e:
|
| 812 |
logging.error(f"Failed to process file: {str(e)}")
|
| 813 |
+
return f"Error: {str(e)}", None, None, '<p>Error processing data.</p>', None, None, None, None, None, None, None, None, last_modified_state, None, None, None, None, None, None, f"Failed to process file: {str(e)}"
|
| 814 |
|
| 815 |
# Update filter options
|
| 816 |
def update_filters(file_obj):
|
|
|
|
| 868 |
.dashboard-section ul {margin: 2px 0; padding-left: 20px;}
|
| 869 |
""") as iface:
|
| 870 |
gr.Markdown("<h1>LabOps Log Analyzer Dashboard</h1>")
|
| 871 |
+
gr.Markdown("Upload a CSV file to analyze. Click 'Analyze' to refresh the dashboard with the latest data. A PDF report will be generated automatically.")
|
| 872 |
|
| 873 |
last_modified_state = gr.State(value=None)
|
| 874 |
summary_state = gr.State()
|
|
|
|
| 878 |
insights_state = gr.State()
|
| 879 |
device_cards_state = gr.State()
|
| 880 |
df_state = gr.State()
|
|
|
|
| 881 |
|
| 882 |
with gr.Row():
|
| 883 |
with gr.Column(scale=1):
|
|
|
|
| 961 |
|
| 962 |
with gr.Group(elem_classes="dashboard-section"):
|
| 963 |
gr.Markdown("### Export Report")
|
|
|
|
| 964 |
pdf_output = gr.File(label="Download Monthly Status Report as PDF")
|
| 965 |
|
| 966 |
file_input.change(
|
|
|
|
| 1001 |
insights_state,
|
| 1002 |
device_cards_state,
|
| 1003 |
df_state,
|
|
|
|
| 1004 |
status_message
|
| 1005 |
]
|
| 1006 |
)
|
| 1007 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
logging.info("Gradio interface initialized successfully")
|
| 1009 |
except Exception as e:
|
| 1010 |
logging.error(f"Failed to initialize Gradio interface: {str(e)}")
|