HEALTHCARE / services.py
angeetoile's picture
Update services.py
6349324 verified
Raw
History Blame Contribute Delete
12.7 kB
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