import gradio as gr import requests import json import plotly.graph_objects as go import plotly.express as px from fastapi import FastAPI from typing import Dict, Any, Optional import os from datetime import datetime # Custom CSS for MoneyMate branding CUSTOM_CSS = """ .gradio-container { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Inter', sans-serif; } .main-header { text-align: center; color: white; margin-bottom: 2rem; } .money-card { background: rgba(255, 255, 255, 0.95); border-radius: 15px; padding: 1.5rem; margin: 1rem 0; box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37); backdrop-filter: blur(4px); border: 1px solid rgba(255, 255, 255, 0.18); } .modal-branding { background: linear-gradient(45deg, #ff6b6b, #4ecdc4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold; text-align: center; margin-top: 1rem; } .advice-box { background: #f8f9ff; border-left: 4px solid #667eea; padding: 1rem; margin: 1rem 0; border-radius: 8px; } .quick-action-btn { background: linear-gradient(45deg, #667eea, #764ba2); color: white; border: none; border-radius: 25px; padding: 0.5rem 1rem; margin: 0.25rem; cursor: pointer; transition: all 0.3s ease; } .quick-action-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); } """ # MCP Server configuration class MCPServer: def __init__(self): self.tools = { "salary_breakdown": self.salary_breakdown, "investment_advice": self.investment_advice, "expense_analysis": self.expense_analysis, "savings_goal": self.savings_goal } def salary_breakdown(self, salary: float, expenses: Dict[str, float]) -> Dict[str, Any]: """Break down salary using 50/30/20 rule with Indian context""" needs = salary * 0.5 # 50% for needs wants = salary * 0.3 # 30% for wants savings = salary * 0.2 # 20% for savings/investments return { "breakdown": { "needs": needs, "wants": wants, "savings": savings }, "recommendations": self._get_indian_recommendations(salary) } def investment_advice(self, age: int, salary: float, risk_appetite: str) -> Dict[str, Any]: """Provide investment advice for Indian market""" equity_percentage = min(100 - age, 80) # Age-based equity allocation debt_percentage = 100 - equity_percentage return { "allocation": { "equity": equity_percentage, "debt": debt_percentage }, "instruments": self._get_indian_instruments(risk_appetite) } def expense_analysis(self, expenses: Dict[str, float]) -> Dict[str, Any]: """Analyze expenses and provide optimization suggestions""" total_expenses = sum(expenses.values()) analysis = {} for category, amount in expenses.items(): percentage = (amount / total_expenses) * 100 analysis[category] = { "amount": amount, "percentage": percentage, "status": self._categorize_expense(category, percentage) } return {"analysis": analysis, "suggestions": self._get_optimization_tips()} def savings_goal(self, goal_amount: float, timeline_months: int, current_savings: float) -> Dict[str, Any]: """Calculate monthly savings needed for a goal""" remaining_amount = goal_amount - current_savings monthly_required = remaining_amount / timeline_months if timeline_months > 0 else 0 return { "monthly_required": monthly_required, "total_goal": goal_amount, "timeline": timeline_months, "feasibility": "achievable" if monthly_required < 15000 else "challenging" } def _get_indian_recommendations(self, salary: float) -> list: """Get India-specific financial recommendations""" recommendations = [ "Build emergency fund of 6-12 months expenses", "Start SIP in diversified equity mutual funds", "Consider ELSS funds for tax saving under 80C", "Open PPF account for long-term tax-free returns" ] if salary > 50000: recommendations.append("Consider NPS for additional retirement planning") if salary > 100000: recommendations.append("Explore direct equity investment after gaining knowledge") return recommendations def _get_indian_instruments(self, risk_appetite: str) -> list: """Get Indian investment instruments based on risk appetite""" instruments = { "conservative": ["PPF", "NSC", "FD", "Debt Mutual Funds"], "moderate": ["Balanced Mutual Funds", "ELSS", "Gold ETF", "Corporate Bonds"], "aggressive": ["Large Cap Funds", "Mid Cap Funds", "Small Cap Funds", "Direct Equity"] } return instruments.get(risk_appetite.lower(), instruments["moderate"]) def _categorize_expense(self, category: str, percentage: float) -> str: """Categorize expense as optimal, high, or low""" thresholds = { "rent": (25, 35), "food": (15, 25), "transport": (10, 15), "utilities": (5, 10), "entertainment": (5, 15) } if category.lower() in thresholds: low, high = thresholds[category.lower()] if percentage < low: return "low" elif percentage > high: return "high" return "optimal" def _get_optimization_tips(self) -> list: """Get expense optimization tips""" return [ "Use public transport or carpool to reduce transport costs", "Cook at home more often to save on food expenses", "Use energy-efficient appliances to reduce utility bills", "Set a monthly entertainment budget and stick to it", "Review and cancel unused subscriptions" ] # Initialize MCP Server mcp_server = MCPServer() # Modal backend URL (replace with your actual Modal deployment URL) MODAL_BACKEND_URL = os.getenv("MODAL_BACKEND_URL", "https://kaustubhme0--moneymate-backend-fastapi-app.modal.run") def call_modal_backend(user_input: str, context: Dict[str, Any] = None) -> str: """Call Modal backend for AI-powered financial advice""" try: payload = { "user_input": user_input, "context": context or {} } response = requests.post( f"{MODAL_BACKEND_URL}/financial_advice", json=payload, timeout=30 ) if response.status_code == 200: return response.json().get("advice", "Unable to get advice at the moment.") else: return "Sorry, I'm having trouble connecting to the financial advisor. Please try again." except requests.exceptions.RequestException as e: return f"Connection error: {str(e)}. Please check your internet connection." def create_salary_breakdown_chart(salary: float, needs: float, wants: float, savings: float): """Create a pie chart for salary breakdown""" labels = ['Needs (50%)', 'Wants (30%)', 'Savings (20%)'] values = [needs, wants, savings] colors = ['#ff6b6b', '#4ecdc4', '#45b7d1'] fig = go.Figure(data=[go.Pie( labels=labels, values=values, hole=0.4, marker_colors=colors, textinfo='label+percent', textfont_size=12 )]) fig.update_layout( title=f"Salary Breakdown for ₹{salary:,.0f}", font=dict(size=14), showlegend=True, height=400 ) return fig def create_expense_analysis_chart(expenses: Dict[str, float]): """Create a bar chart for expense analysis""" categories = list(expenses.keys()) amounts = list(expenses.values()) fig = go.Figure([go.Bar( x=categories, y=amounts, marker_color='#667eea', text=[f"₹{amount:,.0f}" for amount in amounts], textposition='auto' )]) fig.update_layout( title="Monthly Expense Breakdown", xaxis_title="Categories", yaxis_title="Amount (₹)", font=dict(size=12), height=400 ) return fig def process_financial_query( salary: float, rent: float, food: float, transport: float, utilities: float, entertainment: float, other: float, savings_goal: str, user_question: str ) -> tuple: """Process user's financial query and return advice with visualizations""" # Calculate totals total_expenses = rent + food + transport + utilities + entertainment + other remaining_salary = salary - total_expenses # Create expense dictionary expenses = { "Rent": rent, "Food": food, "Transport": transport, "Utilities": utilities, "Entertainment": entertainment, "Other": other } # Get salary breakdown using 50/30/20 rule breakdown = mcp_server.salary_breakdown(salary, expenses) needs = breakdown["breakdown"]["needs"] wants = breakdown["breakdown"]["wants"] savings = breakdown["breakdown"]["savings"] # Create charts salary_chart = create_salary_breakdown_chart(salary, needs, wants, savings) expense_chart = create_expense_analysis_chart(expenses) # Prepare context for Modal backend context = { "salary": salary, "expenses": expenses, "total_expenses": total_expenses, "remaining_salary": remaining_salary, "savings_goal": savings_goal, "breakdown": breakdown } # Get AI advice if user_question.strip(): advice = call_modal_backend(user_question, context) else: advice = call_modal_backend(f"Analyze my finances: Salary ₹{salary}, Total expenses ₹{total_expenses}", context) # Create summary summary = f""" ## 💰 Financial Summary **Monthly Salary:** ₹{salary:,.0f} **Total Expenses:** ₹{total_expenses:,.0f} **Remaining Amount:** ₹{remaining_salary:,.0f} ### 📊 Recommended Allocation (50/30/20 Rule) - **Needs (50%):** ₹{needs:,.0f} - **Wants (30%):** ₹{wants:,.0f} - **Savings (20%):** ₹{savings:,.0f} ### 🎯 Status {'✅ Good job! You have money left over.' if remaining_salary > 0 else '⚠️ You are overspending. Consider reducing expenses.'} """ return salary_chart, expense_chart, summary, advice def handle_quick_question(question: str, salary: float = 50000) -> str: """Handle pre-defined quick questions""" context = {"salary": salary} return call_modal_backend(question, context) # Create Gradio interface def create_moneymate_app(): with gr.Blocks(css=CUSTOM_CSS, title="MoneyMate - Your Financial Assistant") as app: # Header gr.HTML("""

💰 MoneyMate

Your Personal Financial Assistant for Smart Money Management

""") with gr.Row(): with gr.Column(scale=1): gr.HTML('
') gr.Markdown("### 💼 Your Financial Details") salary_input = gr.Number( label="Monthly Salary (₹)", value=50000, minimum=0, step=1000 ) gr.Markdown("#### Monthly Expenses") rent_input = gr.Number(label="Rent (₹)", value=15000, minimum=0) food_input = gr.Number(label="Food (₹)", value=8000, minimum=0) transport_input = gr.Number(label="Transport (₹)", value=3000, minimum=0) utilities_input = gr.Number(label="Utilities (₹)", value=2000, minimum=0) entertainment_input = gr.Number(label="Entertainment (₹)", value=4000, minimum=0) other_input = gr.Number(label="Other Expenses (₹)", value=3000, minimum=0) savings_goal_input = gr.Textbox( label="Savings Goal", placeholder="e.g., Emergency fund, Vacation, House down payment", value="Emergency fund" ) user_question_input = gr.Textbox( label="Ask MoneyMate", placeholder="e.g., How should I invest my savings? What's the best way to save for a house?", lines=3 ) analyze_btn = gr.Button("Analyze My Finances 📊", variant="primary", size="large") gr.HTML('
') # Quick action buttons gr.HTML('
') gr.Markdown("### 🚀 Quick Questions") with gr.Row(): quick_btn1 = gr.Button("💡 Investment Tips", size="small") quick_btn2 = gr.Button("🏠 Save for House", size="small") with gr.Row(): quick_btn3 = gr.Button("✈️ Plan Vacation", size="small") quick_btn4 = gr.Button("🚗 Buy a Car", size="small") gr.HTML('
') with gr.Column(scale=2): gr.HTML('
') # Output components with gr.Tab("📊 Salary Breakdown"): salary_chart_output = gr.Plot() with gr.Tab("💸 Expense Analysis"): expense_chart_output = gr.Plot() with gr.Tab("📋 Summary"): summary_output = gr.Markdown() with gr.Tab("🤖 AI Advice"): advice_output = gr.Markdown(value="Click 'Analyze My Finances' to get personalized advice!") gr.HTML('
') # Event handlers analyze_btn.click( fn=process_financial_query, inputs=[ salary_input, rent_input, food_input, transport_input, utilities_input, entertainment_input, other_input, savings_goal_input, user_question_input ], outputs=[salary_chart_output, expense_chart_output, summary_output, advice_output] ) # Quick question handlers quick_btn1.click( fn=lambda s: handle_quick_question("What are the best investment options for a beginner in India?", s), inputs=[salary_input], outputs=[advice_output] ) quick_btn2.click( fn=lambda s: handle_quick_question("How should I save for buying a house in India?", s), inputs=[salary_input], outputs=[advice_output] ) quick_btn3.click( fn=lambda s: handle_quick_question("What's the best way to save for a vacation?", s), inputs=[salary_input], outputs=[advice_output] ) quick_btn4.click( fn=lambda s: handle_quick_question("How should I plan to buy a car with my salary?", s), inputs=[salary_input], outputs=[advice_output] ) # Footer gr.HTML("""

Made with ❤️ for Agents & MCP Hackathon 2025

🏆 Track 1 — MCP Tool / Server

""") return app # FastAPI wrapper for MCP compatibility app_fastapi = FastAPI() # Create and mount Gradio app gradio_app = create_moneymate_app() # MCP endpoints @app_fastapi.post("/mcp/tools") async def list_tools(): """List available MCP tools""" return { "tools": [ { "name": "salary_breakdown", "description": "Break down salary using 50/30/20 rule", "inputSchema": { "type": "object", "properties": { "salary": {"type": "number"}, "expenses": {"type": "object"} } } }, { "name": "investment_advice", "description": "Get investment advice for Indian market", "inputSchema": { "type": "object", "properties": { "age": {"type": "integer"}, "salary": {"type": "number"}, "risk_appetite": {"type": "string"} } } } ] } @app_fastapi.post("/mcp/call_tool") async def call_tool(request: dict): """Call MCP tool""" tool_name = request.get("name") arguments = request.get("arguments", {}) if tool_name in mcp_server.tools: result = mcp_server.tools[tool_name](**arguments) return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]} else: return {"error": f"Tool {tool_name} not found"} # Mount Gradio app gr.mount_gradio_app(app_fastapi, gradio_app, path="/") if __name__ == "__main__": gradio_app.launch( server_name="0.0.0.0", server_port=7860, share=True )