Spaces:
Sleeping
Sleeping
| import sys | |
| import pandas as pd | |
| import numpy as np | |
| import joblib | |
| import __main__ | |
| from fastapi import FastAPI | |
| from fastapi.responses import RedirectResponse | |
| from pydantic import BaseModel, Field | |
| from sklearn.base import BaseEstimator, TransformerMixin | |
| # --- 1. CLASSE PERSONNALISÉE --- | |
| class DFToHashedTokens(BaseEstimator, TransformerMixin): | |
| def __init__(self, columns=None): | |
| self.columns = columns | |
| def fit(self, X, y=None): | |
| return self | |
| def transform(self, X): | |
| if not isinstance(X, pd.DataFrame): | |
| X = pd.DataFrame(X) | |
| X = X.copy() | |
| cols = getattr(self, 'columns', None) | |
| if cols is not None: | |
| return X[cols].astype(str).values.tolist() | |
| return X.astype(str).values.tolist() | |
| __main__.DFToHashedTokens = DFToHashedTokens | |
| # --- 2. CHARGEMENT DU MODÈLE --- | |
| try: | |
| model = joblib.load('fraud_model_hashing.pkl') | |
| except Exception as e: | |
| model = None | |
| # --- 3. CONFIGURATION API --- | |
| app = FastAPI(title="Fraud Detection API") | |
| class Transaction(BaseModel): | |
| amt: float = Field(..., example=85.20) | |
| trans_date_trans_time: str = Field(..., example="2024-02-18 14:30:00") | |
| dob: str = Field(..., example="1985-05-20") | |
| lat: float = Field(..., example=48.8566) | |
| long: float = Field(..., example=2.3522) | |
| merch_lat: float = Field(..., example=48.8584) | |
| merch_long: float = Field(..., example=2.2945) | |
| city_pop: float = Field(..., example=2000000) | |
| category: str = Field(..., example="shopping_net") | |
| gender: str = Field(..., example="F") | |
| state: str = Field(..., example="NY") | |
| merchant: str = Field(..., example="Amazon") | |
| job: str = Field(..., example="Data Scientist") | |
| cc_num: int = Field(..., example=1234567890123456) | |
| def root(): | |
| return RedirectResponse(url="/docs") | |
| # --- 4. LOGIQUE DE PRÉPARATION --- | |
| def prepare_input(data: dict): | |
| df = pd.DataFrame([data]) | |
| dt = pd.to_datetime(df["trans_date_trans_time"], errors='coerce') | |
| dob = pd.to_datetime(df["dob"], errors='coerce') | |
| if dt.isna().any() or dob.isna().any(): | |
| raise ValueError("Format de date invalide.") | |
| df["hour"] = dt.dt.hour | |
| df["day_of_week"] = dt.dt.dayofweek | |
| df["day"] = dt.dt.day | |
| df["month"] = dt.dt.month | |
| df["age"] = ((dt - dob).dt.days / 365.25).astype("float32") | |
| lat1, lon1 = np.radians(df["lat"]), np.radians(df["long"]) | |
| lat2, lon2 = np.radians(df["merch_lat"]), np.radians(df["merch_long"]) | |
| d = np.sin((lat2-lat1)/2)**2 + np.cos(lat1)*np.cos(lat2)*np.sin((lon2-lon1)/2)**2 | |
| df["distance"] = (6371 * 2 * np.arcsin(np.sqrt(d))).astype("float32") | |
| df["avg_amt"] = df["amt"] | |
| df["std_amt"] = 0.0 | |
| df["nb_trans"] = 1.0 | |
| expected_cols = [ | |
| 'amt', 'hour', 'day_of_week', 'day', 'month', 'age', 'lat', 'long', | |
| 'city_pop', 'distance', 'avg_amt', 'std_amt', 'nb_trans', | |
| 'category', 'gender', 'state', 'merchant', 'job' | |
| ] | |
| return df[expected_cols] | |
| # --- 5. ENDPOINT NETTOYÉ --- | |
| def predict(data: Transaction): | |
| if model is None: | |
| return {"error": "Modèle non chargé"} | |
| try: | |
| X_processed = prepare_input(data.dict()) | |
| prediction = model.predict(X_processed) | |
| # On ne renvoie que la valeur brute | |
| return int(prediction[0]) | |
| except Exception as e: | |
| return {"error": str(e)} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |