Spaces:
Sleeping
Sleeping
create space
Browse files
app.py
CHANGED
|
@@ -18,15 +18,18 @@ import xml.etree.ElementTree as ET
|
|
| 18 |
from fastapi.responses import JSONResponse
|
| 19 |
from sklearn.base import BaseEstimator, TransformerMixin
|
| 20 |
import sys
|
| 21 |
-
from huggingface_hub import hf_hub_download
|
| 22 |
-
from transformers import
|
| 23 |
-
import torch
|
| 24 |
|
| 25 |
app = FastAPI()
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
app.add_middleware(
|
| 28 |
CORSMiddleware,
|
| 29 |
-
allow_origins=
|
| 30 |
allow_credentials=True,
|
| 31 |
allow_methods=["*"],
|
| 32 |
allow_headers=["*"],
|
|
@@ -54,54 +57,42 @@ class NumericConverter(BaseEstimator, TransformerMixin):
|
|
| 54 |
|
| 55 |
sys.modules['__main__'].NumericConverter = NumericConverter
|
| 56 |
|
| 57 |
-
#
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
except Exception as e:
|
| 94 |
-
print(f"❌ Erreur lors du chargement du modèle LLM: {e}")
|
| 95 |
-
raise
|
| 96 |
-
|
| 97 |
-
# Initialiser les modèles avec gestion d'erreur
|
| 98 |
-
try:
|
| 99 |
-
load_models_from_hf()
|
| 100 |
-
print("🎉 Tous les modèles ont été chargés avec succès!")
|
| 101 |
-
except Exception as e:
|
| 102 |
-
print(f"💥 Erreur critique lors du chargement des modèles: {e}")
|
| 103 |
-
print("L'application ne peut pas démarrer sans les modèles.")
|
| 104 |
-
raise
|
| 105 |
|
| 106 |
# Créer un imputer pour gérer les valeurs NaN
|
| 107 |
imputer = SimpleImputer(strategy='constant', fill_value=0)
|
|
@@ -419,35 +410,10 @@ def to_native(val):
|
|
| 419 |
return val
|
| 420 |
|
| 421 |
# ==== API Endpoints ====
|
| 422 |
-
@app.get("/health")
|
| 423 |
-
def health_check():
|
| 424 |
-
"""Vérifie que tous les modèles sont chargés correctement"""
|
| 425 |
-
try:
|
| 426 |
-
# Vérifier que tous les modèles sont disponibles
|
| 427 |
-
models_status = {
|
| 428 |
-
"analyse_medicale_model": pipeline is not None,
|
| 429 |
-
"analyze_risk_model": analyze_risk_model is not None,
|
| 430 |
-
"llm_model": llm_model is not None,
|
| 431 |
-
"llm_tokenizer": llm_tokenizer is not None
|
| 432 |
-
}
|
| 433 |
-
|
| 434 |
-
all_loaded = all(models_status.values())
|
| 435 |
-
|
| 436 |
-
return {
|
| 437 |
-
"status": "healthy" if all_loaded else "unhealthy",
|
| 438 |
-
"models_loaded": models_status,
|
| 439 |
-
"message": "Tous les modèles sont chargés" if all_loaded else "Certains modèles ne sont pas chargés"
|
| 440 |
-
}
|
| 441 |
-
except Exception as e:
|
| 442 |
-
return {
|
| 443 |
-
"status": "error",
|
| 444 |
-
"error": str(e)
|
| 445 |
-
}
|
| 446 |
-
|
| 447 |
@app.post("/predict", response_model=PredictionResult)
|
| 448 |
def predict(data: InputData):
|
| 449 |
df = pd.DataFrame([data.dict()])
|
| 450 |
-
preds =
|
| 451 |
return PredictionResult(
|
| 452 |
**data.dict(),
|
| 453 |
CodParametre=preds[0],
|
|
@@ -494,7 +460,7 @@ async def upload_file(file: UploadFile = File(...)):
|
|
| 494 |
# Si on a du PDF et qu'on a besoin de prédire les paramètres
|
| 495 |
if file_extension == "pdf":
|
| 496 |
# Faire la prédiction
|
| 497 |
-
preds =
|
| 498 |
|
| 499 |
# Créer les résultats avec les prédictions
|
| 500 |
results = []
|
|
@@ -531,8 +497,6 @@ async def upload_file(file: UploadFile = File(...)):
|
|
| 531 |
def analyze_risk(param: dict = Body(...)):
|
| 532 |
import pandas as pd
|
| 533 |
import numpy as np
|
| 534 |
-
# Utiliser le modèle globalement chargé
|
| 535 |
-
model = analyze_risk_model
|
| 536 |
|
| 537 |
# Préparer le DataFrame à partir du paramètre reçu
|
| 538 |
df_test = pd.DataFrame([param])
|
|
@@ -579,7 +543,7 @@ def analyze_risk(param: dict = Body(...)):
|
|
| 579 |
features_for_ml = df_result[['DeltaValeurPrecedente', 'RatioValeurPrecedente',
|
| 580 |
'PourcentageValeurMin', 'PourcentageValeurMax',
|
| 581 |
'EcartNormalise', 'ValeurActuelle', 'CodeParametre']]
|
| 582 |
-
predicted_risk_num =
|
| 583 |
risk_map = {0: 'Aucun', 1: 'Faible', 2: 'Modéré', 3: 'Élevé'}
|
| 584 |
degre_risque = risk_map.get(int(predicted_risk_num), 'Inconnu')
|
| 585 |
|
|
@@ -618,6 +582,78 @@ def analyze_risk(param: dict = Body(...)):
|
|
| 618 |
"conseil": to_native(conseil)
|
| 619 |
}
|
| 620 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
# Fonction de debug temporaire pour tester l'extraction
|
| 622 |
def debug_extraction(line):
|
| 623 |
"""Teste l'extraction d'une ligne et affiche les résultats"""
|
|
|
|
| 18 |
from fastapi.responses import JSONResponse
|
| 19 |
from sklearn.base import BaseEstimator, TransformerMixin
|
| 20 |
import sys
|
| 21 |
+
from huggingface_hub import hf_hub_download, InferenceClient
|
| 22 |
+
from transformers import pipeline as hf_textgen_pipeline
|
|
|
|
| 23 |
|
| 24 |
app = FastAPI()
|
| 25 |
|
| 26 |
+
# Configure CORS for local dev and Spaces by env var (default: allow all)
|
| 27 |
+
cors_env = os.getenv("CORS_ALLOW_ORIGINS", "*")
|
| 28 |
+
allow_origins = ["*"] if cors_env.strip() == "*" else [o.strip() for o in cors_env.split(",") if o.strip()]
|
| 29 |
+
|
| 30 |
app.add_middleware(
|
| 31 |
CORSMiddleware,
|
| 32 |
+
allow_origins=allow_origins,
|
| 33 |
allow_credentials=True,
|
| 34 |
allow_methods=["*"],
|
| 35 |
allow_headers=["*"],
|
|
|
|
| 57 |
|
| 58 |
sys.modules['__main__'].NumericConverter = NumericConverter
|
| 59 |
|
| 60 |
+
# ==== Hugging Face Hub model loading ====
|
| 61 |
+
HF_REPO_MEDICALE = os.getenv("HF_REPO_MEDICALE", "HendSta/analyse_medicale")
|
| 62 |
+
HF_REPO_ROW = os.getenv("HF_REPO_ROW", "HendSta/analyse_row")
|
| 63 |
+
HF_REPO_LLM = os.getenv("HF_REPO_LLM", "HendSta/biomistral-finetuned-fullv3")
|
| 64 |
+
|
| 65 |
+
def load_joblib_from_hub(repo_id: str, candidate_filenames: List[str]):
|
| 66 |
+
last_error: Optional[Exception] = None
|
| 67 |
+
for filename in candidate_filenames:
|
| 68 |
+
try:
|
| 69 |
+
file_path = hf_hub_download(repo_id=repo_id, filename=filename, local_dir="hub_models", local_dir_use_symlinks=False)
|
| 70 |
+
return joblib.load(file_path)
|
| 71 |
+
except Exception as e:
|
| 72 |
+
last_error = e
|
| 73 |
+
continue
|
| 74 |
+
raise RuntimeError(f"Impossible de charger un modèle depuis {repo_id}. Dernière erreur: {last_error}")
|
| 75 |
+
|
| 76 |
+
# Charger les modèles ML depuis le Hub
|
| 77 |
+
medical_pipeline = load_joblib_from_hub(
|
| 78 |
+
HF_REPO_MEDICALE,
|
| 79 |
+
[
|
| 80 |
+
"modele_analyse_medicale_final.joblib",
|
| 81 |
+
"pipeline.joblib",
|
| 82 |
+
"model.joblib",
|
| 83 |
+
"model.pkl",
|
| 84 |
+
],
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
risk_model = load_joblib_from_hub(
|
| 88 |
+
HF_REPO_ROW,
|
| 89 |
+
[
|
| 90 |
+
"analyze_row_final.joblib",
|
| 91 |
+
"analyse_row_final.joblib",
|
| 92 |
+
"model.joblib",
|
| 93 |
+
"model.pkl",
|
| 94 |
+
],
|
| 95 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
# Créer un imputer pour gérer les valeurs NaN
|
| 98 |
imputer = SimpleImputer(strategy='constant', fill_value=0)
|
|
|
|
| 410 |
return val
|
| 411 |
|
| 412 |
# ==== API Endpoints ====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
@app.post("/predict", response_model=PredictionResult)
|
| 414 |
def predict(data: InputData):
|
| 415 |
df = pd.DataFrame([data.dict()])
|
| 416 |
+
preds = medical_pipeline.predict(df)[0]
|
| 417 |
return PredictionResult(
|
| 418 |
**data.dict(),
|
| 419 |
CodParametre=preds[0],
|
|
|
|
| 460 |
# Si on a du PDF et qu'on a besoin de prédire les paramètres
|
| 461 |
if file_extension == "pdf":
|
| 462 |
# Faire la prédiction
|
| 463 |
+
preds = medical_pipeline.predict(df)
|
| 464 |
|
| 465 |
# Créer les résultats avec les prédictions
|
| 466 |
results = []
|
|
|
|
| 497 |
def analyze_risk(param: dict = Body(...)):
|
| 498 |
import pandas as pd
|
| 499 |
import numpy as np
|
|
|
|
|
|
|
| 500 |
|
| 501 |
# Préparer le DataFrame à partir du paramètre reçu
|
| 502 |
df_test = pd.DataFrame([param])
|
|
|
|
| 543 |
features_for_ml = df_result[['DeltaValeurPrecedente', 'RatioValeurPrecedente',
|
| 544 |
'PourcentageValeurMin', 'PourcentageValeurMax',
|
| 545 |
'EcartNormalise', 'ValeurActuelle', 'CodeParametre']]
|
| 546 |
+
predicted_risk_num = risk_model.predict(features_for_ml)[0]
|
| 547 |
risk_map = {0: 'Aucun', 1: 'Faible', 2: 'Modéré', 3: 'Élevé'}
|
| 548 |
degre_risque = risk_map.get(int(predicted_risk_num), 'Inconnu')
|
| 549 |
|
|
|
|
| 582 |
"conseil": to_native(conseil)
|
| 583 |
}
|
| 584 |
|
| 585 |
+
# ==== LLM (text-generation) endpoint ====
|
| 586 |
+
_llm_generator = None
|
| 587 |
+
_llm_via_api = False
|
| 588 |
+
|
| 589 |
+
def get_llm_generator():
|
| 590 |
+
global _llm_generator, _llm_via_api
|
| 591 |
+
if _llm_generator is not None:
|
| 592 |
+
return _llm_generator
|
| 593 |
+
|
| 594 |
+
# Prefer Inference API by default to avoid OOM in CPU Spaces
|
| 595 |
+
use_api_default = os.getenv("USE_INFERENCE_API", "1")
|
| 596 |
+
use_api = use_api_default.lower() in {"1", "true", "yes"}
|
| 597 |
+
|
| 598 |
+
if use_api:
|
| 599 |
+
token = os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN")
|
| 600 |
+
_llm_generator = InferenceClient(model=HF_REPO_LLM, token=token)
|
| 601 |
+
_llm_via_api = True
|
| 602 |
+
return _llm_generator
|
| 603 |
+
|
| 604 |
+
# Fallback to local transformers pipeline
|
| 605 |
+
try:
|
| 606 |
+
trust_code = os.getenv("HF_TRUST_REMOTE_CODE", "1") != "0"
|
| 607 |
+
_llm_generator = hf_textgen_pipeline(
|
| 608 |
+
task="text-generation",
|
| 609 |
+
model=HF_REPO_LLM,
|
| 610 |
+
trust_remote_code=trust_code,
|
| 611 |
+
)
|
| 612 |
+
_llm_via_api = False
|
| 613 |
+
except Exception:
|
| 614 |
+
token = os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN")
|
| 615 |
+
_llm_generator = InferenceClient(model=HF_REPO_LLM, token=token)
|
| 616 |
+
_llm_via_api = True
|
| 617 |
+
return _llm_generator
|
| 618 |
+
|
| 619 |
+
class GenerateRequest(BaseModel):
|
| 620 |
+
prompt: str
|
| 621 |
+
max_new_tokens: int = 256
|
| 622 |
+
temperature: float = 0.7
|
| 623 |
+
top_p: float = 0.95
|
| 624 |
+
repetition_penalty: float = 1.1
|
| 625 |
+
|
| 626 |
+
class GenerateResponse(BaseModel):
|
| 627 |
+
output: str
|
| 628 |
+
|
| 629 |
+
@app.post("/llm-generate", response_model=GenerateResponse)
|
| 630 |
+
def llm_generate(req: GenerateRequest):
|
| 631 |
+
try:
|
| 632 |
+
generator = get_llm_generator()
|
| 633 |
+
if _llm_via_api:
|
| 634 |
+
text = generator.text_generation(
|
| 635 |
+
req.prompt,
|
| 636 |
+
max_new_tokens=req.max_new_tokens,
|
| 637 |
+
temperature=req.temperature,
|
| 638 |
+
top_p=req.top_p,
|
| 639 |
+
repetition_penalty=req.repetition_penalty,
|
| 640 |
+
do_sample=True,
|
| 641 |
+
)
|
| 642 |
+
else:
|
| 643 |
+
outputs = generator(
|
| 644 |
+
req.prompt,
|
| 645 |
+
max_new_tokens=req.max_new_tokens,
|
| 646 |
+
temperature=req.temperature,
|
| 647 |
+
top_p=req.top_p,
|
| 648 |
+
repetition_penalty=req.repetition_penalty,
|
| 649 |
+
do_sample=True,
|
| 650 |
+
pad_token_id=50256,
|
| 651 |
+
)
|
| 652 |
+
text = outputs[0].get("generated_text", "") if isinstance(outputs, list) and outputs else str(outputs)
|
| 653 |
+
return GenerateResponse(output=text)
|
| 654 |
+
except Exception as e:
|
| 655 |
+
raise HTTPException(status_code=500, detail=f"Erreur LLM: {str(e)}")
|
| 656 |
+
|
| 657 |
# Fonction de debug temporaire pour tester l'extraction
|
| 658 |
def debug_extraction(line):
|
| 659 |
"""Teste l'extraction d'une ligne et affiche les résultats"""
|