from fastapi import FastAPI from fastapi import HTTPException from pydantic import BaseModel import mlflow.pyfunc import pandas as pd import config import logging import os logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 1) URL du Space HF qui héberge MLflow (tracking server) MLFLOW_TRACKING_URI = os.getenv("MLFLOW_TRACKING_URI", "https://pradelf-getaround-mlflow.hf.space") MODEL_URI = os.getenv("MODEL_URI", "models:/getaround-price-prediction-model@certification") BOOL_COLS = [ "private_parking_available", "has_gps", "has_air_conditioning", "automatic_car", "has_getaround_connect", "has_speed_regulator", "winter_tires", ] description = """ Bienvenue sur l'API de Getaround pour prédire le prix journalier de location d'une voiture en fonction de son année d'expérience! ## Point de terminaison d'introduction Pour tester le fonctionnement de l'API, vous pouvez utiliser le point de terminaison d'introduction suivant: * `/`: **GET** retourne la version de l'API et un message de bienvenue. ## Point de terminaison de ligne de vie Cette web fonction permet de vérifier que le serveur de l'API est opérationnel et de surveiller son statut. * `/health`: **GET** retourne juste OK pour valider que le serveur de l'API est en cours d'exécution sans problèmes. ## Machine Learning : Point de terminaison du prix de location Cette terminaison de l'API permet de prédire le prix journalier de location d'une voiture en fonction de ses caractéristiques.. * `/predict` accepte une requête POST avec un JSON contenant un objet JSON donnant les caractéristiques d'une voiture et retourne une prédiction du prix journalier de location de celle-ci. La documentation est ci-dessous 👇 pour chaque point de terminaison (endpoints). """ tags_metadata = [ { "name": "Point de terminaison d'introduction", "description": "Terminaison simple de présentation de l'API et de sa version pour vérifier que tout fonctionne correctement.", }, { "name": "Point de terminaison de ligne de vie", "description": "Point de terminaison de ligne de vie pour surveiller le statut du serveur de l'API. Retourne 'ok' si le serveur fonctionne sans problèmes.", }, { "name": "Point de terminaison du prix de location", "description": "Point de terminaison permettant de prédire le prix journalier de location d'une voiture en fonction de ses caractéristiques.", }, { "name": "Machine Learning", "description": "Point de terminaison de prediction du prix de location journalier d'un véhicule en fonction de ses caractéristiques.", }, ] model= None app = FastAPI( title="Getaround API pour le prix journalier de location d'une voiture.", description=description, version=config.__version__, contact={ "name": "Francis Pradel", "url": "https://promotion.francispradel.fr", }, openapi_tags=tags_metadata, swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}}, ) @app.on_event("startup") def load_model_on_startup(): global model try: logger.info(f"Chargement du modèle depuis {MLFLOW_TRACKING_URI}") logger.info(f"Model : {MODEL_URI}") mlflow.set_tracking_uri(MLFLOW_TRACKING_URI) # ou via env MLFLOW_TRACKING_URI model = mlflow.pyfunc.load_model(MODEL_URI) logger.info("Modèle chargé avec succès") except Exception: logger.exception("Impossible de charger le modèle au démarrage") model = None class RentalFeatures(BaseModel): model_key: str mileage: int engine_power: int fuel: str paint_color: str car_type: str private_parking_available: bool has_gps: bool has_air_conditioning: bool automatic_car: bool has_getaround_connect: bool has_speed_regulator: bool winter_tires: bool @app.get("/", tags=["Introduction Endpoints"]) def greet_json(): """ Bonjour """ return {"Hello": "World!", "version": config.__version__} @app.get("/health") def health(): """ Health line to monitor the server status of the API. Returns "ok" if the server is running without issues. """ return {"status": "ok"} @app.post("/predict", tags=["Machine Learning"]) async def predict(predictionFeatures: RentalFeatures): """ Prediction of car daily rental cost for a given properties of a car ! """ # Read data # pf = [ # predictionFeatures.model_key or "Peugeot", # predictionFeatures.mileage or 0, # predictionFeatures.engine_power or 0, # predictionFeatures.fuel or "petrol", # predictionFeatures.car_type or "sedan", # predictionFeatures.private_parking_available or 1, # predictionFeatures.has_gps or 0, # predictionFeatures.has_air_conditioning or 0, # predictionFeatures.automatic_car or 0, # predictionFeatures.has_getaround_connect or 0, # predictionFeatures.has_speed_regulator or 0, # predictionFeatures.winter_tires or 0, # ] try: payload = predictionFeatures.model_dump() car_characteristic = pd.DataFrame([payload]) #car_caracteristic = pd.DataFrame({"Car": pf}) # Log model from mlflow BOOL_COLS = [ "private_parking_available", "has_gps", "has_air_conditioning", "automatic_car", "has_getaround_connect", "has_speed_regulator", "winter_tires", ] # Conversion explicite des colonnes booléennes for col in BOOL_COLS: car_characteristic[col] = car_characteristic[col].astype(bool) logger.info("Payload reçu: %s", payload) logger.info("dtypes:\n%s", car_characteristic.dtypes) # Prediction from previously loaded model as a PyFuncModel. prediction = model.predict(car_characteristic) logger.info(f"Output : {prediction}") # Format response response = { "prediction": float(prediction[0]), "detail": "Prédiction du tarif journalier (nul si aucun modèle : model.pkl).", } return response except Exception as e: logger.exception("Erreur dans /predict") raise HTTPException(status_code=500, detail=str(e))