Spaces:
Sleeping
Sleeping
| """ | |
| 🚨 Fraud Detection API - Level UP Edition | |
| ========================================= | |
| API FastAPI pour la détection de fraude en temps réel | |
| avec preprocessing et feature engineering | |
| Fonctionnalités: | |
| - Download automatique du model + preprocessor depuis HuggingFace | |
| - 3 endpoints: /predict, /preprocess, /feat_eng | |
| - Feature engineering complet (distance GPS, features temporelles, âge) | |
| - Documentation interactive sur /docs | |
| Author: Terorra | |
| Date: January 2026 | |
| Version: 2.0.0 | |
| """ | |
| # ===================================================================== | |
| # IMPORTS | |
| # ===================================================================== | |
| # FastAPI et types | |
| from fastapi import FastAPI, HTTPException, status | |
| from fastapi.responses import JSONResponse | |
| from pydantic import BaseModel, Field | |
| from typing import List, Optional, Dict, Any | |
| # HuggingFace pour télécharger les modèles | |
| from huggingface_hub import hf_hub_download | |
| # ML et data | |
| import joblib | |
| import pandas as pd | |
| import numpy as np | |
| # Utilitaires | |
| import os | |
| from datetime import datetime | |
| import time | |
| # Notre module de feature engineering | |
| from feature_engineering import ( | |
| engineer_features, | |
| prepare_for_model, | |
| get_model_features, | |
| haversine_distance, | |
| extract_time_features | |
| ) | |
| # ===================================================================== | |
| # CONFIGURATION GLOBALE | |
| # ===================================================================== | |
| # Repository HuggingFace où sont stockés les modèles | |
| REPO_ID = "Terorra/fd_model_jedha" | |
| # Noms des fichiers sur HuggingFace | |
| MODEL_FILENAME = "fraud_model.pkl" # Le modèle RandomForest | |
| PREPROCESSOR_FILENAME = "preprocessor.pkl" # Le preprocessor (ColumnTransformer) | |
| # Version du modèle (None = latest, ou "v1", "v2", etc.) | |
| MODEL_VERSION = None | |
| # ===================================================================== | |
| # VARIABLES GLOBALES (modèles chargés en mémoire) | |
| # ===================================================================== | |
| # Ces variables seront remplies au démarrage de l'API | |
| model = None # Le modèle ML (RandomForestClassifier) | |
| preprocessor = None # Le preprocessor (StandardScaler + OneHotEncoder) | |
| # ===================================================================== | |
| # CRÉATION DE L'APPLICATION FASTAPI | |
| # ===================================================================== | |
| app = FastAPI( | |
| # Titre qui apparaît dans la doc | |
| title="🚨 Fraud Detection API - Level UP", | |
| # Description complète (supporte Markdown) | |
| description=""" | |
| # API de Détection de Fraude en Temps Réel | |
| Cette API utilise le Machine Learning pour détecter les transactions frauduleuses | |
| sur les cartes de crédit. | |
| ## 🚀 Fonctionnalités | |
| ### Endpoints Principaux | |
| 1. **`/predict`** - Prédiction complète | |
| - Prend les données brutes | |
| - Applique le feature engineering | |
| - Applique le preprocessing | |
| - Retourne la prédiction de fraude | |
| 2. **`/feat_eng`** - Feature Engineering seulement | |
| - Calcule la distance GPS client-marchand | |
| - Extrait les features temporelles (heure, jour, weekend, etc.) | |
| - Calcule l'âge du porteur | |
| - Retourne les features transformées | |
| 3. **`/preprocess`** - Preprocessing seulement | |
| - Prend les features (déjà engineered) | |
| - Applique StandardScaler (normalisation) | |
| - Applique OneHotEncoder (encoding catégories) | |
| - Retourne les features preprocessed (prêtes pour le modèle) | |
| ### Endpoints Utilitaires | |
| - **`/health`** - Vérifier que l'API fonctionne | |
| - **`/model/info`** - Informations sur le modèle ML | |
| - **`/features`** - Liste des features nécessaires | |
| ## 📊 Workflow Complet | |
| ``` | |
| Données Brutes | |
| ↓ | |
| /feat_eng → Feature Engineering | |
| ↓ | |
| /preprocess → Preprocessing (scaling + encoding) | |
| ↓ | |
| /predict → Prédiction ML | |
| ↓ | |
| Résultat: Fraude ou Non | |
| ``` | |
| ## 🎯 Modèle ML | |
| - **Algorithme**: RandomForestClassifier | |
| - **Recall**: > 90% (optimisé pour détecter les fraudes) | |
| - **Features**: 21 features (17 numériques + 4 catégorielles) | |
| - **Preprocessing**: StandardScaler + OneHotEncoder | |
| - **Hébergement**: HuggingFace Hub | |
| ## 💡 Cas d'Usage | |
| 1. **Validation en temps réel**: Valider une transaction au moment du paiement | |
| 2. **Analyse batch**: Analyser des milliers de transactions historiques | |
| 3. **Monitoring**: Surveiller les patterns de fraude | |
| 4. **Reporting**: Générer des rapports de fraude | |
| ## 🔧 Feature Engineering | |
| L'API calcule automatiquement: | |
| - **distance_km**: Distance GPS entre client et marchand (formule Haversine) | |
| - **hour**: Heure de la transaction (0-23) | |
| - **is_night, is_morning, is_afternoon, is_evening**: Période de la journée | |
| - **is_business_hour**: Transaction pendant heures de bureau (8h-17h) | |
| - **is_weekend**: Transaction le weekend | |
| - **age**: Âge du porteur de carte | |
| - **year, month, day, dayofweek**: Composantes de la date | |
| ## 📚 Documentation | |
| - Cette page: Documentation interactive avec exemples | |
| - Essayez les endpoints directement depuis cette page! | |
| - Chaque endpoint a des exemples pré-remplis | |
| ## 🎓 Pour Commencer | |
| 1. Testez `/health` pour vérifier que l'API fonctionne | |
| 2. Regardez `/features` pour voir les features nécessaires | |
| 3. Essayez `/feat_eng` avec des données de test | |
| 4. Utilisez `/predict` pour une prédiction complète | |
| """, | |
| version="2.0.0", | |
| contact={ | |
| "name": "Terorra", | |
| "email": "your.email@example.com", | |
| }, | |
| license_info={ | |
| "name": "MIT", | |
| }, | |
| # Tags pour organiser les endpoints dans la doc | |
| openapi_tags=[ | |
| { | |
| "name": "🎯 Prediction", | |
| "description": "Endpoints de prédiction de fraude" | |
| }, | |
| { | |
| "name": "🔧 Feature Engineering", | |
| "description": "Transformation des features" | |
| }, | |
| { | |
| "name": "⚙️ Preprocessing", | |
| "description": "Preprocessing des données" | |
| }, | |
| { | |
| "name": "📊 Information", | |
| "description": "Informations sur l'API et le modèle" | |
| }, | |
| ] | |
| ) | |
| # ===================================================================== | |
| # SCHEMAS PYDANTIC (Définition des types de données) | |
| # ===================================================================== | |
| class TransactionRawInput(BaseModel): | |
| """ | |
| Données BRUTES d'une transaction (avant feature engineering) | |
| Ce sont les données telles qu'elles arrivent de la base de données | |
| ou du système de paiement, SANS transformation. | |
| """ | |
| # Informations carte | |
| cc_num: int = Field( | |
| ..., | |
| description="Numéro de carte de crédit (hashé)", | |
| example=374125201044065 | |
| ) | |
| # Montant | |
| amt: float = Field( | |
| ..., | |
| description="Montant de la transaction en dollars", | |
| example=150.75, | |
| gt=0 | |
| ) | |
| # Localisation client | |
| lat: float = Field( | |
| ..., | |
| description="Latitude du client (coordonnées GPS)", | |
| example=40.7128, | |
| ge=-90, | |
| le=90 | |
| ) | |
| long: float = Field( | |
| ..., | |
| description="Longitude du client (coordonnées GPS)", | |
| example=-74.0060, | |
| ge=-180, | |
| le=180 | |
| ) | |
| # Ville | |
| city_pop: int = Field( | |
| ..., | |
| description="Population de la ville du client", | |
| example=8000000, | |
| gt=0 | |
| ) | |
| zip: int = Field( | |
| ..., | |
| description="Code postal", | |
| example=10001 | |
| ) | |
| # Localisation marchand | |
| merch_lat: float = Field( | |
| ..., | |
| description="Latitude du marchand (coordonnées GPS)", | |
| example=40.7589, | |
| ge=-90, | |
| le=90 | |
| ) | |
| merch_long: float = Field( | |
| ..., | |
| description="Longitude du marchand (coordonnées GPS)", | |
| example=-73.9851, | |
| ge=-180, | |
| le=180 | |
| ) | |
| # Marchand | |
| merchant: str = Field( | |
| ..., | |
| description="Nom du marchand", | |
| example="Amazon" | |
| ) | |
| category: str = Field( | |
| ..., | |
| description="Catégorie de transaction", | |
| example="shopping_net" | |
| ) | |
| # Client | |
| gender: str = Field( | |
| ..., | |
| description="Genre du client (M/F)", | |
| example="M" | |
| ) | |
| state: str = Field( | |
| ..., | |
| description="État (US)", | |
| example="NY" | |
| ) | |
| dob: str = Field( | |
| ..., | |
| description="Date de naissance (YYYY-MM-DD)", | |
| example="1990-01-15" | |
| ) | |
| # Transaction | |
| transaction_time: str = Field( | |
| ..., | |
| description="Heure de la transaction (YYYY-MM-DD HH:MM:SS)", | |
| example="2026-01-29 14:30:00" | |
| ) | |
| class Config: | |
| schema_extra = { | |
| "example": { | |
| "cc_num": 374125201044065, | |
| "amt": 150.75, | |
| "lat": 40.7128, | |
| "long": -74.0060, | |
| "city_pop": 8000000, | |
| "zip": 10001, | |
| "merch_lat": 40.7589, | |
| "merch_long": -73.9851, | |
| "merchant": "Amazon", | |
| "category": "shopping_net", | |
| "gender": "M", | |
| "state": "NY", | |
| "dob": "1990-01-15", | |
| "transaction_time": "2026-01-29 14:30:00" | |
| } | |
| } | |
| class FeaturesEngineeredOutput(BaseModel): | |
| """ | |
| Résultat du Feature Engineering | |
| Contient les données originales + les features calculées | |
| """ | |
| # Données originales | |
| original_data: Dict[str, Any] = Field( | |
| ..., | |
| description="Données brutes d'entrée" | |
| ) | |
| # Features engineered | |
| engineered_features: Dict[str, Any] = Field( | |
| ..., | |
| description="Nouvelles features calculées" | |
| ) | |
| # Toutes les features combinées | |
| all_features: Dict[str, Any] = Field( | |
| ..., | |
| description="Données originales + features engineered" | |
| ) | |
| class PreprocessedOutput(BaseModel): | |
| """ | |
| Résultat du Preprocessing | |
| Features transformées (scaled + encoded) prêtes pour le modèle | |
| """ | |
| preprocessed_shape: tuple = Field( | |
| ..., | |
| description="Dimensions des données preprocessed (lignes, colonnes)" | |
| ) | |
| sample_values: List[float] = Field( | |
| ..., | |
| description="Premières valeurs (pour debug)" | |
| ) | |
| message: str = Field( | |
| ..., | |
| description="Message de confirmation" | |
| ) | |
| class PredictionOutput(BaseModel): | |
| """ | |
| Résultat de la Prédiction de Fraude | |
| """ | |
| # Prédiction | |
| is_fraud: bool = Field( | |
| ..., | |
| description="True si la transaction est frauduleuse" | |
| ) | |
| fraud_probability: float = Field( | |
| ..., | |
| description="Probabilité de fraude (0.0 à 1.0)", | |
| ge=0.0, | |
| le=1.0 | |
| ) | |
| # Classification du risque | |
| risk_level: str = Field( | |
| ..., | |
| description="Niveau de risque: LOW, MEDIUM, HIGH, CRITICAL" | |
| ) | |
| # Confiance du modèle | |
| confidence: float = Field( | |
| ..., | |
| description="Confiance du modèle (0.0 à 1.0)", | |
| ge=0.0, | |
| le=1.0 | |
| ) | |
| # Métadonnées | |
| timestamp: str = Field( | |
| ..., | |
| description="Heure de la prédiction (ISO format)" | |
| ) | |
| processing_time_ms: float = Field( | |
| ..., | |
| description="Temps de traitement en millisecondes" | |
| ) | |
| # ===================================================================== | |
| # FONCTIONS HELPER | |
| # ===================================================================== | |
| def load_models_from_hf(): | |
| """ | |
| Télécharge et charge les modèles depuis HuggingFace Hub | |
| Cette fonction: | |
| 1. Télécharge fraud_model.pkl (le modèle ML) | |
| 2. Télécharge preprocessor.pkl (le preprocessor) | |
| 3. Charge les 2 fichiers en mémoire | |
| 4. Met à jour les variables globales model et preprocessor | |
| Returns: | |
| tuple: (success: bool, message: str) | |
| success = True si tout s'est bien passé | |
| message = Message d'information ou d'erreur | |
| """ | |
| global model, preprocessor | |
| try: | |
| print("=" * 70) | |
| print("📥 Téléchargement des modèles depuis HuggingFace...") | |
| print(f" Repository: {REPO_ID}") | |
| print("=" * 70) | |
| # ======================================== | |
| # 1. TÉLÉCHARGER LE MODÈLE ML | |
| # ======================================== | |
| print(f"\n⬇️ Download: {MODEL_FILENAME}...") | |
| model_path = hf_hub_download( | |
| repo_id=REPO_ID, | |
| filename=MODEL_FILENAME, # None = latest | |
| cache_dir="/tmp" # Dossier de cache | |
| ) | |
| print(f"✅ Téléchargé: {model_path}") | |
| # Charger le modèle | |
| model = joblib.load(model_path) | |
| print(f"✅ Modèle chargé: {type(model).__name__}") | |
| # ======================================== | |
| # 2. TÉLÉCHARGER LE PREPROCESSOR | |
| # ======================================== | |
| print(f"\n⬇️ Download: {PREPROCESSOR_FILENAME}...") | |
| preprocessor_path = hf_hub_download( | |
| repo_id=REPO_ID, | |
| filename=PREPROCESSOR_FILENAME, | |
| cache_dir="/tmp" | |
| ) | |
| print(f"✅ Téléchargé: {preprocessor_path}") | |
| # Charger le preprocessor | |
| preprocessor = joblib.load(preprocessor_path) | |
| print(f"✅ Preprocessor chargé: {type(preprocessor).__name__}") | |
| print("\n" + "=" * 70) | |
| print("✅ TOUS LES MODÈLES SONT CHARGÉS ET PRÊTS") | |
| print("=" * 70) | |
| return True, "Models loaded successfully" | |
| except Exception as e: | |
| error_msg = f"Erreur lors du chargement des modèles: {str(e)}" | |
| print(f"\n❌ {error_msg}") | |
| return False, error_msg | |
| def calculate_risk_level(probability: float) -> str: | |
| """ | |
| Calcule le niveau de risque basé sur la probabilité de fraude | |
| Args: | |
| probability (float): Probabilité de fraude (0.0 à 1.0) | |
| Returns: | |
| str: Niveau de risque (LOW, MEDIUM, HIGH, CRITICAL) | |
| Seuils: | |
| < 0.3 : LOW (Risque faible) | |
| < 0.6 : MEDIUM (Risque moyen) | |
| < 0.8 : HIGH (Risque élevé) | |
| >= 0.8 : CRITICAL (Risque critique) | |
| """ | |
| if probability < 0.3: | |
| return "LOW" | |
| elif probability < 0.6: | |
| return "MEDIUM" | |
| elif probability < 0.8: | |
| return "HIGH" | |
| else: | |
| return "CRITICAL" | |
| # ===================================================================== | |
| # ÉVÉNEMENT DE DÉMARRAGE | |
| # ===================================================================== | |
| async def startup_event(): | |
| """ | |
| Fonction appelée AU DÉMARRAGE de l'API | |
| Cette fonction: | |
| - Est exécutée UNE SEULE FOIS quand l'API démarre | |
| - Télécharge et charge les modèles en mémoire | |
| - Les modèles restent en mémoire pour toutes les requêtes | |
| Si les modèles ne se chargent pas, l'API démarre quand même | |
| mais les endpoints de prédiction renverront une erreur 503. | |
| """ | |
| print("\n" + "🚀" * 35) | |
| print("🚀 DÉMARRAGE DE L'API FRAUD DETECTION") | |
| print("🚀" * 35) | |
| # Charger les modèles | |
| success, message = load_models_from_hf() | |
| if success: | |
| print("\n✅ API prête à recevoir des requêtes!\n") | |
| else: | |
| print(f"\n⚠️ API démarrée mais modèles non chargés: {message}") | |
| print("⚠️ Les endpoints de prédiction ne fonctionneront pas.\n") | |
| # ===================================================================== | |
| # ENDPOINTS - INFORMATION | |
| # ===================================================================== | |
| async def root(): | |
| """ | |
| Endpoint racine - Informations sur l'API | |
| Retourne: | |
| - Nom de l'API | |
| - Version | |
| - Liens vers la documentation | |
| - Liste des endpoints disponibles | |
| """ | |
| return { | |
| "message": "🚨 Fraud Detection API - Level UP", | |
| "version": "2.0.0", | |
| "status": "online", | |
| "documentation": "/docs", | |
| "health_check": "/health", | |
| "endpoints": { | |
| "prediction": { | |
| "predict": "/predict - Prédiction complète (feat_eng + preprocess + predict)", | |
| }, | |
| "feature_engineering": { | |
| "feat_eng": "/feat_eng - Feature engineering seulement", | |
| }, | |
| "preprocessing": { | |
| "preprocess": "/preprocess - Preprocessing seulement", | |
| }, | |
| "information": { | |
| "model_info": "/model/info - Informations sur le modèle", | |
| "features": "/features - Liste des features nécessaires", | |
| } | |
| }, | |
| "example_workflow": { | |
| "1": "Données brutes → /feat_eng → Features engineered", | |
| "2": "Features engineered → /preprocess → Features preprocessed", | |
| "3": "Features preprocessed → /predict → Prédiction", | |
| "shortcut": "Données brutes → /predict → Prédiction directe (recommandé)" | |
| } | |
| } | |
| async def health_check(): | |
| """ | |
| Vérifie l'état de santé de l'API | |
| Retourne: | |
| - Status de l'API (healthy/unhealthy) | |
| - État du modèle ML (loaded/not loaded) | |
| - État du preprocessor (loaded/not loaded) | |
| - Timestamp | |
| """ | |
| # Vérifier si les modèles sont chargés | |
| models_loaded = (model is not None) and (preprocessor is not None) | |
| return { | |
| "status": "healthy" if models_loaded else "unhealthy", | |
| "model_loaded": model is not None, | |
| "preprocessor_loaded": preprocessor is not None, | |
| "model_repo": REPO_ID, | |
| "model_type": type(model).__name__ if model else None, | |
| "preprocessor_type": type(preprocessor).__name__ if preprocessor else None, | |
| "timestamp": datetime.utcnow().isoformat() | |
| } | |
| async def model_info(): | |
| """ | |
| Informations détaillées sur le modèle ML | |
| Retourne: | |
| - Type de modèle | |
| - Repository HuggingFace | |
| - Nombre de features | |
| - Liste des features | |
| """ | |
| # Vérifier que les modèles sont chargés | |
| if model is None or preprocessor is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail="Models not loaded. Please check /health endpoint." | |
| ) | |
| # Récupérer la liste des features | |
| features = get_model_features() | |
| return { | |
| "model": { | |
| "type": type(model).__name__, | |
| "repo_id": REPO_ID, | |
| "filename": MODEL_FILENAME, | |
| "version": MODEL_VERSION or "latest" | |
| }, | |
| "preprocessor": { | |
| "type": type(preprocessor).__name__, | |
| "filename": PREPROCESSOR_FILENAME | |
| }, | |
| "features": { | |
| "total": len(features), | |
| "numerical": 17, | |
| "categorical": 4, | |
| "list": features | |
| } | |
| } | |
| async def list_features(): | |
| """ | |
| Liste toutes les features attendues par le modèle | |
| Retourne: | |
| - Features numériques (17) | |
| - Features catégorielles (4) | |
| - Total (21) | |
| """ | |
| features = get_model_features() | |
| numerical = features[:17] # Premières 17 = numériques | |
| categorical = features[17:] # Dernières 4 = catégorielles | |
| return { | |
| "total_features": len(features), | |
| "numerical_features": { | |
| "count": len(numerical), | |
| "list": numerical | |
| }, | |
| "categorical_features": { | |
| "count": len(categorical), | |
| "list": categorical | |
| }, | |
| "all_features_in_order": features | |
| } | |
| # ===================================================================== | |
| # ENDPOINTS - FEATURE ENGINEERING | |
| # ===================================================================== | |
| async def feature_engineering_endpoint(transaction: TransactionRawInput): | |
| """ | |
| Applique le FEATURE ENGINEERING sur une transaction | |
| ## Ce que fait cet endpoint: | |
| 1. **Calcul de distance GPS** | |
| - Calcule la distance entre le client et le marchand | |
| - Utilise la formule Haversine (précision: ±1%) | |
| - Feature créée: `distance_km` | |
| 2. **Extraction des features temporelles** | |
| - Heure de la journée (0-23) | |
| - Jour de la semaine (0-6) | |
| - Période (nuit, matin, après-midi, soir) | |
| - Weekend ou non | |
| - Heures de bureau ou non | |
| - Features créées: `hour`, `dayofweek`, `is_night`, `is_morning`, | |
| `is_afternoon`, `is_evening`, `is_business_hour`, `is_we`, | |
| `year`, `month`, `day` | |
| 3. **Calcul de l'âge** | |
| - À partir de la date de naissance | |
| - Feature créée: `age` | |
| ## Input: | |
| Données brutes de la transaction (voir schema TransactionRawInput) | |
| ## Output: | |
| - `original_data`: Données brutes d'entrée | |
| - `engineered_features`: Nouvelles features calculées | |
| - `all_features`: Toutes les features (original + engineered) | |
| ## Exemple d'utilisation: | |
| ```python | |
| import requests | |
| data = { | |
| "cc_num": 374125201044065, | |
| "amt": 150.75, | |
| "lat": 40.7128, | |
| "long": -74.0060, | |
| # ... autres champs | |
| } | |
| response = requests.post("http://localhost:8000/feat_eng", json=data) | |
| features = response.json()["all_features"] | |
| ``` | |
| """ | |
| try: | |
| # Convertir en dictionnaire | |
| transaction_dict = transaction.dict() | |
| print("\n" + "=" * 70) | |
| print("🔧 FEATURE ENGINEERING") | |
| print("=" * 70) | |
| # Appliquer le feature engineering | |
| # (voir feature_engineering.py pour les détails) | |
| engineered = engineer_features(transaction_dict) | |
| # Identifier les features qui ont été ajoutées | |
| original_keys = set(transaction_dict.keys()) | |
| all_keys = set(engineered.keys()) | |
| new_features = all_keys - original_keys | |
| print(f"\n✅ Feature engineering terminé") | |
| print(f" Features ajoutées: {len(new_features)}") | |
| print(f" Total features: {len(engineered)}") | |
| # Préparer la réponse | |
| return { | |
| "original_data": transaction_dict, | |
| "engineered_features": {k: engineered[k] for k in new_features}, | |
| "all_features": engineered | |
| } | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail=f"Feature engineering failed: {str(e)}" | |
| ) | |
| # ===================================================================== | |
| # ENDPOINTS - PREPROCESSING | |
| # ===================================================================== | |
| async def preprocessing_endpoint(features: Dict[str, Any]): | |
| """ | |
| Applique le PREPROCESSING sur les features | |
| ## Ce que fait cet endpoint: | |
| 1. **StandardScaler** (normalisation) | |
| - Met les features numériques à l'échelle | |
| - Moyenne = 0, Écart-type = 1 | |
| - Exemple: 100$ → 0.52, 5000$ → 2.31 | |
| 2. **OneHotEncoder** (encoding catégoriel) | |
| - Convertit les catégories en colonnes binaires | |
| - Exemple: 'NY' → [0, 0, 1, 0, ...] (vecteur de 50 dimensions) | |
| - Exemple: 'shopping_net' → [0, 1, 0, ...] (vecteur de 14 dimensions) | |
| ## Input: | |
| Dictionnaire avec toutes les features (déjà engineered) | |
| Les 21 features attendues: | |
| - **Numériques** (17): cc_num, amt, zip, city_pop, distance_km, age, | |
| hour, is_night, is_morning, is_afternoon, is_evening, is_business_hour, | |
| year, month, day, dayofweek, is_we | |
| - **Catégorielles** (4): merchant, category, gender, state | |
| ## Output: | |
| - `preprocessed_shape`: Dimensions des données transformées | |
| - `sample_values`: Premières valeurs (pour vérification) | |
| - `message`: Message de confirmation | |
| ## Note: | |
| Les données preprocessed ne sont PAS retournées en entier | |
| (trop volumineuses), seulement leur shape et un échantillon. | |
| Pour obtenir une prédiction, utilisez directement `/predict` | |
| qui fait feat_eng + preprocess + predict. | |
| """ | |
| # Vérifier que le preprocessor est chargé | |
| if preprocessor is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail="Preprocessor not loaded" | |
| ) | |
| try: | |
| print("\n" + "=" * 70) | |
| print("⚙️ PREPROCESSING") | |
| print("=" * 70) | |
| # Préparer les features pour le modèle | |
| # (sélectionne les bonnes colonnes dans le bon ordre) | |
| df = prepare_for_model(features) | |
| if df is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail="Missing required features. Use /features to see the full list." | |
| ) | |
| print(f"\n📊 Features préparées: {df.shape}") | |
| # Appliquer le preprocessing | |
| # Le preprocessor fait: | |
| # 1. StandardScaler sur les numériques | |
| # 2. OneHotEncoder sur les catégorielles | |
| X_preprocessed = preprocessor.transform(df) | |
| print(f"✅ Preprocessing terminé: {X_preprocessed.shape}") | |
| print(f" Input: {df.shape[1]} features") | |
| print(f" Output: {X_preprocessed.shape[1]} features (après encoding)") | |
| # Retourner les informations (pas les données complètes, trop volumineux) | |
| return { | |
| "preprocessed_shape": X_preprocessed.shape, | |
| "sample_values": X_preprocessed[0, :10].tolist(), # 10 premières valeurs | |
| "message": f"Preprocessing successful. Shape: {X_preprocessed.shape}" | |
| } | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail=f"Preprocessing failed: {str(e)}" | |
| ) | |
| # ===================================================================== | |
| # ENDPOINTS - PREDICTION | |
| # ===================================================================== | |
| async def predict_fraud(transaction: TransactionRawInput): | |
| """ | |
| Prédiction COMPLÈTE de fraude | |
| ## Workflow: | |
| ``` | |
| Données Brutes (TransactionRawInput) | |
| ↓ | |
| 1. Feature Engineering | |
| - Calcul distance GPS | |
| - Extraction features temporelles | |
| - Calcul âge | |
| ↓ | |
| 2. Preprocessing | |
| - StandardScaler (normalisation) | |
| - OneHotEncoder (encoding) | |
| ↓ | |
| 3. Prédiction ML | |
| - RandomForestClassifier | |
| - Probabilité de fraude | |
| ↓ | |
| Résultat (PredictionOutput) | |
| ``` | |
| ## Input: | |
| Données brutes de la transaction (voir TransactionRawInput schema) | |
| ## Output: | |
| - **is_fraud**: True/False - Transaction frauduleuse ou non | |
| - **fraud_probability**: 0.0 à 1.0 - Probabilité de fraude | |
| - **risk_level**: LOW/MEDIUM/HIGH/CRITICAL - Niveau de risque | |
| - **confidence**: 0.0 à 1.0 - Confiance du modèle | |
| - **timestamp**: Heure de la prédiction | |
| - **processing_time_ms**: Temps de traitement en millisecondes | |
| ## Niveaux de Risque: | |
| - **LOW**: fraud_probability < 0.3 → Transaction probablement légitime | |
| - **MEDIUM**: 0.3 ≤ fraud_probability < 0.6 → Vérification recommandée | |
| - **HIGH**: 0.6 ≤ fraud_probability < 0.8 → Transaction suspecte | |
| - **CRITICAL**: fraud_probability ≥ 0.8 → Bloquer la transaction | |
| ## Exemple de Code: | |
| ```python | |
| import requests | |
| # Données de transaction | |
| transaction = { | |
| "cc_num": 374125201044065, | |
| "amt": 150.75, | |
| "lat": 40.7128, | |
| "long": -74.0060, | |
| "city_pop": 8000000, | |
| "zip": 10001, | |
| "merch_lat": 40.7589, | |
| "merch_long": -73.9851, | |
| "merchant": "Amazon", | |
| "category": "shopping_net", | |
| "gender": "M", | |
| "state": "NY", | |
| "dob": "1990-01-15", | |
| "transaction_time": "2026-01-29 14:30:00" | |
| } | |
| # Faire la prédiction | |
| response = requests.post( | |
| "http://localhost:8000/predict", | |
| json=transaction | |
| ) | |
| result = response.json() | |
| if result["is_fraud"]: | |
| print(f"⚠️ FRAUDE détectée! Probabilité: {result['fraud_probability']:.1%}") | |
| print(f" Niveau de risque: {result['risk_level']}") | |
| else: | |
| print(f"✅ Transaction légitime. Probabilité de fraude: {result['fraud_probability']:.1%}") | |
| ``` | |
| ## Performance: | |
| - Temps de traitement moyen: 10-50ms | |
| - Throughput: ~100-500 requêtes/seconde (selon hardware) | |
| ## Use Cases: | |
| 1. **Validation temps réel**: Au moment du paiement | |
| 2. **Post-transaction**: Vérification après coup | |
| 3. **Batch processing**: Analyse de milliers de transactions | |
| 4. **Monitoring**: Détection de patterns de fraude | |
| """ | |
| # Vérifier que les modèles sont chargés | |
| if model is None or preprocessor is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail="Models not loaded. Please check /health endpoint." | |
| ) | |
| try: | |
| # Timer pour mesurer le temps de traitement | |
| start_time = time.time() | |
| print("\n" + "🎯" * 35) | |
| print("🎯 PRÉDICTION DE FRAUDE - WORKFLOW COMPLET") | |
| print("🎯" * 35) | |
| # ======================================== | |
| # ÉTAPE 1: FEATURE ENGINEERING | |
| # ======================================== | |
| print("\n[1/3] 🔧 Feature Engineering...") | |
| transaction_dict = transaction.dict() | |
| engineered = engineer_features(transaction_dict) | |
| # ======================================== | |
| # ÉTAPE 2: PREPROCESSING | |
| # ======================================== | |
| print("\n[2/3] ⚙️ Preprocessing...") | |
| df = prepare_for_model(engineered) | |
| if df is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail="Failed to prepare features for model" | |
| ) | |
| # Appliquer le preprocessing | |
| X_preprocessed = preprocessor.transform(df) | |
| print(f" Shape après preprocessing: {X_preprocessed.shape}") | |
| # ======================================== | |
| # ÉTAPE 3: PRÉDICTION ML | |
| # ======================================== | |
| print("\n[3/3] 🤖 Prédiction ML...") | |
| # Faire la prédiction | |
| prediction = model.predict(X_preprocessed)[0] # 0 ou 1 | |
| proba = model.predict_proba(X_preprocessed)[0] # [proba_class_0, proba_class_1] | |
| # Extraire la probabilité de fraude (classe 1) | |
| fraud_prob = float(proba[1]) | |
| # Calculer la confiance | |
| # Confiance = distance par rapport à 0.5 (seuil de décision) | |
| # Plus on est loin de 0.5, plus on est confiant | |
| confidence = abs(fraud_prob - 0.5) * 2 | |
| # Calculer le niveau de risque | |
| risk = calculate_risk_level(fraud_prob) | |
| # Temps de traitement | |
| processing_time = (time.time() - start_time) * 1000 # En millisecondes | |
| # Résultat | |
| result = { | |
| "is_fraud": bool(prediction), | |
| "fraud_probability": round(fraud_prob, 4), | |
| "risk_level": risk, | |
| "confidence": round(confidence, 4), | |
| "timestamp": datetime.utcnow().isoformat(), | |
| "processing_time_ms": round(processing_time, 2) | |
| } | |
| print("\n" + "=" * 70) | |
| print(f"✅ RÉSULTAT:") | |
| print(f" Fraude: {result['is_fraud']}") | |
| print(f" Probabilité: {result['fraud_probability']:.1%}") | |
| print(f" Risque: {result['risk_level']}") | |
| print(f" Temps: {result['processing_time_ms']:.2f}ms") | |
| print("=" * 70) | |
| return result | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail=f"Prediction failed: {str(e)}" | |
| ) | |
| # ===================================================================== | |
| # ERROR HANDLERS (Gestion des erreurs) | |
| # ===================================================================== | |
| async def value_error_handler(request, exc): | |
| """Gère les erreurs de validation de données""" | |
| return JSONResponse( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| content={ | |
| "error": "Invalid input", | |
| "detail": str(exc), | |
| "type": "ValueError" | |
| } | |
| ) | |
| async def general_exception_handler(request, exc): | |
| """Gère toutes les autres erreurs inattendues""" | |
| return JSONResponse( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| content={ | |
| "error": "Internal server error", | |
| "detail": "An unexpected error occurred", | |
| "type": type(exc).__name__ | |
| } | |
| ) | |
| # ===================================================================== | |
| # POINT D'ENTRÉE | |
| # ===================================================================== | |
| if __name__ == "__main__": | |
| """ | |
| Lancer l'API en mode développement | |
| Commande: | |
| python app.py | |
| Ou avec uvicorn: | |
| uvicorn app:app --reload --host 0.0.0.0 --port 8000 | |
| Documentation: | |
| http://localhost:8000/docs | |
| """ | |
| import uvicorn | |
| uvicorn.run( | |
| "app:app", | |
| host="0.0.0.0", | |
| port=8000, | |
| reload=True, # Auto-reload en mode dev | |
| log_level="info" | |
| ) | |