DavidJyes commited on
Commit
a642ce8
·
verified ·
1 Parent(s): fe22c25

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -45
app.py CHANGED
@@ -1,13 +1,14 @@
1
  import sys
2
- from fastapi import FastAPI
3
- from fastapi.responses import RedirectResponse
4
- import joblib
5
  import pandas as pd
6
  import numpy as np
7
- from pydantic import BaseModel
 
 
 
 
8
  from sklearn.base import BaseEstimator, TransformerMixin
9
 
10
- # --- 1. DEFINITION DE LA CLASSE ---
11
  class DFToHashedTokens(BaseEstimator, TransformerMixin):
12
  def __init__(self, columns=None):
13
  self.columns = columns
@@ -17,82 +18,107 @@ class DFToHashedTokens(BaseEstimator, TransformerMixin):
17
 
18
  def transform(self, X):
19
  X = X.copy()
20
- if self.columns:
21
- for col in self.columns:
22
- X[col] = X[col].astype(str)
 
 
 
23
  return X
24
 
25
- # --- 2. L'ASTUCE ANTI-ERREUR (CRUCIAL) ---
26
- # On injecte la classe dans le module '__main__' pour que joblib la trouve
27
- import __main__
28
  __main__.DFToHashedTokens = DFToHashedTokens
29
 
30
- # --- 3. CHARGEMENT DU MODÈLE ---
31
- # On le place dans une fonction ou on le charge après l'injection
32
  try:
33
- # Assure-toi que le nom du fichier est exactement celui-ci sur Hugging Face
34
  model = joblib.load('fraud_model_hashing.pkl')
35
  print("✅ Modèle chargé avec succès")
36
  except Exception as e:
37
  model = None
38
- print(f"❌ Erreur lors du chargement : {e}")
 
 
 
 
 
 
39
 
40
- # --- 4. CONFIGURATION API ---
41
- app = FastAPI(title="Fraud Detection API")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  @app.get("/", include_in_schema=False)
44
  def root():
45
  return RedirectResponse(url="/docs")
46
 
47
- # --- 5. SCHÉMA ET PRÉPARATION ---
48
- class Transaction(BaseModel):
49
- amt: float
50
- trans_date_trans_time: str
51
- dob: str
52
- lat: float
53
- long: float
54
- merch_lat: float
55
- merch_long: float
56
- city_pop: float
57
- category: str
58
- gender: str
59
- state: str
60
- merchant: str
61
- job: str
62
- cc_num: int
63
-
64
  def prepare_input(data: dict):
65
  df = pd.DataFrame([data])
66
- dt = pd.to_datetime(df["trans_date_trans_time"])
 
 
 
 
 
 
 
67
  df["hour"] = dt.dt.hour
68
  df["day_of_week"] = dt.dt.dayofweek
69
  df["day"] = dt.dt.day
70
  df["month"] = dt.dt.month
71
 
72
- dob = pd.to_datetime(df["dob"])
73
  df["age"] = ((dt - dob).dt.days / 365.25).astype("float32")
74
 
 
75
  lat1, lon1 = np.radians(df["lat"]), np.radians(df["long"])
76
  lat2, lon2 = np.radians(df["merch_lat"]), np.radians(df["merch_long"])
77
  d = np.sin((lat2-lat1)/2)**2 + np.cos(lat1)*np.cos(lat2)*np.sin((lon2-lon1)/2)**2
78
  df["distance"] = (6371 * 2 * np.arcsin(np.sqrt(d))).astype("float32")
79
 
 
80
  df["avg_amt"] = df["amt"]
81
  df["std_amt"] = 0.0
82
  df["nb_trans"] = 1.0
83
 
84
- cols = ['amt', 'hour', 'day_of_week', 'day', 'month', 'age', 'lat', 'long',
85
- 'city_pop', 'distance', 'avg_amt', 'std_amt', 'nb_trans',
86
- 'category', 'gender', 'state', 'merchant', 'job']
87
- return df[cols]
 
 
 
88
 
 
89
  @app.post("/predict")
90
  def predict(data: Transaction):
91
  if model is None:
92
- return {"status": "error", "message": "Model not loaded"}
93
  try:
94
- X = prepare_input(data.dict())
95
- prediction = model.predict(X)
96
- return {"is_fraud": int(prediction[0])}
 
 
 
 
97
  except Exception as e:
98
- return {"status": "error", "message": str(e)}
 
 
 
 
 
 
1
  import sys
 
 
 
2
  import pandas as pd
3
  import numpy as np
4
+ import joblib
5
+ import __main__
6
+ from fastapi import FastAPI, Body
7
+ from fastapi.responses import RedirectResponse
8
+ from pydantic import BaseModel, Field
9
  from sklearn.base import BaseEstimator, TransformerMixin
10
 
11
+ # --- 1. CLASSE PERSONNALISÉE (DÉFINITION ROBUSTE) ---
12
  class DFToHashedTokens(BaseEstimator, TransformerMixin):
13
  def __init__(self, columns=None):
14
  self.columns = columns
 
18
 
19
  def transform(self, X):
20
  X = X.copy()
21
+ # Sécurité : on récupère 'columns' dynamiquement pour éviter l'AttributeError
22
+ cols = getattr(self, 'columns', None)
23
+ if cols is not None:
24
+ for col in cols:
25
+ if col in X.columns:
26
+ X[col] = X[col].astype(str)
27
  return X
28
 
29
+ # Injection dans le namespace principal pour joblib
 
 
30
  __main__.DFToHashedTokens = DFToHashedTokens
31
 
32
+ # --- 2. CHARGEMENT DU MODÈLE ---
 
33
  try:
 
34
  model = joblib.load('fraud_model_hashing.pkl')
35
  print("✅ Modèle chargé avec succès")
36
  except Exception as e:
37
  model = None
38
+ print(f"❌ Erreur critique lors du chargement : {e}")
39
+
40
+ # --- 3. CONFIGURATION API ET SCHÉMA ---
41
+ app = FastAPI(
42
+ title="Fraud Detection API",
43
+ description="API de prédiction de fraude bancaire basée sur un modèle de Hashing."
44
+ )
45
 
46
+ class Transaction(BaseModel):
47
+ # Field(..., example=...) permet de remplir automatiquement la doc Swagger
48
+ amt: float = Field(..., example=85.20)
49
+ trans_date_trans_time: str = Field(..., example="2024-02-18 14:30:00")
50
+ dob: str = Field(..., example="1985-05-20")
51
+ lat: float = Field(..., example=48.8566)
52
+ long: float = Field(..., example=2.3522)
53
+ merch_lat: float = Field(..., example=48.8584)
54
+ merch_long: float = Field(..., example=2.2945)
55
+ city_pop: float = Field(..., example=2000000)
56
+ category: str = Field(..., example="shopping_net")
57
+ gender: str = Field(..., example="F")
58
+ state: str = Field(..., example="NY")
59
+ merchant: str = Field(..., example="Amazon")
60
+ job: str = Field(..., example="Data Scientist")
61
+ cc_num: int = Field(..., example=1234567890123456)
62
 
63
  @app.get("/", include_in_schema=False)
64
  def root():
65
  return RedirectResponse(url="/docs")
66
 
67
+ # --- 4. LOGIQUE DE PRÉPARATION ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def prepare_input(data: dict):
69
  df = pd.DataFrame([data])
70
+
71
+ # Dates & Temps
72
+ dt = pd.to_datetime(df["trans_date_trans_time"], errors='coerce')
73
+ dob = pd.to_datetime(df["dob"], errors='coerce')
74
+
75
+ if dt.isna().any() or dob.isna().any():
76
+ raise ValueError("Format de date invalide. Utilisez YYYY-MM-DD HH:MM:SS")
77
+
78
  df["hour"] = dt.dt.hour
79
  df["day_of_week"] = dt.dt.dayofweek
80
  df["day"] = dt.dt.day
81
  df["month"] = dt.dt.month
82
 
83
+ # Âge
84
  df["age"] = ((dt - dob).dt.days / 365.25).astype("float32")
85
 
86
+ # Distance Haversine
87
  lat1, lon1 = np.radians(df["lat"]), np.radians(df["long"])
88
  lat2, lon2 = np.radians(df["merch_lat"]), np.radians(df["merch_long"])
89
  d = np.sin((lat2-lat1)/2)**2 + np.cos(lat1)*np.cos(lat2)*np.sin((lon2-lon1)/2)**2
90
  df["distance"] = (6371 * 2 * np.arcsin(np.sqrt(d))).astype("float32")
91
 
92
+ # Agrégats (valeurs par défaut pour prédiction unitaire)
93
  df["avg_amt"] = df["amt"]
94
  df["std_amt"] = 0.0
95
  df["nb_trans"] = 1.0
96
 
97
+ # Sélection stricte des 18 colonnes attendues par le modèle
98
+ expected_cols = [
99
+ 'amt', 'hour', 'day_of_week', 'day', 'month', 'age', 'lat', 'long',
100
+ 'city_pop', 'distance', 'avg_amt', 'std_amt', 'nb_trans',
101
+ 'category', 'gender', 'state', 'merchant', 'job'
102
+ ]
103
+ return df[expected_cols]
104
 
105
+ # --- 5. ENDPOINT ---
106
  @app.post("/predict")
107
  def predict(data: Transaction):
108
  if model is None:
109
+ return {"status": "error", "message": "Modèle non chargé sur le serveur."}
110
  try:
111
+ X_processed = prepare_input(data.dict())
112
+ prediction = model.predict(X_processed)
113
+ return {
114
+ "prediction": int(prediction[0]),
115
+ "label": "FRAUDE" if int(prediction[0]) == 1 else "LÉGITIME",
116
+ "status": "success"
117
+ }
118
  except Exception as e:
119
+ return {"status": "error", "message": str(e)}
120
+
121
+ if __name__ == "__main__":
122
+ import uvicorn
123
+ # Port 7860 est le port standard pour Hugging Face Spaces
124
+ uvicorn.run(app, host="0.0.0.0", port=7860)