Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -69,15 +69,27 @@ def detect_anomalies(df):
|
|
| 69 |
# AMC reminders
|
| 70 |
def check_amc_reminders(df, current_date):
|
| 71 |
try:
|
|
|
|
| 72 |
if "device_id" not in df.columns or "amc_date" not in df.columns:
|
|
|
|
| 73 |
return "AMC reminders require 'device_id' and 'amc_date' columns.", pd.DataFrame()
|
|
|
|
| 74 |
df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
df["days_to_amc"] = (df["amc_date"] - current_date).dt.days
|
|
|
|
|
|
|
| 77 |
reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]]
|
| 78 |
if reminders.empty:
|
| 79 |
logging.info("No AMC reminders found within the next 30 days.")
|
| 80 |
return "No AMC reminders due within the next 30 days.", reminders
|
|
|
|
| 81 |
reminder_lines = ["Upcoming AMC Reminders:"]
|
| 82 |
for _, row in reminders.head(5).iterrows():
|
| 83 |
reminder_lines.append(f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}")
|
|
@@ -175,10 +187,10 @@ def create_downtime_chart(agg_data):
|
|
| 175 |
color_discrete_map={"green": "#96CEB4", "red": "#FF0000"}
|
| 176 |
)
|
| 177 |
fig.update_traces(
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
fig.update_layout(
|
| 183 |
title_font=dict(size=18, family="Arial", color="#333333"),
|
| 184 |
font=dict(family="Arial", size=12, color="#333333"),
|
|
@@ -207,7 +219,7 @@ def create_downtime_chart(agg_data):
|
|
| 207 |
logging.error(f"Failed to create downtime chart: {str(e)}")
|
| 208 |
return None
|
| 209 |
|
| 210 |
-
# Create Daily Log Trends chart
|
| 211 |
def create_daily_log_trends_chart(df):
|
| 212 |
try:
|
| 213 |
if df.empty or 'timestamp' not in df.columns:
|
|
@@ -218,16 +230,21 @@ def create_daily_log_trends_chart(df):
|
|
| 218 |
df['date'] = df['timestamp'].dt.date
|
| 219 |
log_counts = df.groupby('date').size().reset_index(name='log_count')
|
| 220 |
|
| 221 |
-
fig = px.
|
| 222 |
log_counts,
|
| 223 |
x='date',
|
| 224 |
y='log_count',
|
| 225 |
title="Daily Log Trends",
|
| 226 |
labels={"date": "Date", "log_count": "Number of Logs"}
|
| 227 |
)
|
|
|
|
| 228 |
fig.update_traces(
|
|
|
|
| 229 |
line_color='#4ECDC4',
|
| 230 |
-
line_width=2
|
|
|
|
|
|
|
|
|
|
| 231 |
)
|
| 232 |
fig.update_layout(
|
| 233 |
title_font=dict(size=18, family="Arial", color="#333333"),
|
|
@@ -261,18 +278,31 @@ def create_weekly_uptime_chart(df):
|
|
| 261 |
logging.warning("DataFrame is empty or missing required columns for Weekly Uptime Percentage.")
|
| 262 |
return None
|
| 263 |
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
df['year'] = df['timestamp'].dt.year
|
| 267 |
weekly_data = df.groupby(['year', 'week']).agg({
|
| 268 |
'downtime': 'sum'
|
| 269 |
}).reset_index()
|
| 270 |
|
|
|
|
|
|
|
| 271 |
# Calculate uptime percentage (assuming 24*7 = 168 hours per week)
|
| 272 |
total_hours_per_week = 168
|
| 273 |
weekly_data['uptime_percentage'] = ((total_hours_per_week - weekly_data['downtime']) / total_hours_per_week) * 100
|
|
|
|
| 274 |
weekly_data['week_label'] = weekly_data.apply(lambda x: f"{x['year']}-W{x['week']:02d}", axis=1)
|
| 275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
fig = px.bar(
|
| 277 |
weekly_data,
|
| 278 |
x='week_label',
|
|
@@ -315,14 +345,14 @@ def create_weekly_uptime_chart(df):
|
|
| 315 |
logging.error(f"Failed to create Weekly Uptime Percentage chart: {str(e)}")
|
| 316 |
return None
|
| 317 |
|
| 318 |
-
# Create Anomaly Alerts chart
|
| 319 |
def create_anomaly_alerts_chart(df, anomalies_df):
|
| 320 |
try:
|
| 321 |
if df.empty or anomalies_df.empty:
|
| 322 |
logging.warning("DataFrame or anomalies DataFrame is empty for Anomaly Alerts chart.")
|
| 323 |
return None
|
| 324 |
|
| 325 |
-
# Prepare data for
|
| 326 |
df['is_anomaly'] = df.index.isin(anomalies_df.index)
|
| 327 |
df['color'] = df['is_anomaly'].map({True: 'red', False: 'blue'})
|
| 328 |
|
|
@@ -330,13 +360,18 @@ def create_anomaly_alerts_chart(df, anomalies_df):
|
|
| 330 |
df,
|
| 331 |
x='usage_hours',
|
| 332 |
y='downtime',
|
|
|
|
| 333 |
color='color',
|
| 334 |
title="Anomaly Alerts (Red = Anomaly)",
|
| 335 |
labels={"usage_hours": "Usage Hours", "downtime": "Downtime (Hours)"},
|
| 336 |
color_discrete_map={'blue': '#4ECDC4', 'red': '#FF0000'}
|
| 337 |
)
|
| 338 |
fig.update_traces(
|
| 339 |
-
marker=dict(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
opacity=0.7
|
| 341 |
)
|
| 342 |
fig.update_layout(
|
|
@@ -614,9 +649,9 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 614 |
}
|
| 615 |
df = pd.read_csv(file_path, dtype=dtypes)
|
| 616 |
# Downsample early if dataset is too large
|
| 617 |
-
if len(df) >
|
| 618 |
-
df = df.sample(n=
|
| 619 |
-
logging.info(f"Downsampled DataFrame to
|
| 620 |
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 621 |
if missing_columns:
|
| 622 |
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}"
|
|
@@ -664,11 +699,11 @@ async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_ra
|
|
| 664 |
logging.warning("Filtered DataFrame is empty after applying filters.")
|
| 665 |
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."
|
| 666 |
|
| 667 |
-
logging.info(f"Filtered DataFrame:\n{filtered_df.
|
| 668 |
|
| 669 |
-
if len(filtered_df) >
|
| 670 |
-
filtered_df = filtered_df.sample(n=
|
| 671 |
-
logging.info(f"Downsampled DataFrame to
|
| 672 |
|
| 673 |
# Pre-aggregate data for charts
|
| 674 |
agg_data = {
|
|
|
|
| 69 |
# AMC reminders
|
| 70 |
def check_amc_reminders(df, current_date):
|
| 71 |
try:
|
| 72 |
+
logging.info(f"Input DataFrame for AMC reminders:\n{df.head().to_string()}")
|
| 73 |
if "device_id" not in df.columns or "amc_date" not in df.columns:
|
| 74 |
+
logging.warning("Missing 'device_id' or 'amc_date' columns for AMC reminders.")
|
| 75 |
return "AMC reminders require 'device_id' and 'amc_date' columns.", pd.DataFrame()
|
| 76 |
+
|
| 77 |
df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
|
| 78 |
+
if df["amc_date"].dt.tz is None:
|
| 79 |
+
logging.info("Localizing naive AMC dates to IST")
|
| 80 |
+
df["amc_date"] = df["amc_date"].dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata')
|
| 81 |
+
|
| 82 |
+
current_date = pd.to_datetime(current_date).tz_localize('Asia/Kolkata')
|
| 83 |
+
logging.info(f"Current date for AMC check: {current_date}")
|
| 84 |
+
|
| 85 |
df["days_to_amc"] = (df["amc_date"] - current_date).dt.days
|
| 86 |
+
logging.info(f"Days to AMC:\n{df[['device_id', 'amc_date', 'days_to_amc']].to_string()}")
|
| 87 |
+
|
| 88 |
reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]]
|
| 89 |
if reminders.empty:
|
| 90 |
logging.info("No AMC reminders found within the next 30 days.")
|
| 91 |
return "No AMC reminders due within the next 30 days.", reminders
|
| 92 |
+
|
| 93 |
reminder_lines = ["Upcoming AMC Reminders:"]
|
| 94 |
for _, row in reminders.head(5).iterrows():
|
| 95 |
reminder_lines.append(f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}")
|
|
|
|
| 187 |
color_discrete_map={"green": "#96CEB4", "red": "#FF0000"}
|
| 188 |
)
|
| 189 |
fig.update_traces(
|
| 190 |
+
掍
|
| 191 |
+
marker_line_color='#333333',
|
| 192 |
+
marker_line_width=1.5,
|
| 193 |
+
opacity=0.9
|
| 194 |
fig.update_layout(
|
| 195 |
title_font=dict(size=18, family="Arial", color="#333333"),
|
| 196 |
font=dict(family="Arial", size=12, color="#333333"),
|
|
|
|
| 219 |
logging.error(f"Failed to create downtime chart: {str(e)}")
|
| 220 |
return None
|
| 221 |
|
| 222 |
+
# Create Daily Log Trends chart (changed to area chart with markers)
|
| 223 |
def create_daily_log_trends_chart(df):
|
| 224 |
try:
|
| 225 |
if df.empty or 'timestamp' not in df.columns:
|
|
|
|
| 230 |
df['date'] = df['timestamp'].dt.date
|
| 231 |
log_counts = df.groupby('date').size().reset_index(name='log_count')
|
| 232 |
|
| 233 |
+
fig = px.area(
|
| 234 |
log_counts,
|
| 235 |
x='date',
|
| 236 |
y='log_count',
|
| 237 |
title="Daily Log Trends",
|
| 238 |
labels={"date": "Date", "log_count": "Number of Logs"}
|
| 239 |
)
|
| 240 |
+
# Add markers
|
| 241 |
fig.update_traces(
|
| 242 |
+
fill='tozeroy',
|
| 243 |
line_color='#4ECDC4',
|
| 244 |
+
line_width=2,
|
| 245 |
+
mode='lines+markers',
|
| 246 |
+
marker=dict(size=8, color='#4ECDC4', line=dict(width=1, color='#333333')),
|
| 247 |
+
fillcolor='rgba(78, 205, 196, 0.3)' # Gradient fill with transparency
|
| 248 |
)
|
| 249 |
fig.update_layout(
|
| 250 |
title_font=dict(size=18, family="Arial", color="#333333"),
|
|
|
|
| 278 |
logging.warning("DataFrame is empty or missing required columns for Weekly Uptime Percentage.")
|
| 279 |
return None
|
| 280 |
|
| 281 |
+
logging.info(f"DataFrame for Weekly Uptime:\n{df[['timestamp', 'downtime']].to_string()}")
|
| 282 |
+
|
| 283 |
+
# Group by week (handle pandas 2.x compatibility)
|
| 284 |
+
try:
|
| 285 |
+
df['week'] = df['timestamp'].dt.isocalendar().week
|
| 286 |
+
except AttributeError:
|
| 287 |
+
# For pandas 2.x, use .dt.weekofyear or manual calculation
|
| 288 |
+
df['week'] = df['timestamp'].dt.isocalendar()['week']
|
| 289 |
df['year'] = df['timestamp'].dt.year
|
| 290 |
weekly_data = df.groupby(['year', 'week']).agg({
|
| 291 |
'downtime': 'sum'
|
| 292 |
}).reset_index()
|
| 293 |
|
| 294 |
+
logging.info(f"Weekly data:\n{weekly_data.to_string()}")
|
| 295 |
+
|
| 296 |
# Calculate uptime percentage (assuming 24*7 = 168 hours per week)
|
| 297 |
total_hours_per_week = 168
|
| 298 |
weekly_data['uptime_percentage'] = ((total_hours_per_week - weekly_data['downtime']) / total_hours_per_week) * 100
|
| 299 |
+
weekly_data['uptime_percentage'] = weekly_data['uptime_percentage'].clip(0, 100) # Ensure percentage is between 0 and 100
|
| 300 |
weekly_data['week_label'] = weekly_data.apply(lambda x: f"{x['year']}-W{x['week']:02d}", axis=1)
|
| 301 |
|
| 302 |
+
if weekly_data.empty:
|
| 303 |
+
logging.warning("No weekly data available for Weekly Uptime Percentage chart.")
|
| 304 |
+
return None
|
| 305 |
+
|
| 306 |
fig = px.bar(
|
| 307 |
weekly_data,
|
| 308 |
x='week_label',
|
|
|
|
| 345 |
logging.error(f"Failed to create Weekly Uptime Percentage chart: {str(e)}")
|
| 346 |
return None
|
| 347 |
|
| 348 |
+
# Create Anomaly Alerts chart (changed to bubble chart)
|
| 349 |
def create_anomaly_alerts_chart(df, anomalies_df):
|
| 350 |
try:
|
| 351 |
if df.empty or anomalies_df.empty:
|
| 352 |
logging.warning("DataFrame or anomalies DataFrame is empty for Anomaly Alerts chart.")
|
| 353 |
return None
|
| 354 |
|
| 355 |
+
# Prepare data for bubble chart
|
| 356 |
df['is_anomaly'] = df.index.isin(anomalies_df.index)
|
| 357 |
df['color'] = df['is_anomaly'].map({True: 'red', False: 'blue'})
|
| 358 |
|
|
|
|
| 360 |
df,
|
| 361 |
x='usage_hours',
|
| 362 |
y='downtime',
|
| 363 |
+
size='usage_hours', # Bubble size based on usage hours
|
| 364 |
color='color',
|
| 365 |
title="Anomaly Alerts (Red = Anomaly)",
|
| 366 |
labels={"usage_hours": "Usage Hours", "downtime": "Downtime (Hours)"},
|
| 367 |
color_discrete_map={'blue': '#4ECDC4', 'red': '#FF0000'}
|
| 368 |
)
|
| 369 |
fig.update_traces(
|
| 370 |
+
marker=dict(
|
| 371 |
+
sizemode='area',
|
| 372 |
+
sizeref=0.1, # Adjust bubble size scaling
|
| 373 |
+
line=dict(width=1, color='#333333')
|
| 374 |
+
),
|
| 375 |
opacity=0.7
|
| 376 |
)
|
| 377 |
fig.update_layout(
|
|
|
|
| 649 |
}
|
| 650 |
df = pd.read_csv(file_path, dtype=dtypes)
|
| 651 |
# Downsample early if dataset is too large
|
| 652 |
+
if len(df) > 5000:
|
| 653 |
+
df = df.sample(n=5000, random_state=42)
|
| 654 |
+
logging.info(f"Downsampled DataFrame to 5,000 rows immediately after loading.")
|
| 655 |
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 656 |
if missing_columns:
|
| 657 |
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}"
|
|
|
|
| 699 |
logging.warning("Filtered DataFrame is empty after applying filters.")
|
| 700 |
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."
|
| 701 |
|
| 702 |
+
logging.info(f"Filtered DataFrame before AMC check:\n{filtered_df[['device_id', 'amc_date']].to_string()}")
|
| 703 |
|
| 704 |
+
if len(filtered_df) > 1000:
|
| 705 |
+
filtered_df = filtered_df.sample(n=1000, random_state=42)
|
| 706 |
+
logging.info(f"Downsampled filtered DataFrame to 1,000 rows for chart generation.")
|
| 707 |
|
| 708 |
# Pre-aggregate data for charts
|
| 709 |
agg_data = {
|