Spaces:
Sleeping
Sleeping
| 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, chat_memory | |
| from app.ai.intelligence import generate_weekly_coaching, explain_fraud_alert, generate_spending_narrative | |
| 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 | |
| session_id: Optional[str] = None | |
| language: str = "English" | |
| class ChatSessionCreateRequest(BaseModel): | |
| title: Optional[str] = "New chat" | |
| # βββ FINANCIAL TWIN FORECASTS ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 | |
| 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 | |
| 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 ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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) | |
| 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) | |
| 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 βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 | |
| 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 ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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) | |
| def post_fraud_evaluate(transaction_id: str, db: Session = Depends(get_db)): | |
| return evaluate_transaction_for_fraud(db, transaction_id) | |
| # βββ CONTEXTUAL CHAT ENDPOINT ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def list_chat_sessions( | |
| user_id: Optional[str] = None, | |
| limit: int = Query(30, ge=1, le=50), | |
| db: Session = Depends(get_db), | |
| ): | |
| uid = get_user_id_fallback(db, user_id) | |
| sessions = chat_memory.list_sessions(db, uid, limit=limit) | |
| return {"sessions": sessions, "count": len(sessions)} | |
| def create_chat_session( | |
| body: ChatSessionCreateRequest, | |
| user_id: Optional[str] = None, | |
| db: Session = Depends(get_db), | |
| ): | |
| uid = get_user_id_fallback(db, user_id) | |
| session = chat_memory.create_session(db, uid, title=body.title or "New chat") | |
| return { | |
| "id": session.id, | |
| "title": session.title, | |
| "created_at": session.created_at.isoformat() if session.created_at else None, | |
| "updated_at": session.updated_at.isoformat() if session.updated_at else None, | |
| "message_count": 0, | |
| "preview": "", | |
| } | |
| def delete_chat_session( | |
| session_id: str, | |
| user_id: Optional[str] = None, | |
| db: Session = Depends(get_db), | |
| ): | |
| uid = get_user_id_fallback(db, user_id) | |
| if not chat_memory.delete_session(db, uid, session_id): | |
| raise HTTPException(status_code=404, detail="Chat session not found") | |
| return {"ok": True, "message": "Chat deleted"} | |
| def get_chat_history( | |
| session_id: str = Query(..., description="Chat session ID"), | |
| user_id: Optional[str] = None, | |
| limit: int = Query(100, ge=1, le=200), | |
| db: Session = Depends(get_db), | |
| ): | |
| uid = get_user_id_fallback(db, user_id) | |
| if not chat_memory.get_session(db, uid, session_id): | |
| raise HTTPException(status_code=404, detail="Chat session not found") | |
| messages = chat_memory.list_messages(db, session_id, limit=limit) | |
| return {"session_id": session_id, "messages": messages, "count": len(messages)} | |
| def delete_chat_history( | |
| session_id: str = Query(..., description="Chat session ID"), | |
| user_id: Optional[str] = None, | |
| db: Session = Depends(get_db), | |
| ): | |
| uid = get_user_id_fallback(db, user_id) | |
| if not chat_memory.clear_session_messages(db, uid, session_id): | |
| raise HTTPException(status_code=404, detail="Chat session not found") | |
| return {"ok": True, "message": "Conversation cleared", "session_id": session_id} | |
| 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, session_id = get_chat_response(db, uid, req.message, req.session_id, req.language) | |
| return {"response": response_msg, "session_id": session_id} | |
| # βββ FINANCIAL COACH MODE ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_weekly_coaching(user_id: Optional[str] = None, db: Session = Depends(get_db)): | |
| """ | |
| Proactive weekly coaching report: summary, budget coaching, savings nudge, | |
| anomaly explanations, and top 3 actions β all grounded in real account data. | |
| """ | |
| uid = get_user_id_fallback(db, user_id) | |
| cache_key = f"ai:coach:weekly:{uid}" | |
| cached = cache.get(cache_key) | |
| if cached: | |
| return cached | |
| result = generate_weekly_coaching(db, uid) | |
| cache.set(cache_key, result, ttl=3600) # cache 1 hour | |
| return result | |
| # βββ AI FRAUD EXPLANATION ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_fraud_explanation(fraud_log_id: str, db: Session = Depends(get_db)): | |
| """ | |
| Returns a human-readable AI explanation of exactly why a specific | |
| transaction was flagged, with context about the user's normal patterns. | |
| """ | |
| return explain_fraud_alert(db, fraud_log_id) | |
| # βββ SPENDING NARRATIVE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_monthly_narrative(user_id: Optional[str] = None, db: Session = Depends(get_db)): | |
| """ | |
| Monthly spending narrative: what changed, what improved, what to watch, | |
| investment insights, and a one-paragraph human story of the month. | |
| """ | |
| uid = get_user_id_fallback(db, user_id) | |
| cache_key = f"ai:narrative:monthly:{uid}" | |
| cached = cache.get(cache_key) | |
| if cached: | |
| return cached | |
| result = generate_spending_narrative(db, uid) | |
| cache.set(cache_key, result, ttl=1800) # cache 30 min | |
| return result | |