Spaces:
Runtime error
Runtime error
| from fastapi import FastAPI, File, UploadFile, HTTPException | |
| from fastapi.responses import JSONResponse, FileResponse, HTMLResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from dotenv import load_dotenv | |
| import google.generativeai as genai | |
| from gtts import gTTS | |
| import speech_recognition as sr | |
| import os | |
| import json | |
| import tempfile | |
| import subprocess | |
| from datetime import datetime | |
| import firebase_admin | |
| from firebase_admin import credentials, firestore | |
| # Load environment variables | |
| load_dotenv() | |
| # ==================== ENVIRONMENT VALIDATION ==================== | |
| def validate_environment(): | |
| FIREBASE_CREDENTIALS={ | |
| "type": "service_account", | |
| "project_id": "healbot-36975", | |
| "private_key_id": "ae436d3b915274a488ac3a6e4e6b400a91ebdc9b", | |
| "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYoyg78IrEcjuo\nJms2HcejNbe0PLrZC8MuLnPO9l4wm9HNR6sD4VsPdnqDODwlF51W3U1BSpUIyWj1\nyicqi9LVAGbDXbDNqNDsBDvOxM3MMmDG5oEB+SU+EyXzN5Fhc6ggvJloS04oxCs+\nbUZKJADDmdOObgw5EcMtPHiUwSFZDdCibj9Lr1LfL91kxFJhDl0EeYSMJq93S4yS\nh0l4mKwKwIcaNhl3Qq51YLOC1xszYJHOXkV47xwQ9FA8y5i5xgG/5bMnFX5YKnF8\nlUuYEWqtEjzQcy4XW5fxiyJziEw/wwjtZWCwfuMAiu5DRpOHSvK+iOLuTvB6pCL5\njWDrZb5FAgMBAAECggEATweWUed6eBfEM59wVRmgDqY2EgZlk3B7D4narZGq4si1\nTNHsTUoU0htCrkQBjPaEa3/oAv2WSNJQ+/l3OEox64pt8q9nJF+Fd9RDjTa2bNuj\n+mt0fKfLMk4B9iw7WPW8S9UBkc6HANAvhmKO1dU0gibHypnS067rKMF6q6mY5Mc9\nkEZAoVgwimZx9Y+1kIMnWuqPyQ1WSSFVuVgnpv+nlOqMA0yrmiiPOACmSQhBcuGl\nBCV/BlqLrB4wnwVW6pkjKMxNNp5ufbPnAdjPUksbBj6GUPQ4abngdlVPhTnQ64oI\n/lePsQeH1OkLAR+SWhqW+Gt8FYUE/puEraX9uNaOsQKBgQDvj+YDhWQSpYZ/lpQu\nOnENs7e/FzXywkTrfiDD7COvjOBRYORPu6N+/0BZIGASSD1xr8NmLYpSxsUKWJxC\nFPyDu88x+fJwUuuFqX86oYEd6VR4hL4O2pElFUlt3eKYXh2lC9rvoZSr/tQh2Nmj\n5Zfs+LDhA/CwrJU/vykl3OCX+wKBgQDngJEMBcKvmb321wlCmehO3W+My7QjeYm0\nMka7Arhq0V+JOWtTtB/rkYoe6tqiAVVTBrimCAEC9FTp1brcUcfCnDqW5zKtF8QT\nH+JFcMblJG/PAk+kHunHBU/9tmYIdTWjhgxTabmByN4/IunLKiX4E9r1GSujmtOz\n+SGtpEvuvwKBgQDJR4Zi/viOEjVnjgUCsme6s313OPFC/qcZlefBte5l2V/AAEDU\nHTvJwH04ZVNTCQ9XLe5nM2w9EHUNtFXVz/w6UtpLi05/wavRqhAUGw55K0ql2CI4\nKLw7BB+mCAATNUCDI+rX3FMmD/38Uk7KvmVf3bP/22enidn8rYjNH0A1cQKBgHhS\n45DbIaCBiTHV/JMoSY1MHKGScvOJRSBqjUbAGDg00LITLQyZb4nR4HdHXBGeHcoE\nkU6ClHwDoGrVUsUWoHwvFWi/jCBZXOkPxlyPTGFm+dIfgmNsSdfOlA/rkMbOnO18\nS8XDCs9BJvqr29Zj9s4lC8Yeqgbj/yrozy9gWLMjAoGAIO70i1XlHLTFkg3EqukA\no+pWAVp4LfAlJPQNA6Y7p6v/6mcuP1q4Px/Pp9s1xbSzgJZh5mKp/rNxmIDV4ca3\n/96gGnlPeD4oFs1avO1ndWiRO2ZoH59oP2ega4f0XYErCOUpD4T78ZzNwHRHBm/z\nX0IcvchoI5Wx7GzJ7FFW0A0=\n-----END PRIVATE KEY-----\n", | |
| "client_email": "firebase-adminsdk-fbsvc@healbot-36975.iam.gserviceaccount.com", | |
| "client_id": "104654071106360410641", | |
| "auth_uri": "https://accounts.google.com/o/oauth2/auth", | |
| "token_uri": "https://oauth2.googleapis.com/token", | |
| "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", | |
| "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40healbot-36975.iam.gserviceaccount.com", | |
| "universe_domain": "googleapis.com"} | |
| # """Validate required environment variables""" | |
| required_vars = ["GEMINI_API_KEY"] | |
| missing = [var for var in required_vars if not os.getenv(var)] | |
| if missing: | |
| raise ValueError(f"Missing required environment variables: {', '.join(missing)}") | |
| validate_environment() | |
| # ==================== INITIALIZE SERVICES ==================== | |
| # Initialize Gemini client (NO HARDCODED KEY) | |
| genai.configure(api_key=os.getenv("GEMINI_API_KEY")) | |
| # Initialize Firebase | |
| firebase_creds = "FIREBASE_CREDENTIALS" | |
| cred_dict = json.loads(firebase_creds) | |
| if not firebase_admin._apps: | |
| cred = credentials.Certificate(cred_dict) | |
| firebase_admin.initialize_app(cred) | |
| db = firestore.client() | |
| # Initialize FastAPI | |
| app = FastAPI(title="Dr. HealBot - Medical Consultation API") | |
| # CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ==================== MODELS ==================== | |
| class ChatRequest(BaseModel): | |
| message: str | |
| user_id: str | |
| language: str = "auto" | |
| class PatientData(BaseModel): | |
| name: str | |
| patient_profile: dict | |
| lab_test_results: dict | |
| class TTSRequest(BaseModel): | |
| text: str | |
| language_code: str = "en" | |
| # ==================== SYSTEM PROMPT ==================== | |
| DOCTOR_SYSTEM_PROMPT = """ | |
| You are Dr. HealBot, a calm, knowledgeable, and empathetic virtual doctor. | |
| GOAL: | |
| Hold a natural, focused conversation with the patient to understand their health issue through a series of questions (ONE AT A TIME) before providing comprehensive guidance. | |
| PATIENT HISTORY (IMPORTANT): | |
| The following medical profile belongs to the current patient: | |
| {patient_summary} | |
| RULES FOR PATIENT HISTORY: | |
| - ALWAYS use patient history in your reasoning (chronic diseases, medications, allergies, surgeries, recent labs, lifestyle). | |
| - NEVER ignore relevant risks or medication interactions. | |
| - TAILOR all advice (possible causes, medication safety, red flags) based on the patient's medical profile. | |
| - Keep references to history natural and brief—only if medically relevant. | |
| ⚠️ CRITICAL: ASK ONLY ONE QUESTION AT A TIME - This makes the conversation natural and not overwhelming. | |
| RESTRICTIONS: | |
| - ONLY provide information related to medical, health, or wellness topics. | |
| - If asked anything non-medical, politely decline: | |
| "I'm a medical consultation assistant and can only help with health or medical-related concerns." | |
| CONVERSATION FLOW: | |
| **PHASE 1: INFORMATION GATHERING (Conversational)** | |
| When a patient first mentions a symptom or health concern: | |
| - Acknowledge their concern warmly (1-2 sentences) | |
| - Ask ONLY ONE relevant follow-up question | |
| - Keep it conversational - like a real doctor's consultation | |
| - DO NOT give the final detailed response yet | |
| - DO NOT ask multiple questions at once | |
| Examples of good single questions: | |
| - "How long have you been experiencing this fever?" | |
| - "Have you taken your temperature? If yes, what was the reading?" | |
| - "Are you experiencing any other symptoms along with the fever?" | |
| - "Have you taken any medication for this yet?" | |
| **PHASE 2: CONTINUED CONVERSATION** | |
| - Continue asking clarifying questions ONE AT A TIME until you have enough information | |
| - Typical consultations need 2-3 exchanges before final assessment | |
| - Each response should have: brief acknowledgment + ONE question | |
| - Consider asking about: onset, duration, severity, location, triggers, relieving factors | |
| - Factor in patient's medical history when asking questions | |
| - Never overwhelm the patient with multiple questions at once | |
| **PHASE 3: FINAL COMPREHENSIVE RESPONSE** | |
| Only provide the detailed response format AFTER you have gathered sufficient information through conversation. | |
| 📋 Based on what you've told me... | |
| [Brief summary of patient's symptoms, plus any relevant history factors] | |
| 🔍 Possible Causes (Preliminary) | |
| - 1–2 possible explanations using soft language ("It could be…", "This might be…") | |
| - Include disclaimer that this is not a confirmed diagnosis | |
| - NOTE: Adjust based on patient's history (conditions, meds, allergies) | |
| 💊 Medication Advice (Safe & OTC) | |
| - Suggest only widely available OTC medicines | |
| - ENSURE medication is safe given the patient's: | |
| - allergies | |
| - chronic illnesses | |
| - current medications | |
| - Use disclaimers: | |
| "Use only if you have no allergies to this medication." | |
| "Follow packaging instructions or consult a doctor for exact dosing." | |
| 💡 Lifestyle & Home Care Tips | |
| - 2–3 simple, practical suggestions | |
| ⚠️ When to See a Real Doctor | |
| - Warning signs adjusted to the patient's underlying medical risks | |
| 📅 Follow-Up Advice | |
| - One short recommendation about monitoring symptoms or follow-up timing | |
| **HOW TO DECIDE WHEN TO GIVE FINAL RESPONSE:** | |
| Give the detailed final response when you have: | |
| ✅ Duration of symptoms | |
| ✅ Severity level | |
| ✅ Main accompanying symptoms | |
| ✅ Any relevant patient history considerations | |
| ✅ Patient has answered at least 2-3 of your questions | |
| If patient explicitly asks for immediate advice or says "just tell me what to do", you can provide the final response earlier. | |
| CONVERSATION MODES: | |
| 1. **Doctor Mode** (for symptoms/health issues): | |
| - Start with conversational questions | |
| - Gather information progressively | |
| - Only provide structured final response after sufficient information | |
| 2. **Instructor Mode** (for general medical questions): | |
| - If patient asks "What is diabetes?" or "How does aspirin work?" - provide direct educational answer | |
| - Give clear, educational explanations | |
| - Use short paragraphs or bullet points | |
| - No need for lengthy information gathering | |
| TONE & STYLE: | |
| - Warm, calm, professional—like a caring doctor in a consultation | |
| - Conversational and natural in early exchanges | |
| - Clear, empathetic, no jargon | |
| - Show you're listening by referencing what they've told you | |
| - Never give definitive diagnoses; always use soft language | |
| IMPORTANT: | |
| - This is preliminary guidance, not a substitute for professional care. | |
| - Never provide non-medical information. | |
| - Be conversational first, comprehensive later. | |
| - response has No Emoji or No emojis No smileys No flags No pictographs | |
| """ | |
| # ==================== HELPER FUNCTIONS ==================== | |
| def generate_patient_summary(patient_data: dict) -> str: | |
| """Generate a comprehensive summary of patient's medical profile and lab results""" | |
| if not patient_data: | |
| return "" | |
| summary = "\n🏥 **PATIENT MEDICAL PROFILE**\n" | |
| # Patient Profile Section | |
| if "patient_profile" in patient_data: | |
| profile = patient_data["patient_profile"] | |
| # Critical Medical Info | |
| if "critical_medical_info" in profile: | |
| cmi = profile["critical_medical_info"] | |
| summary += "\n📌 **Critical Medical Information:**\n" | |
| summary += f"- Major Conditions: {cmi.get('major_conditions', 'None')}\n" | |
| summary += f"- Current Medications: {cmi.get('current_medications', 'None')}\n" | |
| summary += f"- Allergies: {cmi.get('allergies', 'None')}\n" | |
| if cmi.get('past_surgeries_or_treatments') and cmi['past_surgeries_or_treatments'] != 'None': | |
| summary += f"- Past Surgeries: {cmi.get('past_surgeries_or_treatments')}\n" | |
| # Vital Risk Factors | |
| if "vital_risk_factors" in profile: | |
| vrf = profile["vital_risk_factors"] | |
| summary += "\n⚠️ **Risk Factors:**\n" | |
| if vrf.get('smoking_status') and 'smok' in vrf['smoking_status'].lower(): | |
| summary += f"- Smoking: {vrf.get('smoking_status')}\n" | |
| if vrf.get('blood_pressure_issue') and vrf['blood_pressure_issue'] != 'No': | |
| summary += f"- Blood Pressure: {vrf.get('blood_pressure_issue')}\n" | |
| if vrf.get('cholesterol_issue') and vrf['cholesterol_issue'] != 'No': | |
| summary += f"- Cholesterol: {vrf.get('cholesterol_issue')}\n" | |
| if vrf.get('diabetes_status') and 'diabetes' in vrf['diabetes_status'].lower(): | |
| summary += f"- Diabetes: {vrf.get('diabetes_status')}\n" | |
| if vrf.get('family_history_major_disease'): | |
| summary += f"- Family History: {vrf.get('family_history_major_disease')}\n" | |
| # Organ Health Summary | |
| if "organ_health_summary" in profile: | |
| ohs = profile["organ_health_summary"] | |
| issues = [] | |
| if ohs.get('heart_health') and 'normal' not in ohs['heart_health'].lower(): | |
| issues.append(f"Heart: {ohs['heart_health']}") | |
| if ohs.get('kidney_health') and 'no' not in ohs['kidney_health'].lower(): | |
| issues.append(f"Kidney: {ohs['kidney_health']}") | |
| if ohs.get('liver_health') and 'normal' not in ohs['liver_health'].lower() and 'no' not in ohs['liver_health'].lower(): | |
| issues.append(f"Liver: {ohs['liver_health']}") | |
| if ohs.get('gut_health') and 'normal' not in ohs['gut_health'].lower(): | |
| issues.append(f"Gut: {ohs['gut_health']}") | |
| if issues: | |
| summary += "\n🫀 **Organ Health Concerns:**\n" | |
| for issue in issues: | |
| summary += f"- {issue}\n" | |
| # Mental & Sleep Health | |
| if "mental_sleep_health" in profile: | |
| msh = profile["mental_sleep_health"] | |
| summary += "\n🧠 **Mental & Sleep Health:**\n" | |
| summary += f"- Mental Status: {msh.get('mental_health_status', 'Not specified')}\n" | |
| if msh.get('mental_conditions'): | |
| summary += f"- Mental Conditions: {msh.get('mental_conditions')}\n" | |
| summary += f"- Sleep: {msh.get('sleep_hours', 'Not specified')} per night" | |
| if msh.get('sleep_problems'): | |
| summary += f" ({msh.get('sleep_problems')})\n" | |
| else: | |
| summary += "\n" | |
| # Lifestyle | |
| if "lifestyle" in profile: | |
| ls = profile["lifestyle"] | |
| summary += "\n🏃 **Lifestyle:**\n" | |
| summary += f"- Activity: {ls.get('physical_activity_level', 'Not specified')}\n" | |
| summary += f"- Diet: {ls.get('diet_type', 'Not specified')}\n" | |
| # Lab Test Results Section | |
| if "lab_test_results" in patient_data: | |
| lab_results = patient_data["lab_test_results"] | |
| abnormal_results = [] | |
| # Check each test category for abnormal results | |
| for test_category, tests in lab_results.items(): | |
| if isinstance(tests, dict): | |
| for test_name, result in tests.items(): | |
| if result and isinstance(result, str): | |
| result_lower = result.lower() | |
| if any(word in result_lower for word in ['high', 'low', 'elevated', 'borderline']): | |
| abnormal_results.append(f"{test_name.replace('_', ' ').title()}: {result}") | |
| if abnormal_results: | |
| summary += "\n🔬 **Key Lab Results (Abnormal):**\n" | |
| for result in abnormal_results[:10]: | |
| summary += f"- {result}\n" | |
| # Health Goals | |
| if "patient_profile" in patient_data and "primary_health_goals" in patient_data["patient_profile"]: | |
| goals = patient_data["patient_profile"]["primary_health_goals"] | |
| summary += f"\n🎯 **Health Goals:** {goals}\n" | |
| return summary | |
| def save_patient_data(user_id: str, data: dict): | |
| """Save patient data to Firebase Firestore""" | |
| data["last_updated"] = datetime.now().isoformat() | |
| db.collection("patients").document(user_id).set(data) | |
| def load_patient_data(user_id: str) -> dict: | |
| """Load patient data from Firebase Firestore""" | |
| doc = db.collection("patients").document(user_id).get() | |
| if doc.exists: | |
| return doc.to_dict() | |
| return None | |
| def save_chat_history(user_id: str, messages: list): | |
| db.collection("chat_history").document(user_id).set({ | |
| "messages": messages, | |
| "last_updated": datetime.now().isoformat() | |
| }) | |
| def load_chat_history(user_id: str) -> list: | |
| doc = db.collection("chat_history").document(user_id).get() | |
| if doc.exists: | |
| return doc.to_dict().get("messages", []) | |
| return [] | |
| def delete_chat_history(user_id: str): | |
| db.collection("chat_history").document(user_id).delete() | |
| import re | |
| def remove_emojis(text: str) -> str: | |
| """ | |
| Remove all emojis from a string. | |
| """ | |
| emoji_pattern = re.compile( | |
| "[" | |
| "\U0001F600-\U0001F64F" # emoticons | |
| "\U0001F300-\U0001F5FF" # symbols & pictographs | |
| "\U0001F680-\U0001F6FF" # transport & map symbols | |
| "\U0001F1E0-\U0001F1FF" # flags | |
| "\U00002700-\U000027BF" # Dingbats | |
| "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs | |
| "\U00002600-\U000026FF" # Misc symbols | |
| "\U00002B00-\U00002BFF" # Misc symbols & arrows | |
| "]+", flags=re.UNICODE | |
| ) | |
| return emoji_pattern.sub(r'', text) | |
| import markdown | |
| def generate_patient_summary_html(patient_data: dict) -> str: | |
| """ | |
| Generate patient summary as HTML instead of Markdown. | |
| """ | |
| md_summary = generate_patient_summary(patient_data) | |
| html_summary = markdown.markdown(md_summary) | |
| return html_summary | |
| # ==================== ROOT ENDPOINT ==================== | |
| async def root(): | |
| """Root endpoint - tries to serve index.html, falls back to JSON""" | |
| try: | |
| with open("index.html", "r", encoding="utf-8") as f: | |
| return f.read() | |
| except FileNotFoundError: | |
| return JSONResponse({ | |
| "status": "healthy", | |
| "service": "Dr. HealBot API", | |
| "version": "1.0.0", | |
| "endpoints": { | |
| "chat": "/chat", | |
| "tts": "/tts", | |
| "stt": "/stt", | |
| "patient_data": "/patient-data/{user_id}", | |
| "chat_history": "/chat-history/{user_id}", | |
| "patient_summary": "/patient-summary/{user_id}" | |
| } | |
| }) | |
| async def ping(): | |
| return {"message": "pong"} | |
| # ==================== CHAT ENDPOINT ==================== | |
| async def chat(request: ChatRequest): | |
| """ | |
| Chat endpoint that: | |
| - Loads patient data and chat history | |
| - Updates patient data if new symptoms are reported | |
| - Sends patient summary + chat history + current message to Gemini | |
| - Returns structured, history-aware medical response | |
| """ | |
| try: | |
| user_id = request.user_id | |
| user_message = request.message.strip() | |
| # Load patient data & chat history | |
| patient_data = load_patient_data(user_id) or {} | |
| chat_history = load_chat_history(user_id) | |
| # Update patient data with new symptom info | |
| if "new_symptoms" not in patient_data: | |
| patient_data["new_symptoms"] = [] | |
| # Simple heuristic: if message contains key symptoms, store it | |
| symptom_keywords = ["fever", "cough", "headache", "ache", "pain", "rash", "vomit", "nausea"] | |
| if any(word in user_message.lower() for word in symptom_keywords): | |
| patient_data["new_symptoms"].append(user_message) | |
| save_patient_data(user_id, patient_data) | |
| # Generate patient summary | |
| persistent_summary = generate_patient_summary(patient_data) if patient_data else "No patient history available." | |
| # Prepare messages for Gemini (convert to single prompt format) | |
| system_context = f""" | |
| {DOCTOR_SYSTEM_PROMPT} | |
| You MUST always consider the following patient medical data when responding: | |
| {persistent_summary} | |
| Instructions: | |
| 1. **Conversational Stage**: | |
| - Start by acknowledging the patient's symptoms warmly. | |
| - Ask **only one question at a time** to clarify their condition. | |
| - Wait for the patient's answer before asking the next question. | |
| - Limit clarifying questions to **3–4 total**, but ask them sequentially, not all at once. | |
| - Example: | |
| - "I'm sorry you're feeling unwell. How long have you had this fever?" | |
| - Wait for response, then: "Are you experiencing any chills or body aches?" | |
| - And so on. | |
| 2. **Structured Guidance Stage**: | |
| - Only after 3–4 clarifying questions, provide the structured advice in the FINAL RESPONSE FORMAT. | |
| - Always factor in patient history (conditions, medications, allergies, labs). | |
| - Keep tone warm, empathetic, professional. | |
| - Never give definitive diagnoses; always use soft language. | |
| """ | |
| # Build conversation prompt | |
| conversation_prompt = system_context + "\n\n=== CONVERSATION HISTORY ===\n" | |
| # Add previous chat history | |
| for msg in chat_history: | |
| role = "Patient" if msg["role"] == "user" else "Dr. HealBot" | |
| conversation_prompt += f"\n{role}: {msg['content']}\n" | |
| # Add current user message | |
| conversation_prompt += f"\nPatient: {user_message}\n\nDr. HealBot:" | |
| # Call Gemini API | |
| model = genai.GenerativeModel('gemini-2.5-flash') | |
| response = model.generate_content( | |
| conversation_prompt, | |
| generation_config=genai.types.GenerationConfig( | |
| temperature=0.7, | |
| max_output_tokens=1024, | |
| ) | |
| ) | |
| reply_text = response.text.strip() | |
| # Update chat history | |
| chat_history.append({"role": "user", "content": user_message}) | |
| chat_history.append({"role": "assistant", "content": reply_text}) | |
| save_chat_history(user_id, chat_history) | |
| return JSONResponse({ | |
| "reply": reply_text, | |
| "user_id": user_id, | |
| "message_count": len(chat_history) | |
| }) | |
| except Exception as e: | |
| print(f"Error in /chat: {str(e)}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ==================== CHAT HISTORY ENDPOINTS ==================== | |
| async def get_chat_history(user_id: str): | |
| """Get chat history for a user""" | |
| try: | |
| history = load_chat_history(user_id) | |
| return JSONResponse({ | |
| "user_id": user_id, | |
| "chat_history": history, | |
| "message_count": len(history) | |
| }) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def clear_chat_history(user_id: str): | |
| """Clear chat history for a user""" | |
| try: | |
| delete_chat_history(user_id) | |
| return JSONResponse({"message": "Chat history cleared", "user_id": user_id}) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ==================== PATIENT DATA ENDPOINTS ==================== | |
| async def save_patient(user_id: str, data: PatientData): | |
| """Save patient profile and lab test results""" | |
| try: | |
| patient_info = { | |
| "name": data.name, | |
| "patient_profile": data.patient_profile, | |
| "lab_test_results": data.lab_test_results, | |
| "last_updated": datetime.now().isoformat() | |
| } | |
| save_patient_data(user_id, patient_info) | |
| return JSONResponse({ | |
| "message": "Patient data saved successfully", | |
| "user_id": user_id | |
| }) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_patient(user_id: str): | |
| """Get patient data""" | |
| try: | |
| data = load_patient_data(user_id) | |
| if data: | |
| return JSONResponse(data) | |
| return JSONResponse({"message": "No patient data found"}, status_code=404) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_patient_summary(user_id: str, format: str = "markdown"): | |
| """ | |
| Get formatted summary of patient's medical profile and lab results. | |
| Supports Markdown (default) or HTML output. | |
| """ | |
| try: | |
| data = load_patient_data(user_id) | |
| if not data: | |
| return JSONResponse({"summary": "No patient data available"}) | |
| if format.lower() == "html": | |
| summary = generate_patient_summary_html(data) | |
| else: | |
| summary = generate_patient_summary(data) | |
| return JSONResponse({"summary": summary, "raw_data": data}) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ==================== TTS ENDPOINT ==================== | |
| async def text_to_speech(req: TTSRequest): | |
| try: | |
| # Remove emojis from the input text | |
| clean_text = remove_emojis(req.text) | |
| tmp_mp3 = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") | |
| tts = gTTS(text=clean_text, lang=req.language_code) | |
| tts.save(tmp_mp3.name) | |
| tmp_wav = tempfile.NamedTemporaryFile(delete=False, suffix=".wav") | |
| subprocess.run( | |
| ["ffmpeg", "-y", "-i", tmp_mp3.name, "-ar", "44100", "-ac", "2", tmp_wav.name], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL | |
| ) | |
| # Delete temporary mp3 | |
| os.remove(tmp_mp3.name) | |
| return FileResponse(tmp_wav.name, media_type="audio/wav", filename="speech.wav") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ==================== STT ENDPOINT ==================== | |
| # Initialize speech recognizer | |
| recognizer = sr.Recognizer() | |
| async def speech_to_text(file: UploadFile = File(...)): | |
| try: | |
| # Save uploaded file temporarily | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp: | |
| tmp.write(await file.read()) | |
| tmp_path = tmp.name | |
| # Use speech_recognition library with Google's free API | |
| with sr.AudioFile(tmp_path) as source: | |
| audio_data = recognizer.record(source) | |
| # Use Google Speech Recognition (free, no API key needed) | |
| transcript = recognizer.recognize_google(audio_data) | |
| # Clean up temp file | |
| os.remove(tmp_path) | |
| return JSONResponse({"transcript": transcript}) | |
| except sr.UnknownValueError: | |
| raise HTTPException(status_code=400, detail="Could not understand audio") | |
| except sr.RequestError as e: | |
| raise HTTPException(status_code=500, detail=f"Speech recognition service error: {str(e)}") | |
| except Exception as e: | |
| print(f"Error in STT: {str(e)}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |