from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from pydantic import BaseModel from typing import List, Optional from app.database.database import get_db from app.database.models import User from app.middleware.cache import cache # Import AI helper engines from app.ai.forecasting import predict_future_balance, forecast_savings_and_investments, simulate_future_scenarios from app.ai.simulation import simulate_purchase_impact, simulate_investment_impact, simulate_subscription_cancellation from app.ai.behavior import analyze_spending_behavior from app.ai.coaching import calculate_financial_health_score, generate_daily_briefing from app.ai.subscriptions import analyze_subscriptions from app.ai.fraud import evaluate_transaction_for_fraud, get_user_fraud_alerts from app.ai.chat import get_chat_response router = APIRouter(prefix="/api/ai", tags=["AI Intelligence"]) # Fallback helper to retrieve a valid user ID for demonstration def get_user_id_fallback(db: Session, user_id: Optional[str] = None) -> str: if user_id: return user_id user = db.query(User).first() if not user: raise HTTPException(status_code=404, detail="No users found in database. Please seed the database first.") return user.id # Pydantic Schemas for input class PurchaseRequest(BaseModel): amount: float merchant: str category: str class InvestmentRequest(BaseModel): monthly_sip: float asset_type: str lump_sum: float = 0.0 class SubscriptionSimulationRequest(BaseModel): subscription_ids: List[str] class ChatMessageRequest(BaseModel): message: str # ─── FINANCIAL TWIN FORECASTS ────────────────────────────────────────────────── @router.get("/twin/predict") def get_twin_predict(user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) cache_key = f"ai:twin:predict:{uid}" cached = cache.get(cache_key) if cached: return cached result = predict_future_balance(db, uid) cache.set(cache_key, result, ttl=300) # cache for 5 minutes return result @router.get("/twin/future") def get_twin_future(user_id: Optional[str] = None, months: int = Query(default=12, ge=1, le=60), db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) cache_key = f"ai:twin:future:{uid}:{months}" cached = cache.get(cache_key) if cached: return cached result = forecast_savings_and_investments(db, uid, months) cache.set(cache_key, result, ttl=300) return result @router.get("/twin/scenarios") def get_twin_scenarios(user_id: Optional[str] = None, months: int = Query(default=6, ge=1, le=24), db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) cache_key = f"ai:twin:scenarios:{uid}:{months}" cached = cache.get(cache_key) if cached: return cached result = simulate_future_scenarios(db, uid, months) cache.set(cache_key, result, ttl=300) return result # ─── SIMULATION ENDPOINTS ────────────────────────────────────────────────────── @router.post("/simulate/purchase") def post_simulate_purchase(req: PurchaseRequest, user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) return simulate_purchase_impact(db, uid, req.amount, req.category, req.merchant) @router.post("/simulate/investment") def post_simulate_investment(req: InvestmentRequest, user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) return simulate_investment_impact(db, uid, req.monthly_sip, req.asset_type, req.lump_sum) @router.post("/simulate/subscription") def post_simulate_subscription(req: SubscriptionSimulationRequest, user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) return simulate_subscription_cancellation(db, uid, req.subscription_ids) # ─── BEHAVIORAL ANALYTICS ───────────────────────────────────────────────────── @router.get("/behavior/insights") def get_behavior_insights(user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) cache_key = f"ai:behavior:insights:{uid}" cached = cache.get(cache_key) if cached: return cached result = analyze_spending_behavior(db, uid) cache.set(cache_key, result, ttl=600) # cache for 10 minutes return result # ─── COACHING & BRIEFINGS ───────────────────────────────────────────────────── @router.get("/coaching/briefing") def get_coaching_briefing(user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) # Cache briefings for 1 hour to prevent excessive LLM costs cache_key = f"ai:coaching:briefing:{uid}" cached = cache.get(cache_key) if cached: return cached result = generate_daily_briefing(db, uid) cache.set(cache_key, result, ttl=3600) return result @router.get("/coaching/score") def get_coaching_score(user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) cache_key = f"ai:coaching:score:{uid}" cached = cache.get(cache_key) if cached: return cached result = calculate_financial_health_score(db, uid) cache.set(cache_key, result, ttl=600) return result # ─── SUBSCRIPTION OPTIMIZATION ──────────────────────────────────────────────── @router.get("/subscriptions/optimize") def get_subscriptions_optimize(user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) cache_key = f"ai:subs:optimize:{uid}" cached = cache.get(cache_key) if cached: return cached result = analyze_subscriptions(db, uid) cache.set(cache_key, result, ttl=600) return result # ─── FRAUD & SECURITY ───────────────────────────────────────────────────────── @router.get("/fraud/analysis") def get_fraud_analysis(user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) return get_user_fraud_alerts(db, uid) @router.post("/fraud/evaluate/{transaction_id}") def post_fraud_evaluate(transaction_id: str, db: Session = Depends(get_db)): return evaluate_transaction_for_fraud(db, transaction_id) # ─── CONTEXTUAL CHAT ENDPOINT ────────────────────────────────────────────────── @router.post("/chat") def post_chat(req: ChatMessageRequest, user_id: Optional[str] = None, db: Session = Depends(get_db)): uid = get_user_id_fallback(db, user_id) response_msg = get_chat_response(db, uid, req.message) return {"response": response_msg}