MoneyMate / app.py
TheSilentOne's picture
Update app.py
1c17f47 verified
'''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("""
<div class="main-header">
<h1>πŸ’° MoneyMate</h1>
<p>Your Personal Financial Assistant for Smart Money Management</p>
<div class="modal-branding">⚑ Powered by Modal Labs</div>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.HTML('<div class="money-card">')
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('</div>')
# Quick action buttons
gr.HTML('<div class="money-card">')
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('</div>')
with gr.Column(scale=2):
gr.HTML('<div class="money-card">')
# 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('</div>')
# 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("""
<div style="text-align: center; margin-top: 2rem; color: white;">
<p>Made with ❀️ for Agents & MCP Hackathon 2025</p>
<p>πŸ† Track 1 β€” MCP Tool / Server</p>
</div>
""")
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
)'''
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
from utils import (
validate_salary, validate_expenses, calculate_50_30_20_breakdown,
calculate_emergency_fund_target, calculate_sip_returns, get_tax_saving_instruments,
get_investment_allocation_by_age, calculate_goal_based_savings, get_expense_optimization_tips,
format_currency, get_financial_milestones_by_age, calculate_retirement_corpus
)
# 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);
}
"""
# Modal backend URL
MODAL_BACKEND_URL = os.getenv("MODAL_BACKEND_URL", "https://kaustubhme0--moneymate-backend-fastapi-app.modal.run")
def call_modal_financial_advice(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=60
)
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 call_modal_analyze_expenses(expenses: Dict[str, float], salary: float) -> Dict[str, Any]:
"""Call Modal backend for expense analysis"""
try:
payload = {
"expenses": expenses,
"salary": salary
}
response = requests.post(
f"{MODAL_BACKEND_URL}/analyze_expenses",
json=payload,
timeout=30
)
if response.status_code == 200:
return response.json().get("analysis", {})
else:
# Fallback to local utils function
return {}
except requests.exceptions.RequestException:
# Fallback to local utils function
return {}
def call_modal_calculate_returns(monthly_investment: float, annual_return: float, years: int) -> Dict[str, Any]:
"""Call Modal backend for investment return calculations"""
try:
payload = {
"monthly_investment": monthly_investment,
"annual_return_rate": annual_return,
"years": years
}
response = requests.post(
f"{MODAL_BACKEND_URL}/calculate_returns",
json=payload,
timeout=30
)
if response.status_code == 200:
return response.json().get("returns", {})
else:
# Fallback to local utils function
return calculate_sip_returns(monthly_investment, annual_return, years)
except requests.exceptions.RequestException:
# Fallback to local utils function
return calculate_sip_returns(monthly_investment, annual_return, years)
def check_modal_health() -> Dict[str, Any]:
"""Check Modal backend health"""
try:
response = requests.get(f"{MODAL_BACKEND_URL}/health", timeout=10)
if response.status_code == 200:
return response.json()
else:
return {"status": "unhealthy", "message": "Backend not responding"}
except requests.exceptions.RequestException as e:
return {"status": "error", "message": str(e)}
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 create_investment_projection_chart(monthly_investment: float, years: int):
"""Create investment projection chart using Modal backend"""
returns_data = call_modal_calculate_returns(monthly_investment, 12.0, years)
if not returns_data:
return go.Figure()
# Create year-wise projection
years_list = list(range(1, years + 1))
investment_values = []
for year in years_list:
year_data = call_modal_calculate_returns(monthly_investment, 12.0, year)
investment_values.append(year_data.get('future_value', 0))
fig = go.Figure()
fig.add_trace(go.Scatter(
x=years_list,
y=investment_values,
mode='lines+markers',
name='Investment Growth',
line=dict(color='#667eea', width=3),
marker=dict(size=8)
))
fig.update_layout(
title=f"SIP Growth Projection (β‚Ή{monthly_investment:,.0f}/month @ 12% annual return)",
xaxis_title="Years",
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 with enhanced Modal integration"""
# Validate inputs using utils
salary_valid, salary_msg = validate_salary(salary)
if not salary_valid:
return None, None, f"❌ {salary_msg}", "Please fix the salary amount and try again."
# 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
}
# Validate expenses using utils
expenses_valid, expenses_msg, warnings = validate_expenses(expenses, salary)
if not expenses_valid:
return None, None, f"❌ {expenses_msg}", "Please adjust your expenses and try again."
# Get salary breakdown using utils
breakdown = calculate_50_30_20_breakdown(salary)
needs = breakdown["needs"]
wants = breakdown["wants"]
savings = breakdown["savings"]
# Get expense analysis from Modal backend
modal_analysis = call_modal_analyze_expenses(expenses, salary)
# Get optimization tips from utils
optimization_tips = get_expense_optimization_tips(expenses, salary)
# Create charts
salary_chart = create_salary_breakdown_chart(salary, needs, wants, savings)
expense_chart = create_expense_analysis_chart({k.title(): v for k, v in expenses.items()})
# Calculate emergency fund target using utils
emergency_fund = calculate_emergency_fund_target(total_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,
"emergency_fund": emergency_fund,
"optimization_tips": optimization_tips[:3] # Top 3 tips
}
# Get AI advice from Modal
if user_question.strip():
advice = call_modal_financial_advice(user_question, context)
else:
advice = call_modal_financial_advice(f"Analyze my finances: Salary β‚Ή{salary}, Total expenses β‚Ή{total_expenses}", context)
# Create enhanced 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.'}
### 🚨 Emergency Fund Target
**Target Amount:** β‚Ή{emergency_fund['target_amount']:,.0f} ({emergency_fund['months_coverage']} months)
### πŸ’‘ Quick Optimization Tips
{chr(10).join(f"β€’ {tip}" for tip in optimization_tips[:3])}
### ⚠️ Warnings
{chr(10).join(f"β€’ {warning}" for warning in warnings) if warnings else "No warnings - you're doing great!"}
"""
return salary_chart, expense_chart, summary, advice
def calculate_sip_projection(monthly_investment: float, annual_return: float, years: int) -> tuple:
"""Calculate and visualize SIP projections using Modal backend"""
if monthly_investment <= 0 or years <= 0:
return None, "Please enter valid investment amount and timeline."
# Get returns data from Modal backend
returns_data = call_modal_calculate_returns(monthly_investment, annual_return, years)
if not returns_data:
return None, "Unable to calculate returns. Please try again."
# Create projection chart
projection_chart = create_investment_projection_chart(monthly_investment, years)
# Create summary
summary = f"""
## πŸ“ˆ SIP Investment Projection
**Monthly Investment:** β‚Ή{monthly_investment:,.0f}
**Annual Return:** {annual_return}%
**Investment Period:** {years} years
### πŸ’° Projected Results
- **Total Invested:** β‚Ή{returns_data.get('total_invested', 0):,.0f}
- **Future Value:** β‚Ή{returns_data.get('future_value', 0):,.0f}
- **Returns Generated:** β‚Ή{returns_data.get('returns', 0):,.0f}
- **Return Percentage:** {returns_data.get('return_percentage', 0):.1f}%
### 🎯 Wealth Creation
Your β‚Ή{monthly_investment:,.0f} monthly investment will grow to β‚Ή{returns_data.get('future_value', 0):,.0f} in {years} years!
"""
return projection_chart, summary
def handle_quick_question(question: str, salary: float = 50000) -> str:
"""Handle pre-defined quick questions"""
context = {"salary": salary}
return call_modal_financial_advice(question, context)
def show_health_status() -> str:
"""Show Modal backend health status"""
health = check_modal_health()
if health.get("status") == "healthy":
return "βœ… Backend is healthy and running!"
else:
return f"❌ Backend issue: {health.get('message', 'Unknown error')}"
# Create Gradio interface
def create_moneymate_app():
with gr.Blocks(css=CUSTOM_CSS, title="MoneyMate - Your Financial Assistant") as app:
# Header
gr.HTML("""
<div class="main-header">
<h1>πŸ’° MoneyMate</h1>
<p>Your Personal Financial Assistant for Smart Money Management</p>
<div class="modal-branding">⚑ Powered by Modal Labs</div>
</div>
""")
with gr.Tabs():
# Main Financial Analysis Tab
with gr.Tab("πŸ’Ό Financial Analysis"):
with gr.Row():
with gr.Column(scale=1):
gr.HTML('<div class="money-card">')
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")
health_btn = gr.Button("Check Backend Status πŸ”", variant="secondary", size="small")
health_status = gr.Textbox(label="Status", interactive=False)
gr.HTML('</div>')
# Quick action buttons
gr.HTML('<div class="money-card">')
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('</div>')
with gr.Column(scale=2):
gr.HTML('<div class="money-card">')
# 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('</div>')
# SIP Calculator Tab
with gr.Tab("πŸ“ˆ SIP Calculator"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### πŸ“ˆ SIP Investment Calculator")
monthly_investment_input = gr.Number(
label="Monthly Investment (β‚Ή)",
value=5000,
minimum=500,
step=500
)
annual_return_input = gr.Number(
label="Expected Annual Return (%)",
value=12.0,
minimum=1.0,
maximum=30.0,
step=0.5
)
years_input = gr.Number(
label="Investment Period (Years)",
value=10,
minimum=1,
maximum=50,
step=1
)
calculate_sip_btn = gr.Button("Calculate SIP Returns πŸ“Š", variant="primary")
with gr.Column(scale=2):
sip_chart_output = gr.Plot()
sip_summary_output = gr.Markdown()
# 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]
)
calculate_sip_btn.click(
fn=calculate_sip_projection,
inputs=[monthly_investment_input, annual_return_input, years_input],
outputs=[sip_chart_output, sip_summary_output]
)
health_btn.click(
fn=show_health_status,
outputs=[health_status]
)
# 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("""
<div style="text-align: center; margin-top: 2rem; color: white;">
<p>Made with ❀️ for Agents & MCP Hackathon 2025</p>
<p>πŸ† Track 1 β€” MCP Tool / Server</p>
</div>
""")
return app
if __name__ == "__main__":
app = create_moneymate_app()
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=True
)