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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +19 -22
app.py CHANGED
@@ -3,12 +3,12 @@ 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
@@ -17,16 +17,20 @@ class DFToHashedTokens(BaseEstimator, TransformerMixin):
17
  return self
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 ---
@@ -35,16 +39,12 @@ try:
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")
@@ -79,22 +79,19 @@ def prepare_input(data: dict):
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',
@@ -106,9 +103,10 @@ def prepare_input(data: dict):
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]),
@@ -120,5 +118,4 @@ def predict(data: Transaction):
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)
 
3
  import numpy as np
4
  import joblib
5
  import __main__
6
+ from fastapi import FastAPI
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 (CORRECTION DU HASHING) ---
12
  class DFToHashedTokens(BaseEstimator, TransformerMixin):
13
  def __init__(self, columns=None):
14
  self.columns = columns
 
17
  return self
18
 
19
  def transform(self, X):
20
+ # On s'assure que X est un DataFrame
21
+ if not isinstance(X, pd.DataFrame):
22
+ X = pd.DataFrame(X)
23
+
24
  X = X.copy()
 
25
  cols = getattr(self, 'columns', None)
26
+
27
  if cols is not None:
28
+ # Pour chaque ligne, on crée une liste de chaînes de caractères
29
+ # C'est ce que "iterable over iterables of strings" signifie
30
+ return X[cols].astype(str).values.tolist()
31
+ return X.astype(str).values.tolist()
32
 
33
+ # Injection pour que joblib retrouve la classe
34
  __main__.DFToHashedTokens = DFToHashedTokens
35
 
36
  # --- 2. CHARGEMENT DU MODÈLE ---
 
39
  print("✅ Modèle chargé avec succès")
40
  except Exception as e:
41
  model = None
42
+ print(f"❌ Erreur critique : {e}")
43
 
44
+ # --- 3. CONFIGURATION API ---
45
+ app = FastAPI(title="Fraud Detection API")
 
 
 
46
 
47
  class Transaction(BaseModel):
 
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")
 
79
  df["day_of_week"] = dt.dt.dayofweek
80
  df["day"] = dt.dt.day
81
  df["month"] = dt.dt.month
 
 
82
  df["age"] = ((dt - dob).dt.days / 365.25).astype("float32")
83
 
84
+ # Distance
85
  lat1, lon1 = np.radians(df["lat"]), np.radians(df["long"])
86
  lat2, lon2 = np.radians(df["merch_lat"]), np.radians(df["merch_long"])
87
  d = np.sin((lat2-lat1)/2)**2 + np.cos(lat1)*np.cos(lat2)*np.sin((lon2-lon1)/2)**2
88
  df["distance"] = (6371 * 2 * np.arcsin(np.sqrt(d))).astype("float32")
89
 
90
+ # Valeurs par défaut pour les agrégats
91
  df["avg_amt"] = df["amt"]
92
  df["std_amt"] = 0.0
93
  df["nb_trans"] = 1.0
94
 
 
95
  expected_cols = [
96
  'amt', 'hour', 'day_of_week', 'day', 'month', 'age', 'lat', 'long',
97
  'city_pop', 'distance', 'avg_amt', 'std_amt', 'nb_trans',
 
103
  @app.post("/predict")
104
  def predict(data: Transaction):
105
  if model is None:
106
+ return {"status": "error", "message": "Modèle non chargé."}
107
  try:
108
  X_processed = prepare_input(data.dict())
109
+ # Le modèle Pipeline appellera automatiquement DFToHashedTokens.transform()
110
  prediction = model.predict(X_processed)
111
  return {
112
  "prediction": int(prediction[0]),
 
118
 
119
  if __name__ == "__main__":
120
  import uvicorn
 
121
  uvicorn.run(app, host="0.0.0.0", port=7860)