Spaces:
Sleeping
Sleeping
| import asyncio | |
| import json | |
| import requests | |
| import os | |
| import random | |
| import pandas as pd | |
| from typing import List, Optional | |
| from sqlalchemy import text | |
| from config import groq_client, CHAT_MODEL, DATABRICKS_ENDPOINT_URL, DATABRICKS_TOKEN, engine | |
| # Chemin local vers ton dataset d'entrainement | |
| CSV_PATH = os.path.join(os.path.dirname(__file__), "medical_dataset_final.csv") | |
| print(f"[INFO] Chargement du dataset depuis le fichier local : {CSV_PATH}") | |
| try: | |
| if os.path.exists(CSV_PATH): | |
| hf_dataset = pd.read_csv(CSV_PATH) | |
| print("Dataset local chargé avec succès.") | |
| else: | |
| print(f"[ALERTE] Le fichier {CSV_PATH} est introuvable.") | |
| hf_dataset = None | |
| except Exception as e: | |
| print(f"[ALERTE] Γchec du chargement du fichier CSV : {e}") | |
| hf_dataset = None | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # DATASET β symptΓ΄mes rΓ©els (Γ©vite les hallucinations du LLM) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_symptoms_from_dataset(disease_name: str) -> str: | |
| """Extrait les symptΓ΄mes rΓ©els depuis le dataset pour Γ©viter les hallucinations du LLM.""" | |
| global hf_dataset | |
| if hf_dataset is None: | |
| return "DonnΓ©es indisponibles." | |
| match = hf_dataset[hf_dataset['disease'].str.lower() == disease_name.lower()] | |
| if not match.empty: | |
| return str(match.iloc[0].get('symptoms', 'Non spΓ©cifiΓ©')) | |
| return "Aucun symptΓ΄me spΓ©cifique enregistrΓ©." | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # USER PROFILE | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_user_profile(user_id: str) -> Optional[dict]: | |
| if not engine or not user_id: | |
| return None | |
| try: | |
| with engine.connect() as conn: | |
| user_row = conn.execute( | |
| text("SELECT full_name, email FROM users WHERE id = :uid"), | |
| {"uid": user_id} | |
| ).fetchone() | |
| if not user_row: | |
| return None | |
| profile_row = conn.execute( | |
| text("SELECT age, sex, family_history, allergies, medications FROM medical_profiles WHERE user_id = :uid"), | |
| {"uid": user_id} | |
| ).fetchone() | |
| profile = {"full_name": user_row[0] or "", "email": user_row[1] or ""} | |
| if profile_row: | |
| fh = profile_row[2] | |
| if isinstance(fh, str): | |
| try: | |
| fh = json.loads(fh) | |
| except: | |
| fh = [] | |
| profile.update({ | |
| "age": profile_row[0], | |
| "sex": profile_row[1], | |
| "family_history": fh or [], | |
| "allergies": profile_row[3] or "", | |
| "medications": profile_row[4] or "" | |
| }) | |
| return profile | |
| except Exception as e: | |
| print(f"Erreur get_user_profile: {e}") | |
| return None | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # NEARBY HOSPITALS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def get_nearby_hospitals(lat: float, lon: float) -> str: | |
| try: | |
| overpass_url = "https://overpass.kumi.systems/api/interpreter" | |
| query = ( | |
| f'[out:json];' | |
| f'(node["amenity"="hospital"](around:5000,{lat},{lon});' | |
| f'way["amenity"="hospital"](around:5000,{lat},{lon}););' | |
| f'out tags;' | |
| ) | |
| response = await asyncio.to_thread( | |
| requests.get, overpass_url, params={'data': query}, timeout=5 | |
| ) | |
| data = response.json() | |
| names = [ | |
| el['tags'].get('name') | |
| for el in data.get('elements', []) | |
| if el['tags'].get('name') | |
| ] | |
| return ", ".join(names[:3]) if names else "" | |
| except Exception as e: | |
| print(f"Erreur GΓ©olocalisation: {e}") | |
| return "" | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TRANSLATION | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def translate_to_english(text_to_translate: str) -> str: | |
| try: | |
| prompt = f"Translate only this medical text into English. Return only the translation:\n{text_to_translate}" | |
| response = await groq_client.chat.completions.create( | |
| messages=[{"role": "user", "content": prompt}], | |
| model=CHAT_MODEL, temperature=0.1 | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception: | |
| return text_to_translate | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # DISEASE DETAILS VIA LLM | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def generate_disease_details_via_llm(disease_name_en: str, lang: str = "fr") -> dict: | |
| """ | |
| Génère description et traitement réels dans la langue demandée. | |
| Les symptΓ΄mes proviennent exclusivement du dataset. | |
| """ | |
| try: | |
| symptomes_reels = get_symptoms_from_dataset(disease_name_en) | |
| lang_label = "French" if lang == "fr" else "English" if lang == "en" else "French" | |
| prompt = ( | |
| f"You are a medical expert. Analyze the disease: '{disease_name_en}'.\n" | |
| f"Respond EXCLUSIVELY in {lang_label}.\n" | |
| "Return ONLY a strict JSON object with these 4 keys:\n" | |
| "- 'name_fr': Translated name of the disease in the response language.\n" | |
| "- 'description': Clear explanation of what this disease is, its causes, " | |
| "and who it typically affects. 2-3 sentences, accessible language, warm tone.\n" | |
| "- 'treatment': Concrete actionable treatment: first-line medications or interventions, " | |
| "lifestyle advice, specific steps the patient can take. " | |
| "Do NOT only say consult a doctor β give real guidance first, " | |
| "then suggest seeing a doctor for confirmation. 3-4 sentences.\n" | |
| f"- 'typical_symptoms': Copy this value exactly without any modification: {repr(symptomes_reels)}\n" | |
| "Return only the JSON, no preamble." | |
| ) | |
| response = await groq_client.chat.completions.create( | |
| messages=[{"role": "user", "content": prompt}], | |
| model=CHAT_MODEL, temperature=0.3, response_format={"type": "json_object"} | |
| ) | |
| return json.loads(response.choices[0].message.content) | |
| except Exception: | |
| # Fallback : appel LLM simplifiΓ© sans response_format | |
| try: | |
| symptomes_reels = get_symptoms_from_dataset(disease_name_en) | |
| fallback_prompt = ( | |
| f"Tu es un mΓ©decin. DΓ©cris en franΓ§ais la maladie '{disease_name_en}' en 2 phrases simples. " | |
| f"Puis propose un traitement concret en 2 phrases (mΓ©dicaments, conseils pratiques). " | |
| f"Format JSON strict avec les clΓ©s: name_fr, description, treatment. " | |
| f"Ne rΓ©ponds que le JSON." | |
| ) | |
| r = await groq_client.chat.completions.create( | |
| messages=[{"role": "user", "content": fallback_prompt}], | |
| model=CHAT_MODEL, temperature=0.3, max_tokens=300 | |
| ) | |
| import re | |
| text = r.choices[0].message.content.strip() | |
| match = re.search(r'\{.*\}', text, re.DOTALL) | |
| data = json.loads(match.group()) if match else {} | |
| return { | |
| "name_fr": data.get("name_fr", disease_name_en), | |
| "description": data.get("description", f"Analyse clinique de {disease_name_en}."), | |
| "treatment": data.get("treatment", "Consultez un mΓ©decin."), | |
| "typical_symptoms": get_symptoms_from_dataset(disease_name_en) | |
| } | |
| except Exception: | |
| return { | |
| "name_fr": disease_name_en, | |
| "description": f"Analyse clinique de {disease_name_en}.", | |
| "treatment": "Consultez un mΓ©decin.", | |
| "typical_symptoms": get_symptoms_from_dataset(disease_name_en) | |
| } | |
| async def transcribe_audio_groq(audio_bytes: bytes) -> str: | |
| try: | |
| from groq import Groq as SyncGroq | |
| api_key = os.getenv("GROQ_API_KEY") | |
| def _sync_transcribe(): | |
| sync_client = SyncGroq(api_key=api_key) | |
| return sync_client.audio.transcriptions.create( | |
| file=("recording.webm", audio_bytes), | |
| model="whisper-large-v3", | |
| response_format="json" | |
| ).text.strip() | |
| return await asyncio.to_thread(_sync_transcribe) | |
| except Exception as e: | |
| print(f"[ERREUR TRANSCRIPTION] : {e}") | |
| return "" | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # IMAGE OCR | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def image_to_text_groq(image_bytes: bytes) -> str: | |
| try: | |
| import base64 | |
| from config import VISION_MODEL | |
| b64_image = base64.b64encode(image_bytes).decode("utf-8") | |
| response = await groq_client.chat.completions.create( | |
| model=VISION_MODEL, | |
| messages=[{ | |
| "role": "user", | |
| "content": [ | |
| { | |
| "type": "image_url", | |
| "image_url": {"url": f"data:image/jpeg;base64,{b64_image}"} | |
| }, | |
| {"type": "text", "text": "Extract all text faithfully."} | |
| ] | |
| }], | |
| max_tokens=1024, temperature=0.1, | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| print(f"[ERREUR OCR] : {e}") | |
| return "" | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # OCR VALIDATION | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def check_is_medical_text(text_content: str) -> bool: | |
| try: | |
| response = await groq_client.chat.completions.create( | |
| messages=[{ | |
| "role": "user", | |
| "content": f"Is this text medical? Answer YES or NO.\n{text_content[:500]}" | |
| }], | |
| model=CHAT_MODEL, max_tokens=5, temperature=0.0, | |
| ) | |
| return "YES" in response.choices[0].message.content.upper() | |
| except Exception: | |
| return False |