"""API REST de prediction de consommation electrique (FastAPI). Le service orchestre la chaine de prediction : 1. recevoir la requete (client_id, date) ; 2. recuperer les features du client (feature store simule) ; 3. charger le modele promu (Model Registry, au demarrage) ; 4. calculer la prediction et repondre en JSON. Lancement : uvicorn lab.serving.api:app --host 0.0.0.0 --port 8000 Swagger UI : /docs """ import logging from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException from . import features, registry from .schemas import ( BatchPredictionRequest, BatchPredictionResponse, PredictionRequest, PredictionResponse, ) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Modele charge une fois au demarrage (couteux) et reutilise a chaque requete. _state: dict[str, registry.LoadedModel] = {} @asynccontextmanager async def lifespan(app: FastAPI): _state["model"] = registry.load_champion() yield _state.clear() app = FastAPI( title="Electricity Consumption Prediction API", description="Expose le modele promu (MLflow Model Registry) via une API REST.", version="1.0.0", lifespan=lifespan, ) def _get_model() -> registry.LoadedModel: model = _state.get("model") if model is None: # modele indisponible au demarrage raise HTTPException(status_code=503, detail="Modele non charge.") return model def _predict(client_id: str, model: registry.LoadedModel) -> PredictionResponse: feats = features.get_features(client_id) # peut lever UnknownClientError prediction = model.predict_one(feats) return PredictionResponse( client_id=client_id, prediction_kwh=prediction, model_name=model.name, model_version=model.version, ) @app.get("/health", summary="Verification de l'etat du service") def health() -> dict[str, str]: """Endpoint de sante : renvoie 200 si le service repond.""" return {"status": "ok"} @app.post("/predict", response_model=PredictionResponse, summary="Prediction unitaire") def predict(request: PredictionRequest) -> PredictionResponse: model = _get_model() try: return _predict(request.client_id, model) except features.UnknownClientError: # Partie 3 : client inconnu -> erreur cliente, pas un plantage du service. raise HTTPException( status_code=404, detail=f"Client inconnu : aucune feature pour '{request.client_id}'.", ) @app.post( "/predict/batch", response_model=BatchPredictionResponse, summary="Prediction en batch (plusieurs clients)", ) def predict_batch(request: BatchPredictionRequest) -> BatchPredictionResponse: model = _get_model() predictions: list[PredictionResponse] = [] unknown: list[str] = [] for client_id in request.client_ids: try: predictions.append(_predict(client_id, model)) except features.UnknownClientError: unknown.append(client_id) return BatchPredictionResponse(predictions=predictions, unknown_client_ids=unknown)