FlorianSC/agritech-interface
0
1import logging2import joblib3import pandas as pd4import uvicorn5import os6from huggingface_hub import hf_hub_download7from pydantic import BaseModel, Field8from fastapi import FastAPI, Request9from fastapi.responses import JSONResponse10from fastapi.exceptions import RequestValidationError11import os12from dotenv import load_dotenv13from huggingface_hub import hf_hub_download14 15# Charge les variables du fichier .env 16load_dotenv() 17 18# Récupère la clé 19token = os.getenv("HF_TOKEN")20 21# On importe la fonction de nettoyage22from scripts.data_cleaning import preparation_yield_df_inference23 24logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')25 26app = FastAPI(title="Agritech Answers")27 28# ==========================================29# CHARGEMENT DYNAMIQUE DU MODÈLE30# ==========================================31def load_remote_pipeline():32 REPO_ID = "FLORIANSC/yield-prediction-model" 33 FILENAME = "randomforest_best_pipeline.joblib"34 35 # On vérifie si on est en local ou sur le Space36 if os.path.exists(FILENAME):37 logging.info("Chargement du modèle local.")38 return joblib.load(FILENAME)39 else:40 logging.info("Modèle local non trouvé. Téléchargement depuis le Hub...")41 token = os.getenv("HF_TOKEN")42 model_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME, token=token)43 return joblib.load(model_path)44 45# Chargement du pipeline au démarrage46try:47 pipeline = load_remote_pipeline()48 logging.info("Pipeline chargé avec succès.")49except Exception as e:50 logging.error(f"Erreur critique de chargement du modèle : {e}")51 pipeline = None52 53from scripts.config import (REGIONS, ITEMS)54 55class InputPrediction(BaseModel):56 region: REGIONS57 item: ITEMS58 avg_temp: float = Field(..., json_schema_extra={"example": 15.5})59 rainfall_mm: float = Field(..., json_schema_extra={"example": 500.0})60 pesticides_tonnes: float = Field(..., json_schema_extra={"example": 100.0})61 62 63class InputRecommendation(BaseModel):64 region: REGIONS65 avg_temp: float = Field(..., json_schema_extra={"example": 15.5})66 rainfall_mm: float = Field(..., json_schema_extra={"example": 500.0})67 pesticides_tonnes: float = Field(..., json_schema_extra={"example": 100.0})68 69# Gestion des erreurs 40470 71@app.exception_handler(404)72async def custom_404_handler(request: Request, exc):73 return JSONResponse(74 status_code=404,75 content={76 "error_code": 404,77 "message": "Cette route n'existe pas.",78 "detail": f"L'URL '{request.url.path}' est inconnue.",79 "suggestion": "Essayez plutôt /predict ou /health"80 }81 )82# Gestion des erreurs 42283@app.exception_handler(RequestValidationError)84async def validation_exception_handler(request: Request, exc: RequestValidationError):85 errors = exc.errors()86 # On logue l'erreur précise pour le Lead Data Scientist87 logging.error(f"Erreur de validation Pydantic : {errors}")88 89 return JSONResponse(90 status_code=422,91 content={92 "error_code": 422,93 "message": "Données d'entrée invalides (Schéma Pydantic)",94 "details": [95 {"champ": err["loc"][-1], "message": err["msg"], "type_attendu": err["type"]} 96 for err in errors97 ]98 }99 )100 101@app.get("/health")102def health_check():103 return {"status": "OK", "message": "API opérationnelle"}104 105 106@app.post("/predict")107def predict_api(data: InputPrediction):108 try:109 donnees_saisies = pd.DataFrame([data.model_dump()])110 111 # Feature engineering uniquement, sans encodage manuel112 donnees_preparees = preparation_yield_df_inference(input_df=donnees_saisies)113 114 logging.info(f"Colonnes envoyées au pipeline : {list(donnees_preparees.columns)}")115 116 prediction = pipeline.predict(donnees_preparees)[0]117 118 return {119 "prediction": float(prediction/10),120 "unit": "yield"121 }122 123 except Exception as e:124 logging.exception("Erreur pendant la prédiction")125 return JSONResponse(126 status_code=500,127 content={128 "error_code": 500,129 "message": "Erreur lors de la prédiction",130 "detail": str(e)131 }132 )133 134 135CROPS = [136 "Wheat", "Rice", "Maize", "Soybean", "Potatoes",137 "Sweet Potatoes", "Sorghum", "Cassava", "Yams", "Plantains and others"138]139 140 141@app.post("/recommend")142def recommend_api(data: InputRecommendation):143 try:144 results = []145 146 for crop in CROPS:147 donnees_saisies = pd.DataFrame([{148 "region": data.region,149 "item": crop,150 "avg_temp": data.avg_temp,151 "rainfall_mm": data.rainfall_mm,152 "pesticides_tonnes": data.pesticides_tonnes153 }])154 155 # Feature engineering uniquement156 donnees_preparees = preparation_yield_df_inference(input_df=donnees_saisies)157 158 prediction = pipeline.predict(donnees_preparees)[0]159 160 results.append({161 "crop": crop,162 "predicted_yield": float(prediction/10)163 })164 165 results = sorted(results, key=lambda x: x["predicted_yield"], reverse=True)166 167 return {168 "best_crop": results[0]["crop"],169 "ranking": results170 }171 172 except Exception as e:173 logging.exception("Erreur pendant la recommandation")174 return JSONResponse(175 status_code=500,176 content={177 "error_code": 500,178 "message": "Erreur lors de la recommandation",179 "detail": str(e)180 }181 )182 183 184if __name__ == "__main__":185 uvicorn.run("app:app", host="0.0.0.0", port=7860)