RathodHarish commited on
Commit
63ec3d4
·
verified ·
1 Parent(s): 7ce506f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +385 -682
app.py CHANGED
@@ -1,729 +1,432 @@
1
  import gradio as gr
2
- import pandas as pd
3
- from datetime import datetime, timedelta
4
- import logging
5
- import plotly.express as px
6
- import plotly.graph_objects as go
7
- from sklearn.ensemble import IsolationForest
8
- from concurrent.futures import ThreadPoolExecutor
9
- import os
10
- import io
11
- import time
12
- import asyncio
13
  from simple_salesforce import Salesforce
14
-
15
- # Configure logging
16
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
-
18
- # Salesforce configuration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  try:
20
  sf = Salesforce(
21
- username='multi-devicelabopsdashboard@sathkrutha.com',
22
- password='Team@1234',
23
- security_token=os.getenv('SF_SECURITY_TOKEN', ''),
24
- domain='login'
25
  )
26
- logging.info("Salesforce connection established")
27
  except Exception as e:
28
- logging.error(f"Failed to connect to Salesforce: {str(e)}")
29
  sf = None
30
 
31
- # Try to import reportlab
32
  try:
33
- from reportlab.lib.pagesizes import letter
34
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
35
- from reportlab.lib.styles import getSampleStyleSheet
36
- from reportlab.lib import colors
37
- reportlab_available = True
38
- logging.info("reportlab module successfully imported")
39
- except ImportError:
40
- logging.warning("reportlab module not found. PDF generation disabled.")
41
- reportlab_available = False
42
-
43
- # Cache picklist values at startup
44
- def get_picklist_values(field_name):
45
- if sf is None:
46
- return []
47
- try:
48
- obj_desc = sf.SmartLog__c.describe()
49
- for field in obj_desc['fields']:
50
- if field['name'] == field_name:
51
- return [value['value'] for value in field['picklistValues'] if value['active']]
52
- return []
53
- except Exception as e:
54
- logging.error(f"Failed to fetch picklist values for {field_name}: {str(e)}")
55
- return []
56
-
57
- status_values = get_picklist_values('Status__c') or ["Active", "Inactive", "Pending"]
58
- log_type_values = get_picklist_values('Log_Type__c') or ["Smart Log", "Cell Analysis", "UV Verification"]
59
- logging.info(f"Valid Status__c values: {status_values}")
60
- logging.info(f"Valid Log_Type__c values: {log_type_values}")
61
-
62
- # Map invalid picklist values
63
- picklist_mapping = {
64
- 'Status__c': {
65
- 'normal': 'Active',
66
- 'error': 'Inactive',
67
- 'warning': 'Pending',
68
- 'ok': 'Active',
69
- 'failed': 'Inactive'
70
- },
71
- 'Log_Type__c': {
72
- 'maint': 'Smart Log',
73
- 'error': 'Cell Analysis',
74
- 'ops': 'UV Verification',
75
- 'maintenance': 'Smart Log',
76
- 'cell': 'Cell Analysis',
77
- 'uv': 'UV Verification',
78
- 'weight log': 'Smart Log'
79
- }
80
- }
81
-
82
- # Cache folder ID for Salesforce reports
83
- def get_folder_id(folder_name):
84
- if sf is None:
85
- return None
86
- try:
87
- query = f"SELECT Id FROM Folder WHERE Name = '{folder_name}' AND Type = 'Report'"
88
- result = sf.query(query)
89
- if result['totalSize'] > 0:
90
- folder_id = result['records'][0]['Id']
91
- logging.info(f"Found folder ID for '{folder_name}': {folder_id}")
92
- return folder_id
93
- else:
94
- logging.error(f"Folder '{folder_name}' not found in Salesforce.")
95
- return None
96
- except Exception as e:
97
- logging.error(f"Failed to fetch folder ID for '{folder_name}': {str(e)}")
98
- return None
99
-
100
- LABOPS_REPORTS_FOLDER_ID = get_folder_id('LabOps Reports')
101
-
102
- # Salesforce report creation
103
- def create_salesforce_reports(df):
104
- if sf is None or not LABOPS_REPORTS_FOLDER_ID:
105
- logging.error("Cannot create Salesforce reports: No connection or folder ID")
106
- return
107
- try:
108
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
109
- reports = [
110
- {
111
- "reportMetadata": {
112
- "name": f"SmartLog_Usage_Report_{timestamp}",
113
- "developerName": f"SmartLog_Usage_Report_{timestamp}",
114
- "reportType": {"type": "CustomEntity", "value": "SmartLog__c"},
115
- "reportFormat": "TABULAR",
116
- "reportBooleanFilter": None,
117
- "reportFilters": [],
118
- "detailColumns": ["SmartLog__c.Device_Id__c", "SmartLog__c.Usage_Hours__c"],
119
- "folderId": LABOPS_REPORTS_FOLDER_ID
120
- }
121
- },
122
- {
123
- "reportMetadata": {
124
- "name": f"SmartLog_AMC_Reminders_{timestamp}",
125
- "developerName": f"SmartLog_AMC_Reminders_{timestamp}",
126
- "reportType": {"type": "CustomEntity", "value": "SmartLog__c"},
127
- "reportFormat": "TABULAR",
128
- "reportBooleanFilter": None,
129
- "reportFilters": [],
130
- "detailColumns": ["SmartLog__c.Device_Id__c", "SmartLog__c.AMC_Date__c"],
131
- "folderId": LABOPS_REPORTS_FOLDER_ID
132
- }
133
- }
134
- ]
135
- for report in reports:
136
- sf.restful('analytics/reports', method='POST', json=report)
137
- logging.info("Salesforce reports created successfully")
138
- except Exception as e:
139
- logging.error(f"Failed to create Salesforce reports: {str(e)}")
140
-
141
- # Save to Salesforce
142
- def save_to_salesforce(df, reminders_df):
143
- if sf is None:
144
- logging.error("No Salesforce connection available")
145
- return
146
- try:
147
- logging.info("Starting Salesforce save operation")
148
- current_date = datetime.now()
149
- next_30_days = current_date + timedelta(days=30)
150
- records = []
151
- reminder_device_ids = set(reminders_df['device_id']) if not reminders_df.empty else set()
152
- logging.info(f"Processing {len(df)} records for Salesforce")
153
-
154
- for idx, row in df.iterrows():
155
- status = str(row['status']).lower()
156
- log_type = str(row['log_type']).lower()
157
- status_mapped = picklist_mapping['Status__c'].get(status, status_values[0] if status_values else 'Active')
158
- log_type_mapped = picklist_mapping['Log_Type__c'].get(log_type, log_type_values[0] if log_type_values else 'Smart Log')
159
-
160
- if not status_mapped or not log_type_mapped:
161
- logging.warning(f"Skipping record {idx}: Invalid status ({status}) or log_type ({log_type})")
162
- continue
163
-
164
- amc_date_str = None
165
- if pd.notna(row['amc_date']):
166
- try:
167
- amc_date = pd.to_datetime(row['amc_date']).strftime('%Y-%m-%d')
168
- amc_date_str = amc_date
169
- amc_date_dt = datetime.strptime(amc_date, '%Y-%m-%d')
170
- if status_mapped == "Active" and current_date.date() <= amc_date_dt.date() <= next_30_days.date():
171
- logging.info(f"AMC Reminder for Device ID {row['device_id']}: {amc_date}")
172
- except Exception as e:
173
- logging.warning(f"Invalid AMC date for Device ID {row['device_id']}: {str(e)}")
174
-
175
- record = {
176
- 'Device_Id__c': str(row['device_id'])[:50],
177
- 'Log_Type__c': log_type_mapped,
178
- 'Status__c': status_mapped,
179
- 'Timestamp__c': row['timestamp'].isoformat() if pd.notna(row['timestamp']) else None,
180
- 'Usage_Hours__c': float(row['usage_hours']) if pd.notna(row['usage_hours']) else 0.0,
181
- 'Downtime__c': float(row['downtime']) if pd.notna(row['downtime']) else 0.0,
182
- 'AMC_Date__c': amc_date_str
183
- }
184
- records.append(record)
185
-
186
- if records:
187
- batch_size = 100
188
- for i in range(0, len(records), batch_size):
189
- batch = records[i:i + batch_size]
190
- try:
191
- result = sf.bulk.SmartLog__c.insert(batch)
192
- logging.info(f"Saved {len(batch)} records to Salesforce in batch {i//batch_size + 1}")
193
- for res in result:
194
- if not res['success']:
195
- logging.error(f"Failed to save record: {res['errors']}")
196
- except Exception as e:
197
- logging.error(f"Failed to save batch {i//batch_size + 1}: {str(e)}")
198
- else:
199
- logging.warning("No records to save to Salesforce")
200
- except Exception as e:
201
- logging.error(f"Failed to save to Salesforce: {str(e)}")
202
-
203
- # Summarize logs
204
- def summarize_logs(df):
205
- try:
206
- total_devices = df["device_id"].nunique()
207
- total_usage = df["usage_hours"].sum() if "usage_hours" in df.columns else 0
208
- return f"{total_devices} devices processed with {total_usage:.2f} total usage hours."
209
- except Exception as e:
210
- logging.error(f"Summary generation failed: {str(e)}")
211
- return "Failed to generate summary."
212
-
213
- # Anomaly detection
214
- def detect_anomalies(df):
215
- try:
216
- if "usage_hours" not in df.columns or "downtime" not in df.columns:
217
- return "Anomaly detection requires 'usage_hours' and 'downtime' columns.", pd.DataFrame()
218
- features = df[["usage_hours", "downtime"]].fillna(0)
219
- if len(features) > 50:
220
- features = features.sample(n=50, random_state=42)
221
- iso_forest = IsolationForest(contamination=0.1, random_state=42)
222
- df["anomaly"] = iso_forest.fit_predict(features)
223
- anomalies = df[df["anomaly"] == -1][["device_id", "usage_hours", "downtime", "timestamp"]]
224
- if anomalies.empty:
225
- return "No anomalies detected.", anomalies
226
- return "\n".join([f"- Device ID: {row['device_id']}, Usage: {row['usage_hours']}, Downtime: {row['downtime']}, Timestamp: {row['timestamp']}" for _, row in anomalies.head(5).iterrows()]), anomalies
227
- except Exception as e:
228
- logging.error(f"Anomaly detection failed: {str(e)}")
229
- return f"Anomaly detection failed: {str(e)}", pd.DataFrame()
230
-
231
- # AMC reminders
232
- def check_amc_reminders(df, current_date):
233
- try:
234
- if "device_id" not in df.columns or "amc_date" not in df.columns:
235
- return "AMC reminders require 'device_id' and 'amc_date' columns.", pd.DataFrame()
236
- df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
237
- current_date = pd.to_datetime(current_date)
238
- df["days_to_amc"] = (df["amc_date"] - current_date).dt.days
239
- reminders = df[(df["days_to_amc"] >= 0) & (df["days_to_amc"] <= 30)][["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]]
240
- if reminders.empty:
241
- return "No AMC reminders due within the next 30 days.", reminders
242
- return "\n".join([f"- Device ID: {row['device_id']}, AMC Date: {row['amc_date']}" for _, row in reminders.head(5).iterrows()]), reminders
243
- except Exception as e:
244
- logging.error(f"AMC reminder generation failed: {str(e)}")
245
- return f"AMC reminder generation failed: {str(e)}", pd.DataFrame()
246
-
247
- # Dashboard insights
248
- def generate_dashboard_insights(df):
249
- try:
250
- total_devices = df["device_id"].nunique()
251
- avg_usage = df["usage_hours"].mean() if "usage_hours" in df.columns else 0
252
- return f"{total_devices} devices with average usage of {avg_usage:.2f} hours."
253
- except Exception as e:
254
- logging.error(f"Dashboard insights generation failed: {str(e)}")
255
- return "Failed to generate insights."
256
-
257
- # Placeholder chart for empty data
258
- def create_placeholder_chart(title):
259
- fig = go.Figure()
260
- fig.add_annotation(
261
- text="No data available for this chart",
262
- xref="paper", yref="paper",
263
- x=0.5, y=0.5, showarrow=False,
264
- font=dict(size=16)
265
- )
266
- fig.update_layout(title=title, margin=dict(l=20, r=20, t=40, b=20))
267
- return fig
268
-
269
- # Create usage chart
270
- def create_usage_chart(df):
271
- try:
272
- if df.empty or "usage_hours" not in df.columns or "device_id" not in df.columns:
273
- logging.warning("Insufficient data for usage chart")
274
- return create_placeholder_chart("Usage Hours per Device")
275
- usage_data = df.groupby("device_id")["usage_hours"].sum().reset_index()
276
- if len(usage_data) > 5:
277
- usage_data = usage_data.nlargest(5, "usage_hours")
278
- fig = px.bar(
279
- usage_data,
280
- x="device_id",
281
- y="usage_hours",
282
- title="Usage Hours per Device",
283
- labels={"device_id": "Device ID", "usage_hours": "Usage Hours"}
284
- )
285
- fig.update_layout(title_font_size=16, margin=dict(l=20, r=20, t=40, b=20))
286
- return fig
287
- except Exception as e:
288
- logging.error(f"Failed to create usage chart: {str(e)}")
289
- return create_placeholder_chart("Usage Hours per Device")
290
-
291
- # Create downtime chart
292
- def create_downtime_chart(df):
293
  try:
294
- if df.empty or "downtime" not in df.columns or "device_id" not in df.columns:
295
- logging.warning("Insufficient data for downtime chart")
296
- return create_placeholder_chart("Downtime per Device")
297
- downtime_data = df.groupby("device_id")["downtime"].sum().reset_index()
298
- if len(downtime_data) > 5:
299
- downtime_data = downtime_data.nlargest(5, "downtime")
300
- fig = px.bar(
301
- downtime_data,
302
- x="device_id",
303
- y="downtime",
304
- title="Downtime per Device",
305
- labels={"device_id": "Device ID", "downtime": "Downtime (Hours)"}
306
- )
307
- fig.update_layout(title_font_size=16, margin=dict(l=20, r=20, t=40, b=20))
308
- return fig
309
  except Exception as e:
310
- logging.error(f"Failed to create downtime chart: {str(e)}")
311
- return create_placeholder_chart("Downtime per Device")
312
 
313
- # Create daily log trends chart
314
- def create_daily_log_trends_chart(df):
315
  try:
316
- if df.empty or "timestamp" not in df.columns:
317
- logging.warning("Insufficient data for daily log trends chart")
318
- return create_placeholder_chart("Daily Log Trends")
319
- df['date'] = pd.to_datetime(df['timestamp'], errors='coerce').dt.date
320
- daily_logs = df.groupby('date').size().reset_index(name='log_count')
321
- if daily_logs.empty:
322
- return create_placeholder_chart("Daily Log Trends")
323
- fig = px.line(
324
- daily_logs,
325
- x='date',
326
- y='log_count',
327
- title="Daily Log Trends",
328
- labels={"date": "Date", "log_count": "Number of Logs"}
329
- )
330
- fig.update_layout(title_font_size=16, margin=dict(l=20, r=20, t=40, b=20))
331
- return fig
332
  except Exception as e:
333
- logging.error(f"Failed to create daily log trends chart: {str(e)}")
334
- return create_placeholder_chart("Daily Log Trends")
335
 
336
- # Create weekly uptime chart
337
- def create_weekly_uptime_chart(df):
338
  try:
339
- if df.empty or "timestamp" not in df.columns or "usage_hours" not in df.columns or "downtime" not in df.columns:
340
- logging.warning("Insufficient data for weekly uptime chart")
341
- return create_placeholder_chart("Weekly Uptime Percentage")
342
- df['week'] = pd.to_datetime(df['timestamp'], errors='coerce').dt.isocalendar().week
343
- df['year'] = pd.to_datetime(df['timestamp'], errors='coerce').dt.year
344
- weekly_data = df.groupby(['year', 'week']).agg({
345
- 'usage_hours': 'sum',
346
- 'downtime': 'sum'
347
- }).reset_index()
348
- weekly_data['uptime_percent'] = (weekly_data['usage_hours'] / (weekly_data['usage_hours'] + weekly_data['downtime'])) * 100
349
- weekly_data['year_week'] = weekly_data['year'].astype(str) + '-W' + weekly_data['week'].astype(str)
350
- if weekly_data.empty:
351
- return create_placeholder_chart("Weekly Uptime Percentage")
352
- fig = px.bar(
353
- weekly_data,
354
- x='year_week',
355
- y='uptime_percent',
356
- title="Weekly Uptime Percentage",
357
- labels={"year_week": "Year-Week", "uptime_percent": "Uptime %"}
358
- )
359
- fig.update_layout(title_font_size=16, margin=dict(l=20, r=20, t=40, b=20))
360
- return fig
 
 
 
 
 
 
 
 
 
 
361
  except Exception as e:
362
- logging.error(f"Failed to create weekly uptime chart: {str(e)}")
363
- return create_placeholder_chart("Weekly Uptime Percentage")
364
 
365
- # Create anomaly alerts chart
366
- def create_anomaly_alerts_chart(anomalies_df):
367
  try:
368
- if anomalies_df is None or anomalies_df.empty or "timestamp" not in anomalies_df.columns:
369
- logging.warning("Insufficient data for anomaly alerts chart")
370
- return create_placeholder_chart("Anomaly Alerts Over Time")
371
- anomalies_df['date'] = pd.to_datetime(anomalies_df['timestamp'], errors='coerce').dt.date
372
- anomaly_counts = anomalies_df.groupby('date').size().reset_index(name='anomaly_count')
373
- if anomaly_counts.empty:
374
- return create_placeholder_chart("Anomaly Alerts Over Time")
375
- fig = px.scatter(
376
- anomaly_counts,
377
- x='date',
378
- y='anomaly_count',
379
- title="Anomaly Alerts Over Time",
380
- labels={"date": "Date", "anomaly_count": "Number of Anomalies"}
381
  )
382
- fig.update_layout(title_font_size=16, margin=dict(l=20, r=20, t=40, b=20))
383
- return fig
 
 
 
 
384
  except Exception as e:
385
- logging.error(f"Failed to create anomaly alerts chart: {str(e)}")
386
- return create_placeholder_chart("Anomaly Alerts Over Time")
387
 
388
- # Generate device cards
389
- def generate_device_cards(df):
 
 
390
  try:
391
- if df.empty:
392
- return '<p>No devices available to display.</p>'
393
- device_stats = df.groupby('device_id').agg({
394
- 'status': 'last',
395
- 'timestamp': 'max',
396
- }).reset_index()
397
- device_stats['count'] = df.groupby('device_id').size().reindex(device_stats['device_id']).values
398
- device_stats['health'] = device_stats['status'].map({
399
- 'Active': 'Healthy',
400
- 'Inactive': 'Unhealthy',
401
- 'Pending': 'Warning'
402
- }).fillna('Unknown')
403
- cards_html = '<div style="display: flex; flex-wrap: wrap; gap: 20px;">'
404
- for _, row in device_stats.iterrows():
405
- health_color = {'Healthy': 'green', 'Unhealthy': 'red', 'Warning': 'orange', 'Unknown': 'gray'}.get(row['health'], 'gray')
406
- timestamp_str = str(row['timestamp']) if pd.notna(row['timestamp']) else 'Unknown'
407
- cards_html += f"""
408
- <div style="border: 1px solid #e0e0e0; padding: 10px; border-radius: 5px; width: 200px;">
409
- <h4>Device: {row['device_id']}</h4>
410
- <p><b>Health:</b> <span style="color: {health_color}">{row['health']}</span></p>
411
- <p><b>Usage Count:</b> {row['count']}</p>
412
- <p><b>Last Log:</b> {timestamp_str}</p>
413
- </div>
414
- """
415
- cards_html += '</div>'
416
- return cards_html
417
  except Exception as e:
418
- logging.error(f"Failed to generate device cards: {str(e)}")
419
- return f'<p>Error generating device cards: {str(e)}</p>'
420
-
421
- # Generate PDF content
422
- 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):
423
- if not reportlab_available:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  return None
425
  try:
426
- pdf_path = f"status_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
427
- doc = SimpleDocTemplate(pdf_path, pagesize=letter)
428
- styles = getSampleStyleSheet()
429
- story = []
430
-
431
- def safe_paragraph(text, style):
432
- return Paragraph(str(text).replace('\n', '<br/>'), style) if text else Paragraph("", style)
433
-
434
- story.append(Paragraph("LabOps Status Report", styles['Title']))
435
- story.append(Paragraph(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
436
- story.append(Spacer(1, 12))
437
-
438
- story.append(Paragraph("Summary Report", styles['Heading2']))
439
- story.append(safe_paragraph(summary, styles['Normal']))
440
- story.append(Spacer(1, 12))
441
-
442
- story.append(Paragraph("Log Preview", styles['Heading2']))
443
- if not preview_df.empty:
444
- data = [preview_df.columns.tolist()] + preview_df.head(5).values.tolist()
445
- table = Table(data)
446
- table.setStyle(TableStyle([
447
- ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
448
- ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
449
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
450
- ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
451
- ('FONTSIZE', (0, 0), (-1, 0), 12),
452
- ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
453
- ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
454
- ('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
455
- ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
456
- ('FONTSIZE', (0, 1), (-1, -1), 10),
457
- ('GRID', (0, 0), (-1, -1), 1, colors.black)
458
- ]))
459
- story.append(table)
460
  else:
461
- story.append(safe_paragraph("No preview available.", styles['Normal']))
462
- story.append(Spacer(1, 12))
463
-
464
- story.append(Paragraph("Device Cards", styles['Heading2']))
465
- device_cards_text = device_cards_html.replace('<div>', '').replace('</div>', '\n').replace('<h4>', '').replace('</h4>', '\n').replace('<p>', '').replace('</p>', '\n').replace('<b>', '').replace('</b>', '').replace('<span style="color: green">', '').replace('<span style="color: red">', '').replace('<span style="color: orange">', '').replace('<span style="color: gray">', '').replace('</span>', '')
466
- story.append(safe_paragraph(device_cards_text, styles['Normal']))
467
- story.append(Spacer(1, 12))
468
-
469
- story.append(Paragraph("Anomaly Detection", styles['Heading2']))
470
- story.append(safe_paragraph(anomalies, styles['Normal']))
471
- story.append(Spacer(1, 12))
472
-
473
- story.append(Paragraph("AMC Reminders", styles['Heading2']))
474
- story.append(safe_paragraph(amc_reminders, styles['Normal']))
475
- story.append(Spacer(1, 12))
476
-
477
- story.append(Paragraph("Dashboard Insights", styles['Heading2']))
478
- story.append(safe_paragraph(insights, styles['Normal']))
479
- story.append(Spacer(1, 12))
480
-
481
- story.append(Paragraph("Charts", styles['Heading2']))
482
- story.append(Paragraph("[Chart placeholders - see dashboard for visuals]", styles['Normal']))
483
-
484
- doc.build(story)
485
- logging.info(f"PDF generated at {pdf_path}")
486
- return pdf_path
487
  except Exception as e:
488
- logging.error(f"Failed to generate PDF: {str(e)}")
489
  return None
490
 
491
- # Main processing function
492
- async def process_logs(file_obj, lab_site_filter, equipment_type_filter, date_range, cached_df_state, last_modified_state):
493
- start_time = time.time()
494
  try:
495
- if not file_obj:
496
- return "No file uploaded.", "<p>No data available.</p>", None, '<p>No device cards available.</p>', None, None, None, None, "No anomalies detected.", "No AMC reminders.", "No insights generated.", None, cached_df_state, last_modified_state
497
-
498
- file_path = file_obj.name
499
- current_modified_time = os.path.getmtime(file_path)
500
-
501
- # Read file only if it's new or modified
502
- if cached_df_state is None or current_modified_time != last_modified_state:
503
- logging.info(f"Processing new or modified file: {file_path}")
504
- if not file_path.endswith(".csv"):
505
- return "Please upload a CSV file.", "<p>Invalid file format.</p>", None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, cached_df_state, last_modified_state
506
-
507
- required_columns = ["device_id", "log_type", "status", "timestamp", "usage_hours", "downtime", "amc_date"]
508
- dtypes = {
509
- "device_id": "string",
510
- "log_type": "string",
511
- "status": "string",
512
- "usage_hours": "float32",
513
- "downtime": "float32",
514
- "amc_date": "string"
515
- }
516
- df = pd.read_csv(file_path, dtype=dtypes)
517
- missing_columns = [col for col in required_columns if col not in df.columns]
518
- if missing_columns:
519
- return f"Missing columns: {missing_columns}", "<p>Missing required columns.</p>", None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, cached_df_state, last_modified_state
520
-
521
- df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
522
- df["amc_date"] = pd.to_datetime(df["amc_date"], errors='coerce')
523
- if df["timestamp"].dt.tz is None:
524
- df["timestamp"] = df["timestamp"].dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata')
525
- if df.empty:
526
- return "No data available.", "<p>No data available.</p>", None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, df, current_modified_time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  else:
528
- df = cached_df_state
529
-
530
- # Apply filters
531
- filtered_df = df.copy()
532
- if lab_site_filter and lab_site_filter != 'All' and 'lab_site' in filtered_df.columns:
533
- filtered_df = filtered_df[filtered_df['lab_site'] == lab_site_filter]
534
- if equipment_type_filter and equipment_type_filter != 'All' and 'equipment_type' in filtered_df.columns:
535
- filtered_df = filtered_df[filtered_df['equipment_type'] == equipment_type_filter]
536
- if date_range is not None:
537
- if isinstance(date_range, (int, float)):
538
- # Convert single value to a range for a single day
539
- days = int(date_range)
540
- date_range = [days, days]
541
- logging.info(f"Converted single value {days} to range {date_range}")
542
- if len(date_range) != 2 or not all(isinstance(x, (int, float)) for x in date_range):
543
- logging.error(f"Invalid date range format: {date_range}. Expected [start, end] or single integer.")
544
- return "Invalid date range format. Please use [start, end] (e.g., [-7, 0]) or a single integer (e.g., -1).", "<p>Error processing data.</p>", None, '<p>Error processing data.</p>', None, None, None, None, "", "", "", None, df, current_modified_time
545
- days_start, days_end = date_range
546
- today = pd.to_datetime(datetime.now()).tz_localize('Asia/Kolkata')
547
- start_date = today + pd.Timedelta(days=days_start)
548
- end_date = today + pd.Timedelta(days=days_end) + pd.Timedelta(days=1) - pd.Timedelta(seconds=1)
549
- start_date = start_date.tz_convert('Asia/Kolkata') if start_date.tzinfo else start_date.tz_localize('Asia/Kolkata')
550
- end_date = end_date.tz_convert('Asia/Kolkata') if end_date.tzinfo else end_date.tz_localize('Asia/Kolkata')
551
- logging.info(f"Date range filter applied: start_date={start_date}, end_date={end_date}")
552
- logging.info(f"Before date filter: {len(filtered_df)} rows")
553
- filtered_df = filtered_df[(filtered_df['timestamp'] >= start_date) & (filtered_df['timestamp'] <= end_date)]
554
- logging.info(f"After date filter: {len(filtered_df)} rows")
555
- if days_start > days_end:
556
- logging.warning("Start date is after end date; results may be empty or unexpected.")
557
-
558
- if filtered_df.empty:
559
- return "No data after applying filters.", "<p>No data after filters.</p>", None, '<p>No device cards available.</p>', None, None, None, None, "", "", "", None, df, current_modified_time
560
-
561
- # Generate table for preview
562
- preview_df = filtered_df[['device_id', 'log_type', 'status', 'timestamp', 'usage_hours', 'downtime', 'amc_date']].head(5)
563
- preview_html = preview_df.to_html(index=False, classes='table table-striped', border=0)
564
-
565
- # Run critical tasks concurrently
566
- with ThreadPoolExecutor(max_workers=2) as executor:
567
- future_anomalies = executor.submit(detect_anomalies, filtered_df)
568
- future_amc = executor.submit(check_amc_reminders, filtered_df, datetime.now())
569
-
570
- summary = f"Step 1: Summary Report\n{summarize_logs(filtered_df)}"
571
- anomalies, anomalies_df = future_anomalies.result()
572
- anomalies = f"Anomaly Detection\n{anomalies}"
573
- amc_reminders, reminders_df = future_amc.result()
574
- amc_reminders = f"AMC Reminders\n{amc_reminders}"
575
- insights = f"Dashboard Insights\n{generate_dashboard_insights(filtered_df)}"
576
-
577
- # Generate charts sequentially
578
- usage_chart = create_usage_chart(filtered_df)
579
- downtime_chart = create_downtime_chart(filtered_df)
580
- daily_log_chart = create_daily_log_trends_chart(filtered_df)
581
- weekly_uptime_chart = create_weekly_uptime_chart(filtered_df)
582
- anomaly_alerts_chart = create_anomaly_alerts_chart(anomalies_df)
583
- device_cards = generate_device_cards(filtered_df)
584
-
585
- # Save to Salesforce after all other processing
586
- save_to_salesforce(filtered_df, reminders_df)
587
- create_salesforce_reports(filtered_df)
588
-
589
- elapsed_time = time.time() - start_time
590
- logging.info(f"Processing completed in {elapsed_time:.2f} seconds")
591
- if elapsed_time > 3:
592
- logging.warning(f"Processing time exceeded 3 seconds: {elapsed_time:.2f} seconds")
593
-
594
- return (summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights, None, df, current_modified_time)
595
- except Exception as e:
596
- logging.error(f"Failed to process file: {str(e)}")
597
- return f"Error: {str(e)}", "<p>Error processing data.</p>", None, '<p>Error processing data.</p>', None, None, None, None, "", "", "", None, cached_df_state, last_modified_state
598
-
599
- # Generate PDF separately
600
- async def generate_pdf(summary, preview_html, usage_chart, device_cards, daily_log_chart, weekly_uptime_chart, anomaly_alerts_chart, downtime_chart, anomalies, amc_reminders, insights):
601
- try:
602
- preview_df = pd.read_html(preview_html)[0]
603
- 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)
604
- return pdf_file
605
  except Exception as e:
606
- logging.error(f"Failed to generate PDF: {str(e)}")
607
  return None
608
 
609
- # Update filters
610
- def update_filters(file_obj, current_file_state):
611
- if not file_obj or file_obj.name == current_file_state:
612
- return gr.update(), gr.update(), current_file_state
613
  try:
614
- with open(file_obj.name, 'rb') as f:
615
- csv_content = f.read().decode('utf-8')
616
- df = pd.read_csv(io.StringIO(csv_content))
617
- df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
- 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']
620
- 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']
 
 
621
 
622
- return gr.update(choices=lab_site_options, value='All'), gr.update(choices=equipment_type_options, value='All'), file_obj.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  except Exception as e:
624
- logging.error(f"Failed to update filters: {str(e)}")
625
- return gr.update(choices=['All'], value='All'), gr.update(choices=['All'], value='All'), current_file_state
626
 
627
- # Gradio Interface
628
- try:
629
- logging.info("Initializing Gradio interface...")
630
- with gr.Blocks(css="""
631
- .dashboard-container {border: 1px solid #e0e0e0; padding: 10px; border-radius: 5px;}
632
- .dashboard-title {font-size: 24px; font-weight: bold; margin-bottom: 5px;}
633
- .dashboard-section {margin-bottom: 20px;}
634
- .dashboard-section h3 {font-size: 18px; margin-bottom: 2px;}
635
- .dashboard-section p {margin: 1px 0; line-height: 1.2;}
636
- .dashboard-section ul {margin: 2px 0; padding-left: 20px;}
637
- .table {width: 100%; border-collapse: collapse;}
638
- .table th, .table td {border: 1px solid #ddd; padding: 8px; text-align: left;}
639
- .table th {background-color: #f2f2f2;}
640
- .table tr:nth-child(even) {background-color: #f9f9f9;}
641
- """) as iface:
642
- gr.Markdown("<h1>LabOps Log Analyzer Dashboard</h1>")
643
- gr.Markdown("Upload a CSV file to analyze. Click 'Analyze' to refresh the dashboard. Use 'Export PDF' for report download. Date Range can be [start, end] (e.g., [-7, 0] for June 11 to June 18) or a single integer (e.g., -1 for June 17).")
644
-
645
- last_modified_state = gr.State(value=None)
646
- current_file_state = gr.State(value=None)
647
- cached_df_state = gr.State(value=None)
648
 
649
  with gr.Row():
650
- with gr.Column(scale=1):
651
- file_input = gr.File(label="Upload Logs (CSV)", file_types=[".csv"])
652
- with gr.Group():
653
- gr.Markdown("### Filters")
654
- lab_site_filter = gr.Dropdown(label="Lab Site", choices=['All'], value='All', interactive=True)
655
- equipment_type_filter = gr.Dropdown(label="Equipment Type", choices=['All'], value='All', interactive=True)
656
- date_range_filter = gr.Slider(label="Date Range (Days from Today)", minimum=-365, maximum=0, step=1, value=[-7, 0], interactive=True)
657
- submit_button = gr.Button("Analyze", variant="primary")
658
- pdf_button = gr.Button("Export PDF", variant="secondary")
659
-
660
- with gr.Column(scale=2):
661
- with gr.Group(elem_classes="dashboard-container"):
662
- gr.Markdown("<div class='dashboard-title'>Analysis Results</div>")
663
- with gr.Group(elem_classes="dashboard-section"):
664
- gr.Markdown("### Step 1: Summary Report")
665
- summary_output = gr.Markdown()
666
- with gr.Group(elem_classes="dashboard-section"):
667
- gr.Markdown("### Step 2: Log Preview")
668
- preview_output = gr.HTML()
669
- with gr.Group(elem_classes="dashboard-section"):
670
- gr.Markdown("### Device Cards")
671
- device_cards_output = gr.HTML()
672
- with gr.Group(elem_classes="dashboard-section"):
673
- gr.Markdown("### Charts")
674
- with gr.Tab("Usage Hours per Device"):
675
- usage_chart_output = gr.Plot()
676
- with gr.Tab("Downtime per Device"):
677
- downtime_chart_output = gr.Plot()
678
- with gr.Tab("Daily Log Trends"):
679
- daily_log_trends_output = gr.Plot()
680
- with gr.Tab("Weekly Uptime Percentage"):
681
- weekly_uptime_output = gr.Plot()
682
- with gr.Tab("Anomaly Alerts"):
683
- anomaly_alerts_output = gr.Plot()
684
- with gr.Group(elem_classes="dashboard-section"):
685
- gr.Markdown("### Step 4: Anomaly Detection")
686
- anomaly_output = gr.Markdown()
687
- with gr.Group(elem_classes="dashboard-section"):
688
- gr.Markdown("### Step 5: AMC Reminders")
689
- amc_output = gr.Markdown()
690
- with gr.Group(elem_classes="dashboard-section"):
691
- gr.Markdown("### Step 6: Insights")
692
- insights_output = gr.Markdown()
693
- with gr.Group(elem_classes="dashboard-section"):
694
- gr.Markdown("### Export Report")
695
- pdf_output = gr.File(label="Download Status Report as PDF")
696
-
697
- file_input.change(
698
- fn=update_filters,
699
- inputs=[file_input, current_file_state],
700
- outputs=[lab_site_filter, equipment_type_filter, current_file_state],
701
- queue=False
702
  )
703
-
704
- submit_button.click(
705
- fn=process_logs,
706
- inputs=[file_input, lab_site_filter, equipment_type_filter, date_range_filter, cached_df_state, last_modified_state],
707
- outputs=[summary_output, preview_output, usage_chart_output, device_cards_output, daily_log_trends_output, weekly_uptime_output, anomaly_alerts_output, downtime_chart_output, anomaly_output, amc_output, insights_output, pdf_output, cached_df_state, last_modified_state]
708
  )
709
-
710
- pdf_button.click(
711
- fn=generate_pdf,
712
- inputs=[summary_output, preview_output, usage_chart_output, device_cards_output, daily_log_trends_output, weekly_uptime_output, anomaly_alerts_output, downtime_chart_output, anomaly_output, amc_output, insights_output],
713
- outputs=[pdf_output]
714
  )
715
-
716
- logging.info("Gradio interface initialized successfully")
717
- except Exception as e:
718
- logging.error(f"Failed to initialize Gradio interface: {str(e)}")
719
- raise e
 
720
 
721
  if __name__ == "__main__":
722
- try:
723
- logging.info("Launching Gradio interface...")
724
- iface.launch(server_name="0.0.0.0", server_port=7860, debug=True, share=False)
725
- logging.info("Gradio interface launched successfully")
726
- except Exception as e:
727
- logging.error(f"Failed to launch Gradio interface: {str(e)}")
728
- print(f"Error launching app: {str(e)}")
729
- raise e
 
1
  import gradio as gr
2
+ import librosa
3
+ import numpy as np
4
+ import torch
5
+ from transformers import WhisperProcessor, WhisperForConditionalGeneration
 
 
 
 
 
 
 
6
  from simple_salesforce import Salesforce
7
+ import os
8
+ from datetime import datetime
9
+ import logging
10
+ import webrtcvad
11
+ import google.generativeai as genai
12
+ from gtts import gTTS
13
+ import tempfile
14
+ import base64
15
+ import re
16
+ import subprocess
17
+ from cryptography.fernet import Fernet
18
+
19
+ # Set up logging
20
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
21
+ logger = logging.getLogger(__name__)
22
+ usage_metrics = {"total_assessments": 0, "assessments_by_language": {}}
23
+
24
+ # Environment variables
25
+ SF_USERNAME = os.getenv("SF_USERNAME", "smartvoicebot@voice.com")
26
+ SF_PASSWORD = os.getenv("SF_PASSWORD", "voicebot1")
27
+ SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN", "jq4VVHUFti6TmzJDjjegv2h6b")
28
+ SF_INSTANCE_URL = os.getenv("SF_INSTANCE_URL", "https://swe42.sfdc-cehfhs.salesforce.com")
29
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyBzr5vVpbe8CV1v70l3pGDp9vRJ76yCxdk")
30
+ ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", Fernet.generate_key().decode())
31
+ DEFAULT_EMAIL = os.getenv("SALESFORCE_USER_EMAIL", "default@mindcare.com")
32
+
33
+ # Initialize encryption
34
+ cipher = Fernet(ENCRYPTION_KEY)
35
+
36
+ # Initialize Salesforce
37
  try:
38
  sf = Salesforce(
39
+ username=SF_USERNAME,
40
+ password=SF_PASSWORD,
41
+ security_token=SF_SECURITY_TOKEN,
42
+ instance_url=SF_INSTANCE_URL
43
  )
44
+ logger.info(f"Connected to Salesforce at {SF_INSTANCE_URL}")
45
  except Exception as e:
46
+ logger.error(f"Salesforce connection failed: {str(e)}")
47
  sf = None
48
 
49
+ # Initialize Google Gemini
50
  try:
51
+ genai.configure(api_key=GEMINI_API_KEY)
52
+ gemini_model = genai.GenerativeModel('gemini-1.5-flash')
53
+ chat = gemini_model.start_chat(history=[])
54
+ logger.info("Connected to Google Gemini")
55
+ except Exception as e:
56
+ logger.error(f"Google Gemini initialization failed: {str(e)}")
57
+ chat = None
58
+
59
+ # Load Whisper model
60
+ SUPPORTED_LANGUAGES = {"en": "english", "es": "spanish", "hi": "hindi", "zh": "mandarin"}
61
+ SALESFORCE_LANGUAGE_MAP = {"en": "English", "es": "Spanish", "hi": "Hindi", "zh": "Mandarin"}
62
+ whisper_processor = WhisperProcessor.from_pretrained("openai/whisper-small")
63
+ whisper_model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small")
64
+ vad = webrtcvad.Vad(mode=2)
65
+
66
+ # Context for chatbot
67
+ base_info = """
68
+ You are MindCare, an AI health assistant providing support in:
69
+ - Mental health: Emotional support, stress management
70
+ - Medical guidance: Symptom analysis, general advice
71
+ - General health: Lifestyle and wellness recommendations
72
+ Tone: Empathetic, supportive, informative. Always suggest professional consultation for medical issues.
73
+ """
74
+ context = [base_info]
75
+
76
+ def encrypt_data(data):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  try:
78
+ return cipher.encrypt(data.encode('utf-8')).decode('utf-8')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  except Exception as e:
80
+ logger.error(f"Encryption failed: {str(e)}")
81
+ return data
82
 
83
+ def decrypt_data(encrypted_data):
 
84
  try:
85
+ return cipher.decrypt(encrypted_data.encode('utf-8')).decode('utf-8')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  except Exception as e:
87
+ logger.error(f"Decryption failed: {str(e)}")
88
+ return encrypted_data
89
 
90
+ def extract_health_features(audio, sr):
 
91
  try:
92
+ audio = audio / np.max(np.abs(audio)) if np.max(np.abs(audio)) != 0 else audio
93
+ frame_duration = 30
94
+ frame_samples = int(sr * frame_duration / 1000)
95
+ frames = [audio[i:i + frame_samples] for i in range(0, len(audio), frame_samples)]
96
+ voiced_frames = [frame for frame in frames if len(frame) == frame_samples and vad.is_speech((frame * 32768).astype(np.int16).tobytes(), sr)]
97
+ if not voiced_frames:
98
+ raise ValueError("No voiced segments detected")
99
+ voiced_audio = np.concatenate(voiced_frames)
100
+
101
+ # Enhanced feature extraction
102
+ pitches, magnitudes = librosa.piptrack(y=voiced_audio, sr=sr, fmin=75, fmax=300)
103
+ valid_pitches = [p for p in pitches[magnitudes > 0] if 75 <= p <= 300]
104
+ pitch = np.mean(valid_pitches) if valid_pitches else 0
105
+ jitter = np.std(valid_pitches) / pitch if pitch and valid_pitches else 0
106
+ jitter = min(jitter, 10) # Cap jitter
107
+ amplitudes = librosa.feature.rms(y=voiced_audio, frame_length=2048, hop_length=512)[0]
108
+ shimmer = np.std(amplitudes) / np.mean(amplitudes) if np.mean(amplitudes) else 0
109
+ shimmer = min(shimmer, 10) # Cap shimmer
110
+ energy = np.mean(librosa.feature.rms(y=voiced_audio, frame_length=2048, hop_length=512)[0])
111
+
112
+ # Additional features
113
+ mfcc = np.mean(librosa.feature.mfcc(y=voiced_audio, sr=sr, n_mfcc=13), axis=1)
114
+ spectral_centroid = np.mean(librosa.feature.spectral_centroid(y=voiced_audio, sr=sr))
115
+
116
+ return {
117
+ "pitch": pitch,
118
+ "jitter": jitter * 100,
119
+ "shimmer": shimmer * 100,
120
+ "energy": energy,
121
+ "mfcc_mean": np.mean(mfcc),
122
+ "spectral_centroid": spectral_centroid
123
+ }
124
  except Exception as e:
125
+ logger.error(f"Feature extraction failed: {str(e)}")
126
+ raise
127
 
128
+ def transcribe_audio(audio, language="en"):
 
129
  try:
130
+ whisper_model.config.forced_decoder_ids = whisper_processor.get_decoder_prompt_ids(
131
+ language=SUPPORTED_LANGUAGES.get(language, "english"), task="transcribe"
 
 
 
 
 
 
 
 
 
 
 
132
  )
133
+ inputs = whisper_processor(audio, sampling_rate=16000, return_tensors="pt")
134
+ with torch.no_grad():
135
+ generated_ids = whisper_model.generate(inputs["input_features"])
136
+ transcription = whisper_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
137
+ logger.info(f"Transcription (language: {language}): {transcription}")
138
+ return transcription
139
  except Exception as e:
140
+ logger.error(f"Transcription failed: {str(e)}")
141
+ return None
142
 
143
+ def get_chatbot_response(message, language="en"):
144
+ if not chat or not message:
145
+ return "Unable to generate response.", None
146
+ full_context = "\n".join(context) + f"\nUser: {message}\nMindCare:"
147
  try:
148
+ response = chat.send_message(full_context).text
149
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
150
+ tts = gTTS(text=response, lang=language, slow=False)
151
+ tts.save(temp_audio.name)
152
+ audio_path = temp_audio.name
153
+ return response, audio_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  except Exception as e:
155
+ logger.error(f"Chatbot response failed: {str(e)}")
156
+ return "Error generating response.", None
157
+
158
+ def analyze_symptoms(text, features):
159
+ feedback = []
160
+ text = text.lower() if text else ""
161
+
162
+ # Voice-based health assessment
163
+ if features["jitter"] > 2.0 or features["shimmer"] > 3.0:
164
+ feedback.append("Your voice shows signs of irregularity (high jitter or shimmer), which may indicate respiratory or vocal strain. Consider a medical evaluation.")
165
+ if features["energy"] < 0.01:
166
+ feedback.append("Low vocal energy detected, which might suggest fatigue or low mood. Rest and professional consultation are recommended.")
167
+ if features["pitch"] < 100 or features["pitch"] > 250:
168
+ feedback.append(f"Unusual pitch range ({features['pitch']:.2f} Hz) detected, which could indicate vocal cord issues or emotional stress.")
169
+ if features["spectral_centroid"] > 2000:
170
+ feedback.append("High spectral centroid suggests tense or strained speech, possibly linked to stress or anxiety.")
171
+
172
+ # Text-based symptom analysis
173
+ if "cough" in text or "breath" in text:
174
+ feedback.append("Your description suggests respiratory symptoms. Possible conditions include bronchitis or asthma. Please consult a doctor.")
175
+ if "stress" in text or "anxious" in text:
176
+ feedback.append("You mentioned stress or anxiety. Try deep breathing exercises or mindfulness. Consider speaking with a mental health professional.")
177
+ if "pain" in text:
178
+ feedback.append("Pain reported. For mild pain, consider Paracetamol; for inflammation, Ibuprofen may help. Consult a doctor before taking medication.")
179
+ if not feedback:
180
+ feedback.append("No specific health concerns detected from voice or text. Maintain a healthy lifestyle and consult a doctor if symptoms arise.")
181
+
182
+ return "\n".join(feedback)
183
+
184
+ def store_user_consent(language):
185
+ if not sf:
186
+ logger.warning("Salesforce not connected; skipping consent storage")
187
  return None
188
  try:
189
+ user = sf.query(f"SELECT Id FROM HealthUser__c WHERE Email__c = '{DEFAULT_EMAIL}'")
190
+ user_id = None
191
+ if user["totalSize"] == 0:
192
+ user = sf.HealthUser__c.create({
193
+ "Email__c": DEFAULT_EMAIL,
194
+ "Language__c": SALESFORCE_LANGUAGE_MAP.get(language, "English"),
195
+ "ConsentGiven__c": True
196
+ })
197
+ user_id = user["id"]
198
+ logger.info(f"Created new user with email: {DEFAULT_EMAIL}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  else:
200
+ user_id = user["records"][0]["Id"]
201
+ sf.HealthUser__c.update(user_id, {
202
+ "Language__c": SALESFORCE_LANGUAGE_MAP.get(language, "English"),
203
+ "ConsentGiven__c": True
204
+ })
205
+ logger.info(f"Updated user with email: {DEFAULT_EMAIL}")
206
+ sf.ConsentLog__c.create({
207
+ "HealthUser__c": user_id,
208
+ "ConsentType__c": "Voice Analysis",
209
+ "ConsentDate__c": datetime.utcnow().isoformat()
210
+ })
211
+ return user_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  except Exception as e:
213
+ logger.error(f"Consent storage failed: {str(e)}")
214
  return None
215
 
216
+ def generate_pdf_report(feedback, transcription, features, language):
 
 
217
  try:
218
+ feedback = feedback.replace('&', '\\&').replace('%', '\\%').replace('$', '\\$').replace('#', '\\#')
219
+ transcription = transcription.replace('&', '\\&').replace('%', '\\%').replace('$', '\\$').replace('#', '\\#') if transcription else "None"
220
+ email = DEFAULT_EMAIL.replace('&', '\\&').replace('%', '\\%').replace('$', '\\$').replace('#', '\\#')
221
+ latex_content = f"""
222
+ \\documentclass[a4paper,12pt]{{article}}
223
+ \\usepackage[utf8]{{inputenc}}
224
+ \\usepackage{{geometry}}
225
+ \\usepackage{{parskip}}
226
+ \\usepackage{{titlesec}}
227
+ \\usepackage{{times}}
228
+ \\geometry{{margin=1in}}
229
+ \\titleformat{{\\section}}{{\\large\\bfseries}}{{\\thesection}}{{1em}}{{}}
230
+ \\titleformat{{\\subsection}}{{\\bfseries}}{{\\thesubsection}}{{1em}}{{}}
231
+ \\usepackage{{datetime}}
232
+ \\newdateformat{{isodate}}{{\\THEDAY{ }\\shortmonthname[\\THEMONTH] \\THEYEAR}}
233
+ \\begin{{document}}
234
+ \\begin{{center}}
235
+ \\textbf{{\\large MindCare Health Assistant Report}} \\\\
236
+ \\vspace{{0.5cm}}
237
+ Generated on \\isodate\\today\\ at \\currenttime
238
+ \\end{{center}}
239
+ \\section*{{User Information}}
240
+ \\begin{{itemize}}
241
+ \\item \\textbf{{Email}}: {email}
242
+ \\item \\textbf{{Language}}: {SALESFORCE_LANGUAGE_MAP.get(language, "English")}
243
+ \\end{{itemize}}
244
+ \\section*{{Voice Analysis Results}}
245
+ \\subsection*{{Health Assessment}}
246
+ {feedback}
247
+ \\subsection*{{Transcription}}
248
+ {transcription}
249
+ \\subsection*{{Voice Metrics}}
250
+ \\begin{{itemize}}
251
+ \\item \\textbf{{Pitch}}: {features['pitch']:.2f} Hz
252
+ \\item \\textbf{{Jitter}}: {features['jitter']:.2f}\\%
253
+ \\item \\textbf{{Shimmer}}: {features['shimmer']:.2f}\\%
254
+ \\item \\textbf{{Energy}}: {features['energy']:.4f}
255
+ \\item \\textbf{{MFCC Mean}}: {features['mfcc_mean']:.2f}
256
+ \\item \\textbf{{Spectral Centroid}}: {features['spectral_centroid']:.2f} Hz
257
+ \\end{{itemize}}
258
+ \\section*{{Disclaimer}}
259
+ This report is a preliminary analysis and not a medical diagnosis. Always consult a healthcare provider.
260
+ \\end{{document}}
261
+ """
262
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".tex") as tex_file:
263
+ tex_file.write(latex_content.encode('utf-8'))
264
+ tex_file_path = tex_file.name
265
+ pdf_path = tex_file_path.replace('.tex', '.pdf')
266
+ result = subprocess.run(['latexmk', '-pdf', '-pdflatex=pdflatex', '-interaction=nonstopmode', tex_file_path],
267
+ capture_output=True, text=True, check=True)
268
+ logger.info(f"PDF generation output: {result.stdout}")
269
+ for ext in ['.aux', '.log', '.out', '.fls', '.fdb_latexmk']:
270
+ try:
271
+ os.remove(tex_file_path.replace('.tex', ext))
272
+ except:
273
+ pass
274
+ if os.path.exists(pdf_path):
275
+ logger.info(f"Generated PDF report: {pdf_path}")
276
+ return pdf_path
277
  else:
278
+ logger.error("PDF file was not created")
279
+ return None
280
+ except subprocess.CalledProcessError as e:
281
+ logger.error(f"PDF generation failed: {e.stderr}")
282
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  except Exception as e:
284
+ logger.error(f"PDF generation failed: {str(e)}")
285
  return None
286
 
287
+ def store_in_salesforce(user_id, audio_file, feedback, respiratory_score, mental_health_score, features, transcription, language):
288
+ if not sf:
289
+ logger.warning("Salesforce not connected; skipping storage")
290
+ return
291
  try:
292
+ with open(audio_file, "rb") as f:
293
+ audio_content = base64.b64encode(f.read()).decode()
294
+ content_version = sf.ContentVersion.create({
295
+ "Title": f"Voice_Assessment_{datetime.utcnow().isoformat()}",
296
+ "PathOnClient": os.path.basename(audio_file),
297
+ "VersionData": audio_content,
298
+ "IsMajorVersion": True
299
+ })
300
+ content_document_id = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version['id']}'")["records"][0]["ContentDocumentId"]
301
+ file_url = f"{SF_INSTANCE_URL}/lightning/r/ContentDocument/{content_document_id}/view"
302
+
303
+ feedback_str = feedback.encode('utf-8').decode('utf-8')
304
+ encrypted_feedback = encrypt_data(feedback_str)
305
+ if len(encrypted_feedback) > 131072:
306
+ encrypted_feedback = encrypted_feedback[:131072]
307
+
308
+ assessment = sf.VoiceAssessment__c.create({
309
+ "HealthUser__c": user_id,
310
+ "VoiceRecording__c": file_url,
311
+ "AssessmentResult__c": encrypted_feedback,
312
+ "AssessmentDate__c": datetime.utcnow().isoformat(),
313
+ "ConfidenceScore__c": 95.0,
314
+ "RespiratoryScore__c": float(respiratory_score),
315
+ "MentalHealthScore__c": float(mental_health_score),
316
+ "Pitch__c": float(features["pitch"]),
317
+ "Jitter__c": float(features["jitter"]),
318
+ "Shimmer__c": float(features["shimmer"]),
319
+ "Energy__c": float(features["energy"]),
320
+ "Transcription__c": transcription or "None",
321
+ "Language__c": SALESFORCE_LANGUAGE_MAP.get(language, "English")
322
+ })
323
+ sf.ContentDocumentLink.create({
324
+ "ContentDocumentId": content_document_id,
325
+ "LinkedEntityId": assessment["id"],
326
+ "ShareType": "V"
327
+ })
328
+ logger.info(f"Stored assessment in Salesforce: {assessment['id']}")
329
+ except Exception as e:
330
+ logger.error(f"Salesforce storage failed: {str(e)}")
331
+ raise
332
 
333
+ def analyze_voice(audio_file=None, language="en"):
334
+ global usage_metrics
335
+ usage_metrics["total_assessments"] += 1
336
+ usage_metrics["assessments_by_language"][language] = usage_metrics["assessments_by_language"].get(language, 0) + 1
337
 
338
+ try:
339
+ if not audio_file or not os.path.exists(audio_file):
340
+ raise ValueError("No valid audio file provided")
341
+
342
+ audio, sr = librosa.load(audio_file, sr=16000)
343
+ if len(audio) < sr:
344
+ raise ValueError("Audio too short (minimum 1 second)")
345
+
346
+ user_id = store_user_consent(language)
347
+ if not user_id:
348
+ return "Error: Failed to store user consent.", None
349
+
350
+ features = extract_health_features(audio, sr)
351
+ transcription = transcribe_audio(audio, language)
352
+ feedback = analyze_symptoms(transcription, features)
353
+
354
+ respiratory_score = features["jitter"]
355
+ mental_health_score = features["shimmer"]
356
+
357
+ feedback += f"\n\n**Voice Analysis Details**:\n"
358
+ feedback += f"- Pitch: {features['pitch']:.2f} Hz\n"
359
+ feedback += f"- Jitter: {features['jitter']:.2f}% (voice stability)\n"
360
+ feedback += f"- Shimmer: {features['shimmer']:.2f}% (amplitude variation)\n"
361
+ feedback += f"- Energy: {features['energy']:.4f} (vocal intensity)\n"
362
+ feedback += f"- MFCC Mean: {features['mfcc_mean']:.2f} (timbre quality)\n"
363
+ feedback += f"- Spectral Centroid: {features['spectral_centroid']:.2f} Hz (voice brightness)\n"
364
+ feedback += f"- Transcription: {transcription if transcription else 'None'}\n"
365
+ feedback += "\n**Disclaimer**: This is a preliminary analysis. Consult a healthcare provider for professional evaluation."
366
+
367
+ if sf:
368
+ store_in_salesforce(user_id, audio_file, feedback, respiratory_score, mental_health_score, features, transcription, language)
369
+
370
+ pdf_path = generate_pdf_report(feedback, transcription, features, language)
371
+
372
+ try:
373
+ os.remove(audio_file)
374
+ logger.info(f"Deleted audio file: {audio_file}")
375
+ except Exception as e:
376
+ logger.error(f"Failed to delete audio file: {str(e)}")
377
+
378
+ return feedback, pdf_path
379
  except Exception as e:
380
+ logger.error(f"Audio processing failed: {str(e)}")
381
+ return f"Error: {str(e)}", None
382
 
383
+ def launch():
384
+ with gr.Blocks(title="MindCare Health Assistant", css=".gradio-container {max-width: 1200px; margin: auto; font-family: Arial, sans-serif;}") as demo:
385
+ gr.Markdown("# MindCare Health Assistant")
386
+ gr.Markdown("Record your voice or type a message for health assessments and suggestions.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
  with gr.Row():
389
+ with gr.Column():
390
+ gr.Markdown("### Voice Analysis")
391
+ gr.Markdown("Record or upload voice (1+ sec) describing symptoms (e.g., 'I have a cough' or 'I feel stressed').")
392
+ language_input = gr.Dropdown(choices=list(SUPPORTED_LANGUAGES.keys()), label="Select Language", value="en")
393
+ consent_input = gr.Checkbox(label="I consent to data storage and voice analysis", value=True, interactive=False)
394
+ audio_input = gr.Audio(type="filepath", label="Record or Upload Voice (WAV, MP3, FLAC)", format="wav")
395
+ voice_output = gr.Textbox(label="Health Assessment Results", elem_id="health-results")
396
+ pdf_output = gr.File(label="Download Assessment Report (PDF)")
397
+ submit_btn = gr.Button("Submit")
398
+ clear_btn = gr.Button("Clear")
399
+
400
+ with gr.Column():
401
+ gr.Markdown("### Health Suggestions")
402
+ gr.Markdown("Enter a message for personalized health advice.")
403
+ text_input = gr.Textbox(label="Enter your message")
404
+ text_output = gr.Textbox(label="Response")
405
+ audio_output = gr.Audio(label="Response Audio")
406
+ suggest_submit_btn = gr.Button("Submit")
407
+ suggest_clear_btn = gr.Button("Clear")
408
+
409
+ submit_btn.click(
410
+ fn=analyze_voice,
411
+ inputs=[audio_input, language_input],
412
+ outputs=[voice_output, pdf_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  )
414
+ clear_btn.click(
415
+ fn=lambda: (gr.update(value=None), gr.update(value="en"), gr.update(value=""), gr.update(value=None)),
416
+ inputs=None,
417
+ outputs=[audio_input, language_input, voice_output, pdf_output]
 
418
  )
419
+ suggest_submit_btn.click(
420
+ fn=get_chatbot_response,
421
+ inputs=[text_input, language_input],
422
+ outputs=[text_output, audio_output]
 
423
  )
424
+ suggest_clear_btn.click(
425
+ fn=lambda: (gr.update(value=""), gr.update(value=""), gr.update(value=None)),
426
+ inputs=None,
427
+ outputs=[text_input, text_output, audio_output]
428
+ )
429
+ demo.launch(server_name="0.0.0.0", server_port=7860)
430
 
431
  if __name__ == "__main__":
432
+ launch()