CoolFace
Apppublic

pylord/API-BFSI

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
app.py650 linesDownload Raw Back to root
1from fastapi import FastAPI, Depends, HTTPException2from fastapi.middleware.cors import CORSMiddleware3from sqlalchemy.orm import Session4from sqlalchemy import func, extract5from database import Base, engine, SessionLocal6from models import User, Prediction7from schemas import (8    UserCreate,9    UserLogin,10    PredictRequest,11    PredictResponse,12    TransactionHistoryResponse,13    AnalyticsResponse,14    MetricsResponse,15    BulkPredictRequest,16    BulkPredictResult,17    BulkPredictResponse18)19 20from utils.features import derive_features_auto21from utils.auth import hash_password, verify_password22from utils.hf_model import generate_explanation23from datetime import datetime, timedelta24from catboost import CatBoostClassifier25import pandas as pd26import numpy as np27from typing import List28from collections import defaultdict29import time30from typing import Dict, List, Any31# ------------------ FASTAPI APP ------------------32app = FastAPI(33    title="RiskShield Fraud Detection API",34    description="AI-powered fraud detection system with hybrid rule-based and ML approach",35    version="1.0.0"36)37 38# CORS Configuration39origins = [40    "http://127.0.0.1:5500",41    "http://localhost:5500",42    "http://localhost:3000",43    "http://127.0.0.1:3000",44    "https://risk-shield.onrender.com"45]46 47app.add_middleware(48    CORSMiddleware,49    allow_origins=origins,50    allow_credentials=True,51    allow_methods=["*"],52    allow_headers=["*"],53)54 55# Create tables if not existing56Base.metadata.create_all(bind=engine)57 58# ------------------ LOAD MODEL ------------------59model_path = "model/catboost_fraud_model_balanced_tuned.cbm"60cat_model = CatBoostClassifier()61try:62    cat_model.load_model(model_path)63    print("✅ Model loaded successfully")64except Exception as e:65    print(f"⚠️ Warning: Could not load model - {e}")66    cat_model = None67 68 69# ------------------ DB SESSION DEPENDENCY ------------------70def get_db():71    db = SessionLocal()72    try:73        yield db74    finally:75        db.close()76 77 78# ------------------ HEALTH CHECK ------------------79@app.get("/api/health")80def health_check():81    """Health check endpoint"""82    return {83        "status": "healthy",84        "timestamp": datetime.utcnow().isoformat(),85        "model_loaded": cat_model is not None86    }87 88 89# ------------------ REGISTER ------------------90@app.post("/api/register")91def register_user(user: UserCreate, db: Session = Depends(get_db)):92    """93    Register a new user94    """95    try:96        existing_user = db.query(User).filter(User.email == user.email).first()97        if existing_user:98            raise HTTPException(status_code=400, detail="Email already registered")99 100        hashed_password = hash_password(user.password)101        new_user = User(102            email=user.email,103            full_name=user.full_name,104            password=hashed_password105        )106        db.add(new_user)107        db.commit()108        db.refresh(new_user)109        110        return {111            "status": "success",112            "message": "User registered successfully",113            "data": {114                "email": new_user.email,115                "full_name": new_user.full_name116            }117        }118    except HTTPException:119        raise120    except Exception as e:121        db.rollback()122        raise HTTPException(status_code=500, detail=f"Registration failed: {str(e)}")123 124 125# ------------------ LOGIN ------------------126@app.post("/api/login")127def login_user(user: UserLogin, db: Session = Depends(get_db)):128    """129    Authenticate user login130    """131    try:132        db_user = db.query(User).filter(User.email == user.email).first()133        if not db_user or not verify_password(user.password, db_user.password):134            raise HTTPException(status_code=401, detail="Invalid email or password")135        136        return {137            "status": "success",138            "message": "Login successful",139            "data": {140                "email": db_user.email,141                "full_name": db_user.full_name142            }143        }144    except HTTPException:145        raise146    except Exception as e:147        raise HTTPException(status_code=500, detail=f"Login failed: {str(e)}")148 149 150# ------------------ PREDICT FRAUD ------------------151@app.post("/api/predict", response_model=PredictResponse)152def predict_transaction(data: PredictRequest, db: Session = Depends(get_db)):153    """154    Predict fraud for a transaction using hybrid approach (ML model + rule-based system)155    """156    try:157        # Validate model is loaded158        if cat_model is None:159            raise HTTPException(status_code=503, detail="Model not available")160        161        # Validate user162        user = db.query(User).filter(User.email == data.email).first()163        if not user:164            raise HTTPException(status_code=401, detail="User not registered")165 166        # Convert to dict167        data_dict = data.dict()168 169        # Derive auto features170        features_df = derive_features_auto(data_dict)171        features = features_df.to_dict(orient="records")[0]172 173        # Model prediction174        model_proba = float(cat_model.predict_proba(features_df)[0, 1])175        176        # -----------------------------177        # RULE-BASED CHECKS178        # -----------------------------179        rule_flags = []180        rule_score = 0.0181 182        # Rule 1: High amount transaction183        if features["transaction_amount"] > 100000:184            rule_flags.append("High amount transaction (>₹100K)")185            rule_score += 0.2186 187        # Rule 2: Large night-time transaction188        if features["is_night_txn"] == 1 and features["transaction_amount"] > 50000:189            rule_flags.append("Large night-time transaction")190            rule_score += 0.2191 192        # Rule 3: New unverified account193        if features["account_age_days"] < 10 and features["kyc_verified"] == 0:194            rule_flags.append("New unverified account")195            rule_score += 0.25196 197        # Rule 4: Weekend high-value transaction198        if features["is_weekend_txn"] == 1 and features["transaction_amount"] > 80000:199            rule_flags.append("Weekend high-value transaction")200            rule_score += 0.15201 202        # Rule 5: Holiday transaction risk203        if features.get("is_holiday_txn", 0) == 1 and features["transaction_amount"] > 70000:204            rule_flags.append("High-value holiday transaction")205            rule_score += 0.1206 207        # Rule 6: Historical pattern - repeated high-risk transactions208        one_hour_ago = datetime.utcnow() - timedelta(hours=1)209        recent_txns = db.query(Prediction).filter(210            Prediction.customer_id == data_dict["customer_id"],211            Prediction.timestamp >= one_hour_ago212        ).all()213        214        high_value_txns = sum(1 for txn in recent_txns if txn.risk_score > 0.7)215        if high_value_txns >= 3:216            rule_flags.append("Multiple high-risk transactions in last hour")217            rule_score += 0.3218 219        # -----------------------------220        # HYBRID DECISION221        # -----------------------------222        combined_score = round(min(1.0, model_proba + rule_score), 4)223        final_is_fraud = int(combined_score >= 0.6)224 225        # Generate explanation226        explanation = generate_explanation(227            data_dict, 228            features, 229            combined_score, 230            rule_score, 231            rule_flags232        )233 234        # Store in database235        new_pred = Prediction(236            customer_id=data_dict["customer_id"],237            transaction_id=data_dict["transaction_id"],238            email=data_dict["email"],239            risk_score=combined_score,240            is_fraud=final_is_fraud,241            derived_features={**features, "rule_flags": rule_flags},242            explanation=explanation243        )244        db.add(new_pred)245        db.commit()246        db.refresh(new_pred)247 248        return PredictResponse(249            status="success",250            message="Prediction completed successfully",251            data={252                "prediction_id": new_pred.id,253                "user": user.full_name,254                "model_risk_score": round(model_proba, 4),255                "rule_score": round(rule_score, 2),256                "combined_score": combined_score,257                "is_fraud": final_is_fraud,258                "rules_triggered": rule_flags,259                "derived_features": features,260                "explanation": explanation,261                "timestamp": new_pred.timestamp.isoformat()262            }263        )264 265    except HTTPException:266        raise267    except Exception as e:268        db.rollback()269        raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")270 271 272# ------------------ TRANSACTION HISTORY ------------------273@app.get("/api/transactions/{email}", response_model=TransactionHistoryResponse)274def get_transaction_history(email: str, db: Session = Depends(get_db)):275    """276    Get complete transaction history for a specific user277    """278    try:279        # Verify user exists280        user = db.query(User).filter(User.email == email).first()281        if not user:282            raise HTTPException(status_code=404, detail="User not found")283 284        # Fetch all predictions for this user285        predictions = db.query(Prediction).filter(286            Prediction.email == email287        ).order_by(Prediction.timestamp.desc()).all()288 289        transactions = []290        for pred in predictions:291            transactions.append({292                "id": pred.id,293                "customer_id": pred.customer_id,294                "transaction_id": pred.transaction_id,295                "risk_score": pred.risk_score,296                "is_fraud": pred.is_fraud,297                "derived_features": pred.derived_features,298                "explanation": pred.explanation,299                "timestamp": pred.timestamp.isoformat()300            })301 302        return TransactionHistoryResponse(303            status="success",304            message=f"Found {len(transactions)} transactions",305            data={306                "user_email": email,307                "user_name": user.full_name,308                "total_transactions": len(transactions),309                "transactions": transactions310            }311        )312 313    except HTTPException:314        raise315    except Exception as e:316        raise HTTPException(status_code=500, detail=f"Failed to fetch transactions: {str(e)}")317 318 319# ------------------ ANALYTICS DASHBOARD ------------------320@app.get("/api/analytics", response_model=AnalyticsResponse)321def get_analytics(db: Session = Depends(get_db)):322    """323    Get comprehensive analytics for dashboard visualization324    """325    try:326        # Fetch all predictions327        all_predictions = db.query(Prediction).all()328        329        if not all_predictions:330            return AnalyticsResponse(331                status="success",332                message="No data available yet",333                data={334                    "kpis": {335                        "total_transactions": 0,336                        "fraud_detected": 0,337                        "accuracy_rate": 0.0,338                        "amount_protected": 0.0339                    },340                    "graphs": {341                        "fraud_vs_legitimate": {"fraud": 0, "legitimate": 0},342                        "fraud_rate_trend": [],343                        "fraud_by_channel": {},344                        "amount_vs_risk_scatter": []345                    }346                }347            )348 349        # Calculate KPIs350        total_txns = len(all_predictions)351        fraud_count = sum(1 for p in all_predictions if p.is_fraud == 1)352        legitimate_count = total_txns - fraud_count353        354        # Estimate amount protected (fraud transactions)355        amount_protected = sum(356            p.derived_features.get("transaction_amount", 0) 357            for p in all_predictions if p.is_fraud == 1358        )359        360        # Simulated accuracy (you should calculate this based on actual labels if available)361        accuracy_rate = 93.3  # Placeholder - replace with actual calculation362        363        # Graph 1: Fraud vs Legitimate364        fraud_vs_legit = {365            "fraud": fraud_count,366            "legitimate": legitimate_count367        }368        369        # Graph 2: Fraud Rate Trend (Monthly)370        monthly_data = defaultdict(lambda: {"total": 0, "fraud": 0})371        for pred in all_predictions:372            month_key = pred.timestamp.strftime("%Y-%m")373            monthly_data[month_key]["total"] += 1374            if pred.is_fraud == 1:375                monthly_data[month_key]["fraud"] += 1376        377        fraud_rate_trend = []378        for month in sorted(monthly_data.keys()):379            total = monthly_data[month]["total"]380            fraud = monthly_data[month]["fraud"]381            fraud_rate = round((fraud / total * 100) if total > 0 else 0, 2)382            fraud_rate_trend.append({383                "month": month,384                "fraud_rate": fraud_rate,385                "total_transactions": total,386                "fraud_count": fraud387            })388        389        # Graph 3: Fraud Distribution by Channel390        channel_mapping = {0: "Online", 1: "ATM", 2: "POS", 3: "Mobile"}391        channel_fraud = defaultdict(int)392        393        for pred in all_predictions:394            if pred.is_fraud == 1:395                channel_code = pred.derived_features.get("channel_encoded", 0)396                channel_name = channel_mapping.get(channel_code, "Unknown")397                channel_fraud[channel_name] += 1398        399        # Graph 4: Transaction Amount vs Risk Score (Scatter Plot)400        scatter_data = []401        for pred in all_predictions:402            scatter_data.append({403                "transaction_amount": pred.derived_features.get("transaction_amount", 0),404                "risk_score": pred.risk_score,405                "is_fraud": pred.is_fraud406            })407        408        return AnalyticsResponse(409            status="success",410            message="Analytics generated successfully",411            data={412                "kpis": {413                    "total_transactions": total_txns,414                    "fraud_detected": fraud_count,415                    "accuracy_rate": accuracy_rate,416                    "amount_protected": round(amount_protected, 2)417                },418                "graphs": {419                    "fraud_vs_legitimate": fraud_vs_legit,420                    "fraud_rate_trend": fraud_rate_trend,421                    "fraud_by_channel": dict(channel_fraud),422                    "amount_vs_risk_scatter": scatter_data423                }424            }425        )426 427    except Exception as e:428        raise HTTPException(status_code=500, detail=f"Analytics generation failed: {str(e)}")429 430# ------------------ MODEL METRICS ------------------431@app.get("/api/metrics", response_model=MetricsResponse)432def get_model_metrics():433    """434    Get model performance metrics (based on actual training results)435    """436    return MetricsResponse(437        status="success",438        message="Model metrics retrieved successfully",439        data={440            "model_name": "CatBoost Fraud Detection Model (Recall-Optimized)",441            "version": "1.0.0",442            "training_date": "2025-01-15",443            "metrics": {444                "accuracy": 0.76,445                "precision": 0.24,446                "recall": 0.83,   # This is the important part - FRAUD DETECTION RECALL447                "f1_score": 0.37,448                "auc_roc": 0.80,449                "confusion_matrix": {450                    "true_positive": 71,451                    "false_positive": 224,452                    "true_negative": 690,453                    "false_negative": 15454                }455            },456            # Keep these unless you want to regenerate actual importance values457            "feature_importance": [458                {"feature": "transaction_amount", "importance": 0.234},459                {"feature": "account_age_days", "importance": 0.189},460                {"feature": "kyc_verified", "importance": 0.156},461                {"feature": "is_night_txn", "importance": 0.123},462                {"feature": "hour_of_day", "importance": 0.098},463                {"feature": "channel_encoded", "importance": 0.087},464                {"feature": "is_high_amount_transaction", "importance": 0.073},465                {"feature": "day_of_week", "importance": 0.040}466            ],467            "performance_summary": {468                "total_predictions": 1000,469                "fraud_detected": 71,470                "false_positives": 224,471                "false_negatives": 15,472                "detection_rate": 0.83,473                "false_positive_rate": round(224 / (224 + 690), 3)474            }475        }476    )477 478 479# ------------------ BULK PREDICT ------------------480@app.post("/api/bulk-predict", response_model=BulkPredictResponse)481def bulk_predict_transactions(data: BulkPredictRequest, db: Session = Depends(get_db)):482    """483    Bulk fraud prediction for multiple transactions484    Accepts up to 1000 transactions at once485    """486    start_time = time.time()487    488    try:489        # Validate model is loaded490        if cat_model is None:491            raise HTTPException(status_code=503, detail="Model not available")492        493        # Validate user494        user = db.query(User).filter(User.email == data.email).first()495        if not user:496            raise HTTPException(status_code=401, detail="User not registered")497        498        results = []499        successful = 0500        failed = 0501        fraud_detected = 0502        503        # Process each transaction504        for txn_data in data.transactions:505            try:506                # Add email to transaction data507                txn_data["email"] = data.email508                509                # Derive features510                features_df = derive_features_auto(txn_data)511                features = features_df.to_dict(orient="records")[0]512                513                # Model prediction514                model_proba = float(cat_model.predict_proba(features_df)[0, 1])515                516                # Rule-based checks517                rule_flags = []518                rule_score = 0.0519                520                # Rule 1: High amount521                if features["transaction_amount"] > 100000:522                    rule_flags.append("High amount transaction (>₹100K)")523                    rule_score += 0.2524                525                # Rule 2: Night transaction526                if features["is_night_txn"] == 1 and features["transaction_amount"] > 50000:527                    rule_flags.append("Large night-time transaction")528                    rule_score += 0.2529                530                # Rule 3: New unverified account531                if features["account_age_days"] < 10 and features["kyc_verified"] == 0:532                    rule_flags.append("New unverified account")533                    rule_score += 0.25534                535                # Rule 4: Weekend high-value536                if features["is_weekend_txn"] == 1 and features["transaction_amount"] > 80000:537                    rule_flags.append("Weekend high-value transaction")538                    rule_score += 0.15539                540                # Rule 5: Holiday risk541                if features.get("is_holiday_txn", 0) == 1 and features["transaction_amount"] > 70000:542                    rule_flags.append("High-value holiday transaction")543                    rule_score += 0.1544                545                # Combined score546                combined_score = round(min(1.0, model_proba + rule_score), 4)547                final_is_fraud = int(combined_score >= 0.6)548                549                if final_is_fraud:550                    fraud_detected += 1551                552                # Generate explanation553                explanation = generate_explanation(554                    txn_data, features, combined_score, rule_score, rule_flags555                )556                557                # Store in database558                new_pred = Prediction(559                    customer_id=txn_data["customer_id"],560                    transaction_id=txn_data["transaction_id"],561                    email=data.email,562                    risk_score=combined_score,563                    is_fraud=final_is_fraud,564                    derived_features={**features, "rule_flags": rule_flags},565                    explanation=explanation566                )567                db.add(new_pred)568                569                # Add to results570                results.append({571                    "transaction_id": txn_data["transaction_id"],572                    "customer_id": txn_data["customer_id"],573                    "risk_score": combined_score,574                    "is_fraud": final_is_fraud,575                    "model_risk_score": round(model_proba, 4),576                    "rule_score": round(rule_score, 2),577                    "rules_triggered": rule_flags,578                    "status": "success",579                    "error_message": None580                })581                582                successful += 1583                584            except Exception as e:585                failed += 1586                results.append({587                    "transaction_id": txn_data.get("transaction_id", "unknown"),588                    "customer_id": txn_data.get("customer_id", "unknown"),589                    "risk_score": 0.0,590                    "is_fraud": 0,591                    "model_risk_score": 0.0,592                    "rule_score": 0.0,593                    "rules_triggered": [],594                    "status": "error",595                    "error_message": str(e)596                })597        598        # Commit all successful predictions599        db.commit()600        601        # Calculate processing time602        processing_time = round(time.time() - start_time, 2)603        604        return BulkPredictResponse(605            status="success",606            message=f"Bulk prediction completed: {successful} successful, {failed} failed",607            data={608                "user": user.full_name,609                "total_processed": len(data.transactions),610                "successful": successful,611                "failed": failed,612                "fraud_detected": fraud_detected,613                "fraud_rate": round((fraud_detected / successful * 100) if successful > 0 else 0, 2),614                "processing_time_seconds": processing_time,615                "avg_time_per_transaction_ms": round((processing_time / len(data.transactions)) * 1000, 2),616                "results": results617            }618        )619        620    except HTTPException:621        raise622    except Exception as e:623        db.rollback()624        raise HTTPException(status_code=500, detail=f"Bulk prediction failed: {str(e)}")625# ------------------ ROOT ENDPOINT ------------------626@app.get("/")627def root():628    """Root endpoint with API information"""629    return {630        "message": "Welcome to RiskShield Fraud Detection API",631        "version": "1.0.0",632        "endpoints": {633            "health": "/api/health",634            "register": "/api/register",635            "login": "/api/login",636            "predict": "/api/predict",637            "transactions": "/api/transactions/{email}",638            "analytics": "/api/analytics",639            "metrics": "/api/metrics"640        },641        "documentation": "/docs"642    }643 644 645    646if __name__ == "__main__":647    import uvicorn648    uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)649 650