File size: 6,404 Bytes
10c3735
5cc9fa6
41c36a3
 
 
35969a2
75dfb95
45ba6f5
75dfb95
e288985
 
75dfb95
10c3735
9136785
e288985
 
4c91f84
e288985
 
 
 
 
 
 
 
 
 
9136785
83f0f75
 
 
 
 
 
 
 
b5bf2af
83f0f75
 
b5bf2af
 
83f0f75
b5bf2af
 
83f0f75
 
 
b5bf2af
 
83f0f75
 
b5bf2af
83f0f75
5f8477f
83f0f75
 
e288985
b5bf2af
 
 
e288985
b5bf2af
83f0f75
 
b5bf2af
 
83f0f75
 
 
b5bf2af
83f0f75
 
e288985
 
c8447c0
e288985
83f0f75
 
 
35969a2
83f0f75
 
 
 
 
46bb294
83f0f75
10c3735
c8447c0
 
 
 
 
4c91f84
 
c8447c0
 
 
 
 
41c36a3
a0bc8db
5889de4
dd44a35
 
 
 
 
5cc9fa6
 
 
 
 
 
 
b40bba3
 
35969a2
10c3735
35969a2
 
 
 
41c36a3
 
e288985
b40bba3
35969a2
 
 
b40bba3
41c36a3
 
 
b40bba3
41c36a3
b40bba3
41c36a3
 
e288985
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5cc9fa6
18c9ca8
e288985
5cc9fa6
e288985
 
 
 
 
 
 
 
 
 
 
18c9ca8
5cc9fa6
 
 
 
 
e288985
18c9ca8
17940dc
e288985
 
5cc9fa6
e288985
 
 
 
 
5cc9fa6
e288985
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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))