Gayatri kancharla commited on
Commit
a7afe96
·
verified ·
1 Parent(s): c1a38f2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -1277
app.py CHANGED
@@ -1,1294 +1,114 @@
1
- import gradio as gr
2
-
3
- import librosa
4
-
5
- import numpy as np
6
-
7
- import torch
8
-
9
- from transformers import WhisperProcessor, WhisperForConditionalGeneration
10
-
11
- from simple_salesforce import Salesforce
12
-
13
- import os
14
-
15
- from datetime import datetime
16
-
17
- import logging
18
-
19
- import webrtcvad
20
-
21
- import google.generativeai as genai
22
-
23
- from gtts import gTTS
24
-
25
- import tempfile
26
-
27
  import base64
 
 
 
28
 
29
- import re
30
-
31
- from cryptography.fernet import Fernet
32
-
33
- import pytz
34
-
35
- from reportlab.lib.pagesizes import A4
36
-
37
- from reportlab.lib import colors
38
-
39
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, ListFlowable, ListItem
40
-
41
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
42
-
43
- from reportlab.lib.units import inch
44
-
45
- import asyncio
46
-
47
- import hashlib
48
-
49
- from functools import lru_cache
50
-
51
-
52
- # Set up logging with DEBUG level, adjusted for IST
53
-
54
- logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
55
-
56
- logger = logging.getLogger(__name__)
57
-
58
- usage_metrics = {"total_assessments": 0, "assessments_by_language": {}}
59
-
60
-
61
- # Environment variables
62
-
63
- SF_USERNAME = os.getenv("SF_USERNAME", "smartvoicebot@voice.com")
64
-
65
- SF_PASSWORD = os.getenv("SF_PASSWORD", "voicebot1")
66
-
67
- SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN", "jq4VVHUFti6TmzJDjjegv2h6b")
68
-
69
- SF_INSTANCE_URL = os.getenv("SF_INSTANCE_URL", "https://swe42.sfdc-cehfhs.salesforce.com")
70
-
71
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyBzr5vVpbe8CV1v70l3pGDp9vRJ76yCxdk")
72
-
73
- ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", Fernet.generate_key().decode())
74
-
75
- DEFAULT_EMAIL = os.getenv("SALESFORCE_USER_EMAIL", "default@mindcare.com")
76
-
77
-
78
- # Initialize encryption
79
-
80
- cipher = Fernet(ENCRYPTION_KEY)
81
-
82
-
83
- # Initialize Salesforce
84
-
85
- try:
86
-
87
- sf = Salesforce(
88
-
89
- username=SF_USERNAME,
90
-
91
- password=SF_PASSWORD,
92
-
93
- security_token=SF_SECURITY_TOKEN,
94
-
95
- instance_url=SF_INSTANCE_URL
96
-
97
- )
98
-
99
- logger.info(f"Connected to Salesforce at {SF_INSTANCE_URL}")
100
-
101
- except Exception as e:
102
-
103
- logger.error(f"Salesforce connection failed: {str(e)}")
104
-
105
- sf = None
106
-
107
-
108
- # Initialize Google Gemini
109
-
110
- try:
111
-
112
- genai.configure(api_key=GEMINI_API_KEY)
113
-
114
- gemini_model = genai.GenerativeModel('gemini-1.5-flash')
115
-
116
- chat = gemini_model.start_chat(history=[])
117
-
118
- logger.info("Connected to Google Gemini")
119
-
120
- except Exception as e:
121
-
122
- logger.error(f"Google Gemini initialization failed: {str(e)}")
123
-
124
- chat = None
125
-
126
-
127
- # Load Whisper model
128
-
129
- SUPPORTED_LANGUAGES = {"English": "english", "Hindi": "hindi", "Spanish": "spanish", "Mandarin": "mandarin"}
130
-
131
- SALESFORCE_LANGUAGE_MAP = {"English": "English", "Hindi": "Hindi", "Spanish": "Spanish", "Mandarin": "Mandarin"}
132
-
133
- whisper_processor = WhisperProcessor.from_pretrained("openai/whisper-small")
134
-
135
- whisper_model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small")
136
-
137
- vad = webrtcvad.Vad(mode=2)
138
-
139
-
140
- # Context for chatbot
141
-
142
- base_info = """
143
-
144
- MindCare is an AI health assistant focused on:
145
-
146
- - **Mental health**: Emotional support, mindfulness, stress-relief, anxiety management.
147
-
148
- - **Medical guidance**: Symptom analysis, possible conditions, medicine recommendations.
149
-
150
- - **Decision-making**: Personal, professional, emotional choices.
151
-
152
- - **General health**: Lifestyle, nutrition, physical and mental wellness.
153
-
154
- - **Emergency assistance**: Suggest professional help or helplines for distress.
155
-
156
- Tone: Empathetic, supportive, informative.
157
-
158
- """
159
-
160
- mental_health = """
161
-
162
- For stress/anxiety:
163
-
164
- - Suggest mindfulness, deep breathing, gratitude journaling.
165
-
166
- - Encourage breaks, hobbies, nature.
167
-
168
- - Provide affirmations, self-care routines.
169
-
170
- For distress:
171
-
172
- - Offer emotional support, assure they’re not alone.
173
-
174
- - Suggest trusted contacts or professionals.
175
-
176
- - Provide crisis helplines.
177
-
178
- """
179
-
180
- medical_assistance = """
181
-
182
- For symptoms:
183
-
184
- - Analyze and suggest possible conditions.
185
-
186
- - Offer general advice, not replacing doctor consultation.
187
-
188
- - Suggest lifestyle changes, home remedies.
189
-
190
- - Advise medical attention for severe symptoms.
191
-
192
- """
193
-
194
- medicine_recommendation = """
195
-
196
- For medicine queries:
197
-
198
- - Suggest common antibiotics (e.g., Amoxicillin), painkillers (e.g., Paracetamol, Ibuprofen).
199
-
200
- - Note precautions, side effects.
201
-
202
- - Stress doctor consultation before use.
203
-
204
- """
205
-
206
- decision_guidance = """
207
-
208
- For decisions:
209
-
210
- - Weigh pros/cons logically.
211
-
212
- - Consider values, goals, emotions.
213
-
214
- - Suggest decision matrices or intuitive checks.
215
-
216
- - Encourage trusted advice if needed.
217
-
218
- """
219
-
220
- emergency_help = """
221
-
222
- For severe distress:
223
-
224
- - Provide immediate emotional support.
225
-
226
- - Offer crisis helplines (region-specific).
227
-
228
- - Encourage talking to trusted contacts or professionals.
229
-
230
- - Assure help is available.
231
-
232
- """
233
-
234
- context = [base_info, mental_health, medical_assistance, medicine_recommendation, decision_guidance, emergency_help]
235
-
236
-
237
- def encrypt_data(data):
238
-
239
- try:
240
-
241
- return cipher.encrypt(data.encode('utf-8')).decode('utf-8')
242
-
243
- except Exception as e:
244
-
245
- logger.error(f"Encryption failed: {str(e)}")
246
-
247
- return data
248
 
 
 
 
 
 
 
249
 
250
- def decrypt_data(encrypted_data):
 
251
 
 
 
252
  try:
253
-
254
- return cipher.decrypt(encrypted_data.encode('utf-8')).decode('utf-8')
255
-
256
- except Exception as e:
257
-
258
- logger.error(f"Decryption failed: {str(e)}")
259
-
260
- return encrypted_data
261
-
262
-
263
- @lru_cache(maxsize=100)
264
-
265
- def cached_transcribe(audio_file, language):
266
-
267
- audio, sr = librosa.load(audio_file, sr=16000)
268
-
269
- language_code = {"English": "en", "Hindi": "hi", "Spanish": "es", "Mandarin": "zh"}.get(language, "en")
270
-
271
- return transcribe_audio(audio, language_code)
272
-
273
-
274
- def extract_health_features(audio, sr):
275
-
276
  try:
277
-
278
- audio = librosa.util.normalize(audio)
279
-
280
- frame_duration = 30
281
-
282
- frame_samples = int(sr * frame_duration / 1000)
283
-
284
- frames = [audio[i:i + frame_samples] for i in range(0, len(audio), frame_samples)]
285
-
286
- voiced_frames = [frame for frame in frames if len(frame) == frame_samples and vad.is_speech((frame * 32768).astype(np.int16).tobytes(), sr)]
287
-
288
- if not voiced_frames:
289
-
290
- raise ValueError("No voiced segments detected")
291
-
292
- voiced_audio = np.concatenate(voiced_frames)
293
-
294
-
295
-
296
- frame_step = max(1, len(voiced_audio) // (sr // 4))
297
-
298
- pitches, magnitudes = librosa.piptrack(y=voiced_audio[::frame_step], sr=sr, fmin=75, fmax=300)
299
-
300
- valid_pitches = [p for p in pitches[magnitudes > 0] if 75 <= p <= 300]
301
-
302
- pitch = np.mean(valid_pitches) if valid_pitches else 0
303
-
304
- jitter = np.std(valid_pitches) / pitch if pitch and valid_pitches else 0
305
-
306
- jitter = min(jitter, 10)
307
-
308
- amplitudes = librosa.feature.rms(y=voiced_audio, frame_length=512, hop_length=128)[0]
309
-
310
- shimmer = np.std(amplitudes) / np.mean(amplitudes) if np.mean(amplitudes) else 0
311
-
312
- shimmer = min(shimmer, 10)
313
-
314
- energy = np.mean(amplitudes)
315
-
316
-
317
-
318
- mfcc = np.mean(librosa.feature.mfcc(y=voiced_audio[::2], sr=sr, n_mfcc=4), axis=1)
319
-
320
- spectral_centroid = np.mean(librosa.feature.spectral_centroid(y=voiced_audio[::2], sr=sr, n_fft=512, hop_length=128))
321
-
322
-
323
-
324
- logger.debug(f"Extracted features: pitch={pitch:.2f}, jitter={jitter*100:.2f}%, shimmer={shimmer*100:.2f}%, energy={energy:.4f}, mfcc_mean={np.mean(mfcc):.2f}, spectral_centroid={spectral_centroid:.2f}")
325
-
326
- return {
327
-
328
- "pitch": pitch,
329
-
330
- "jitter": jitter * 100,
331
-
332
- "shimmer": shimmer * 100,
333
-
334
- "energy": energy,
335
-
336
- "mfcc_mean": np.mean(mfcc),
337
-
338
- "spectral_centroid": spectral_centroid
339
-
340
  }
341
 
342
- except Exception as e:
343
-
344
- logger.error(f"Feature extraction failed: {str(e)}")
345
-
346
- raise
347
-
348
-
349
- def transcribe_audio(audio, language="en"):
350
-
351
- try:
352
-
353
- whisper_model.config.forced_decoder_ids = whisper_processor.get_decoder_prompt_ids(
354
-
355
- language=SUPPORTED_LANGUAGES.get({"en": "English", "hi": "Hindi", "es": "Spanish", "zh": "Mandarin"}.get(language, "English"), "english"), task="transcribe"
356
-
357
- )
358
-
359
- inputs = whisper_processor(audio, sampling_rate=16000, return_tensors="pt")
360
-
361
- with torch.no_grad():
362
-
363
- generated_ids = whisper_model.generate(inputs["input_features"], max_new_tokens=50)
364
-
365
- transcription = whisper_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
366
-
367
- logger.info(f"Transcription (language: {language}): {transcription}")
368
-
369
- return transcription
370
 
371
  except Exception as e:
 
372
 
373
- logger.error(f"Transcription failed: {str(e)}")
374
-
375
- return None
376
-
377
-
378
- async def get_chatbot_response(message, language="en"):
379
-
380
- if not chat or not message:
381
-
382
- return "Unable to generate response.", None
383
-
384
- language_code = {"English": "en", "Hindi": "hi", "Spanish": "es", "Mandarin": "zh"}.get(language, "en")
385
-
386
- full_context = "\n".join(context) + f"\nUser: {message}\nMindCare:"
387
-
388
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
- response = await asyncio.get_event_loop().run_in_executor(None, lambda: chat.send_message(full_context).text)
391
-
392
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
393
-
394
- tts = gTTS(text=response, lang=language_code, slow=False)
395
-
396
- tts.save(temp_audio.name)
397
-
398
- audio_path = temp_audio.name
399
-
400
- logger.info(f"Generated response: {response[:100]}... and audio at {audio_path}")
401
-
402
- return response, audio_path
403
 
404
  except Exception as e:
 
405
 
406
- logger.error(f"Chatbot response failed: {str(e)}")
407
-
408
- return "Error generating response. Please check your input or API key.", None
409
-
410
-
411
- def analyze_symptoms(text, features):
412
-
413
- feedback = []
414
-
415
- text = text.lower() if text else ""
416
-
417
-
418
-
419
- if "cough" in text or "coughing" in text:
420
-
421
- feedback.append("You mentioned a cough. This could indicate a respiratory issue like a cold or bronchitis. Stay hydrated, rest, and consider consulting a doctor if it persists.")
422
-
423
- elif "fever" in text or "temperature" in text:
424
-
425
- feedback.append("You mentioned a fever. This could be a sign of infection or illness. Monitor your temperature, stay hydrated, and seek medical advice if it exceeds 100.4°F (38°C).")
426
-
427
- elif "headache" in text:
428
-
429
- feedback.append("You mentioned a headache. This could be due to stress, dehydration, or tension. Try resting, drinking water, and using a mild pain reliever like Paracetamol. Consult a doctor if severe.")
430
-
431
- elif "stress" in text or "anxious" in text or "mental stress" in text:
432
-
433
- feedback.append("You mentioned stress or anxiety. Try deep breathing exercises or mindfulness. If persistent, consider speaking with a mental health professional for support.")
434
-
435
- elif "respiratory" in text or "breathing" in text or "shortness of breath" in text:
436
-
437
- feedback.append("You mentioned respiratory issues or shortness of breath. This could indicate asthma or an infection. Seek medical attention if it worsens.")
438
-
439
- elif "cold" in text:
440
-
441
- feedback.append("You mentioned a cold. This could be a viral infection. Rest, stay hydrated, and consider over-the-counter remedies like decongestants or honey for cough. Consult a doctor if symptoms worsen.")
442
-
443
-
444
-
445
- if features["jitter"] > 6.5:
446
-
447
- feedback.append(f"High jitter ({features['jitter']:.2f}%) detected, suggesting potential vocal cord strain or respiratory issues. Consult a doctor.")
448
-
449
- elif features["jitter"] > 4.0:
450
-
451
- feedback.append(f"Moderate jitter ({features['jitter']:.2f}%) detected, indicating possible vocal instability. Monitor and rest your voice.")
452
-
453
-
454
-
455
- if features["shimmer"] > 7.5:
456
-
457
- feedback.append(f"High shimmer ({features['shimmer']:.2f}%) suggests possible emotional stress or vocal fatigue. Consider professional evaluation.")
458
-
459
- elif features["shimmer"] > 5.0:
460
-
461
- feedback.append(f"Moderate shimmer ({features['shimmer']:.2f}%) indicates mild vocal strain. Rest and hydrate recommended.")
462
-
463
-
464
-
465
- if features["energy"] < 0.003:
466
-
467
- feedback.append(f"Very low vocal energy ({features['energy']:.4f}) detected, possibly indicating fatigue or low mood. Rest and consult a doctor if needed.")
468
-
469
- elif features["energy"] < 0.007:
470
-
471
- feedback.append(f"Low vocal energy ({features['energy']:.4f}) suggests possible fatigue. Ensure adequate rest.")
472
-
473
-
474
-
475
- if features["pitch"] < 70 or features["pitch"] > 290:
476
-
477
- feedback.append(f"Unusual pitch ({features['pitch']:.2f} Hz) detected, which may indicate vocal cord issues. Consult a doctor.")
478
-
479
- elif 70 <= features["pitch"] <= 90 or 270 <= features["pitch"] <= 290:
480
-
481
- feedback.append(f"Pitch ({features['pitch']:.2f} Hz) is slightly outside typical range, possibly due to tension. Monitor your voice.")
482
-
483
-
484
-
485
- if features["spectral_centroid"] > 2700:
486
-
487
- feedback.append(f"High spectral centroid ({features['spectral_centroid']:.2f} Hz) suggests tense speech, potentially linked to stress or anxiety.")
488
-
489
- elif features["spectral_centroid"] > 2200:
490
-
491
- feedback.append(f"Elevated spectral centroid ({features['spectral_centroid']:.2f} Hz) may indicate mild tension in speech.")
492
-
493
-
494
-
495
- if not feedback:
496
-
497
- feedback.append("No significant health concerns detected from voice or text analysis. Maintain a healthy lifestyle and consult a doctor if symptoms arise.")
498
-
499
-
500
-
501
- logger.debug(f"Generated feedback: {feedback}")
502
-
503
- return "\n".join(feedback)
504
-
505
-
506
- def store_user_consent(email, language):
507
-
508
- if not sf:
509
-
510
- logger.warning("Salesforce not connected; skipping consent storage")
511
-
512
- return None
513
-
514
- try:
515
-
516
- email_to_use = email.strip() if email and email.strip() else DEFAULT_EMAIL
517
-
518
- sanitized_email = email_to_use.replace("'", "\\'").replace('"', '\\"')
519
-
520
- query = f"SELECT Id FROM HealthUser__c WHERE Email__c = '{sanitized_email}'"
521
-
522
- logger.debug(f"Executing SOQL query: {query}")
523
-
524
- user = sf.query(query)
525
-
526
- user_id = None
527
-
528
- if user["totalSize"] == 0:
529
-
530
- logger.info(f"No user found for email: {sanitized_email}, creating new user")
531
-
532
- user = sf.HealthUser__c.create({
533
-
534
- "Email__c": sanitized_email,
535
-
536
- "Language__c": SALESFORCE_LANGUAGE_MAP.get(language, "English"),
537
-
538
- "ConsentGiven__c": True
539
-
540
- })
541
-
542
- user_id = user["id"]
543
-
544
- logger.info(f"Created new user with email: {sanitized_email}, ID: {user_id}")
545
-
546
- else:
547
-
548
- user_id = user["records"][0]["Id"]
549
-
550
- logger.info(f"Found existing user with email: {sanitized_email}, ID: {user_id}")
551
-
552
- sf.HealthUser__c.update(user_id, {
553
-
554
- "Language__c": SALESFORCE_LANGUAGE_MAP.get(language, "English"),
555
-
556
- "ConsentGiven__c": True
557
-
558
- })
559
-
560
- logger.info(f"Updated user with email: {sanitized_email}")
561
-
562
- sf.ConsentLog__c.create({
563
-
564
- "HealthUser__c": user_id,
565
-
566
- "ConsentType__c": "Voice Analysis",
567
-
568
- "ConsentDate__c": datetime.utcnow().isoformat()
569
-
570
- })
571
-
572
- logger.info(f"Stored consent log for user ID: {user_id}")
573
-
574
- return user_id
575
-
576
- except Exception as e:
577
-
578
- logger.error(f"Consent storage failed: {str(e)}")
579
-
580
- logger.exception("Stack trace for consent storage failure:")
581
-
582
- return None
583
-
584
-
585
- def generate_pdf_report(feedback, transcription, features, language, email, suggestions):
586
-
587
- try:
588
-
589
- feedback = feedback.replace('<', '<').replace('>', '>').replace('&', '&')
590
-
591
- transcription = transcription.replace('<', '<').replace('>', '>').replace('&', '&') if transcription else "None"
592
-
593
- suggestions = suggestions.replace('<', '<').replace('>', '>').replace('&', '&') if suggestions else "None"
594
-
595
- email_to_use = email.strip() if email and email.strip() else DEFAULT_EMAIL
596
-
597
- email = email_to_use.replace('<', '<').replace('>', '>').replace('&', '&')
598
-
599
- language_display = SALESFORCE_LANGUAGE_MAP.get(language, "English")
600
-
601
-
602
-
603
- ist = pytz.timezone('Asia/Kolkata')
604
-
605
- ist_time = datetime.now(ist).strftime("%I:%M %p IST on %B %d, %Y")
606
-
607
- logger.debug(f"Generating PDF with IST time: {ist_time}, feedback: {feedback[:100]}..., transcription: {transcription[:100]}..., suggestions: {suggestions[:100]}..., language: {language_display}, email: {email}")
608
-
609
-
610
-
611
- debug_dir = "/tmp/mindcare_logs"
612
-
613
- os.makedirs(debug_dir, exist_ok=True)
614
-
615
- timestamp = datetime.now(ist).strftime("%Y%m%d_%H%M%S")
616
-
617
- pdf_path = os.path.join(debug_dir, f"report_{timestamp}.pdf")
618
-
619
-
620
-
621
- doc = SimpleDocTemplate(pdf_path, pagesize=A4, rightMargin=inch, leftMargin=inch, topMargin=inch, bottomMargin=inch)
622
-
623
- styles = getSampleStyleSheet()
624
-
625
- title_style = ParagraphStyle(
626
-
627
- name='Title',
628
-
629
- fontSize=16,
630
-
631
- leading=20,
632
-
633
- alignment=1,
634
-
635
- spaceAfter=12,
636
-
637
- fontName='Times-Bold'
638
-
639
- )
640
-
641
- heading_style = ParagraphStyle(
642
-
643
- name='Heading1',
644
-
645
- fontSize=14,
646
-
647
- leading=16,
648
-
649
- spaceBefore=12,
650
-
651
- spaceAfter=8,
652
-
653
- fontName='Times-Bold'
654
-
655
- )
656
-
657
- subheading_style = ParagraphStyle(
658
-
659
- name='Heading2',
660
-
661
- fontSize=12,
662
-
663
- leading=14,
664
-
665
- spaceBefore=10,
666
-
667
- spaceAfter=6,
668
-
669
- fontName='Times-Bold'
670
-
671
- )
672
-
673
- normal_style = ParagraphStyle(
674
-
675
- name='Normal',
676
-
677
- fontSize=12,
678
-
679
- leading=14,
680
-
681
- spaceAfter=6,
682
-
683
- fontName='Times-Roman'
684
-
685
- )
686
-
687
- bullet_style = ParagraphStyle(
688
-
689
- name='Bullet',
690
-
691
- fontSize=12,
692
-
693
- leading=14,
694
-
695
- leftIndent=20,
696
-
697
- firstLineIndent=-10,
698
-
699
- spaceAfter=4,
700
-
701
- fontName='Times-Roman'
702
-
703
- )
704
-
705
-
706
-
707
- story = []
708
-
709
- story.append(Paragraph("MindCare Health Assistant Report", title_style))
710
-
711
- story.append(Paragraph(f"Generated on {ist_time}", normal_style))
712
-
713
- story.append(Spacer(1, 0.5 * inch))
714
-
715
-
716
-
717
- story.append(Paragraph("User Information", heading_style))
718
-
719
- user_info = [
720
-
721
- ListItem(Paragraph(f"<b>Email</b>: {email}", bullet_style), bulletText="•"),
722
-
723
- ListItem(Paragraph(f"<b>Language</b>: {language_display}", bullet_style), bulletText="•")
724
-
725
- ]
726
-
727
- story.append(ListFlowable(user_info, bulletType='bullet'))
728
-
729
- story.append(Spacer(1, 0.25 * inch))
730
-
731
-
732
-
733
- story.append(Paragraph("Voice Analysis Results", heading_style))
734
-
735
- story.append(Paragraph("Health Assessment", subheading_style))
736
-
737
- for line in feedback.split('\n'):
738
-
739
- if line.strip():
740
-
741
- story.append(Paragraph(line, normal_style))
742
-
743
- story.append(Spacer(1, 0.1 * inch))
744
-
745
-
746
-
747
- story.append(Paragraph("Health Suggestions", subheading_style))
748
-
749
- for line in suggestions.split('\n'):
750
-
751
- if line.strip():
752
-
753
- story.append(Paragraph(line, normal_style))
754
-
755
- story.append(Spacer(1, 0.1 * inch))
756
-
757
-
758
-
759
- story.append(Paragraph("Voice Analysis Details", subheading_style))
760
-
761
- details = [
762
-
763
- ListItem(Paragraph(f"Pitch: {features['pitch']:.2f} Hz", bullet_style), bulletText="•"),
764
-
765
- ListItem(Paragraph(f"Jitter: {features['jitter']:.2f}% (voice stability)", bullet_style), bulletText="•"),
766
-
767
- ListItem(Paragraph(f"Shimmer: {features['shimmer']:.2f}% (amplitude variation)", bullet_style), bulletText="•"),
768
-
769
- ListItem(Paragraph(f"Energy: {features['energy']:.4f} (vocal intensity)", bullet_style), bulletText="•"),
770
-
771
- ListItem(Paragraph(f"MFCC Mean: {features['mfcc_mean']:.2f} (timbre quality)", bullet_style), bulletText="•"),
772
-
773
- ListItem(Paragraph(f"Spectral Centroid: {features['spectral_centroid']:.2f} Hz (voice brightness)", bullet_style), bulletText="•"),
774
-
775
- ListItem(Paragraph(f"Transcription: {transcription}", bullet_style), bulletText="•")
776
-
777
- ]
778
-
779
- story.append(ListFlowable(details, bulletType='bullet'))
780
-
781
- story.append(Spacer(1, 0.1 * inch))
782
-
783
-
784
-
785
- story.append(Paragraph("Transcription", subheading_style))
786
-
787
- story.append(Paragraph(transcription, normal_style))
788
-
789
- story.append(Spacer(1, 0.1 * inch))
790
-
791
-
792
-
793
- story.append(Paragraph("Voice Metrics", subheading_style))
794
-
795
- metrics = [
796
-
797
- ListItem(Paragraph(f"<b>Pitch</b>: {features['pitch']:.2f} Hz", bullet_style), bulletText="•"),
798
-
799
- ListItem(Paragraph(f"<b>Jitter</b>: {features['jitter']:.2f}%", bullet_style), bulletText="•"),
800
-
801
- ListItem(Paragraph(f"<b>Shimmer</b>: {features['shimmer']:.2f}%", bullet_style), bulletText="•"),
802
-
803
- ListItem(Paragraph(f"<b>Energy</b>: {features['energy']:.4f}", bullet_style), bulletText="•"),
804
-
805
- ListItem(Paragraph(f"<b>MFCC Mean</b>: {features['mfcc_mean']:.2f}", bullet_style), bulletText="•"),
806
-
807
- ListItem(Paragraph(f"<b>Spectral Centroid</b>: {features['spectral_centroid']:.2f} Hz", bullet_style), bulletText="•")
808
-
809
- ]
810
-
811
- story.append(ListFlowable(metrics, bulletType='bullet'))
812
-
813
- story.append(Spacer(1, 0.1 * inch))
814
-
815
-
816
-
817
- story.append(Paragraph("Disclaimer", heading_style))
818
-
819
- story.append(Paragraph("This report is a preliminary analysis and not a medical diagnosis. Always consult a healthcare provider.", normal_style))
820
-
821
-
822
-
823
- doc.build(story)
824
-
825
- logger.info(f"Generated PDF report: {pdf_path}")
826
-
827
-
828
-
829
- try:
830
-
831
- with open(pdf_path, 'rb') as f:
832
-
833
- pdf_content = f.read()
834
-
835
- if len(pdf_content) > 0 and pdf_content.startswith(b'%PDF'):
836
-
837
- return pdf_path, None
838
-
839
- else:
840
-
841
- logger.error(f"PDF file {pdf_path} is corrupt or empty")
842
-
843
- return None, f"PDF generation failed: Generated PDF is corrupt or empty."
844
-
845
- except Exception as e:
846
-
847
- logger.error(f"Failed to verify PDF {pdf_path}: {str(e)}")
848
-
849
- return None, f"PDF generation failed: Unable to verify PDF. Error: {str(e)}."
850
-
851
- except Exception as e:
852
-
853
- logger.error(f"PDF generation failed: {str(e)}")
854
-
855
- logger.exception("Stack trace for PDF generation failure:")
856
-
857
- return None, f"PDF generation failed: {str(e)}."
858
-
859
-
860
- def store_in_salesforce(user_id, audio_file, feedback, respiratory_score, mental_health_score, features, transcription, language):
861
-
862
- if not sf:
863
-
864
- logger.warning("Salesforce not connected; skipping storage")
865
-
866
- return
867
-
868
- try:
869
-
870
- with open(audio_file, "rb") as f:
871
-
872
- audio_content = base64.b64encode(f.read()).decode()
873
-
874
- content_version = sf.ContentVersion.create({
875
-
876
- "Title": f"Voice_Assessment_{datetime.utcnow().isoformat()}",
877
-
878
- "PathOnClient": os.path.basename(audio_file),
879
-
880
- "VersionData": audio_content,
881
-
882
- "IsMajorVersion": True
883
-
884
- })
885
-
886
- content_document_id = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version['id']}'")["records"][0]["ContentDocumentId"]
887
-
888
- file_url = f"{SF_INSTANCE_URL}/lightning/r/ContentDocument/{content_document_id}/view"
889
-
890
-
891
- feedback_str = feedback[:32767]
892
-
893
- assessment = sf.VoiceAssessment__c.create({
894
-
895
- "HealthUser__c": user_id,
896
-
897
- "VoiceRecording__c": file_url,
898
-
899
- "AssessmentResult__c": feedback_str,
900
-
901
- "AssessmentDate__c": datetime.utcnow().isoformat(),
902
-
903
- "ConfidenceScore__c": 95.0,
904
-
905
- "RespiratoryScore__c": float(respiratory_score),
906
-
907
- "MentalHealthScore__c": float(mental_health_score),
908
-
909
- "Pitch__c": float(features["pitch"]),
910
-
911
- "Jitter__c": float(features["jitter"]),
912
-
913
- "Shimmer__c": float(features["shimmer"]),
914
-
915
- "Energy__c": float(features["energy"]),
916
-
917
- "Transcription__c": transcription or "None",
918
-
919
- "Language__c": SALESFORCE_LANGUAGE_MAP.get(language, "English")
920
-
921
- })
922
-
923
- sf.ContentDocumentLink.create({
924
-
925
- "ContentDocumentId": content_document_id,
926
-
927
- "LinkedEntityId": assessment["id"],
928
-
929
- "ShareType": "V"
930
-
931
- })
932
-
933
- logger.info(f"Stored assessment in Salesforce: {assessment['id']}")
934
-
935
- except Exception as e:
936
-
937
- logger.error(f"Salesforce storage failed: {str(e)}")
938
-
939
- logger.exception("Stack trace for Salesforce storage failure:")
940
-
941
- raise
942
-
943
-
944
- async def analyze_voice(audio_file=None, language="English", email=None):
945
-
946
- global usage_metrics
947
-
948
- usage_metrics["total_assessments"] += 1
949
-
950
- usage_metrics["assessments_by_language"][language] = usage_metrics["assessments_by_language"].get(language, 0) + 1
951
-
952
-
953
- try:
954
-
955
- if not audio_file or not os.path.exists(audio_file):
956
-
957
- raise ValueError("No valid audio file provided")
958
-
959
-
960
-
961
- audio, sr = librosa.load(audio_file, sr=16000)
962
-
963
- max_duration = 10
964
-
965
- if len(audio) > max_duration * sr:
966
-
967
- audio = audio[:max_duration * sr]
968
-
969
- logger.info(f"Truncated audio to first {max_duration} seconds for faster processing")
970
-
971
- if len(audio) < sr:
972
-
973
- raise ValueError("Audio too short (minimum 1 second)")
974
-
975
-
976
- language_code = {"English": "en", "Hindi": "hi", "Spanish": "es", "Mandarin": "zh"}.get(language, "en")
977
-
978
- user_id = store_user_consent(email, language)
979
-
980
- if not user_id:
981
-
982
- logger.warning("Proceeding with analysis despite consent storage failure")
983
-
984
- feedback_message = "Warning: User consent could not be stored in Salesforce, but analysis will proceed.\n"
985
-
986
- else:
987
-
988
- feedback_message = ""
989
-
990
-
991
- features = extract_health_features(audio, sr)
992
-
993
- transcription = cached_transcribe(audio_file, language)
994
-
995
- feedback = analyze_symptoms(transcription, features)
996
-
997
-
998
- respiratory_score = features["jitter"]
999
-
1000
- mental_health_score = features["shimmer"]
1001
-
1002
-
1003
- feedback = feedback_message + feedback + "\n\n**Voice Analysis Details**:\n"
1004
-
1005
- feedback += f"- Pitch: {features['pitch']:.2f} Hz\n"
1006
-
1007
- feedback += f"- Jitter: {features['jitter']:.2f}% (voice stability)\n"
1008
-
1009
- feedback += f"- Shimmer: {features['shimmer']:.2f}% (amplitude variation)\n"
1010
-
1011
- feedback += f"- Energy: {features['energy']:.4f} (vocal intensity)\n"
1012
-
1013
- feedback += f"- MFCC Mean: {features['mfcc_mean']:.2f} (timbre quality)\n"
1014
-
1015
- feedback += f"- Spectral Centroid: {features['spectral_centroid']:.2f} Hz (voice brightness)\n"
1016
-
1017
- feedback += f"- Transcription: {transcription if transcription else 'None'}\n"
1018
-
1019
- feedback += f"- Email: {email if email and email.strip() else DEFAULT_EMAIL}\n"
1020
-
1021
- feedback += "\n**Disclaimer**: This is a preliminary analysis. Consult a healthcare provider for professional evaluation."
1022
-
1023
-
1024
- suggestions, suggestion_audio = await get_chatbot_response(feedback, language)
1025
-
1026
-
1027
- if user_id and sf:
1028
-
1029
- store_in_salesforce(user_id, audio_file, feedback, respiratory_score, mental_health_score, features, transcription, language)
1030
-
1031
- else:
1032
-
1033
- logger.warning("Skipping Salesforce storage due to missing user_id or Salesforce connection")
1034
-
1035
-
1036
- file_path, pdf_error = generate_pdf_report(feedback, transcription, features, language, email, suggestions)
1037
-
1038
- if pdf_error:
1039
-
1040
- feedback += f"\n\n**Error**: {pdf_error}"
1041
-
1042
- return feedback, file_path, suggestions, suggestion_audio
1043
-
1044
-
1045
- try:
1046
-
1047
- os.remove(audio_file)
1048
-
1049
- logger.info(f"Deleted audio file: {audio_file}")
1050
-
1051
- except Exception as e:
1052
-
1053
- logger.error(f"Failed to delete audio file: {str(e)}")
1054
-
1055
-
1056
- return feedback, file_path, suggestions, suggestion_audio
1057
-
1058
- except Exception as e:
1059
-
1060
- logger.error(f"Audio processing failed: {str(e)}")
1061
-
1062
- return f"Error: {str(e)}", None, "Error: Could not generate suggestions due to audio processing failure.", None
1063
-
1064
-
1065
- def launch():
1066
-
1067
- custom_css = """
1068
-
1069
- .gradio-container {
1070
-
1071
- max-width: 1200px;
1072
-
1073
- margin: auto;
1074
-
1075
- font-family: 'Roboto', sans-serif;
1076
-
1077
- background-color: #f5f7fa;
1078
-
1079
- color: #333;
1080
-
1081
- }
1082
-
1083
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
1084
-
1085
- h1, h3 {
1086
-
1087
- background: linear-gradient(to right, #007bff, #0056b3);
1088
-
1089
- color: white;
1090
-
1091
- padding: 15px;
1092
-
1093
- border-radius: 8px;
1094
-
1095
- text-align: center;
1096
-
1097
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1098
-
1099
- }
1100
-
1101
- .gr-column {
1102
-
1103
- background: white;
1104
-
1105
- border-radius: 8px;
1106
-
1107
- padding: 20px;
1108
-
1109
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1110
-
1111
- margin: 10px;
1112
-
1113
- }
1114
-
1115
- .gr-button {
1116
-
1117
- background: #007bff;
1118
-
1119
- color: white;
1120
-
1121
- border: none;
1122
-
1123
- border-radius: 6px;
1124
-
1125
- padding: 10px 20px;
1126
-
1127
- font-weight: bold;
1128
-
1129
- transition: background 0.3s;
1130
-
1131
- }
1132
-
1133
- .gr-button:hover {
1134
-
1135
- background: #0056b3;
1136
-
1137
- }
1138
-
1139
- .gr-textbox, .gr-dropdown, .gr-checkbox, .gr-file, .gr-audio {
1140
-
1141
- border-radius: 6px;
1142
-
1143
- border: 1px solid #ced4da;
1144
-
1145
- }
1146
-
1147
- .gr-textbox textarea {
1148
-
1149
- font-size: 14px;
1150
-
1151
- }
1152
-
1153
- #health-results {
1154
-
1155
- background: #e9f7ef;
1156
-
1157
- border: 1px solid #28a745;
1158
-
1159
- border-radius: 6px;
1160
-
1161
- }
1162
-
1163
- .no-microphone-warning {
1164
-
1165
- color: red;
1166
-
1167
- font-weight: bold;
1168
-
1169
- }
1170
-
1171
- """
1172
-
1173
-
1174
- def check_microphone_access():
1175
-
1176
- try:
1177
-
1178
- from navigator import mediaDevices
1179
-
1180
- devices = mediaDevices.enumerateDevices()
1181
-
1182
- for device in devices:
1183
-
1184
- if device.kind == "audioinput":
1185
-
1186
- return None
1187
-
1188
- return "Microphone access is not available. Please upload an audio file or check browser permissions."
1189
-
1190
- except Exception as e:
1191
-
1192
- logger.error(f"Microphone access check failed: {str(e)}")
1193
-
1194
- return "Microphone access is not available. Please upload an audio file or check browser permissions."
1195
-
1196
-
1197
- with gr.Blocks(title="MindCare Health Assistant", css=custom_css) as demo:
1198
-
1199
- gr.Markdown("Record your voice or type a message for health assessments and suggestions.")
1200
-
1201
-
1202
- with gr.Row():
1203
-
1204
- with gr.Column():
1205
-
1206
- gr.Markdown("### Voice Analysis")
1207
-
1208
- mic_warning = gr.Markdown()
1209
-
1210
- mic_warning.value = check_microphone_access() or ""
1211
-
1212
- gr.Markdown("Upload voice (1+ sec) describing symptoms (e.g., 'I have a cough' or 'I feel stressed'). Note: Microphone recording may not be supported in all contexts; use file upload instead.")
1213
-
1214
- email_input = gr.Textbox(label="Enter Your Email", placeholder="e.g., user@example.com", value="")
1215
-
1216
- language_input = gr.Dropdown(choices=list(SUPPORTED_LANGUAGES.keys()), label="Select Language", value="English")
1217
-
1218
- consent_input = gr.Checkbox(label="I consent to data storage and voice analysis", value=True, interactive=False)
1219
-
1220
- audio_input = gr.Audio(type="filepath", label="Upload Voice (WAV, MP3, FLAC)", format="wav", interactive=True)
1221
-
1222
- voice_output = gr.Textbox(label="Health Assessment Results", elem_id="health-results")
1223
-
1224
- file_output = gr.File(label="Download Assessment Report (PDF)", file_types=[".pdf"])
1225
-
1226
- submit_btn = gr.Button("Submit")
1227
-
1228
- clear_btn = gr.Button("Clear")
1229
-
1230
-
1231
- with gr.Column():
1232
-
1233
- gr.Markdown("### Health Suggestions")
1234
-
1235
- gr.Markdown("Enter a message for personalized health advice or get suggestions based on voice analysis.")
1236
-
1237
- text_input = gr.Textbox(label="Enter your message (optional)")
1238
-
1239
- text_output = gr.Textbox(label="Response")
1240
-
1241
- audio_output = gr.Audio(label="Response Audio")
1242
-
1243
- suggest_submit_btn = gr.Button("Submit")
1244
-
1245
- suggest_clear_btn = gr.Button("Clear")
1246
-
1247
-
1248
- submit_btn.click(
1249
-
1250
- fn=analyze_voice,
1251
-
1252
- inputs=[audio_input, language_input, email_input],
1253
-
1254
- outputs=[voice_output, file_output, text_output, audio_output]
1255
-
1256
- )
1257
-
1258
- clear_btn.click(
1259
-
1260
- fn=lambda: (gr.update(value=None), gr.update(value="English"), gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(value=""), gr.update(value=None)),
1261
-
1262
- inputs=None,
1263
-
1264
- outputs=[audio_input, language_input, email_input, voice_output, file_output, text_output, audio_output]
1265
-
1266
- )
1267
-
1268
- suggest_submit_btn.click(
1269
-
1270
- fn=get_chatbot_response,
1271
-
1272
- inputs=[text_input, language_input],
1273
-
1274
- outputs=[text_output, audio_output]
1275
-
1276
- )
1277
-
1278
- suggest_clear_btn.click(
1279
-
1280
- fn=lambda: (gr.update(value=""), gr.update(value=""), gr.update(value=None)),
1281
-
1282
- inputs=None,
1283
-
1284
- outputs=[text_input, text_output, audio_output]
1285
-
1286
- )
1287
-
1288
- demo.launch(server_name="0.0.0.0", server_port=7860)
1289
-
1290
-
1291
- if __name__ == "__main__":
1292
-
1293
- launch()
1294
-
 
1
+ from flask import Flask, request, jsonify
2
+ import requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import base64
4
+ import json
5
+ from datetime import datetime
6
+ import os
7
 
8
+ app = Flask(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # Hugging Face API configuration
11
+ HUGGING_FACE_TOKEN = os.getenv('HUGGING_FACE_TOKEN', 'YOUR_HUGGING_FACE_TOKEN')
12
+ WHISPER_API_URL = 'https://api-inference.huggingface.co/models/openai/whisper-tiny.en'
13
+ DISEASE_API_URL = 'https://api-inference.huggingface.co/models/abhirajeshbhai/symptom-2-disease-net'
14
+ NLP_API_URL = 'https://api-inference.huggingface.co/models/bert-base-uncased'
15
+ HEADERS = {'Authorization': f'Bearer {HUGGING_FACE_TOKEN}'}
16
 
17
+ # Supported languages
18
+ SUPPORTED_LANGUAGES = ['en', 'es', 'hi', 'zh']
19
 
20
+ def call_hugging_face_api(api_url, data, is_binary=False):
21
+ """Helper function to call Hugging Face API"""
22
  try:
23
+ if is_binary:
24
+ response = requests.post(api_url, headers=HEADERS, data=data, timeout=10)
25
+ else:
26
+ response = requests.post(api_url, headers=HEADERS, json={'inputs': data}, timeout=10)
27
+ response.raise_for_status()
28
+ return response.json()
29
+ except requests.RequestException as e:
30
+ return {'error': str(e)}
31
+
32
+ @app.route('/process_voice', methods=['POST'])
33
+ def process_voice():
34
+ """Process voice input for health assessment"""
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
+ data = request.get_json()
37
+ base64_audio = data.get('audio')
38
+ language = data.get('language', 'en')
39
+ user_email = data.get('user_email')
40
+
41
+ if not base64_audio or not user_email:
42
+ return jsonify({'error': 'Missing audio or user_email'}), 400
43
+
44
+ if language not in SUPPORTED_LANGUAGES:
45
+ return jsonify({'error': f'Unsupported language. Supported: {SUPPORTED_LANGUAGES}'}), 400
46
+
47
+ # Decode base64 audio
48
+ audio_binary = base64.b64decode(base64_audio)
49
+
50
+ # Step 1: Speech-to-text using Whisper
51
+ whisper_result = call_hugging_face_api(WHISPER_API_URL, audio_binary, is_binary=True)
52
+ if 'error' in whisper_result:
53
+ return jsonify({'error': 'Speech recognition failed', 'details': whisper_result['error']}), 500
54
+
55
+ text_output = whisper_result.get('text', '')
56
+ if not text_output:
57
+ return jsonify({'error': 'No text recognized from audio'}), 400
58
+
59
+ # Step 2: Health analysis using symptom-to-disease model
60
+ health_result = call_hugging_face_api(DISEASE_API_URL, text_output)
61
+ if 'error' in health_result:
62
+ return jsonify({'error': 'Health analysis failed', 'details': health_result['error']}), 500
63
+
64
+ # Extract disease and confidence
65
+ disease = health_result[0]['label'] if health_result else 'Unknown'
66
+ confidence = health_result[0]['score'] * 100 if health_result else 0.0
67
+
68
+ # Step 3: Generate response with disclaimer
69
+ response = {
70
+ 'assessment_result': f'Possible {disease} detected. Consult a doctor.',
71
+ 'confidence_score': confidence,
72
+ 'timestamp': datetime.utcnow().isoformat(),
73
+ 'disclaimer': 'This is not a medical diagnosis. Please consult a healthcare professional.',
74
+ 'transcribed_text': text_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
+ return jsonify(response), 200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  except Exception as e:
80
+ return jsonify({'error': str(e)}), 500
81
 
82
+ @app.route('/health_query', methods=['POST'])
83
+ def health_query():
84
+ """Handle health-related queries using BERT"""
 
 
 
 
 
 
 
 
 
 
 
 
85
  try:
86
+ data = request.get_json()
87
+ query = data.get('query')
88
+ language = data.get('language', 'en')
89
+
90
+ if not query:
91
+ return jsonify({'error': 'Missing query'}), 400
92
+
93
+ if language not in SUPPORTED_LANGUAGES:
94
+ return jsonify({'error': f'Unsupported language. Supported: {SUPPORTED_LANGUAGES}'}), 400
95
+
96
+ # Call BERT for query understanding
97
+ nlp_result = call_hugging_face_api(NLP_API_URL, query)
98
+ if 'error' in nlp_result:
99
+ return jsonify({'error': 'Query processing failed', 'details': nlp_result['error']}), 500
100
+
101
+ # Simplified response (mocked for demo, extend with actual logic)
102
+ response = {
103
+ 'answer': f'Understood: {query}. For accurate information, consult a healthcare provider.',
104
+ 'timestamp': datetime.utcnow().isoformat(),
105
+ 'disclaimer': 'This is not a medical diagnosis. Please consult a healthcare professional.'
106
+ }
107
 
108
+ return jsonify(response), 200
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  except Exception as e:
111
+ return jsonify({'error': str(e)}), 500
112
 
113
+ if __name__ == '__main__':
114
+ app.run(host='0.0.0.0', port=5000, debug=False)