File size: 6,090 Bytes
e07612a
49d61d9
e07612a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c73a45
 
 
 
 
 
 
 
 
d2a9e4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e07612a
 
 
 
 
3c73a45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e07612a
 
 
252515f
e07612a
bb3f74f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71c1dfa
bb3f74f
 
 
 
 
49d61d9
 
 
 
 
 
 
 
 
 
 
bb3f74f
e07612a
 
 
 
bb3f74f
0bd26fe
 
 
 
e07612a
 
71c1dfa
e07612a
71c1dfa
e07612a
 
 
71c1dfa
 
 
96c4c27
2de7a63
e07612a
3c73a45
e07612a
71c1dfa
e07612a
71c1dfa
 
e07612a
 
 
 
 
 
71c1dfa
e07612a
 
49d61d9
 
 
 
 
 
 
 
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
from fastapi import FastAPI, HTTPException
from fastapi import BackgroundTasks
from pydantic import BaseModel
from typing import Literal
import pandas as pd
import boto3
import joblib
import os
import io

# === Initialisation FastAPI ===
app = FastAPI(
    title="Fraude Détection API",
    description="""
### 🎯 Description  
Cette API permet de détecter automatiquement les transactions potentiellement frauduleuses en se basant sur un modèle de machine learning entraîné avec des données historiques de paiements.

Elle reçoit en entrée les caractéristiques complètes d’une transaction bancaire (hors identifiants techniques comme Unnamed: 0 et trans_num), puis renvoie une prédiction :

1 → transaction frauduleuse

0 → transaction légitime

Le modèle est chargé dynamiquement depuis le registre de modèles MLflow (ou S3), garantissant une traçabilité complète et une mise à jour continue.

🧾 Champs d’entrée attendus (JSON)
Champ	Type	Description
cc_num	float	Numéro de carte anonymisé
merchant	string	Nom du commerçant ou de l’établissement
category	string	Catégorie du commerçant (ex : "gas_transport", "shopping_net", "travel")
amt	float	Montant de la transaction
first	string	Prénom du client
last	string	Nom du client
gender	string	Sexe du client ("M" ou "F")
street	string	Adresse postale du client
city	string	Ville du client
state	string	Code État ou Région (ex : "TX", "CA")
zip	int	Code postal
lat	float	Latitude du domicile
long	float	Longitude du domicile
city_pop	int	Population de la ville
job	string	Profession du client
merch_lat	float	Latitude du commerçant
merch_long	float	Longitude du commerçant
year    int	Année de la transaction
month   int	Mois de la transaction
day     int	Jour de la transaction
hour    int	Heure de la transaction
minute  int	Minute de la transaction
second  int	Seconde de la transaction
dob_year : int année de naissance
dob_month : int mois de naissance
dob_day : int jour de naissance



Voici un exemple :

{
  "cc_num": 3593118134380341,
  "merchant": "fraud_Jacobi and Sons",
  "category": "shopping_pos",
  "amt": 9.01,
  "first": "Laura",
  "last": "Casey",
  "gender": "F",
  "street": "6114 Moran Way",
  "city": "Steuben",
  "state": "WI",
  "zip": 54657,
  "lat": 43.1457,
  "long": -91.1021,
  "city_pop": 291,
  "job": "Scientist, clinical (histocompatibility and immunogenetics)",
  "merch_lat": 43.1503,
  "merch_long": -91.0976,
  "year": 2025,
  "month": 11,
  "day": 22,
  "hour": 10,
  "minute": 29,
  "second": 6,
  "dob_year": 1959,
  "dob_month": 5,
  "dob_day": 10
}


""",
version="1.0"
)

# === Schéma attendu pour l'entrée ===
class InputData(BaseModel):
    cc_num: int
    merchant: str
    category: str
    amt: float
    first: str
    last: str
    gender: Literal["M", "F"]
    street: str
    city: str
    state: str
    zip: int
    lat: float
    long: float
    city_pop: int
    job: str
    merch_lat: float
    merch_long: float
    year : int         
    month : int
    day : int
    hour : int         
    minute : int       
    second : int
    dob_year : int
    dob_month : int
    dob_day : int   

# === Configuration S3 ===
S3_BUCKET = os.getenv("S3_BUCKET")
S3_PREFIX = "mlflow/models/"
s3 = boto3.client("s3")

def get_latest_model_key():
    try:
        response = s3.list_objects_v2(
            Bucket=S3_BUCKET,
            Prefix=S3_PREFIX
        )

        if "Contents" not in response:
            raise ValueError("Aucun modèle trouvé dans le bucket S3.")

        # Filtrer uniquement les .joblib
        models = [
            obj for obj in response["Contents"]
            if obj["Key"].endswith(".joblib")
        ]

        if not models:
            raise ValueError("Aucun fichier .joblib trouvé.")

        # Trier par LastModified (date d'upload dans S3)
        models.sort(key=lambda x: x["LastModified"], reverse=True)

        latest_key = models[0]["Key"]
        print(f"Dernier modèle détecté : {latest_key}")
        return latest_key

    except Exception as e:
        raise RuntimeError(f"Erreur récupération modèle S3 : {e}")

def load_latest_model():
    global model
    latest_model_key = get_latest_model_key()
    print(f"Rechargement du modèle : {latest_model_key}")

    response = s3.get_object(Bucket=S3_BUCKET, Key=latest_model_key)
    model_bytes = io.BytesIO(response["Body"].read())

    model = joblib.load(model_bytes)
    print("Nouveau modèle chargé avec succès")
    return latest_model_key

@app.on_event("startup")
def load_model():
    global model
    try:
        latest_model_key = get_latest_model_key()
        print(f"Téléchargement du dernier modèle depuis s3://{S3_BUCKET}/{latest_model_key}")

        # print(f"Téléchargement du modèle depuis s3://{S3_BUCKET}/{MODEL_KEY}")
        response = s3.get_object(Bucket=S3_BUCKET, Key=latest_model_key)
        model_bytes = io.BytesIO(response["Body"].read())
        model = joblib.load(model_bytes)
        print("Modèle chargé avec succès")
    except Exception as e:
        print(f"Erreur chargement modèle : {e}")
        raise RuntimeError(f"Impossible de charger le modèle : {e}")

# === Routes ===
@app.get("/")
def home():
    return {"message": "Bienvenue sur l'API Fraude détéction - Utilisez /predict pour faire une prédiction"}


@app.post("/predict")
def predict(data: InputData):
    try:
        
        df = pd.DataFrame([data.dict()])
        print("Données reçues :", df.head(1).to_dict())
        
        prediction = model.predict(df)
        is_fraud = int(prediction[0])

        return {"is_fraud": is_fraud}

    except Exception as e:
        print(f"Erreur prédiction : {e}")
        raise HTTPException(status_code=500, detail=str(e))
    
@app.post("/reload-model")
def reload_model(background_tasks: BackgroundTasks):
    """
    Recharge le dernier modèle S3 sans redémarrer l'API.
    """
    background_tasks.add_task(load_latest_model)
    latest_key = get_latest_model_key()
    return {"status - Rechargement en arrière-plan modèle :", latest_key}