Spaces:
Running
Running
'''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 | |
) | |